woods 2.0.0.beta4 → 2.0.0

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +480 -477
  3. data/CONTRIBUTING.md +2 -2
  4. data/README.md +11 -26
  5. data/docs/AGENT_GUIDE.md +31 -12
  6. data/docs/AGENT_SETUP.md +17 -10
  7. data/docs/AUTOMATIC_MAINTENANCE.md +222 -0
  8. data/docs/BACKEND_MATRIX.md +13 -7
  9. data/docs/CLIENT_HOOKS.md +1 -1
  10. data/docs/CONFIGURATION_REFERENCE.md +36 -26
  11. data/docs/CONSOLE_MCP_SETUP.md +10 -8
  12. data/docs/DOCKER_SETUP.md +15 -0
  13. data/docs/EVALUATION.md +10 -4
  14. data/docs/EXTRACTOR_REFERENCE.md +14 -2
  15. data/docs/FAQ.md +14 -3
  16. data/docs/GETTING_STARTED.md +18 -17
  17. data/docs/INCREMENTAL_EXTRACTION.md +8 -3
  18. data/docs/INDEX_LAYOUT.md +2 -2
  19. data/docs/MCP_SERVERS.md +28 -11
  20. data/docs/MCP_TOOL_COOKBOOK.md +1 -1
  21. data/docs/MCP_WORKTREE_SETUP.md +13 -1
  22. data/docs/PUBLISHED_INDEX.md +1 -1
  23. data/docs/README.md +2 -1
  24. data/docs/RETRIEVAL_GUIDE.md +57 -8
  25. data/docs/SOURCE_FRESHNESS.md +1 -1
  26. data/docs/TOKEN_BENCHMARK.md +16 -10
  27. data/docs/TROUBLESHOOTING.md +133 -37
  28. data/docs/UPGRADING_TO_2.md +9 -7
  29. data/docs/WATCH_DAEMON.md +172 -17
  30. data/docs/WHY_WOODS.md +9 -5
  31. data/exe/woods-console +13 -11
  32. data/exe/woods-watch +5 -0
  33. data/lib/generators/woods/watch_generator.rb +53 -0
  34. data/lib/puma/plugin/woods.rb +10 -0
  35. data/lib/tasks/woods.rake +14 -0
  36. data/lib/woods/cache/cache_middleware.rb +6 -0
  37. data/lib/woods/console/stdio_transport.rb +27 -0
  38. data/lib/woods/extractor.rb +25 -7
  39. data/lib/woods/git_command.rb +6 -7
  40. data/lib/woods/git_provenance.rb +4 -6
  41. data/lib/woods/mcp/bootstrapper.rb +3 -1
  42. data/lib/woods/mcp/initialization_guidance.rb +1 -1
  43. data/lib/woods/mcp/server.rb +41 -9
  44. data/lib/woods/retrieval/corpus_status.rb +46 -0
  45. data/lib/woods/retriever.rb +19 -7
  46. data/lib/woods/storage/local_corpus_stats.rb +32 -0
  47. data/lib/woods/storage/metadata_store.rb +20 -0
  48. data/lib/woods/storage/vector_store.rb +10 -0
  49. data/lib/woods/version.rb +1 -1
  50. data/lib/woods/watch/child_environment.rb +30 -0
  51. data/lib/woods/watch/cli.rb +91 -0
  52. data/lib/woods/watch/daemon.rb +55 -7
  53. data/lib/woods/watch/event_stream.rb +70 -0
  54. data/lib/woods/watch/guardian.rb +142 -0
  55. data/lib/woods/watch/installation/layout.rb +70 -0
  56. data/lib/woods/watch/installation/options.rb +128 -0
  57. data/lib/woods/watch/installation/planner.rb +128 -0
  58. data/lib/woods/watch/installation/probe.rb +101 -0
  59. data/lib/woods/watch/installation/receipt.rb +77 -0
  60. data/lib/woods/watch/installation/recovery.rb +64 -0
  61. data/lib/woods/watch/installation/templates.rb +58 -0
  62. data/lib/woods/watch/installation.rb +56 -0
  63. data/lib/woods/watch/lifecycle.rb +182 -0
  64. data/lib/woods/watch/managed_child.rb +113 -0
  65. data/lib/woods/watch/managed_cleanup.rb +48 -0
  66. data/lib/woods/watch/managed_process.rb +144 -0
  67. data/lib/woods/watch/puma_adapter.rb +87 -0
  68. data/lib/woods/watch/puma_child.rb +66 -0
  69. data/lib/woods/watch/supervision_records.rb +95 -0
  70. data/lib/woods/watch/supervision_status.rb +104 -0
  71. data/lib/woods/watch/supervisor.rb +161 -0
  72. data/lib/woods/watch/supervisor_reporting.rb +46 -0
  73. data/plugin/.claude-plugin/plugin.json +1 -1
  74. data/plugin/skills/woods-agent-enable/SKILL.md +1 -1
  75. data/plugin/skills/woods-diagnose/SKILL.md +77 -8
  76. data/plugin/skills/woods-investigate/SKILL.md +6 -6
  77. data/plugin/skills/woods-mcp-config/SKILL.md +28 -1
  78. data/plugin/skills/woods-setup/SKILL.md +58 -4
  79. metadata +35 -5
data/CHANGELOG.md CHANGED
@@ -7,489 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
- ## [2.0.0.beta4] - 2026-09-22
11
-
12
- ### Build
13
-
14
- - Allow the exact 1.6.3 maintenance tag and branch from the released 1.6.2 base while keeping publication disabled until a separately reviewed candidate SHA is pinned. Preserve all CI, immutable-artifact, package-test and protected-environment release gates.
15
-
16
- ### Documentation
17
-
18
- - Correct watcher retry guidance to include heartbeat-driven recovery, identify
19
- session identity corrections as available in 2.0.0.beta3, and align the
20
- coding-agent Docker synopsis with the container-first setup guide.
21
- - Scope orphan results to recorded relationships, document the limits of artifact
22
- permission defaults, correct Claude Code worktree registration guidance, and
23
- expose embedding-free lexical retrieval in the MCP tool cookbook.
24
- Update the distributed agent guides to identify Woods 2.0.0.beta3 capabilities and plugin 2.3.36 hook recovery accurately, while preserving installed-version and schema checks.
25
- - Clarify that beta/RC development retains the last prepared version, source
26
- candidates require Git revision evidence, and development reopening applies
27
- only after a final release. Release transitions and version ordering are unchanged.
28
-
29
- ### Fixed
30
-
31
- Accept ASCII case variants of the HTTP `Bearer` authentication scheme for Index and Console MCP. Token bytes, constant-time comparison, and existing delimiter rules remain unchanged. (#497)
32
- - Console SQL validation now distinguishes keyword grammar from identically named functions before applying the read-only function allowlist. SQLite application-defined keyword functions are rejected before execution; ordinary window, predicate, and numeric pagination syntax remains supported.
33
- - Report malformed generation marker shapes during embedded Index MCP startup as an actionable `ArgumentError` with the selected directory and published-layout guidance, matching executable preflight behavior. Preserve legacy flat and payload indexes and leave later generation-refresh error handling unchanged.
34
- - Make file-backed session clearing idempotent for Unicode and punctuated IDs, and discard expired history before appending or merging legacy and encoded files. This prevents expired events from becoming visible again through record, read, or listing; live history, disabled TTL, and retention limits retain their existing behavior.
35
- Dependency and dependent responses now disclose that published relationships are not exhaustive source-reference coverage, including compact and empty answers. Human witness labels say “witness types unambiguous” while preserving the JSON `typed_path_complete` contract. Successful traversal responses add `total_is_exact`; budget-limited text reports a root-inclusive lower bound and its cutoff reason, independently of pagination. (#470, #471)
36
- GraphQL parent metadata and summary chunks now read the selected declaration's superclass, rather than borrowing a nested or sibling class's parent. Compact qualified declarations preserve their explicit parent; implicit, dynamic, or unavailable parents remain unknown. Identifiers and dependency classification are unchanged.
37
- - Preserve the published generation when incremental extraction or named refresh encounters handled source errors. Failed consumers no longer replace last-good units with empty or partial output; watch retains the complete batch for retry.
38
- - Make missing-index startup headlines describe an unresolved published index instead of implying that atomic indexes require a root `manifest.json`; retain the examined path, layout explanation and existing-index-first remedy (#482).
39
- - Make `woods-mcp-start` resolve generation payload symlinks before checking their manifest, matching the library's index-root containment check. Preflight rejects escaping links and avoids manifest probes through broken links; contained links and symlinked index roots remain supported. The library already rejected escaping payloads before serving the index.
40
- - Clarify lexical retrieval responses with actual included-source and considered-candidate counts alongside the shortlist limit. Charge count text to the existing estimated context budget across full, compact, outline and scoped results, distinguishing no matches from matching candidates whose sources do not fit without changing ranking or MCP response structure.
41
- Stabilize mailer output across Rails processes by sorting action-name inventories consistently and labeling direct Proc defaults and callback filters by source location and callable kind. Preserve callback order, duplicate registrations and ordinary default value types without executing callables. Run a full extraction after upgrading to refresh retained mailer records; affected source hashes may change once. (#484)
42
- - Stabilize mailer object callback labels across boots by reusing the model extractor's guarded default-representation formatter. Preserve custom labels, literal hexadecimal text, callback order, duplicates and conditions; do not execute callbacks or change non-Proc defaults. Run a full extraction after upgrading to refresh retained mailer metadata; stored index schemas are unchanged.
43
- - Accept `WOODS_OUTPUT` after the explicit path and `WOODS_DIR` in Index MCP startup, while preserving the launcher's no-path error. Missing-index diagnostics now name the examined directory, describe atomic and legacy layouts, and suggest pointing at an existing index before re-extracting.
44
- - Recommend explicit embedding-free lexical mode in no-provider startup and retrieval messages, including the required MCP environment change and restart; semantic defaults and typed errors are unchanged.
45
- - Reconcile Rake tasks across all contributing files on incremental changes and deletions, retaining surviving definitions and removing obsolete source from shared tasks.
46
- Scope PORO and lib `parent_class` metadata and source annotations to the selected declaration. Nested or sibling error classes no longer supply an unrelated superclass; implicit parents remain nil and explicit constant-path parents retain their names (#474).
47
- - Reject pgvector HNSW `vector` widths above 2,000 before database writes, and reject invalid migration dimensions before creating files. Correct the 3,072-dimensional generator example and document explicit provider output sizing; no vector truncation or implicit conversion is performed (#524).
48
- - Describe prepared prereleases without claiming RubyGems publication from VERSION alone, and retain the published 1.6.2 maintenance changelog so generated stable-version guidance stays accurate.
49
- - Preserve actual GraphQL and gem-source types in `Woods::PublishedIndex` typed lookup and enumeration while retaining the `graphql` and `rails_source` family aliases (#518).
50
- - Enforce `graph_analysis`'s advertised default of 20 rows per section and retain total/offset context on last and empty pages in every renderer. Correct the structure glossary: graph nodes include isolated units, and retriever entry counts depend on mode and store coverage (#519).
51
- - Coordinate agent configuration apply and recovery on shared managed targets across application roots, preventing concurrent user-scoped installations from overwriting each other. Keep application-specific receipts, refuse stale plans, and report/protect every coordination path in previews (#520).
52
- - Apply JSON temporal history limits after matching the requested unit, preserving access to retained records across gaps and deletions with SQLite parity (#521).
53
- - Reconcile reused in-memory vector and metadata stores during full embedding rebuilds, removing vanished units and publishing empty corpora without changing incremental purge guards. Failed embedding preserves the previous promoted dump and checkpoint (#522).
54
- Validate persisted JSON snapshot shapes before reading history or capturing the next snapshot. Unusable nested unit records and summary fields now warn and skip the entire snapshot, preserving valid legacy records, corrupt-file retention, and caller SHA validation (#492).
55
- - Use the same JSON snapshot validity checks for reads and retention: reject filename/content SHA mismatches before selecting a capture baseline, and prune corrupt files before valid legacy snapshots with missing or null timestamps. Timestamp-less snapshots remain eligible for ordinary oldest-first retention.
56
- Expose the paginated dependencies/dependents payload in `structuredContent.data` with every renderer, including packaged stdio and HTTP defaults, while preserving rendered text and existing tool arguments (#481).
57
- - Stabilize direct Proc/lambda model validation option values in extracted and published metadata using callable kind and source-location labels, without executing them. Preserve validation order, duplicates, conditions and non-Proc values; nested containers are not recursively normalized. Run a full extraction to refresh retained validation metadata; index schemas are unchanged.
58
- - Reconcile registered initializers deleted during watch downtime with a full extraction after a fresh environment boot, preserving whole-application runtime facts.
59
- - Keep the stable extraction guard and output directory after `woods:clean`, so a concurrent writer can finish acquiring its lock safely.
60
-
61
- ### Security
62
-
63
- - Redact protected Console fields before serialization, normalize the remaining values to their JSON-compatible representation, and redact and scan again before JSON or Markdown rendering. Symbol values and custom serializers cannot introduce unscanned credentials in the final response, and protected serializers remain uncalled. The direct credential scanner also scans Symbol values while preserving their type.
64
- - Check resolved relations against the Console blocked-table policy before default model tools fetch records or counts, including association parent lookups; reuse the checked relation so dynamic default scopes are evaluated once.
65
- - Refuse unsupported SQLite identifier and table-reference syntax before Console SQL execution, preserving blocked-table and function policies; ordinary bare and simple quoted identifiers remain supported.
66
- - Preserve blocked-table visibility after subqueries and parenthesized JOIN predicates, including quoted aliases, across supported SQL dialects.
67
-
68
- ## [2.0.0.beta3] - 2026-09-18
69
-
70
- ### Added
71
-
72
- - Optional `changelog/<type>_<slug>.md` entry files let parallel branches record changes without editing the same Unreleased block. `release:prepare` validates, folds, and removes consumed entries; release validation refuses leftover entries at the tagged SHA (B-179).
73
- - Add optional `volatile_dependency_limit_per_target` to keep one hot dependency
74
- from filling the volatility report (B-188). Apply the per-target edge cap before
75
- the global top 20, preserve the default output, and expose the configured cap
76
- and reported count alongside the full qualifying count when enabled.
77
-
78
- - Gate retrieval quality in CI with a versioned Canopy runtime corpus, captured
79
- real MiniLM vectors, per-strategy quality floors, latency observations, and
80
- exact output-token counts. Preserve known failed queries in the evidence (#227).
81
- - Bound `dependencies` and `dependents` traversal work independently of response
82
- pagination, with configurable node/edge budgets and explicit partial-result
83
- reasons. Filtered edges and reverse relationship checks consume the edge
84
- budget; complete small results retain their existing shape (#311).
85
-
86
- - Document the published filesystem layout for non-Ruby consumers, with pinned
87
- Bash/jq and Python reads, retention-race handling, and structural snapshot
88
- publication guidance (#306).
89
-
90
- - `WOODS_WATCH_TRUST_FOREIGN_HOST=1` lets watch status, incremental/clean guards,
91
- daemon startup checks, and MCP trust fresh foreign-container heartbeats without
92
- checking an unrelated local pid. This is opt-in; foreign records expire after
93
- 15 minutes, and malformed or excessively future timestamps are rejected (#321).
94
-
95
- - `WOODS_WATCH_POLL_INTERVAL` configures positive, finite seconds between watch
96
- polling scans, including fallback from native watching (default 1.0; #322).
97
-
98
- - Published manifests record `woods_version`, the last publisher's gem version,
99
- for Rails extraction and static self-maps. MCP exposes it independently of the
100
- reader version, and `woods:validate` gives nonfatal warnings for malformed
101
- writer versions or different major versions. Older manifests remain valid
102
- without this optional provenance field (#323).
103
- - Supply concise, capability-aware Index MCP instructions through initialization and modern discovery, with status-first retrieval guidance, bounded traversal and source verification. Preserve the SDK's omission for protocol `2024-11-05`; tool registration and authorization remain unchanged. (#402)
104
- - Added generation-bound source-input evidence with bounded `current` / `drifted` / `unknown` status, per-consumer incremental provenance, private keyed content identities, and a fresh-process `woods-extract` launcher. The opt-in session hook now checks source content through the shared no-environment status task. Old indexes and unproved boot/runtime consumption remain explicitly unknown.
105
- Add separately opt-in, bounded Claude orientation and post-edit candidate context from a retained published generation, with explicit uncertainty, repeat suppression, independent refresh controls, and an installed `woods-hook-context` helper.
106
- Explicit Claude and OpenCode edit adapters preserve every affected patch path,
107
- including both rename sides, through the shared durable refresh queue. Native
108
- OpenCode registration is optional and version-pinned; unsupported or escaping
109
- events produce diagnostics without claiming refresh.
110
- MCP `search` now reports whether its returned matches exhaust the requested domain, prove an additional match, or leave the remainder unknown after a scan budget or regex timeout. Bounded lookahead shares the existing scan budget, deep reads preserve typed identities, and detected artifact corruption remains an error. JSON and text formats distinguish returned counts from exact totals; agent guidance explains how to narrow partial searches. Mixed Rails/gem source directories remain searchable and loadable by lexical retrieval while rejecting unrelated type mismatches.
111
- `woods:validate` now checks semantic graph invariants against typed unit indexes and artifacts within one pinned published generation. It detects broken reverse membership, invalid sources, duplicate typed variants, file/type index drift, and missing indexed nodes while accepting cycles, unresolved targets, legacy string edges, and the Woods static source map. Validation remains read-only, preserves existing report/exit behavior, and reports actionable identities without repairing the graph.
112
-
113
- The validator, deep search, and lexical retrieval accept all four GraphQL unit types in the shared `graphql/` directory, preserving actual typed identities and existing directory-family search labels.
114
- `dependencies` and `dependents` accept optional `explain: true` to retain typed source ownership, original edge direction and relationship attributes, and bounded shortest witnesses. Explanations distinguish direct records from transitive reachability, preserve unknown labels and ambiguous target candidates, and retain ancestor context across pages. Existing compact responses remain unchanged; explanation work shares traversal budgets.
115
- Publish additive `reverse_via` target buckets with typed source identities, relationship labels and association attributes, preserving existing `reverse` arrays and legacy graph loading.
116
- - Mark whole-file caching, configuration, test mapping, Rails source, and gem source graph nodes and typed variants with `kind: "file_profile"`, preserving file membership and identifiers while letting consumers distinguish profiles from constant-owned units (#417).
117
- Add `woods-agent-config` for explicit Claude Code project/user setup, update, and removal. Preview saves one private edit plan; apply checks its original snapshots, preserves unrelated configuration, tracks owned entries and instruction sections, and supports recovery after interrupted writes. Host/Compose preflight checks the installed Index Server and published index.
118
- - Add opt-in compact source evidence and declared API outlines to retrieval and lookup, with complete published spans, explicit omissions, honest generation/source provenance, and typed SHA-guarded full-source follow-up. Existing full-source behavior remains the default.
119
- - Add explicit embedding-free lexical retrieval over published extraction units. Set `WOODS_RETRIEVAL_MODE=lexical` for Index MCP or `config.retrieval_mode = :lexical` for Ruby builders; field-aware ranked results preserve typed identity, generation consistency and budgeted matching evidence without provider or vector access. Semantic retrieval remains the default.
120
- - Add explicit package and application-relative source-path scopes to ranked retrieval and discovery, with eligibility before candidate limits, typed scope metadata, and native scoped vector searches without re-embedding.
121
-
122
- ### Fixed
123
-
124
- - Reflect model callbacks from Rails' per-event chains instead of nonexistent
125
- per-kind readers; include `before_commit` and keep `callback_count` equal to
126
- the emitted callback list. Preserve framework callbacks with stable Proc/lambda
127
- source-site labels and address-free default callback-object descriptions in
128
- metadata and chunks, including Rails 6's `raw_filter`, so separate-process
129
- extractions of unchanged models remain equivalent. Preserve custom object labels.
130
-
131
- - Explain the Zeitwerk 2.6.9 naming requirement in identifier-collision errors
132
- and upgrade guidance before suggesting changes to valid namespace wrappers
133
- on older-loader or classic-mode hosts (B-149).
134
-
135
- - Stabilize controller inline callback and condition labels across processes and
136
- checkout paths, including action chunks; retain `unless` conditions in the
137
- generated filter-chain header (B-167).
138
-
139
- - Publish metadata-only changes in local embedding snapshots without re-embedding
140
- unchanged source; retain no-op dumps and source-hash checkpoints (B-119).
141
-
142
- - Search Boolean metadata fields as `true`/`false` consistently in SQLite and
143
- InMemory, preserving numeric `1`/`0` and null semantics (B-199, #356).
144
-
145
- - Keep the default SQLite metadata database inside the effective `WOODS_OUTPUT`
146
- directory for embedding tasks, isolating indexes while preserving explicit
147
- database overrides (B-156). Existing databases are not moved; run `woods:embed`
148
- for the selected index after upgrading.
149
-
150
- - Treat non-object JSON snapshots and files removed or made unreadable during
151
- a read as absent across lookup, listing, diffs, and unit history (B-158).
152
-
153
- - Count corrupt and unreadable SHA-named JSON snapshots toward retention and
154
- evict them before valid history, preserving the just-captured snapshot and
155
- unrelated files (B-163).
156
-
157
- - Count the final retrieval context after formatting and type-rank metadata,
158
- keeping `tokens_used` and its trace consistent with the configured counter
159
- or estimate (B-197, #354).
160
-
161
- - Order tied hybrid retrieval candidates deterministically before graph seed
162
- selection, truncation and reciprocal-rank fusion across supported Ruby versions
163
- (B-192).
164
-
165
- - Bound OpenAI embedding requests to 36 valid inputs, preserving chunk order and
166
- rejecting partial or dimension-inconsistent batches (B-157). Chunk-heavy runs
167
- use more HTTP requests to stay within the API's input and total-token limits.
168
-
169
- - Match embedded NUL literally in SQLite metadata searches, including substrings after NUL; preserve ASCII case folding and literal wildcard characters (B-198, #355).
170
-
171
- - Prevent single-component cache keys from colliding with multi-component or
172
- empty keys by uniformly length-prefixing components (B-155). Custom callers
173
- using persistent single-component keys should clear that cache domain on upgrade.
174
-
175
- - Make middleware argument metadata, generated source and hashes stable across Rails processes by describing runtime identities structurally while preserving literal and nested configuration (#362).
176
-
177
- - Omit per-unit git enrichment for shallow checkouts or unverifiable repository
178
- depth, with one warning and full-history recovery guidance, instead of
179
- reporting truncated commit counts as complete churn data (B-189).
180
-
181
- - Validate direct/legacy Console scope arrays with the active SQL dialect and
182
- MySQL session quote modes, refusing subqueries hidden by mismatched quote
183
- stripping while retaining the supported tools' narrower scope grammar (B-154).
184
-
185
- - Read and clear legacy Redis session indexes before any new record without
186
- `WRONGTYPE`; atomic SET/ZSET access tolerates concurrent index migration
187
- and keeps reader-only upgrades compatible with older SET writers (B-162).
188
-
189
- - Allow full extraction when ActionMailer is absent and skip non-app mailers
190
- instead of publishing empty units at fabricated paths; share that ownership
191
- gate with incremental class discovery (B-153).
192
-
193
- - Repair corrupt pipeline cooldown state on an explicit all-reset, including
194
- `pipeline_repair` in custom operator-configured servers; ordinary reads still
195
- deny operations and scoped resets preserve corrupt state (B-159).
196
-
197
- - Normalize incremental change paths before deduplication and dispatch, including
198
- trailing root slashes, repeated separators and dot segments (B-148).
199
-
200
- - Load lazy Rails routes before caching navigation helpers, preserving view-to-controller dependencies during fresh-process incremental extraction (#360).
201
-
202
- - Reflect model methods after schema loading so full and incremental runs agree
203
- on Rails-generated constructors while preserving application overrides (B-202, #363).
204
-
205
- - Parse job `perform_params` and shared `initialize_params` from Ruby parameter
206
- syntax, avoiding phantom names from keyword/default expressions while preserving
207
- the existing metadata fields and named rest/block arguments (B-151).
208
-
209
- - Resolve ERB-backed Solid Queue recurring schedules using Rails configuration
210
- loading, including relative requires, conditional entries, aliases and custom
211
- environment sections (B-203, #364).
212
-
213
- - Preserve navigation edges for real named routes such as `file_path`,
214
- `image_url`, `download_path`, and `root_path`; unresolved asset/filesystem
215
- helper names still produce no edge (B-152).
216
-
217
- - Resolve and track app-owned nested model mixins through runtime source locations, refreshing includer source and callbacks on incremental edits (B-150, #361).
218
-
219
- - Explain the full-extraction recovery for runtime job removals and bundle
220
- upgrades; missing gem-path warnings now include the bundle-update remedy
221
- without changing incremental discovery rules (B-165, B-166).
222
-
223
- - Preserve all reverse dependencies on symbolic external targets such as `http_api` after incremental graph reloads and re-registration (B-193, #305).
224
-
225
- - Preserve changes to nested `extracted_at` metadata when deciding whether to
226
- rewrite a unit; only Woods' top-level extraction stamp is ignored (B-147).
227
- - Read per-unit git enrichment in one streamed HEAD history walk instead of
228
- repeated 500-path batches (B-195, #305). Merge commits compare with their first
229
- parent while all HEAD ancestry is visited; counts can change from legacy
230
- pathspec simplification. Optional enrichment now requires Git 2.31 or newer;
231
- incomplete/failed history is omitted with a warning. Run full extraction
232
- after upgrading to refresh retained metadata. Host speedup remains unmeasured.
233
-
234
- - Apply the same app-owned path exclusions to full and incremental git enrichment;
235
- external, vendored, and node_modules units no longer gain empty git metadata (B-194).
236
-
237
- - Prune default excluded package directories before recursively discovering
238
- `package.yml`, avoiding scans of indexes and snapshots under `tmp/` (B-196, #305).
239
-
240
- - Preserve namespaced and CamelCase graph-retrieval subjects, keep snake_case
241
- lookup working, and exclude tracing instructions from fallback metadata searches (B-190).
242
-
243
- - Order equal PageRank scores by identifier before assigning retrieval importance
244
- percentiles, making their ranking weights deterministic across Ruby versions
245
- and graph insertion orders (B-191).
246
-
247
- - Reduce payload-seed metadata lookups by classifying each entry once. Preserve
248
- per-file hardlink/copy fallback, atomic publication and generation retention
249
- while reducing full and incremental seed overhead (#305).
250
-
251
- - Keep runtime trace evidence for same-name instance and singleton methods separate, including inherited singleton owners and caller kinds. Legacy untyped events enrich instance methods only; re-record old singleton traces.
252
-
253
- - Ruby trace enrichment now records the nearest observed calling Ruby method
254
- instead of the callee receiver. Recording keeps separate fiber stacks, handles
255
- recursive and unwound calls, and leaves outside-recording callers unknown
256
- without binding or backtrace inspection (#309).
257
-
258
- - Scope per-file git metadata to HEAD history instead of every ref, excluding
259
- unmerged branches and tool checkpoints from churn and authorship (#319). Run
260
- a full `woods:extract` after upgrading to refresh previously published metadata.
261
-
262
- - Full extraction no longer creates empty type directories at the index root
263
- before publishing its generation payload. Existing root directories and legacy
264
- flat-index files are preserved (#320).
265
-
266
- - Watch startup reconciles environment-boot-covered restart inputs with one full
267
- extraction instead of repeatedly exiting 75. Live changes and changes during
268
- environment initialization still require restart; failed reconciliation and
269
- deleted restart inputs survive retries and supervisor restarts. Built-in
270
- watchers establish detection before startup extraction begins (#318).
271
-
272
- - Add `console_mcp_http_enabled` (default `true`) so stdio-only Console setups
273
- can explicitly disable HTTP and its boot-time token warnings. Production
274
- still refuses to boot without a token when HTTP Console is enabled (#304).
275
-
276
- - Solid Cache conditional writes on MySQL no longer claim ownership of a
277
- pre-existing row when the adapter reports matched rows as affected rows.
278
- Validate ownership using the per-attempt serialized payload, with live MySQL
279
- contention/recovery coverage and documented crash/eviction limits (#228).
280
- - Keep Notion model/column/migration data and Unblocked full/partial documents tied to their exact extracted type. Refuse incomplete export reads before mutation, and preserve remote documents when same-name, same-file types cannot be represented safely by Unblocked's existing URI scheme.
281
- Preserve the selected extraction type when framework search and recent changes read colliding identifiers. Session controller lookup and root outgoing-edge selection now retain the controller type. Public untyped lookup, downstream references and the bare-name multi-step context pool retain their existing contracts.
282
- Obsidian export preserves same-name units of different types, their separate outgoing links, and original public identifiers. Collision-bearing vaults publish a version-2 typed manifest; ordinary version-1 manifests and note paths remain unchanged. Ambiguous bare targets are omitted with a diagnostic, and incomplete typed reads cannot trigger stale-note deletion.
283
- - Reject ambiguous cross-type dependencies in session context with an actionable `ambiguous_identity` MCP error instead of silently selecting a source or reusing another type's context key. Leave unresolved controllers in the timeline without a misleading source reference. Pin candidate discovery and all assembly reads to one generation; retain typed controller roots, metadata-only timelines, and successful response shapes. Refs #213; this does not migrate global identifiers.
284
- - Refresh Console credential indexes from a fresh encrypted-file and key snapshot, retain the last valid index when refresh fails, and update all live embedded servers without retaining abandoned servers. Each response scan uses one complete credential index.
285
- Keep credential scanner refresh snapshots limited to live weakly referenced
286
- scanners on older Ruby versions, avoiding unsafe receiver access after garbage
287
- collection while preserving updates to every live Console server.
288
- - Refuse incomplete native embedding input before changing stores or checkpoints, retaining legacy flat-index support while rejecting malformed JSON. A source-empty unit now retires superseded vectors and checkpoints its no-content state without calling the embedding provider (#442, #444).
289
- - Terminate the evaluation command's owned process group on timeout, including ordinary descendants whose parent has already exited.
290
- - Let only the hook that removes a recorded dead-owner marker replace its mkdir lock, so concurrent recovery does not leave all edits queued behind an abandoned empty directory.
291
- - Refuse Obsidian exports that would overwrite unmanaged notes, indexes, settings, or sidecars. Preflight all destinations, record generated asset digests separately from the public manifest, and suppress sweeps on conflicts or write failures. Legacy assets are adopted only when byte-identical; changed legacy sidecars require inspection and backup or a fresh export directory.
292
- - Coordinate `Woods.extract!` and `Woods.extract_changed!` with task/watch writers and raise on lock timeout or failed generation publication, so background jobs can retry unsuccessful extraction.
293
- - Accept Rails 6.0 positional middleware options on Ruby 3 while preserving explicit keyword precedence, required bearer tokens, and unknown-option refusal. Add an installed-gem CI contract for all advertised direct runtime dependency floors, with a constrained Rails 6.0.0 fixture and recorded transitive resolution.
294
- - Honor configured context-token defaults in Builder-created semantic/lexical retrieval, caches, and MCP while preserving explicit budgets and the legacy custom-collaborator fallback. Deprecate the inert similarity_threshold option with a warning instead of changing ranking behavior (#446).
295
- - Preserve the dispatched controller's runtime class name in session traces, with a Rails-inflector fallback, so acronym namespaces retain source context in `session_trace`.
296
- - Preserve each logical directory alias in polling and startup catch-up so an earlier irrelevant alias cannot hide an extraction input such as `app/models`. Detect cycles per traversal branch while retaining ignored-subtree pruning.
297
-
298
- ### Changed
299
-
300
- - Clarify the existing search-regex timeout exclusion: Ruby 3.0/3.1 remain
301
- supported without a per-match time bound. Run the Index MCP process on Ruby
302
- 3.2+ for the one-second per-match limit; no runtime mitigation was added
303
- for older interpreters (B-161).
304
-
305
- - Reduce payload-clone allocation and traversal overhead while preserving
306
- hardlinks, copy fallback and immutable generation ownership (#305).
307
- - Make extraction profiling additive: report git enrichment, reconciliation,
308
- finalization, pointer publication and retention separately, with a distinct
309
- whole-run wall-time line (#305).
310
- - Expand opt-in refresh hooks to the shared extraction input rules, including
311
- services, controllers, jobs, views, locales, and supported tests/lib paths.
312
- Restart inputs request fresh full extraction. Preserve queued edits across
313
- failures and daemon deferral, bound the worker lifetime, and carry JSON batches
314
- through Docker command prefixes without host-bundle or environment-forwarding
315
- assumptions. Existing manual incremental and watch behavior is unchanged.
316
- Add a trusted, disabled-by-default release profile for the supported 1.6.2 security maintenance line. Publication requires a separately reviewed exact candidate SHA pin on main, all fixed maintenance CI rows, and the existing immutable artifact and protected publication safeguards. The v2 release path retains its existing requirements.
317
-
318
- ### Documentation
319
-
320
- - Clarify that source literals and optional session traces can contain sensitive information, and correct the session FileStore configuration example.
321
- - Qualify the `~> 2.0` installation examples for stable releases and direct prerelease adopters to the exact published version in the README release table. Keep agent setup and plugin guidance aligned with installed-version capabilities; clarify that the refresh-hook deadline starts after complete event input is collected and queued.
322
-
323
- ### Testing
324
-
325
- - Raise the default-suite CI line-coverage floor from 85% to 90%, calibrated against current local and CI measurements. Branch coverage remains measured without a gate; per-file floors and combined opt-in coverage remain separate work.
326
-
327
- ## [2.0.0.beta2] - 2026-09-10
328
-
329
- ### Added
330
-
331
- - **`release:prepare` runs a live preflight before printing the tag and dispatch
332
- commands.** The new `release:preflight` task (also runnable standalone) checks, via
333
- `gh api`, that the live `release` environment still requires review and disallows admin
334
- bypass; that `REQUIRED_CI_JOBS` in `script/validate-release-run` still matches
335
- `ci.yml`'s job names; and that both `release.yml` download-artifact steps set
336
- `merge-multiple: true`. Every check is advisory: a missing or failing `gh` skips with a
337
- note rather than blocking prepare. CONTRIBUTING.md and the release-flow skill also gained
338
- a "when a dispatch fails" guide (which failures a main merge alone fixes versus which need
339
- the tag moved, and only before publication) and the documented final step for creating the
340
- GitHub Release entry by hand, which the workflow deliberately never automates.
341
- - **A release_v2 spec greps `spec/` for hard-coded current-version literals** (`'= 2.0.0'`,
342
- `"2.0.0\n"`, `gem_version: '2.0.0'`-shaped strings), built from `Woods::VERSION`'s base, so
343
- the next version bump cannot leave one behind the way the beta1 cut did.
344
- - **`WOODS_PROFILE=1` logs a timing line per extraction phase.** The per-extractor
345
- lines already reported extraction itself; everything after it (payload seed, previous
346
- graph load, eager load, blast radius, re-extraction, type index, graph analysis, flows,
347
- manifest and summary, publish) was unattributed, so a slow run could only be split by
348
- guessing. One `[Woods] [profile] <phase> in N.NNs` line per phase, on the monotonic
349
- clock. Off by default and free when off.
350
- - **`durable_payload_writes` restores the per-file `fsync` on payload files.** Boolean,
351
- default `false`. Off is not the weaker setting: readers resolve only through
352
- `generation.json`, and every publish now flushes the whole payload before writing that
353
- pointer, so the contract holds either way. Turning the key on buys exactly one thing, an
354
- individual payload file being durable before the pointer exists, and pays two forced
355
- flushes per file (about 8.9ms each on btrfs) for it. It cannot disable the publish
356
- flush, which has no opt-out.
357
-
358
- - **`incremental_blast_radius_depth` bounds how far an incremental run re-extracts.**
359
- `extract_changed` walked the unbounded transitive dependent closure of every changed
360
- file, so one edit to a widely referenced unit re-extracted most of the app. The new key
361
- caps the walk at N reverse hops (`nil`, the default, keeps the unbounded closure). A
362
- unit outside the cap keeps its content and still gets its `dependents` list refreshed by
363
- the run's second pass, which the equivalence harness now covers under a cap of 1. The
364
- default stays unbounded on purpose: an STI grandchild inherits its grandparent's
365
- associations, validations and callback chain while sitting two hops away in the graph,
366
- and nested `has_many :through` resolves the same way, so a host opts in for a tree it
367
- knows has neither. On a 200-service chain in the dummy app, one leaf edit re-extracts
368
- 200 units unbounded and 2 under a depth of 1.
369
-
370
- ### Changed
371
-
372
- - **Payload durability moved from every file to the generation pointer.** `AtomicFile.write`
373
- fsynced the temp file and the containing directory for every file it wrote, so a full
374
- extraction of a large application paid two forced flushes 8323 times: 71.3s of the write
375
- phase for 8000 files on btrfs, against 1.0s for one filesystem flush. Payload writes (unit
376
- files, type indexes, the dependency graph, the graph analysis, the manifest, the summary,
377
- the flow documents) now skip both, and `publish_generation` calls the new
378
- `AtomicFile.sync_directory_tree` on the payload directory immediately before writing
379
- `generation.json`.
380
-
381
- The guarantee that replaces the old one: **when `generation.json` is durable, every file
382
- in the payload it names is durable.** What is given up is an individual payload file being
383
- durable before the pointer exists, and nothing reads a payload file in that window, since
384
- every reader resolves through the pointer and a crash there leaves an unreferenced partial
385
- payload the next run prunes. `generation.json` itself, the watch daemon's status file, the
386
- update check cache, the Obsidian and Unblocked exports, Notion sync state, temporal
387
- snapshots, embedding checkpoints and MCP task records all keep the per-file fsync: their
388
- readers do not go through the pointer. The gem mapper's self-map publishes through the
389
- same pointer but keeps its per-file fsync for now; it is a small payload and adopts the
390
- single flush in a follow-up.
391
-
392
- `sync_directory_tree` tries `syncfs(2)` through Fiddle, then `sync -f <dir>`, then a bare
393
- `sync`, then an `fsync` on every file in the tree, and returns which one ran. The last
394
- resort is what keeps this honest: the chain never silently does nothing, it only gets
395
- slower. Fiddle is required inside a rescue and stays out of the gemspec, since it is a
396
- bundled gem from Ruby 3.5. `bench/atomic_write_bench.rb` measures all four modes.
397
-
398
- - **Seeding a payload creates each directory once rather than once per file.**
399
- `PayloadStore#clone` ran `FileUtils.mkdir_p` before every file it replicated.
400
- `Pathname#find` visits a directory before its children, so the directory branch had
401
- already created every parent a file could need. 8001 files across four type directories
402
- on btrfs: 0.906s before, 0.847s after.
403
-
404
- - **Cycle detection is capped, and says so.** `GraphAnalyzer#analyze` runs on every
405
- extraction regardless of the change set, and enumerating every cycle was the largest
406
- part of it: one cycle per DFS back-edge, uncapped in count and in length, each
407
- canonicalized by joining a path that on a deep DFS is thousands of nodes long. Two new
408
- config keys bound it, `graph_cycle_limit` (default 500) and `graph_cycle_max_length`
409
- (default 50, in distinct nodes); either firing sets the new
410
- `stats.cycle_limit_reached` in `graph_analysis.json`. Set either to `nil` to remove
411
- its cap and restore exhaustive enumeration. Signatures are now keyed by a digest of
412
- the rotated cycle rather than the joined path. On a synthetic 8201-node graph, cycle
413
- detection went from 8.6s to 0.4s.
414
- - **Bridge detection reuses its work.** `bfs_shortest_path` carries parent pointers
415
- instead of enqueueing a copy of the path so far for every node it reaches, and forward
416
- adjacency resolves through a per-analyzer memo instead of being re-derived on each of
417
- the 200 sampled traversals. Output is unchanged. On the same graph, bridges went from
418
- 2.2s to 0.6s and the whole report from 3.1s to 1.0s.
419
- - **Flow assembly caches loaded units and parsed sources.** One `FlowAssembler` serves a
420
- whole precompute run, but nothing was cached across it: a unit's JSON was re-globbed
421
- and re-parsed on every expansion, and its source re-parsed once per action of every
422
- controller that reached it. Three per-instance LRU memos (unit data, whole-source AST,
423
- and the method index derived from it) bound at 1000 entries each. On 435 controllers x
424
- 7 actions over 3000 services, precompute went from 20.5s to 3.8s.
425
- - **The manifest and `SUMMARY.md` read each type index once.** Both derive their totals
426
- from the per-type `_index.json` files and run back to back at the end of every
427
- incremental run, so every index was globbed, read and parsed twice. An unreadable index
428
- still drops that type from both, now with one warning instead of two.
429
- - **The incremental flow refresh is scoped to the flow assembly radius.** Flows were
430
- reassembled for every controller in the run's touched set, and the touched set is the
431
- graph's whole reverse closure, so one leaf edit re-ran flow assembly for nearly every
432
- controller in the app. A flow document reaches `FlowPrecomputer::DEFAULT_MAX_DEPTH`
433
- units, so the run now walks the pre-change graph to that same depth and reassembles
434
- only the controllers inside it. Controllers outside it carry their
435
- `metadata[:flow_paths]` annotation forward out of the previous flow index rather than
436
- losing it. A targeted `refresh`, a routes re-run, and a controller whose action set no
437
- longer matches the index all still reassemble in full. On 50 controllers over a
438
- 50-service chain in the dummy app, one edit at the far end went from 50 controllers
439
- reassembled to 3 plus 47 carried.
440
- - **`graph_sha` is digested from the bytes written.** `graph_analysis.json`'s digest of
441
- `dependency_graph.json` came from reading the file back off disk, one whole-file read
442
- per run of an artifact that on a large app is tens of megabytes, to digest bytes the
443
- run had just serialized. The value is unchanged.
444
-
445
- ### Fixed
446
-
447
- - `TraceEnricher.record` rejects calls without a block before creating a
448
- TracePoint, preventing an enabled hook from leaking into subsequent execution (#308).
449
-
450
- - **The Changed list only names the surface inventory when regenerating it actually moved
451
- it.** `release:prepare` used to list `.Codex/release-v2/surface-inventory.json`
452
- unconditionally, even on the ordinary run where nothing in the public surface changed.
453
- - **The release banner no longer links an upgrade guide that does not exist yet.** A major
454
- version bump past `docs/UPGRADING_TO_2.md`'s own major derived a
455
- `docs/UPGRADING_TO_<major>.md` link without checking the file exists.
456
- - **Changelog entries merged from a duplicate heading no longer carry a stray blank line.**
457
- Two occurrences of the same `###` heading in one `## [Unreleased]` cycle folded into a
458
- release section with a blank line between their bullets, splitting one list into two; they
459
- now join tight.
460
- - **Release validation names the live-backends CI job as it is called.** The release
461
- validator required a CI job named `Live backends (pgvector + Qdrant + Solid Cache)`,
462
- but the job gained `+ Redis` in its name, so every release dispatch failed at
463
- release-context with nothing published. The prefix now matches, and a spec checks every
464
- required contract job against the names in `ci.yml` so a rename cannot drift again.
465
- - **Release candidate jobs find the downloaded artifact.** `actions/download-artifact` with
466
- `artifact-ids` extracts into `dist/<artifact-name>/`, so the digest check and the install
467
- in `dist/` failed with a missing file on every dispatch since the switch to artifact ids.
468
- Both downloads now set `merge-multiple: true`; the workflow spec requires it.
469
- - **Release candidate tests accept a prerelease version.** The clean-install smoke specs
470
- pinned `2.0.0` as a literal in the dummy app's Gemfile, the loaded-version check, and a
471
- snapshot fixture, so the first prerelease failed them (Bundler never resolves a prerelease
472
- from an unpinned requirement). They now use `Woods::VERSION`. The candidate host also
473
- installs `webrick` so `woods-mcp-http` finds a Rack handler on Rubies that no longer ship one.
474
- - **Inspector contract specs track the pinned `@modelcontextprotocol/inspector` version instead
475
- of a literal.** Bumping the dev dependency to 2.6.0 fixed the SDK bug where Inspector sent a
476
- legacy `logging/setLevel` call after negotiating the modern 2026-07-28 protocol, so the two
477
- `pending` stdio/HTTP examples in `mcp_inspector_contract_spec.rb` asserted a stderr message
478
- that no longer occurs. Those examples now assert a clean modern handshake, and every version
479
- literal in that file reads from `package.json` instead. `sdk_dependency_spec.rb`'s deliberate
480
- version-and-integrity trip wire is bumped to match the new pin.
481
-
482
- ## [2.0.0.beta1] - 2026-09-09
10
+ ## [2.0.0] - 2026-09-23
483
11
 
484
12
  ### Added
485
13
 
486
- - **`WOODS_GIT_DIR` names the canonical git directory outright.** It wins over
14
+ - **`WOODS_GIT_DIR` selects a Git directory and its HEAD explicitly.** It wins over
487
15
  whatever repository Woods would otherwise find, at all three of Woods's git
488
16
  call sites: per-unit enrichment, `manifest.json` provenance, and the
489
17
  `woods:incremental` diff range. All three build their command line with the
490
- new `Woods::GitCommand.argv`. This is the escape hatch for a container that
491
- can mount the canonical git directory but not the host path a linked
492
- worktree's `gitdir:` pointer names.
18
+ new `Woods::GitCommand.argv`. For a container with a relocated complete Git
19
+ layout, select the linked worktree's `worktrees/<id>` directory, deriving
20
+ the ID from Git metadata. Selecting the shared root instead selects the
21
+ primary checkout's HEAD. A same-path mount needs no override.
493
22
  - **Database-partition layer for multi-database apps (#280).** Model units record
494
23
  `metadata[:database]` from `connection_db_config` (Rails 6.1+, `nil` on 6.0), so a model
495
24
  that inherits `connects_to` from an abstract class reports the inherited database.
@@ -696,6 +225,97 @@ Add a trusted, disabled-by-default release profile for the supported 1.6.2 secur
696
225
  - `Woods::ChangeSet` — one normalization of "what changed" (absolutize, de-duplicate, split
697
226
  present from vanished) shared by every entry point, so the git-diff caller and the watch
698
227
  daemon can't drift apart.
228
+ - **`release:prepare` runs a live preflight before printing the tag and dispatch
229
+ commands.** The new `release:preflight` task (also runnable standalone) checks, via
230
+ `gh api`, that the live `release` environment still requires review and disallows admin
231
+ bypass; that `REQUIRED_CI_JOBS` in `script/validate-release-run` still matches
232
+ `ci.yml`'s job names; and that both `release.yml` download-artifact steps set
233
+ `merge-multiple: true`. Every check is advisory: a missing or failing `gh` skips with a
234
+ note rather than blocking prepare. CONTRIBUTING.md and the release-flow skill also gained
235
+ a "when a dispatch fails" guide (which failures a main merge alone fixes versus which need
236
+ the tag moved, and only before publication) and the documented final step for creating the
237
+ GitHub Release entry by hand, which the workflow deliberately never automates.
238
+ - **A release_v2 spec greps `spec/` for hard-coded current-version literals** (`'= 2.0.0'`,
239
+ `"2.0.0\n"`, `gem_version: '2.0.0'`-shaped strings), built from `Woods::VERSION`'s base, so
240
+ the next version bump cannot leave one behind the way the beta1 cut did.
241
+ - **`WOODS_PROFILE=1` logs a timing line per extraction phase.** The per-extractor
242
+ lines already reported extraction itself; everything after it (payload seed, previous
243
+ graph load, eager load, blast radius, re-extraction, type index, graph analysis, flows,
244
+ manifest and summary, publish) was unattributed, so a slow run could only be split by
245
+ guessing. One `[Woods] [profile] <phase> in N.NNs` line per phase, on the monotonic
246
+ clock. Off by default and free when off.
247
+ - **`durable_payload_writes` restores the per-file `fsync` on payload files.** Boolean,
248
+ default `false`. Off is not the weaker setting: readers resolve only through
249
+ `generation.json`, and every publish now flushes the whole payload before writing that
250
+ pointer, so the contract holds either way. Turning the key on buys exactly one thing, an
251
+ individual payload file being durable before the pointer exists, and pays two forced
252
+ flushes per file (about 8.9ms each on btrfs) for it. It cannot disable the publish
253
+ flush, which has no opt-out.
254
+
255
+ - **`incremental_blast_radius_depth` bounds how far an incremental run re-extracts.**
256
+ `extract_changed` walked the unbounded transitive dependent closure of every changed
257
+ file, so one edit to a widely referenced unit re-extracted most of the app. The new key
258
+ caps the walk at N reverse hops (`nil`, the default, keeps the unbounded closure). A
259
+ unit outside the cap keeps its content and still gets its `dependents` list refreshed by
260
+ the run's second pass, which the equivalence harness now covers under a cap of 1. The
261
+ default stays unbounded on purpose: an STI grandchild inherits its grandparent's
262
+ associations, validations and callback chain while sitting two hops away in the graph,
263
+ and nested `has_many :through` resolves the same way, so a host opts in for a tree it
264
+ knows has neither. On a 200-service chain in the dummy app, one leaf edit re-extracts
265
+ 200 units unbounded and 2 under a depth of 1.
266
+ - Optional `changelog/<type>_<slug>.md` entry files let parallel branches record changes without editing the same Unreleased block. `release:prepare` validates, folds, and removes consumed entries; release validation refuses leftover entries at the tagged SHA (B-179).
267
+ - Add optional `volatile_dependency_limit_per_target` to keep one hot dependency
268
+ from filling the volatility report (B-188). Apply the per-target edge cap before
269
+ the global top 20, preserve the default output, and expose the configured cap
270
+ and reported count alongside the full qualifying count when enabled.
271
+
272
+ - Gate retrieval quality in CI with a versioned Canopy runtime corpus, captured
273
+ real MiniLM vectors, per-strategy quality floors, latency observations, and
274
+ exact output-token counts. Preserve known failed queries in the evidence (#227).
275
+ - Bound `dependencies` and `dependents` traversal work independently of response
276
+ pagination, with configurable node/edge budgets and explicit partial-result
277
+ reasons. Filtered edges and reverse relationship checks consume the edge
278
+ budget; complete small results retain their existing shape (#311).
279
+
280
+ - Document the published filesystem layout for non-Ruby consumers, with pinned
281
+ Bash/jq and Python reads, retention-race handling, and structural snapshot
282
+ publication guidance (#306).
283
+
284
+ - `WOODS_WATCH_TRUST_FOREIGN_HOST=1` lets watch status, incremental/clean guards,
285
+ daemon startup checks, and MCP trust fresh foreign-container heartbeats without
286
+ checking an unrelated local pid. This is opt-in; foreign records expire after
287
+ 15 minutes, and malformed or excessively future timestamps are rejected (#321).
288
+
289
+ - `WOODS_WATCH_POLL_INTERVAL` configures positive, finite seconds between watch
290
+ polling scans, including fallback from native watching (default 1.0; #322).
291
+
292
+ - Published manifests record `woods_version`, the last publisher's gem version,
293
+ for Rails extraction and static self-maps. MCP exposes it independently of the
294
+ reader version, and `woods:validate` gives nonfatal warnings for malformed
295
+ writer versions or different major versions. Older manifests remain valid
296
+ without this optional provenance field (#323).
297
+ - Supply concise, capability-aware Index MCP instructions through initialization and modern discovery, with status-first retrieval guidance, bounded traversal and source verification. Preserve the SDK's omission for protocol `2024-11-05`; tool registration and authorization remain unchanged. (#402)
298
+ - Added generation-bound source-input evidence with bounded `current` / `drifted` / `unknown` status, per-consumer incremental provenance, private keyed content identities, and a fresh-process `woods-extract` launcher. The opt-in session hook now checks source content through the shared no-environment status task. Old indexes and unproved boot/runtime consumption remain explicitly unknown.
299
+ Add separately opt-in, bounded Claude orientation and post-edit candidate context from a retained published generation, with explicit uncertainty, repeat suppression, independent refresh controls, and an installed `woods-hook-context` helper.
300
+ Explicit Claude and OpenCode edit adapters preserve every affected patch path,
301
+ including both rename sides, through the shared durable refresh queue. Native
302
+ OpenCode registration is optional and version-pinned; unsupported or escaping
303
+ events produce diagnostics without claiming refresh.
304
+ MCP `search` now reports whether its returned matches exhaust the requested domain, prove an additional match, or leave the remainder unknown after a scan budget or regex timeout. Bounded lookahead shares the existing scan budget, deep reads preserve typed identities, and detected artifact corruption remains an error. JSON and text formats distinguish returned counts from exact totals; agent guidance explains how to narrow partial searches. Mixed Rails/gem source directories remain searchable and loadable by lexical retrieval while rejecting unrelated type mismatches.
305
+ `woods:validate` now checks semantic graph invariants against typed unit indexes and artifacts within one pinned published generation. It detects broken reverse membership, invalid sources, duplicate typed variants, file/type index drift, and missing indexed nodes while accepting cycles, unresolved targets, legacy string edges, and the Woods static source map. Validation remains read-only, preserves existing report/exit behavior, and reports actionable identities without repairing the graph.
306
+
307
+ The validator, deep search, and lexical retrieval accept all four GraphQL unit types in the shared `graphql/` directory, preserving actual typed identities and existing directory-family search labels.
308
+ `dependencies` and `dependents` accept optional `explain: true` to retain typed source ownership, original edge direction and relationship attributes, and bounded shortest witnesses. Explanations distinguish direct records from transitive reachability, preserve unknown labels and ambiguous target candidates, and retain ancestor context across pages. Existing compact responses remain unchanged; explanation work shares traversal budgets.
309
+ Publish additive `reverse_via` target buckets with typed source identities, relationship labels and association attributes, preserving existing `reverse` arrays and legacy graph loading.
310
+ - Mark whole-file caching, configuration, test mapping, Rails source, and gem source graph nodes and typed variants with `kind: "file_profile"`, preserving file membership and identifiers while letting consumers distinguish profiles from constant-owned units (#417).
311
+ Add `woods-agent-config` for explicit Claude Code project/user setup, update, and removal. Preview saves one private edit plan; apply checks its original snapshots, preserves unrelated configuration, tracks owned entries and instruction sections, and supports recovery after interrupted writes. Host/Compose preflight checks the installed Index Server and published index.
312
+ - Add opt-in compact source evidence and declared API outlines to retrieval and lookup, with complete published spans, explicit omissions, honest generation/source provenance, and typed SHA-guarded full-source follow-up. Existing full-source behavior remains the default.
313
+ - Add explicit embedding-free lexical retrieval over published extraction units. Set `WOODS_RETRIEVAL_MODE=lexical` for Index MCP or `config.retrieval_mode = :lexical` for Ruby builders; field-aware ranked results preserve typed identity, generation consistency and budgeted matching evidence without provider or vector access. Semantic retrieval remains the default.
314
+ - Add explicit package and application-relative source-path scopes to ranked retrieval and discovery, with eligibility before candidate limits, typed scope metadata, and native scoped vector searches without re-embedding.
315
+ - Add managed development watcher startup with fresh-process restart handling,
316
+ explicit Foreman/external/Puma installation modes, portable owned configuration,
317
+ preview/update/removal/recovery, and separate supervision diagnostics. Keep raw
318
+ `woods:watch` supervision compatible. See [#538](https://github.com/lost-in-the/woods/issues/538).
699
319
 
700
320
  ### Performance
701
321
 
@@ -812,6 +432,30 @@ Add a trusted, disabled-by-default release profile for the supported 1.6.2 secur
812
432
  the new task exit codes, the incremental baseline guard, and `reload`'s
813
433
  write-access requirement; every claim that expires at tag time is wrapped in a
814
434
  `release-state` fence, listed in the new release note in `docs/README.md`.
435
+ - Clarify that source literals and optional session traces can contain sensitive information, and correct the session FileStore configuration example.
436
+ - Qualify the `~> 2.0` installation examples for stable releases and direct prerelease adopters to the exact published version in the README release table. Keep agent setup and plugin guidance aligned with installed-version capabilities; clarify that the refresh-hook deadline starts after complete event input is collected and queued.
437
+ - Correct watcher retry guidance to include heartbeat-driven recovery, identify
438
+ session identity corrections as available in 2.0.0.beta3, and align the
439
+ coding-agent Docker synopsis with the container-first setup guide.
440
+ - Scope orphan results to recorded relationships, document the limits of artifact
441
+ permission defaults, correct Claude Code worktree registration guidance, and
442
+ expose embedding-free lexical retrieval in the MCP tool cookbook.
443
+ Update the distributed agent guides to identify Woods 2.0.0.beta3 capabilities and plugin 2.3.36 hook recovery accurately, while preserving installed-version and schema checks.
444
+ - Clarify that beta/RC development retains the last prepared version, source
445
+ candidates require Git revision evidence, and development reopening applies
446
+ only after a final release. Release transitions and version ordering are unchanged.
447
+ Add a guide mapping automatic index maintenance to the canonical watcher, Docker,
448
+ Grove, MCP, freshness, and client-hook documentation. Correct startup/idle-revival
449
+ claims, explain managed versus external supervision and portable setup ownership,
450
+ and require observed updates in the installation handoff. Update the paired plugin
451
+ guidance with installed-version checks for [#538](https://github.com/lost-in-the/woods/issues/538).
452
+ - Prepare the installation guides and plugin capability notes for the final
453
+ 2.0 release while retaining installed-version and publication checks. Correct
454
+ backend pairing, incremental scope and timing, execution-coverage, and token
455
+ estimation claims so the guides describe the implemented contracts. Preserve
456
+ typed identity in lookup examples and explain provider-free lexical retrieval
457
+ in the FAQ.
458
+ - Clarify that `trace_flow` addresses an exact indexed unit, optionally followed by `#method`. Bare names can select factories, and receiverless local calls may remain unexpanded; flow output does not prove runtime execution or complete call coverage.
815
459
 
816
460
  ### Upgrade Notes
817
461
 
@@ -941,6 +585,95 @@ derive unit identifiers, which changes the index format's observable contract.
941
585
  EXTA-15, CON-4, STO-7, STO-10, STO-14, INF-5, INF-6, INF-13, R2-4, R2-5, R2-6).
942
586
 
943
587
  - **STO-12**: Corrected the `Storage::Snapshotter` doc comment: `Snapshotter::Metadata.validate_store!` can use plain `respond_to?` only because `MetadataStore::Interface` defines neither `#each_entry` nor `#bulk_load` — adding either stub would silently convert the check into the B-108 bug. `spec/storage/snapshotter/vector_spec.rb`'s float-truncation tolerance block (which passed whether or not the load raised, stale since the M10 fix) now asserts the raise.
588
+ - **Payload durability moved from every file to the generation pointer.** `AtomicFile.write`
589
+ fsynced the temp file and the containing directory for every file it wrote, so a full
590
+ extraction of a large application paid two forced flushes 8323 times: 71.3s of the write
591
+ phase for 8000 files on btrfs, against 1.0s for one filesystem flush. Payload writes (unit
592
+ files, type indexes, the dependency graph, the graph analysis, the manifest, the summary,
593
+ the flow documents) now skip both, and `publish_generation` calls the new
594
+ `AtomicFile.sync_directory_tree` on the payload directory immediately before writing
595
+ `generation.json`.
596
+
597
+ The guarantee that replaces the old one: **when `generation.json` is durable, every file
598
+ in the payload it names is durable.** What is given up is an individual payload file being
599
+ durable before the pointer exists, and nothing reads a payload file in that window, since
600
+ every reader resolves through the pointer and a crash there leaves an unreferenced partial
601
+ payload the next run prunes. `generation.json` itself, the watch daemon's status file, the
602
+ update check cache, the Obsidian and Unblocked exports, Notion sync state, temporal
603
+ snapshots, embedding checkpoints and MCP task records all keep the per-file fsync: their
604
+ readers do not go through the pointer. The gem mapper's self-map publishes through the
605
+ same pointer but keeps its per-file fsync for now; it is a small payload and adopts the
606
+ single flush in a follow-up.
607
+
608
+ `sync_directory_tree` tries `syncfs(2)` through Fiddle, then `sync -f <dir>`, then a bare
609
+ `sync`, then an `fsync` on every file in the tree, and returns which one ran. The last
610
+ resort is what keeps this honest: the chain never silently does nothing, it only gets
611
+ slower. Fiddle is required inside a rescue and stays out of the gemspec, since it is a
612
+ bundled gem from Ruby 3.5. `bench/atomic_write_bench.rb` measures all four modes.
613
+
614
+ - **Seeding a payload creates each directory once rather than once per file.**
615
+ `PayloadStore#clone` ran `FileUtils.mkdir_p` before every file it replicated.
616
+ `Pathname#find` visits a directory before its children, so the directory branch had
617
+ already created every parent a file could need. 8001 files across four type directories
618
+ on btrfs: 0.906s before, 0.847s after.
619
+
620
+ - **Cycle detection is capped, and says so.** `GraphAnalyzer#analyze` runs on every
621
+ extraction regardless of the change set, and enumerating every cycle was the largest
622
+ part of it: one cycle per DFS back-edge, uncapped in count and in length, each
623
+ canonicalized by joining a path that on a deep DFS is thousands of nodes long. Two new
624
+ config keys bound it, `graph_cycle_limit` (default 500) and `graph_cycle_max_length`
625
+ (default 50, in distinct nodes); either firing sets the new
626
+ `stats.cycle_limit_reached` in `graph_analysis.json`. Set either to `nil` to remove
627
+ its cap and restore exhaustive enumeration. Signatures are now keyed by a digest of
628
+ the rotated cycle rather than the joined path. On a synthetic 8201-node graph, cycle
629
+ detection went from 8.6s to 0.4s.
630
+ - **Bridge detection reuses its work.** `bfs_shortest_path` carries parent pointers
631
+ instead of enqueueing a copy of the path so far for every node it reaches, and forward
632
+ adjacency resolves through a per-analyzer memo instead of being re-derived on each of
633
+ the 200 sampled traversals. Output is unchanged. On the same graph, bridges went from
634
+ 2.2s to 0.6s and the whole report from 3.1s to 1.0s.
635
+ - **Flow assembly caches loaded units and parsed sources.** One `FlowAssembler` serves a
636
+ whole precompute run, but nothing was cached across it: a unit's JSON was re-globbed
637
+ and re-parsed on every expansion, and its source re-parsed once per action of every
638
+ controller that reached it. Three per-instance LRU memos (unit data, whole-source AST,
639
+ and the method index derived from it) bound at 1000 entries each. On 435 controllers x
640
+ 7 actions over 3000 services, precompute went from 20.5s to 3.8s.
641
+ - **The manifest and `SUMMARY.md` read each type index once.** Both derive their totals
642
+ from the per-type `_index.json` files and run back to back at the end of every
643
+ incremental run, so every index was globbed, read and parsed twice. An unreadable index
644
+ still drops that type from both, now with one warning instead of two.
645
+ - **The incremental flow refresh is scoped to the flow assembly radius.** Flows were
646
+ reassembled for every controller in the run's touched set, and the touched set is the
647
+ graph's whole reverse closure, so one leaf edit re-ran flow assembly for nearly every
648
+ controller in the app. A flow document reaches `FlowPrecomputer::DEFAULT_MAX_DEPTH`
649
+ units, so the run now walks the pre-change graph to that same depth and reassembles
650
+ only the controllers inside it. Controllers outside it carry their
651
+ `metadata[:flow_paths]` annotation forward out of the previous flow index rather than
652
+ losing it. A targeted `refresh`, a routes re-run, and a controller whose action set no
653
+ longer matches the index all still reassemble in full. On 50 controllers over a
654
+ 50-service chain in the dummy app, one edit at the far end went from 50 controllers
655
+ reassembled to 3 plus 47 carried.
656
+ - **`graph_sha` is digested from the bytes written.** `graph_analysis.json`'s digest of
657
+ `dependency_graph.json` came from reading the file back off disk, one whole-file read
658
+ per run of an artifact that on a large app is tens of megabytes, to digest bytes the
659
+ run had just serialized. The value is unchanged.
660
+ - Clarify the existing search-regex timeout exclusion: Ruby 3.0/3.1 remain
661
+ supported without a per-match time bound. Run the Index MCP process on Ruby
662
+ 3.2+ for the one-second per-match limit; no runtime mitigation was added
663
+ for older interpreters (B-161).
664
+
665
+ - Reduce payload-clone allocation and traversal overhead while preserving
666
+ hardlinks, copy fallback and immutable generation ownership (#305).
667
+ - Make extraction profiling additive: report git enrichment, reconciliation,
668
+ finalization, pointer publication and retention separately, with a distinct
669
+ whole-run wall-time line (#305).
670
+ - Expand opt-in refresh hooks to the shared extraction input rules, including
671
+ services, controllers, jobs, views, locales, and supported tests/lib paths.
672
+ Restart inputs request fresh full extraction. Preserve queued edits across
673
+ failures and daemon deferral, bound the worker lifetime, and carry JSON batches
674
+ through Docker command prefixes without host-bundle or environment-forwarding
675
+ assumptions. Existing manual incremental and watch behavior is unchanged.
676
+ Add a trusted, disabled-by-default release profile for the supported 1.6.2 security maintenance line. Publication requires a separately reviewed exact candidate SHA pin on main, all fixed maintenance CI rows, and the existing immutable artifact and protected publication safeguards. The v2 release path retains its existing requirements.
944
677
 
945
678
  ### Fixed
946
679
 
@@ -2323,6 +2056,256 @@ derive unit identifiers, which changes the index format's observable contract.
2323
2056
  lock-timeout abort). The diff is also rooted at the extracted application
2324
2057
  (`git -C Rails.root`), consistent with the provenance rooting, so it can no
2325
2058
  longer diff whatever checkout the process happened to start in.
2059
+ - `TraceEnricher.record` rejects calls without a block before creating a
2060
+ TracePoint, preventing an enabled hook from leaking into subsequent execution (#308).
2061
+
2062
+ - **The Changed list only names the surface inventory when regenerating it actually moved
2063
+ it.** `release:prepare` used to list `.Codex/release-v2/surface-inventory.json`
2064
+ unconditionally, even on the ordinary run where nothing in the public surface changed.
2065
+ - **The release banner no longer links an upgrade guide that does not exist yet.** A major
2066
+ version bump past `docs/UPGRADING_TO_2.md`'s own major derived a
2067
+ `docs/UPGRADING_TO_<major>.md` link without checking the file exists.
2068
+ - **Changelog entries merged from a duplicate heading no longer carry a stray blank line.**
2069
+ Two occurrences of the same `###` heading in one `## [Unreleased]` cycle folded into a
2070
+ release section with a blank line between their bullets, splitting one list into two; they
2071
+ now join tight.
2072
+ - **Release validation names the live-backends CI job as it is called.** The release
2073
+ validator required a CI job named `Live backends (pgvector + Qdrant + Solid Cache)`,
2074
+ but the job gained `+ Redis` in its name, so every release dispatch failed at
2075
+ release-context with nothing published. The prefix now matches, and a spec checks every
2076
+ required contract job against the names in `ci.yml` so a rename cannot drift again.
2077
+ - **Release candidate jobs find the downloaded artifact.** `actions/download-artifact` with
2078
+ `artifact-ids` extracts into `dist/<artifact-name>/`, so the digest check and the install
2079
+ in `dist/` failed with a missing file on every dispatch since the switch to artifact ids.
2080
+ Both downloads now set `merge-multiple: true`; the workflow spec requires it.
2081
+ - **Release candidate tests accept a prerelease version.** The clean-install smoke specs
2082
+ pinned `2.0.0` as a literal in the dummy app's Gemfile, the loaded-version check, and a
2083
+ snapshot fixture, so the first prerelease failed them (Bundler never resolves a prerelease
2084
+ from an unpinned requirement). They now use `Woods::VERSION`. The candidate host also
2085
+ installs `webrick` so `woods-mcp-http` finds a Rack handler on Rubies that no longer ship one.
2086
+ - **Inspector contract specs track the pinned `@modelcontextprotocol/inspector` version instead
2087
+ of a literal.** Bumping the dev dependency to 2.6.0 fixed the SDK bug where Inspector sent a
2088
+ legacy `logging/setLevel` call after negotiating the modern 2026-07-28 protocol, so the two
2089
+ `pending` stdio/HTTP examples in `mcp_inspector_contract_spec.rb` asserted a stderr message
2090
+ that no longer occurs. Those examples now assert a clean modern handshake, and every version
2091
+ literal in that file reads from `package.json` instead. `sdk_dependency_spec.rb`'s deliberate
2092
+ version-and-integrity trip wire is bumped to match the new pin.
2093
+ - Reflect model callbacks from Rails' per-event chains instead of nonexistent
2094
+ per-kind readers; include `before_commit` and keep `callback_count` equal to
2095
+ the emitted callback list. Preserve framework callbacks with stable Proc/lambda
2096
+ source-site labels and address-free default callback-object descriptions in
2097
+ metadata and chunks, including Rails 6's `raw_filter`, so separate-process
2098
+ extractions of unchanged models remain equivalent. Preserve custom object labels.
2099
+
2100
+ - Explain the Zeitwerk 2.6.9 naming requirement in identifier-collision errors
2101
+ and upgrade guidance before suggesting changes to valid namespace wrappers
2102
+ on older-loader or classic-mode hosts (B-149).
2103
+
2104
+ - Stabilize controller inline callback and condition labels across processes and
2105
+ checkout paths, including action chunks; retain `unless` conditions in the
2106
+ generated filter-chain header (B-167).
2107
+
2108
+ - Publish metadata-only changes in local embedding snapshots without re-embedding
2109
+ unchanged source; retain no-op dumps and source-hash checkpoints (B-119).
2110
+
2111
+ - Search Boolean metadata fields as `true`/`false` consistently in SQLite and
2112
+ InMemory, preserving numeric `1`/`0` and null semantics (B-199, #356).
2113
+
2114
+ - Keep the default SQLite metadata database inside the effective `WOODS_OUTPUT`
2115
+ directory for embedding tasks, isolating indexes while preserving explicit
2116
+ database overrides (B-156). Existing databases are not moved; run `woods:embed`
2117
+ for the selected index after upgrading.
2118
+
2119
+ - Treat non-object JSON snapshots and files removed or made unreadable during
2120
+ a read as absent across lookup, listing, diffs, and unit history (B-158).
2121
+
2122
+ - Count corrupt and unreadable SHA-named JSON snapshots toward retention and
2123
+ evict them before valid history, preserving the just-captured snapshot and
2124
+ unrelated files (B-163).
2125
+
2126
+ - Count the final retrieval context after formatting and type-rank metadata,
2127
+ keeping `tokens_used` and its trace consistent with the configured counter
2128
+ or estimate (B-197, #354).
2129
+
2130
+ - Order tied hybrid retrieval candidates deterministically before graph seed
2131
+ selection, truncation and reciprocal-rank fusion across supported Ruby versions
2132
+ (B-192).
2133
+
2134
+ - Bound OpenAI embedding requests to 36 valid inputs, preserving chunk order and
2135
+ rejecting partial or dimension-inconsistent batches (B-157). Chunk-heavy runs
2136
+ use more HTTP requests to stay within the API's input and total-token limits.
2137
+
2138
+ - Match embedded NUL literally in SQLite metadata searches, including substrings after NUL; preserve ASCII case folding and literal wildcard characters (B-198, #355).
2139
+
2140
+ - Prevent single-component cache keys from colliding with multi-component or
2141
+ empty keys by uniformly length-prefixing components (B-155). Custom callers
2142
+ using persistent single-component keys should clear that cache domain on upgrade.
2143
+
2144
+ - Make middleware argument metadata, generated source and hashes stable across Rails processes by describing runtime identities structurally while preserving literal and nested configuration (#362).
2145
+
2146
+ - Omit per-unit git enrichment for shallow checkouts or unverifiable repository
2147
+ depth, with one warning and full-history recovery guidance, instead of
2148
+ reporting truncated commit counts as complete churn data (B-189).
2149
+
2150
+ - Validate direct/legacy Console scope arrays with the active SQL dialect and
2151
+ MySQL session quote modes, refusing subqueries hidden by mismatched quote
2152
+ stripping while retaining the supported tools' narrower scope grammar (B-154).
2153
+
2154
+ - Read and clear legacy Redis session indexes before any new record without
2155
+ `WRONGTYPE`; atomic SET/ZSET access tolerates concurrent index migration
2156
+ and keeps reader-only upgrades compatible with older SET writers (B-162).
2157
+
2158
+ - Allow full extraction when ActionMailer is absent and skip non-app mailers
2159
+ instead of publishing empty units at fabricated paths; share that ownership
2160
+ gate with incremental class discovery (B-153).
2161
+
2162
+ - Repair corrupt pipeline cooldown state on an explicit all-reset, including
2163
+ `pipeline_repair` in custom operator-configured servers; ordinary reads still
2164
+ deny operations and scoped resets preserve corrupt state (B-159).
2165
+
2166
+ - Normalize incremental change paths before deduplication and dispatch, including
2167
+ trailing root slashes, repeated separators and dot segments (B-148).
2168
+
2169
+ - Load lazy Rails routes before caching navigation helpers, preserving view-to-controller dependencies during fresh-process incremental extraction (#360).
2170
+
2171
+ - Reflect model methods after schema loading so full and incremental runs agree
2172
+ on Rails-generated constructors while preserving application overrides (B-202, #363).
2173
+
2174
+ - Parse job `perform_params` and shared `initialize_params` from Ruby parameter
2175
+ syntax, avoiding phantom names from keyword/default expressions while preserving
2176
+ the existing metadata fields and named rest/block arguments (B-151).
2177
+
2178
+ - Resolve ERB-backed Solid Queue recurring schedules using Rails configuration
2179
+ loading, including relative requires, conditional entries, aliases and custom
2180
+ environment sections (B-203, #364).
2181
+
2182
+ - Preserve navigation edges for real named routes such as `file_path`,
2183
+ `image_url`, `download_path`, and `root_path`; unresolved asset/filesystem
2184
+ helper names still produce no edge (B-152).
2185
+
2186
+ - Resolve and track app-owned nested model mixins through runtime source locations, refreshing includer source and callbacks on incremental edits (B-150, #361).
2187
+
2188
+ - Explain the full-extraction recovery for runtime job removals and bundle
2189
+ upgrades; missing gem-path warnings now include the bundle-update remedy
2190
+ without changing incremental discovery rules (B-165, B-166).
2191
+
2192
+ - Preserve all reverse dependencies on symbolic external targets such as `http_api` after incremental graph reloads and re-registration (B-193, #305).
2193
+
2194
+ - Preserve changes to nested `extracted_at` metadata when deciding whether to
2195
+ rewrite a unit; only Woods' top-level extraction stamp is ignored (B-147).
2196
+ - Read per-unit git enrichment in one streamed HEAD history walk instead of
2197
+ repeated 500-path batches (B-195, #305). Merge commits compare with their first
2198
+ parent while all HEAD ancestry is visited; counts can change from legacy
2199
+ pathspec simplification. Optional enrichment now requires Git 2.31 or newer;
2200
+ incomplete/failed history is omitted with a warning. Run full extraction
2201
+ after upgrading to refresh retained metadata. Host speedup remains unmeasured.
2202
+
2203
+ - Apply the same app-owned path exclusions to full and incremental git enrichment;
2204
+ external, vendored, and node_modules units no longer gain empty git metadata (B-194).
2205
+
2206
+ - Prune default excluded package directories before recursively discovering
2207
+ `package.yml`, avoiding scans of indexes and snapshots under `tmp/` (B-196, #305).
2208
+
2209
+ - Preserve namespaced and CamelCase graph-retrieval subjects, keep snake_case
2210
+ lookup working, and exclude tracing instructions from fallback metadata searches (B-190).
2211
+
2212
+ - Order equal PageRank scores by identifier before assigning retrieval importance
2213
+ percentiles, making their ranking weights deterministic across Ruby versions
2214
+ and graph insertion orders (B-191).
2215
+
2216
+ - Reduce payload-seed metadata lookups by classifying each entry once. Preserve
2217
+ per-file hardlink/copy fallback, atomic publication and generation retention
2218
+ while reducing full and incremental seed overhead (#305).
2219
+
2220
+ - Keep runtime trace evidence for same-name instance and singleton methods separate, including inherited singleton owners and caller kinds. Legacy untyped events enrich instance methods only; re-record old singleton traces.
2221
+
2222
+ - Ruby trace enrichment now records the nearest observed calling Ruby method
2223
+ instead of the callee receiver. Recording keeps separate fiber stacks, handles
2224
+ recursive and unwound calls, and leaves outside-recording callers unknown
2225
+ without binding or backtrace inspection (#309).
2226
+
2227
+ - Scope per-file git metadata to HEAD history instead of every ref, excluding
2228
+ unmerged branches and tool checkpoints from churn and authorship (#319). Run
2229
+ a full `woods:extract` after upgrading to refresh previously published metadata.
2230
+
2231
+ - Full extraction no longer creates empty type directories at the index root
2232
+ before publishing its generation payload. Existing root directories and legacy
2233
+ flat-index files are preserved (#320).
2234
+
2235
+ - Watch startup reconciles environment-boot-covered restart inputs with one full
2236
+ extraction instead of repeatedly exiting 75. Live changes and changes during
2237
+ environment initialization still require restart; failed reconciliation and
2238
+ deleted restart inputs survive retries and supervisor restarts. Built-in
2239
+ watchers establish detection before startup extraction begins (#318).
2240
+
2241
+ - Add `console_mcp_http_enabled` (default `true`) so stdio-only Console setups
2242
+ can explicitly disable HTTP and its boot-time token warnings. Production
2243
+ still refuses to boot without a token when HTTP Console is enabled (#304).
2244
+
2245
+ - Solid Cache conditional writes on MySQL no longer claim ownership of a
2246
+ pre-existing row when the adapter reports matched rows as affected rows.
2247
+ Validate ownership using the per-attempt serialized payload, with live MySQL
2248
+ contention/recovery coverage and documented crash/eviction limits (#228).
2249
+ - Keep Notion model/column/migration data and Unblocked full/partial documents tied to their exact extracted type. Refuse incomplete export reads before mutation, and preserve remote documents when same-name, same-file types cannot be represented safely by Unblocked's existing URI scheme.
2250
+ Preserve the selected extraction type when framework search and recent changes read colliding identifiers. Session controller lookup and root outgoing-edge selection now retain the controller type. Public untyped lookup, downstream references and the bare-name multi-step context pool retain their existing contracts.
2251
+ Obsidian export preserves same-name units of different types, their separate outgoing links, and original public identifiers. Collision-bearing vaults publish a version-2 typed manifest; ordinary version-1 manifests and note paths remain unchanged. Ambiguous bare targets are omitted with a diagnostic, and incomplete typed reads cannot trigger stale-note deletion.
2252
+ - Reject ambiguous cross-type dependencies in session context with an actionable `ambiguous_identity` MCP error instead of silently selecting a source or reusing another type's context key. Leave unresolved controllers in the timeline without a misleading source reference. Pin candidate discovery and all assembly reads to one generation; retain typed controller roots, metadata-only timelines, and successful response shapes. Refs #213; this does not migrate global identifiers.
2253
+ - Refresh Console credential indexes from a fresh encrypted-file and key snapshot, retain the last valid index when refresh fails, and update all live embedded servers without retaining abandoned servers. Each response scan uses one complete credential index.
2254
+ Keep credential scanner refresh snapshots limited to live weakly referenced
2255
+ scanners on older Ruby versions, avoiding unsafe receiver access after garbage
2256
+ collection while preserving updates to every live Console server.
2257
+ - Refuse incomplete native embedding input before changing stores or checkpoints, retaining legacy flat-index support while rejecting malformed JSON. A source-empty unit now retires superseded vectors and checkpoints its no-content state without calling the embedding provider (#442, #444).
2258
+ - Terminate the evaluation command's owned process group on timeout, including ordinary descendants whose parent has already exited.
2259
+ - Let only the hook that removes a recorded dead-owner marker replace its mkdir lock, so concurrent recovery does not leave all edits queued behind an abandoned empty directory.
2260
+ - Refuse Obsidian exports that would overwrite unmanaged notes, indexes, settings, or sidecars. Preflight all destinations, record generated asset digests separately from the public manifest, and suppress sweeps on conflicts or write failures. Legacy assets are adopted only when byte-identical; changed legacy sidecars require inspection and backup or a fresh export directory.
2261
+ - Coordinate `Woods.extract!` and `Woods.extract_changed!` with task/watch writers and raise on lock timeout or failed generation publication, so background jobs can retry unsuccessful extraction.
2262
+ - Accept Rails 6.0 positional middleware options on Ruby 3 while preserving explicit keyword precedence, required bearer tokens, and unknown-option refusal. Add an installed-gem CI contract for all advertised direct runtime dependency floors, with a constrained Rails 6.0.0 fixture and recorded transitive resolution.
2263
+ - Honor configured context-token defaults in Builder-created semantic/lexical retrieval, caches, and MCP while preserving explicit budgets and the legacy custom-collaborator fallback. Deprecate the inert similarity_threshold option with a warning instead of changing ranking behavior (#446).
2264
+ - Preserve the dispatched controller's runtime class name in session traces, with a Rails-inflector fallback, so acronym namespaces retain source context in `session_trace`.
2265
+ - Preserve each logical directory alias in polling and startup catch-up so an earlier irrelevant alias cannot hide an extraction input such as `app/models`. Detect cycles per traversal branch while retaining ignored-subtree pruning.
2266
+ Accept ASCII case variants of the HTTP `Bearer` authentication scheme for Index and Console MCP. Token bytes, constant-time comparison, and existing delimiter rules remain unchanged. (#497)
2267
+ - Console SQL validation now distinguishes keyword grammar from identically named functions before applying the read-only function allowlist. SQLite application-defined keyword functions are rejected before execution; ordinary window, predicate, and numeric pagination syntax remains supported.
2268
+ - Report malformed generation marker shapes during embedded Index MCP startup as an actionable `ArgumentError` with the selected directory and published-layout guidance, matching executable preflight behavior. Preserve legacy flat and payload indexes and leave later generation-refresh error handling unchanged.
2269
+ - Make file-backed session clearing idempotent for Unicode and punctuated IDs, and discard expired history before appending or merging legacy and encoded files. This prevents expired events from becoming visible again through record, read, or listing; live history, disabled TTL, and retention limits retain their existing behavior.
2270
+ Dependency and dependent responses now disclose that published relationships are not exhaustive source-reference coverage, including compact and empty answers. Human witness labels say “witness types unambiguous” while preserving the JSON `typed_path_complete` contract. Successful traversal responses add `total_is_exact`; budget-limited text reports a root-inclusive lower bound and its cutoff reason, independently of pagination. (#470, #471)
2271
+ GraphQL parent metadata and summary chunks now read the selected declaration's superclass, rather than borrowing a nested or sibling class's parent. Compact qualified declarations preserve their explicit parent; implicit, dynamic, or unavailable parents remain unknown. Identifiers and dependency classification are unchanged.
2272
+ - Preserve the published generation when incremental extraction or named refresh encounters handled source errors. Failed consumers no longer replace last-good units with empty or partial output; watch retains the complete batch for retry.
2273
+ - Make missing-index startup headlines describe an unresolved published index instead of implying that atomic indexes require a root `manifest.json`; retain the examined path, layout explanation and existing-index-first remedy (#482).
2274
+ - Make `woods-mcp-start` resolve generation payload symlinks before checking their manifest, matching the library's index-root containment check. Preflight rejects escaping links and avoids manifest probes through broken links; contained links and symlinked index roots remain supported. The library already rejected escaping payloads before serving the index.
2275
+ - Clarify lexical retrieval responses with actual included-source and considered-candidate counts alongside the shortlist limit. Charge count text to the existing estimated context budget across full, compact, outline and scoped results, distinguishing no matches from matching candidates whose sources do not fit without changing ranking or MCP response structure.
2276
+ Stabilize mailer output across Rails processes by sorting action-name inventories consistently and labeling direct Proc defaults and callback filters by source location and callable kind. Preserve callback order, duplicate registrations and ordinary default value types without executing callables. Run a full extraction after upgrading to refresh retained mailer records; affected source hashes may change once. (#484)
2277
+ - Stabilize mailer object callback labels across boots by reusing the model extractor's guarded default-representation formatter. Preserve custom labels, literal hexadecimal text, callback order, duplicates and conditions; do not execute callbacks or change non-Proc defaults. Run a full extraction after upgrading to refresh retained mailer metadata; stored index schemas are unchanged.
2278
+ - Accept `WOODS_OUTPUT` after the explicit path and `WOODS_DIR` in Index MCP startup, while preserving the launcher's no-path error. Missing-index diagnostics now name the examined directory, describe atomic and legacy layouts, and suggest pointing at an existing index before re-extracting.
2279
+ - Recommend explicit embedding-free lexical mode in no-provider startup and retrieval messages, including the required MCP environment change and restart; semantic defaults and typed errors are unchanged.
2280
+ - Reconcile Rake tasks across all contributing files on incremental changes and deletions, retaining surviving definitions and removing obsolete source from shared tasks.
2281
+ Scope PORO and lib `parent_class` metadata and source annotations to the selected declaration. Nested or sibling error classes no longer supply an unrelated superclass; implicit parents remain nil and explicit constant-path parents retain their names (#474).
2282
+ - Reject pgvector HNSW `vector` widths above 2,000 before database writes, and reject invalid migration dimensions before creating files. Correct the 3,072-dimensional generator example and document explicit provider output sizing; no vector truncation or implicit conversion is performed (#524).
2283
+ - Describe prepared prereleases without claiming RubyGems publication from VERSION alone, and retain the published 1.6.2 maintenance changelog so generated stable-version guidance stays accurate.
2284
+ - Preserve actual GraphQL and gem-source types in `Woods::PublishedIndex` typed lookup and enumeration while retaining the `graphql` and `rails_source` family aliases (#518).
2285
+ - Enforce `graph_analysis`'s advertised default of 20 rows per section and retain total/offset context on last and empty pages in every renderer. Correct the structure glossary: graph nodes include isolated units, and retriever entry counts depend on mode and store coverage (#519).
2286
+ - Coordinate agent configuration apply and recovery on shared managed targets across application roots, preventing concurrent user-scoped installations from overwriting each other. Keep application-specific receipts, refuse stale plans, and report/protect every coordination path in previews (#520).
2287
+ - Apply JSON temporal history limits after matching the requested unit, preserving access to retained records across gaps and deletions with SQLite parity (#521).
2288
+ - Reconcile reused in-memory vector and metadata stores during full embedding rebuilds, removing vanished units and publishing empty corpora without changing incremental purge guards. Failed embedding preserves the previous promoted dump and checkpoint (#522).
2289
+ Validate persisted JSON snapshot shapes before reading history or capturing the next snapshot. Unusable nested unit records and summary fields now warn and skip the entire snapshot, preserving valid legacy records, corrupt-file retention, and caller SHA validation (#492).
2290
+ - Use the same JSON snapshot validity checks for reads and retention: reject filename/content SHA mismatches before selecting a capture baseline, and prune corrupt files before valid legacy snapshots with missing or null timestamps. Timestamp-less snapshots remain eligible for ordinary oldest-first retention.
2291
+ Expose the paginated dependencies/dependents payload in `structuredContent.data` with every renderer, including packaged stdio and HTTP defaults, while preserving rendered text and existing tool arguments (#481).
2292
+ - Stabilize direct Proc/lambda model validation option values in extracted and published metadata using callable kind and source-location labels, without executing them. Preserve validation order, duplicates, conditions and non-Proc values; nested containers are not recursively normalized. Run a full extraction to refresh retained validation metadata; index schemas are unchanged.
2293
+ - Reconcile registered initializers deleted during watch downtime with a full extraction after a fresh environment boot, preserving whole-application runtime facts.
2294
+ - Keep the stable extraction guard and output directory after `woods:clean`, so a concurrent writer can finish acquiring its lock safely.
2295
+ Keep Console MCP application stdout redirected to stderr throughout stdio operation, with a dedicated pipe for protocol responses and notifications. Rails SQL/application loggers and runtime writes to standard output no longer corrupt JSON-RPC after boot. Preserve SDK framing, negotiation, errors, EOF and interrupt handling; use the rake launcher to capture Rails environment boot output too. (#536)
2296
+ - Warn when Git cannot execute during extraction in an expected repository. Extraction remains usable without per-file history; intentional source archives and build-provenance fallback remain supported.
2297
+ Guard generated Puma startup using plugin files in the active Woods gem, including Git/path bundles. Branches with an older Woods gem that lacks the plugin can boot Puma without a watcher. Explicit watcher installation updates now refresh an existing owned directive in place, preserving surrounding text, newline style, and removal ownership; repeating setup leaves it unchanged. Existing generated Puma setups should preview and apply `bin/rails generate woods:watch --operation update --mode puma` with a supporting bundle. (#542)
2298
+ - Distinguish semantic retrieval corpus counts from structural index readiness. Known-empty in-memory stores now produce actionable embedding or explicit lexical-mode guidance; status reports local vector and metadata record counts by type without treating unavailable counts as zero. Preserve useful metadata-only retrieval and report counts from the currently served stores.
2299
+ - Watcher startup generator refusals now exit nonzero, so unsupported Puma versions and installation conflicts cannot appear successful to automation. The existing diagnostics and file-preservation behavior remain intact (#544).
2300
+ - Preserve application Bundler path, external configuration, group selection, and
2301
+ application environment during managed watcher installation preflight. Reset
2302
+ inherited activation state without making a working container bundle appear
2303
+ missing; retain selected lockfiles and frozen-resolution requests (#540).
2304
+ - Correct linked-worktree Git mount guidance and the runtime repair warning to
2305
+ select the worktree-specific HEAD within the complete shared Git layout.
2306
+ Regressions cover exact branch/SHA, feature-only history and incremental paths
2307
+ after relocation. Document the need for full extraction when current Git
2308
+ metadata is required after a commit that does not trigger the source watcher.
2326
2309
 
2327
2310
  ### Testing
2328
2311
 
@@ -2348,6 +2331,7 @@ derive unit identifiers, which changes the index format's observable contract.
2348
2331
  migration race) never executed anywhere. The `live-backends` job now
2349
2332
  lists it; that job already provides the ephemeral `redis` client install,
2350
2333
  the redis service, and `WOODS_REDIS_URL`.
2334
+ - Raise the default-suite CI line-coverage floor from 85% to 90%, calibrated against current local and CI measurements. Branch coverage remains measured without a gate; per-file floors and combined opt-in coverage remain separate work.
2351
2335
 
2352
2336
  ### Security
2353
2337
 
@@ -2360,6 +2344,25 @@ derive unit identifiers, which changes the index format's observable contract.
2360
2344
  before optional libraries can select conflicting dependency versions.
2361
2345
  - Update Inspector transitive dependencies `fast-uri` and `qs` to patched versions
2362
2346
  and audit the pinned Node dependency tree in CI.
2347
+ - Redact protected Console fields before serialization, normalize the remaining values to their JSON-compatible representation, and redact and scan again before JSON or Markdown rendering. Symbol values and custom serializers cannot introduce unscanned credentials in the final response, and protected serializers remain uncalled. The direct credential scanner also scans Symbol values while preserving their type.
2348
+ - Check resolved relations against the Console blocked-table policy before default model tools fetch records or counts, including association parent lookups; reuse the checked relation so dynamic default scopes are evaluated once.
2349
+ - Refuse unsupported SQLite identifier and table-reference syntax before Console SQL execution, preserving blocked-table and function policies; ordinary bare and simple quoted identifiers remain supported.
2350
+ - Preserve blocked-table visibility after subqueries and parenthesized JOIN predicates, including quoted aliases, across supported SQL dialects.
2351
+
2352
+ ### Build
2353
+
2354
+ - Allow the exact 1.6.3 maintenance tag and branch from the released 1.6.2 base while keeping publication disabled until a separately reviewed candidate SHA is pinned. Preserve all CI, immutable-artifact, package-test and protected-environment release gates.
2355
+
2356
+ ## [1.6.3] - 2026-09-22
2357
+
2358
+ ### Build
2359
+
2360
+ - Add the explicit, no-publish preparation cycle for the 1.6.3 security maintenance release; keep version writes task-owned and publication dependent on a reviewed trusted-main SHA pin.
2361
+
2362
+ ### Security
2363
+
2364
+ - Apply blocked-table policy to resolved model relations and SQLite table syntax before reads, retain table boundaries across comments, and reject quoted spellings of existing denied SQL functions.
2365
+ - Scan the serialized Console response after protected-field redaction so Symbol values and custom JSON output cannot bypass credential scanning.
2363
2366
 
2364
2367
  ## [1.6.2] - 2026-09-18
2365
2368