woods 2.0.0.beta1 → 2.0.0.beta2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +138 -0
- data/CONTRIBUTING.md +53 -2
- data/README.md +3 -3
- data/docs/CONFIGURATION_REFERENCE.md +35 -0
- data/docs/INCREMENTAL_EXTRACTION.md +41 -1
- data/docs/INTERNALS.md +8 -5
- data/docs/PUBLISHED_INDEX.md +16 -0
- data/docs/UPGRADING_TO_2.md +2 -2
- data/lib/woods/atomic_file.rb +133 -3
- data/lib/woods/extractor.rb +329 -95
- data/lib/woods/flow_assembler.rb +87 -8
- data/lib/woods/flow_precomputer.rb +44 -7
- data/lib/woods/graph_analyzer.rb +161 -58
- data/lib/woods/payload_store.rb +14 -1
- data/lib/woods/ruby_analyzer/trace_enricher.rb +3 -0
- data/lib/woods/version.rb +1 -1
- data/lib/woods.rb +86 -1
- metadata +5 -5
data/lib/woods.rb
CHANGED
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
require_relative 'woods/version'
|
|
25
25
|
# Configuration's `component_paths` default lives with the discovery it feeds.
|
|
26
26
|
require_relative 'woods/extractors/component_discovery'
|
|
27
|
+
# Configuration defaults the cycle caps to the analyzer's own constants.
|
|
28
|
+
require_relative 'woods/graph_analyzer'
|
|
27
29
|
|
|
28
30
|
module Woods
|
|
29
31
|
class Error < StandardError; end
|
|
@@ -146,7 +148,9 @@ module Woods
|
|
|
146
148
|
:cache_store, :cache_options,
|
|
147
149
|
:dump_retention_count, :component_paths
|
|
148
150
|
attr_reader :embedding_model, :max_context_tokens, :similarity_threshold, :extractors, :pretty_json,
|
|
149
|
-
:context_format, :cache_enabled, :volatile_dependency_ratio
|
|
151
|
+
:context_format, :cache_enabled, :volatile_dependency_ratio,
|
|
152
|
+
:graph_cycle_limit, :graph_cycle_max_length,
|
|
153
|
+
:incremental_blast_radius_depth, :durable_payload_writes
|
|
150
154
|
|
|
151
155
|
def initialize # rubocop:disable Metrics/MethodLength
|
|
152
156
|
@output_dir = nil # Resolved lazily; Rails.root is nil at require time
|
|
@@ -205,6 +209,15 @@ module Woods
|
|
|
205
209
|
@cache_options = {} # { redis: client, cache: store, ttl: { embeddings: 86400, ... } }
|
|
206
210
|
@dump_retention_count = 3
|
|
207
211
|
@volatile_dependency_ratio = 3.0
|
|
212
|
+
@graph_cycle_limit = GraphAnalyzer::DEFAULT_CYCLE_LIMIT
|
|
213
|
+
@graph_cycle_max_length = GraphAnalyzer::DEFAULT_CYCLE_MAX_LENGTH
|
|
214
|
+
# nil = the unbounded transitive closure. See the setter for why the
|
|
215
|
+
# default is not a small number.
|
|
216
|
+
@incremental_blast_radius_depth = nil
|
|
217
|
+
# false = payload files are written without a per-file fsync and the
|
|
218
|
+
# whole payload is flushed once before `generation.json` is written.
|
|
219
|
+
# See the setter.
|
|
220
|
+
@durable_payload_writes = false
|
|
208
221
|
# Directories the component extractors walk before reading `descendants`,
|
|
209
222
|
# relative to Rails.root. See Extractors::ComponentDiscovery.
|
|
210
223
|
@component_paths = Extractors::ComponentDiscovery::DEFAULT_COMPONENT_PATHS.dup
|
|
@@ -274,6 +287,29 @@ module Woods
|
|
|
274
287
|
@extractors = value
|
|
275
288
|
end
|
|
276
289
|
|
|
290
|
+
# Force an fsync on every payload file as it is written, on top of the
|
|
291
|
+
# single flush the publish already performs.
|
|
292
|
+
#
|
|
293
|
+
# Off by default, and off is not a weaker guarantee. Readers resolve only
|
|
294
|
+
# through `generation.json`; a payload file has no reader until that
|
|
295
|
+
# pointer names it, and {Woods::Extractor#sync_payload} makes the whole
|
|
296
|
+
# payload durable before the pointer is written. Turning this on buys
|
|
297
|
+
# exactly one thing: an individual payload file being durable before the
|
|
298
|
+
# pointer exists.
|
|
299
|
+
#
|
|
300
|
+
# It costs a lot for it. Two forced flushes per file, 8.9ms each on btrfs:
|
|
301
|
+
# 8000 units is 71s of writing against 1s.
|
|
302
|
+
#
|
|
303
|
+
# Turning it on cannot disable the publish flush. That flush *is* the
|
|
304
|
+
# durability contract, so it has no opt-out.
|
|
305
|
+
#
|
|
306
|
+
# @param value [Boolean]
|
|
307
|
+
# @raise [Woods::ConfigurationError] when value is not a boolean
|
|
308
|
+
def durable_payload_writes=(value)
|
|
309
|
+
validate_boolean!(:durable_payload_writes, value)
|
|
310
|
+
@durable_payload_writes = value
|
|
311
|
+
end
|
|
312
|
+
|
|
277
313
|
# @param value [Boolean] Must be true or false
|
|
278
314
|
# @raise [ConfigurationError] if value is not a boolean
|
|
279
315
|
def pretty_json=(value)
|
|
@@ -312,6 +348,46 @@ module Woods
|
|
|
312
348
|
@volatile_dependency_ratio = value.to_f
|
|
313
349
|
end
|
|
314
350
|
|
|
351
|
+
# How many distinct cycles {Woods::GraphAnalyzer#cycles} enumerates before
|
|
352
|
+
# it stops. `nil` removes the cap and restores exhaustive enumeration.
|
|
353
|
+
#
|
|
354
|
+
# @param value [Integer, nil] must be a positive Integer, or nil
|
|
355
|
+
# @raise [ConfigurationError] otherwise
|
|
356
|
+
def graph_cycle_limit=(value)
|
|
357
|
+
@graph_cycle_limit = validate_optional_positive_integer!(:graph_cycle_limit, value)
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# The longest cycle {Woods::GraphAnalyzer#cycles} records, in distinct
|
|
361
|
+
# nodes. `nil` removes the cap.
|
|
362
|
+
#
|
|
363
|
+
# @param value [Integer, nil] must be a positive Integer, or nil
|
|
364
|
+
# @raise [ConfigurationError] otherwise
|
|
365
|
+
def graph_cycle_max_length=(value)
|
|
366
|
+
@graph_cycle_max_length = validate_optional_positive_integer!(:graph_cycle_max_length, value)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# How many reverse hops an incremental run walks from a changed file
|
|
370
|
+
# before it stops re-extracting dependents. `nil` (the default) keeps the
|
|
371
|
+
# unbounded transitive closure.
|
|
372
|
+
#
|
|
373
|
+
# A unit's extracted content is mostly a function of its own source and
|
|
374
|
+
# its own reflection, so on most graphs a small cap re-extracts the same
|
|
375
|
+
# bytes far faster. It is not safe everywhere, which is why the default
|
|
376
|
+
# is unbounded: an STI grandchild reads its grandparent's reflection
|
|
377
|
+
# (`reflect_on_all_associations`, `_validators` and the callback chain
|
|
378
|
+
# are all inherited), so `SportsCar < Car < Vehicle` has content that
|
|
379
|
+
# changes when `Vehicle` changes while sitting two hops away in the
|
|
380
|
+
# graph. A nested `has_many :through` resolves through the same kind of
|
|
381
|
+
# chain. Set this only on a tree you know has neither.
|
|
382
|
+
#
|
|
383
|
+
# @param value [Integer, nil] must be a positive Integer, or nil
|
|
384
|
+
# @raise [ConfigurationError] otherwise
|
|
385
|
+
def incremental_blast_radius_depth=(value)
|
|
386
|
+
@incremental_blast_radius_depth = validate_optional_positive_integer!(
|
|
387
|
+
:incremental_blast_radius_depth, value
|
|
388
|
+
)
|
|
389
|
+
end
|
|
390
|
+
|
|
315
391
|
# Accepted for forward compatibility. Nothing reads {gem_configs}; gem
|
|
316
392
|
# source indexing is not implemented.
|
|
317
393
|
#
|
|
@@ -325,6 +401,15 @@ module Woods
|
|
|
325
401
|
|
|
326
402
|
private
|
|
327
403
|
|
|
404
|
+
# @return [Integer, nil] +value+ when it is nil or a positive Integer
|
|
405
|
+
# @raise [ConfigurationError] otherwise
|
|
406
|
+
def validate_optional_positive_integer!(name, value)
|
|
407
|
+
return value if value.nil?
|
|
408
|
+
return value if value.is_a?(Integer) && value.positive?
|
|
409
|
+
|
|
410
|
+
raise ConfigurationError, "#{name} must be a positive Integer or nil, got #{value.inspect}"
|
|
411
|
+
end
|
|
412
|
+
|
|
328
413
|
def validate_boolean!(name, value)
|
|
329
414
|
return if value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
|
330
415
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: woods
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.0.
|
|
4
|
+
version: 2.0.0.beta2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Leah Armstrong
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: mcp
|
|
@@ -435,10 +435,10 @@ licenses:
|
|
|
435
435
|
- MIT
|
|
436
436
|
metadata:
|
|
437
437
|
homepage_uri: https://github.com/lost-in-the/woods
|
|
438
|
-
source_code_uri: https://github.com/lost-in-the/woods/tree/v2.0.0.
|
|
439
|
-
changelog_uri: https://github.com/lost-in-the/woods/blob/v2.0.0.
|
|
438
|
+
source_code_uri: https://github.com/lost-in-the/woods/tree/v2.0.0.beta2
|
|
439
|
+
changelog_uri: https://github.com/lost-in-the/woods/blob/v2.0.0.beta2/CHANGELOG.md
|
|
440
440
|
bug_tracker_uri: https://github.com/lost-in-the/woods/issues
|
|
441
|
-
documentation_uri: https://github.com/lost-in-the/woods/tree/v2.0.0.
|
|
441
|
+
documentation_uri: https://github.com/lost-in-the/woods/tree/v2.0.0.beta2/docs
|
|
442
442
|
rubygems_mfa_required: 'true'
|
|
443
443
|
post_install_message:
|
|
444
444
|
rdoc_options: []
|