woods 2.0.0.beta2 → 2.0.0.beta3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +262 -1
- data/CONTRIBUTING.md +173 -9
- data/README.md +7 -3
- data/SECURITY.md +9 -6
- data/docs/AGENT_GUIDE.md +83 -4
- data/docs/AGENT_SETUP.md +82 -1
- data/docs/BACKEND_MATRIX.md +20 -0
- data/docs/CLIENT_HOOKS.md +111 -0
- data/docs/CONFIGURATION_REFERENCE.md +199 -14
- data/docs/CONSOLE_MCP_SETUP.md +35 -5
- data/docs/DOCKER_SETUP.md +21 -2
- data/docs/EVALUATION.md +464 -1
- data/docs/EXTRACTOR_REFERENCE.md +36 -5
- data/docs/FAQ.md +11 -12
- data/docs/GETTING_STARTED.md +17 -5
- data/docs/INCREMENTAL_EXTRACTION.md +117 -1
- data/docs/INDEX_LAYOUT.md +382 -0
- data/docs/INTERNALS.md +7 -2
- data/docs/MCP_SERVERS.md +221 -5
- data/docs/MCP_TOOL_COOKBOOK.md +33 -18
- data/docs/NOTION_INTEGRATION.md +13 -0
- data/docs/OBSIDIAN_INTEGRATION.md +57 -9
- data/docs/PUBLISHED_INDEX.md +55 -0
- data/docs/README.md +7 -0
- data/docs/RETRIEVAL_GUIDE.md +253 -11
- data/docs/RUNTIME_TRACING.md +71 -0
- data/docs/SOURCE_FRESHNESS.md +143 -0
- data/docs/TROUBLESHOOTING.md +117 -5
- data/docs/UNBLOCKED_INTEGRATION.md +25 -0
- data/docs/UPGRADING_TO_2.md +44 -22
- data/docs/WATCH_DAEMON.md +259 -59
- data/exe/woods-agent-config +6 -0
- data/exe/woods-extract +5 -0
- data/exe/woods-hook-context +6 -0
- data/lib/generators/woods/templates/woods.rb.tt +1 -3
- data/lib/tasks/woods.rake +47 -397
- data/lib/woods/agent_configuration/applier.rb +133 -0
- data/lib/woods/agent_configuration/cli.rb +101 -0
- data/lib/woods/agent_configuration/cli_options.rb +29 -0
- data/lib/woods/agent_configuration/document.rb +105 -0
- data/lib/woods/agent_configuration/error.rb +7 -0
- data/lib/woods/agent_configuration/launcher.rb +75 -0
- data/lib/woods/agent_configuration/layout.rb +59 -0
- data/lib/woods/agent_configuration/managed_section.rb +62 -0
- data/lib/woods/agent_configuration/plan.rb +98 -0
- data/lib/woods/agent_configuration/plan_diff.rb +38 -0
- data/lib/woods/agent_configuration/planned_files.rb +61 -0
- data/lib/woods/agent_configuration/planner.rb +63 -0
- data/lib/woods/agent_configuration/planner_validation.rb +77 -0
- data/lib/woods/agent_configuration/preflight.rb +100 -0
- data/lib/woods/agent_configuration/recovery.rb +49 -0
- data/lib/woods/ast/node.rb +2 -0
- data/lib/woods/ast/parser.rb +38 -5
- data/lib/woods/builder.rb +21 -5
- data/lib/woods/cache/cache_middleware.rb +28 -7
- data/lib/woods/cache/cache_store.rb +4 -5
- data/lib/woods/change_set.rb +5 -4
- data/lib/woods/console/credential_index.rb +20 -2
- data/lib/woods/console/credential_scanner.rb +14 -14
- data/lib/woods/console/credential_scanner_registry.rb +36 -0
- data/lib/woods/console/embedded_executor.rb +1 -1
- data/lib/woods/console/encrypted_credential_snapshot.rb +16 -0
- data/lib/woods/console/rack_middleware.rb +22 -13
- data/lib/woods/console/server.rb +18 -16
- data/lib/woods/dependency_graph.rb +65 -13
- data/lib/woods/embedding/corpus.rb +94 -0
- data/lib/woods/embedding/indexer.rb +90 -46
- data/lib/woods/embedding/openai.rb +17 -6
- data/lib/woods/evaluation/ablation_executor.rb +6 -1
- data/lib/woods/evaluation/ablation_timed_executor.rb +22 -4
- data/lib/woods/export/typed_reader.rb +56 -0
- data/lib/woods/extractor.rb +232 -137
- data/lib/woods/extractors/action_cable_extractor.rb +3 -1
- data/lib/woods/extractors/behavioral_profile.rb +9 -7
- data/lib/woods/extractors/caching_extractor.rb +3 -1
- data/lib/woods/extractors/concern_extractor.rb +64 -6
- data/lib/woods/extractors/configuration_extractor.rb +7 -3
- data/lib/woods/extractors/controller_extractor.rb +13 -4
- data/lib/woods/extractors/database_view_extractor.rb +3 -1
- data/lib/woods/extractors/decorator_extractor.rb +3 -1
- data/lib/woods/extractors/engine_extractor.rb +3 -1
- data/lib/woods/extractors/event_extractor.rb +4 -2
- data/lib/woods/extractors/factory_extractor.rb +3 -1
- data/lib/woods/extractors/graphql_extractor.rb +8 -2
- data/lib/woods/extractors/i18n_extractor.rb +3 -1
- data/lib/woods/extractors/job_extractor.rb +6 -19
- data/lib/woods/extractors/lib_extractor.rb +3 -1
- data/lib/woods/extractors/mailer_extractor.rb +20 -5
- data/lib/woods/extractors/manager_extractor.rb +3 -1
- data/lib/woods/extractors/method_parameters.rb +53 -0
- data/lib/woods/extractors/middleware_argument.rb +65 -0
- data/lib/woods/extractors/middleware_extractor.rb +9 -3
- data/lib/woods/extractors/migration_extractor.rb +3 -1
- data/lib/woods/extractors/model_extractor.rb +39 -33
- data/lib/woods/extractors/package_extractor.rb +24 -4
- data/lib/woods/extractors/phlex_extractor.rb +3 -1
- data/lib/woods/extractors/policy_extractor.rb +3 -1
- data/lib/woods/extractors/poro_extractor.rb +3 -1
- data/lib/woods/extractors/pundit_extractor.rb +3 -1
- data/lib/woods/extractors/rails_source_extractor.rb +4 -2
- data/lib/woods/extractors/rake_task_extractor.rb +4 -2
- data/lib/woods/extractors/route_extractor.rb +3 -1
- data/lib/woods/extractors/route_helper_resolver.rb +10 -33
- data/lib/woods/extractors/scheduled_job_extractor.rb +41 -15
- data/lib/woods/extractors/serializer_extractor.rb +4 -2
- data/lib/woods/extractors/service_extractor.rb +3 -1
- data/lib/woods/extractors/shared_dependency_scanner.rb +2 -2
- data/lib/woods/extractors/shared_utility_methods.rb +27 -15
- data/lib/woods/extractors/source_nesting.rb +1 -1
- data/lib/woods/extractors/state_machine_extractor.rb +3 -1
- data/lib/woods/extractors/test_mapping_extractor.rb +3 -1
- data/lib/woods/extractors/validator_extractor.rb +3 -1
- data/lib/woods/extractors/view_component_extractor.rb +3 -1
- data/lib/woods/extractors/view_template_extractor.rb +3 -1
- data/lib/woods/gem_mapper.rb +2 -0
- data/lib/woods/git_history.rb +116 -0
- data/lib/woods/graph_analyzer.rb +35 -6
- data/lib/woods/hooks/context_cli.rb +54 -0
- data/lib/woods/hooks/context_event.rb +88 -0
- data/lib/woods/hooks/context_hint.rb +73 -0
- data/lib/woods/hooks/context_impact.rb +77 -0
- data/lib/woods/hooks/context_output.rb +47 -0
- data/lib/woods/hooks/context_state.rb +102 -0
- data/lib/woods/hooks/refresh.rb +79 -0
- data/lib/woods/hooks/rule_projection.rb +78 -0
- data/lib/woods/input_rules.rb +19 -0
- data/lib/woods/mcp/bearer_auth.rb +20 -12
- data/lib/woods/mcp/bootstrapper.rb +62 -0
- data/lib/woods/mcp/index_reader.rb +323 -160
- data/lib/woods/mcp/initialization_guidance.rb +27 -0
- data/lib/woods/mcp/origin_guard.rb +17 -9
- data/lib/woods/mcp/published_lexical_retriever.rb +115 -0
- data/lib/woods/mcp/renderers/markdown_renderer.rb +8 -1
- data/lib/woods/mcp/renderers/plain_renderer.rb +7 -1
- data/lib/woods/mcp/search_results.rb +74 -0
- data/lib/woods/mcp/server.rb +158 -37
- data/lib/woods/mcp/tool_contract.rb +2 -0
- data/lib/woods/mcp/tool_response_renderer.rb +25 -0
- data/lib/woods/mcp/traversal_evidence.rb +113 -0
- data/lib/woods/mcp/traversal_evidence_index.rb +100 -0
- data/lib/woods/mcp/traversal_evidence_page.rb +41 -0
- data/lib/woods/mcp/traversal_evidence_text.rb +52 -0
- data/lib/woods/notion/exporter.rb +56 -17
- data/lib/woods/obsidian/destination_plan.rb +98 -0
- data/lib/woods/obsidian/name_mapper.rb +19 -3
- data/lib/woods/obsidian/note_builder.rb +19 -10
- data/lib/woods/obsidian/vault_exporter.rb +88 -32
- data/lib/woods/operator/pipeline_guard.rb +18 -13
- data/lib/woods/path_dispatcher.rb +7 -1
- data/lib/woods/payload_store.rb +27 -26
- data/lib/woods/railtie.rb +3 -3
- data/lib/woods/railtie_support.rb +12 -12
- data/lib/woods/rake_helpers.rb +392 -0
- data/lib/woods/resilience/graph_invariant_validator/membership_checks.rb +71 -0
- data/lib/woods/resilience/graph_invariant_validator/node_checks.rb +61 -0
- data/lib/woods/resilience/graph_invariant_validator/reverse_relationship_checks.rb +46 -0
- data/lib/woods/resilience/graph_invariant_validator.rb +119 -0
- data/lib/woods/resilience/index_validator/graph_checks.rb +80 -0
- data/lib/woods/resilience/index_validator.rb +112 -23
- data/lib/woods/retrieval/context_assembler.rb +50 -15
- data/lib/woods/retrieval/lexical_assembler.rb +73 -0
- data/lib/woods/retrieval/lexical_index.rb +119 -0
- data/lib/woods/retrieval/ranker.rb +4 -2
- data/lib/woods/retrieval/scope.rb +108 -0
- data/lib/woods/retrieval/scoped_graph_store.rb +32 -0
- data/lib/woods/retrieval/scoped_vector_store.rb +55 -0
- data/lib/woods/retrieval/search_executor.rb +86 -27
- data/lib/woods/retrieval/source_evidence.rb +200 -0
- data/lib/woods/retriever.rb +98 -22
- data/lib/woods/ruby_analyzer/trace_enricher.rb +77 -38
- data/lib/woods/session_tracer/middleware.rb +10 -12
- data/lib/woods/session_tracer/redis_store.rb +22 -6
- data/lib/woods/session_tracer/session_flow_assembler.rb +23 -17
- data/lib/woods/session_tracer/solid_cache_coordination.rb +6 -4
- data/lib/woods/session_tracer/unit_resolver.rb +63 -0
- data/lib/woods/source_inputs/consumer_errors.rb +27 -0
- data/lib/woods/source_inputs/handoff.rb +102 -0
- data/lib/woods/source_inputs/launcher.rb +157 -0
- data/lib/woods/source_inputs/manifest.rb +124 -0
- data/lib/woods/source_inputs/private_key.rb +55 -0
- data/lib/woods/source_inputs/scanner.rb +171 -0
- data/lib/woods/source_inputs/scopes.rb +71 -0
- data/lib/woods/source_inputs/session.rb +214 -0
- data/lib/woods/source_inputs/status.rb +84 -0
- data/lib/woods/source_inputs/verifier.rb +107 -0
- data/lib/woods/storage/metadata_store.rb +25 -25
- data/lib/woods/storage/pgvector.rb +29 -8
- data/lib/woods/storage/qdrant.rb +17 -7
- data/lib/woods/storage/vector_store.rb +18 -6
- data/lib/woods/tasks.rb +3 -2
- data/lib/woods/temporal/json_snapshot_store.rb +29 -8
- data/lib/woods/unblocked/exporter.rb +59 -70
- data/lib/woods/version.rb +1 -1
- data/lib/woods/watch/boot_snapshot.rb +52 -0
- data/lib/woods/watch/daemon.rb +136 -28
- data/lib/woods/watch/listen_watcher.rb +4 -0
- data/lib/woods/watch/polling_watcher.rb +5 -1
- data/lib/woods/watch/status.rb +20 -15
- data/lib/woods/watch/tree_scan.rb +21 -13
- data/lib/woods/watch/watcher.rb +4 -1
- data/lib/woods.rb +50 -11
- data/plugin/.claude-plugin/plugin.json +1 -1
- data/plugin/hooks/adapters/normalize.jq +15 -0
- data/plugin/hooks/adapters/normalize.rb +63 -0
- data/plugin/hooks/hooks.json +20 -0
- data/plugin/hooks/woods-context.sh +50 -0
- data/plugin/hooks/woods-input-rules.sh +159 -0
- data/plugin/hooks/woods-opencode.mjs +65 -0
- data/plugin/hooks/woods-post-edit.sh +2 -225
- data/plugin/hooks/woods-refresh.sh +260 -0
- data/plugin/hooks/woods-session-start.sh +47 -55
- data/plugin/skills/woods-agent-enable/SKILL.md +13 -0
- data/plugin/skills/woods-diagnose/SKILL.md +288 -1
- data/plugin/skills/woods-investigate/SKILL.md +106 -0
- data/plugin/skills/woods-mcp-config/SKILL.md +89 -1
- data/plugin/skills/woods-setup/SKILL.md +107 -6
- metadata +84 -5
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Woods
|
|
4
|
+
module Extractors
|
|
5
|
+
# Stable, readable middleware arguments without inspecting opaque runtime state.
|
|
6
|
+
# Literal strings and custom #to_s implementations remain application data.
|
|
7
|
+
module MiddlewareArgument
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# @param value [Object] Runtime middleware argument
|
|
11
|
+
# @return [String] Deterministic description for ordinary Ruby runtime values
|
|
12
|
+
def render(value, ancestors = [], nested: false)
|
|
13
|
+
return "<recursive #{value.class}>" if ancestors.any? { |ancestor| ancestor.equal?(value) }
|
|
14
|
+
|
|
15
|
+
ancestors += [value]
|
|
16
|
+
case value
|
|
17
|
+
when String then nested ? value.inspect : value
|
|
18
|
+
when Symbol then nested ? value.inspect : value.to_s
|
|
19
|
+
when NilClass then nested ? 'nil' : ''
|
|
20
|
+
when Array then "[#{value.map { |item| render(item, ancestors, nested: true) }.join(', ')}]"
|
|
21
|
+
when Hash
|
|
22
|
+
pairs = value.map { |key, item| "#{render(key, ancestors, nested: true)}=>#{render(item, ancestors, nested: true)}" }
|
|
23
|
+
"{#{pairs.join(', ')}}"
|
|
24
|
+
when Module then module_name(value)
|
|
25
|
+
when Proc then "#<#{value.lambda? ? 'lambda' : 'Proc'} #{value.source_location&.join(':') || 'native'}>"
|
|
26
|
+
else
|
|
27
|
+
# Only Object's identity-based implementation is replaced. Never scrub
|
|
28
|
+
# arbitrary hex strings or override application-defined textual values.
|
|
29
|
+
if [Kernel, Object].include?(value.method(:to_s).owner)
|
|
30
|
+
"#<#{module_name(value.class)}>"
|
|
31
|
+
else
|
|
32
|
+
value.to_s
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def module_name(value)
|
|
38
|
+
return value.name if constant_path?(value.name)
|
|
39
|
+
|
|
40
|
+
parent = value.is_a?(Class) ? " < #{module_name(value.superclass)}" : ''
|
|
41
|
+
methods = (value.instance_methods(false) + value.private_instance_methods(false)).uniq.sort.map do |name|
|
|
42
|
+
"#{name}@#{value.instance_method(name).source_location&.join(':') || 'native'}"
|
|
43
|
+
end
|
|
44
|
+
"#<anonymous #{value.class}#{parent}#{" (#{methods.join(', ')})" unless methods.empty?}>"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Ruby temporary names are explicitly not constant paths and can contain
|
|
48
|
+
# runtime identities (Rails 8.2 uses these for executor/reloader classes).
|
|
49
|
+
# Validate each segment with Ruby itself, including Unicode constants,
|
|
50
|
+
# against an empty namespace: never resolve or autoload application names.
|
|
51
|
+
def constant_path?(name)
|
|
52
|
+
return false if name.nil? || name.empty?
|
|
53
|
+
|
|
54
|
+
namespace = Module.new
|
|
55
|
+
name.split('::', -1).all? do |segment|
|
|
56
|
+
namespace.const_defined?(segment, false)
|
|
57
|
+
true
|
|
58
|
+
end
|
|
59
|
+
rescue NameError
|
|
60
|
+
false
|
|
61
|
+
end
|
|
62
|
+
private_class_method :module_name, :constant_path?
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../source_inputs/consumer_errors'
|
|
4
|
+
|
|
5
|
+
require_relative 'middleware_argument'
|
|
6
|
+
|
|
3
7
|
module Woods
|
|
4
8
|
module Extractors
|
|
5
9
|
# MiddlewareExtractor handles Rack middleware stack extraction via runtime introspection.
|
|
@@ -42,7 +46,7 @@ module Woods
|
|
|
42
46
|
|
|
43
47
|
[unit]
|
|
44
48
|
rescue StandardError => e
|
|
45
|
-
|
|
49
|
+
SourceInputs::ConsumerErrors.log(self, "Failed to extract middleware stack: #{e.message}")
|
|
46
50
|
[]
|
|
47
51
|
end
|
|
48
52
|
|
|
@@ -80,7 +84,9 @@ module Woods
|
|
|
80
84
|
# @param position [Integer] Position in the stack
|
|
81
85
|
# @return [Hash, nil]
|
|
82
86
|
def extract_single_middleware(middleware, position)
|
|
83
|
-
name = if middleware.respond_to?(:
|
|
87
|
+
name = if middleware.respond_to?(:klass) && middleware.klass.is_a?(Module)
|
|
88
|
+
MiddlewareArgument.render(middleware.klass)
|
|
89
|
+
elsif middleware.respond_to?(:name)
|
|
84
90
|
middleware.name
|
|
85
91
|
elsif middleware.respond_to?(:klass)
|
|
86
92
|
middleware.klass.to_s
|
|
@@ -89,7 +95,7 @@ module Woods
|
|
|
89
95
|
end
|
|
90
96
|
|
|
91
97
|
args = if middleware.respond_to?(:args)
|
|
92
|
-
middleware.args.map(
|
|
98
|
+
middleware.args.map { |argument| MiddlewareArgument.render(argument) }
|
|
93
99
|
else
|
|
94
100
|
[]
|
|
95
101
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../source_inputs/consumer_errors'
|
|
4
|
+
|
|
3
5
|
require_relative 'shared_utility_methods'
|
|
4
6
|
require_relative 'shared_dependency_scanner'
|
|
5
7
|
|
|
@@ -122,7 +124,7 @@ module Woods
|
|
|
122
124
|
|
|
123
125
|
unit
|
|
124
126
|
rescue StandardError => e
|
|
125
|
-
|
|
127
|
+
SourceInputs::ConsumerErrors.log(self, "Failed to extract migration #{file_path}: #{e.message}")
|
|
126
128
|
nil
|
|
127
129
|
end
|
|
128
130
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../source_inputs/consumer_errors'
|
|
4
|
+
|
|
3
5
|
require 'digest'
|
|
4
6
|
require_relative '../ast/parser'
|
|
5
7
|
require_relative 'shared_utility_methods'
|
|
@@ -36,6 +38,12 @@ module Woods
|
|
|
36
38
|
(?:after|before)_(?:add|remove)_for_ # collection callbacks
|
|
37
39
|
)/x
|
|
38
40
|
|
|
41
|
+
# Rails exposes one chain per event; each entry supplies its own kind.
|
|
42
|
+
# before_commit is itself an event, unlike before_save and after_commit.
|
|
43
|
+
CALLBACK_EVENTS = %i[
|
|
44
|
+
validation save create update destroy commit rollback before_commit initialize find touch
|
|
45
|
+
].freeze
|
|
46
|
+
|
|
39
47
|
# Warnings collected during extraction (skipped associations, failed models)
|
|
40
48
|
attr_reader :warnings
|
|
41
49
|
|
|
@@ -97,7 +105,7 @@ module Woods
|
|
|
97
105
|
unit
|
|
98
106
|
rescue StandardError => e
|
|
99
107
|
@warnings << "Failed to extract model #{model.name}: #{e.message}"
|
|
100
|
-
|
|
108
|
+
SourceInputs::ConsumerErrors.log(self, "Failed to extract model #{model.name}: #{e.message}")
|
|
101
109
|
nil
|
|
102
110
|
end
|
|
103
111
|
|
|
@@ -384,6 +392,9 @@ module Woods
|
|
|
384
392
|
|
|
385
393
|
# Find the file path for a concern
|
|
386
394
|
def concern_path_for(mod)
|
|
395
|
+
reflected = resolve_source_location(mod, app_root: Rails.root.to_s, fallback: nil)
|
|
396
|
+
return reflected if app_source?(reflected, "#{Rails.root}/") && File.file?(reflected)
|
|
397
|
+
|
|
387
398
|
possible_paths = [
|
|
388
399
|
Rails.root.join("app/models/concerns/#{mod.name.underscore}.rb"),
|
|
389
400
|
Rails.root.join("app/controllers/concerns/#{mod.name.underscore}.rb"),
|
|
@@ -407,6 +418,7 @@ module Woods
|
|
|
407
418
|
# composite source does not carry.
|
|
408
419
|
# @return [Hash]
|
|
409
420
|
def extract_metadata(model, source = nil, inlined_concerns: nil)
|
|
421
|
+
callbacks = extract_callbacks(model)
|
|
410
422
|
{
|
|
411
423
|
# Core identifiers
|
|
412
424
|
table_name: model.table_name,
|
|
@@ -416,15 +428,11 @@ module Woods
|
|
|
416
428
|
# Relationships and behaviors
|
|
417
429
|
associations: extract_associations(model),
|
|
418
430
|
validations: extract_validations(model),
|
|
419
|
-
callbacks:
|
|
431
|
+
callbacks: callbacks,
|
|
420
432
|
scopes: extract_scopes(model, source),
|
|
421
433
|
enums: extract_enums(model),
|
|
422
434
|
inlined_concerns: inlined_concerns || build_model_source_with_concerns(model, source).last,
|
|
423
435
|
|
|
424
|
-
# API surface
|
|
425
|
-
class_methods: model.methods(false).sort,
|
|
426
|
-
instance_methods: filter_instance_methods(model.instance_methods(false)).sort,
|
|
427
|
-
|
|
428
436
|
# Inheritance
|
|
429
437
|
sti_column: model.inheritance_column,
|
|
430
438
|
is_sti_base: sti_base?(model),
|
|
@@ -433,7 +441,7 @@ module Woods
|
|
|
433
441
|
|
|
434
442
|
# Metrics for retrieval ranking
|
|
435
443
|
loc: count_loc(model, source),
|
|
436
|
-
callback_count:
|
|
444
|
+
callback_count: callbacks.length,
|
|
437
445
|
association_count: model.reflect_on_all_associations.size,
|
|
438
446
|
validation_count: model._validators.values.flatten.size,
|
|
439
447
|
|
|
@@ -449,6 +457,12 @@ module Woods
|
|
|
449
457
|
[]
|
|
450
458
|
end,
|
|
451
459
|
|
|
460
|
+
# Schema loading can define methods (including Rails' optimized
|
|
461
|
+
# constructor). Reflect only after the schema reads above so a cold
|
|
462
|
+
# full extraction and a warmed incremental run see the same API.
|
|
463
|
+
class_methods: model.methods(false).sort,
|
|
464
|
+
instance_methods: filter_instance_methods(model.instance_methods(false)).sort,
|
|
465
|
+
|
|
452
466
|
# Foreign keys as the connection reports them. The target table's
|
|
453
467
|
# database is a graph-level concern, resolved by the
|
|
454
468
|
# cross_database_edges report, not stored per model here (#280).
|
|
@@ -616,23 +630,12 @@ module Woods
|
|
|
616
630
|
|
|
617
631
|
# Extract all callbacks with their full chain
|
|
618
632
|
def extract_callbacks(model)
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
before_save after_save around_save
|
|
622
|
-
before_create after_create around_create
|
|
623
|
-
before_update after_update around_update
|
|
624
|
-
before_destroy after_destroy around_destroy
|
|
625
|
-
after_commit after_rollback
|
|
626
|
-
after_initialize after_find
|
|
627
|
-
after_touch
|
|
628
|
-
]
|
|
629
|
-
|
|
630
|
-
callback_types.flat_map do |type|
|
|
631
|
-
callbacks = model.send("_#{type}_callbacks")
|
|
633
|
+
CALLBACK_EVENTS.flat_map do |event|
|
|
634
|
+
callbacks = model.send("_#{event}_callbacks")
|
|
632
635
|
callbacks.map do |cb|
|
|
633
636
|
{
|
|
634
|
-
type:
|
|
635
|
-
filter: cb
|
|
637
|
+
type: event == :before_commit ? :before_commit : :"#{cb.kind}_#{event}",
|
|
638
|
+
filter: callback_filter_label(cb),
|
|
636
639
|
kind: cb.kind, # :before, :after, :around
|
|
637
640
|
conditions: format_callback_conditions(cb)
|
|
638
641
|
}
|
|
@@ -647,6 +650,20 @@ module Woods
|
|
|
647
650
|
end.compact
|
|
648
651
|
end
|
|
649
652
|
|
|
653
|
+
# Preserve application labels; strip addresses only from Ruby's actual
|
|
654
|
+
# default representation. Nested identities occur for anonymous classes.
|
|
655
|
+
# These are descriptive labels, not serialization of callback object state.
|
|
656
|
+
def callback_filter_label(callback)
|
|
657
|
+
filter = callback_filter(callback)
|
|
658
|
+
label = filter.to_s
|
|
659
|
+
return label unless label.start_with?('#<')
|
|
660
|
+
|
|
661
|
+
owner = filter.is_a?(Module) ? Module : Kernel
|
|
662
|
+
return label unless label == owner.instance_method(:to_s).bind(filter).call
|
|
663
|
+
|
|
664
|
+
label.gsub(/:0x[0-9a-f]+(?=>)/i, '')
|
|
665
|
+
end
|
|
666
|
+
|
|
650
667
|
# Extract scopes with their source if available.
|
|
651
668
|
# Parses the full source with the AST layer to get accurate scope
|
|
652
669
|
# boundaries, falling back to regex line-scanning on parse failure.
|
|
@@ -1221,17 +1238,6 @@ module Woods
|
|
|
1221
1238
|
# Helper methods
|
|
1222
1239
|
# ──────────────────────────────────────────────────────────────────────
|
|
1223
1240
|
|
|
1224
|
-
def callback_count(model)
|
|
1225
|
-
%i[validation save create update destroy commit rollback].sum do |type|
|
|
1226
|
-
# CallbackChain includes Enumerable but defines no #size on any
|
|
1227
|
-
# supported Rails version — #size raises and the rescue would
|
|
1228
|
-
# zero the count. #count is the only correct API here.
|
|
1229
|
-
model.send("_#{type}_callbacks").count
|
|
1230
|
-
rescue StandardError
|
|
1231
|
-
0
|
|
1232
|
-
end
|
|
1233
|
-
end
|
|
1234
|
-
|
|
1235
1241
|
def count_loc(model, source = nil)
|
|
1236
1242
|
if source
|
|
1237
1243
|
source.lines.count { |l| l.strip.present? && !l.strip.start_with?('#') }
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../source_inputs/consumer_errors'
|
|
4
|
+
|
|
3
5
|
require 'yaml'
|
|
4
6
|
require 'pathname'
|
|
5
7
|
|
|
@@ -69,7 +71,7 @@ module Woods
|
|
|
69
71
|
unit.dependencies = dependencies.map { |dep| { type: :package, target: dep, via: :package_dependency } }
|
|
70
72
|
unit
|
|
71
73
|
rescue StandardError => e
|
|
72
|
-
|
|
74
|
+
SourceInputs::ConsumerErrors.log(self, "Failed to extract package from #{file_path}: #{e.message}")
|
|
73
75
|
nil
|
|
74
76
|
end
|
|
75
77
|
|
|
@@ -103,13 +105,30 @@ module Woods
|
|
|
103
105
|
@package_files ||= begin
|
|
104
106
|
patterns = Array(@packwerk['package_paths'] || DEFAULT_PACKAGE_PATHS)
|
|
105
107
|
excludes = Array(@packwerk['exclude'] || DEFAULT_EXCLUDE)
|
|
106
|
-
found = patterns
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
found = if patterns == DEFAULT_PACKAGE_PATHS && excludes == DEFAULT_EXCLUDE
|
|
109
|
+
default_package_files
|
|
110
|
+
else
|
|
111
|
+
patterns.flat_map { |pattern| Dir.glob(@rails_root.join(pattern.to_s, PACKAGE_FILE).to_s) }
|
|
112
|
+
end
|
|
109
113
|
found.uniq.reject { |path| excluded?(relativize(path), excludes) }.sort
|
|
110
114
|
end
|
|
111
115
|
end
|
|
112
116
|
|
|
117
|
+
# Prune only the known default top-level exclusions before recursion.
|
|
118
|
+
# Recursive glob does not descend through symlink directories; keep that
|
|
119
|
+
# behavior when turning top-level entries into separate glob roots.
|
|
120
|
+
def default_package_files
|
|
121
|
+
found = Dir.glob(PACKAGE_FILE, base: @rails_root).map { |name| @rails_root.join(name).to_s }
|
|
122
|
+
Dir.glob('*', base: @rails_root).each do |name|
|
|
123
|
+
directory = @rails_root.join(name)
|
|
124
|
+
next unless directory.directory? && !directory.symlink?
|
|
125
|
+
next if excluded?("#{name}/#{PACKAGE_FILE}", DEFAULT_EXCLUDE)
|
|
126
|
+
|
|
127
|
+
found.concat(Dir.glob("**/#{PACKAGE_FILE}", base: directory).map { |path| directory.join(path).to_s })
|
|
128
|
+
end
|
|
129
|
+
found
|
|
130
|
+
end
|
|
131
|
+
|
|
113
132
|
def excluded?(relative, excludes)
|
|
114
133
|
excludes.any? { |glob| File.fnmatch?(glob.to_s, relative, File::FNM_PATHNAME | File::FNM_EXTGLOB) }
|
|
115
134
|
end
|
|
@@ -122,6 +141,7 @@ module Woods
|
|
|
122
141
|
data = YAML.safe_load_file(path, permitted_classes: [Symbol], aliases: true)
|
|
123
142
|
data.is_a?(Hash) ? data : {}
|
|
124
143
|
rescue StandardError => e
|
|
144
|
+
SourceInputs::ConsumerErrors.record(self)
|
|
125
145
|
Rails.logger.warn("[Woods] Could not read #{PACKWERK_CONFIG}: #{e.message}")
|
|
126
146
|
{}
|
|
127
147
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../source_inputs/consumer_errors'
|
|
4
|
+
|
|
3
5
|
require_relative 'component_discovery'
|
|
4
6
|
require_relative 'shared_utility_methods'
|
|
5
7
|
require_relative 'shared_dependency_scanner'
|
|
@@ -87,7 +89,7 @@ module Woods
|
|
|
87
89
|
|
|
88
90
|
unit
|
|
89
91
|
rescue StandardError => e
|
|
90
|
-
|
|
92
|
+
SourceInputs::ConsumerErrors.log(self, "Failed to extract component #{component.name}: #{e.message}")
|
|
91
93
|
nil
|
|
92
94
|
end
|
|
93
95
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../source_inputs/consumer_errors'
|
|
4
|
+
|
|
3
5
|
require_relative 'shared_utility_methods'
|
|
4
6
|
require_relative 'shared_dependency_scanner'
|
|
5
7
|
|
|
@@ -69,7 +71,7 @@ module Woods
|
|
|
69
71
|
|
|
70
72
|
unit
|
|
71
73
|
rescue StandardError => e
|
|
72
|
-
|
|
74
|
+
SourceInputs::ConsumerErrors.log(self, "Failed to extract policy #{file_path}: #{e.message}")
|
|
73
75
|
nil
|
|
74
76
|
end
|
|
75
77
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../source_inputs/consumer_errors'
|
|
4
|
+
|
|
3
5
|
require_relative 'shared_utility_methods'
|
|
4
6
|
require_relative 'shared_dependency_scanner'
|
|
5
7
|
require_relative 'source_nesting'
|
|
@@ -87,7 +89,7 @@ module Woods
|
|
|
87
89
|
|
|
88
90
|
unit
|
|
89
91
|
rescue StandardError => e
|
|
90
|
-
|
|
92
|
+
SourceInputs::ConsumerErrors.log(self, "Failed to extract PORO #{file_path}: #{e.message}")
|
|
91
93
|
nil
|
|
92
94
|
end
|
|
93
95
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../source_inputs/consumer_errors'
|
|
4
|
+
|
|
3
5
|
require_relative 'shared_utility_methods'
|
|
4
6
|
require_relative 'shared_dependency_scanner'
|
|
5
7
|
|
|
@@ -67,7 +69,7 @@ module Woods
|
|
|
67
69
|
|
|
68
70
|
unit
|
|
69
71
|
rescue StandardError => e
|
|
70
|
-
|
|
72
|
+
SourceInputs::ConsumerErrors.log(self, "Failed to extract Pundit policy #{file_path}: #{e.message}")
|
|
71
73
|
nil
|
|
72
74
|
end
|
|
73
75
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../source_inputs/consumer_errors'
|
|
4
|
+
|
|
3
5
|
require_relative '../extracted_unit'
|
|
4
6
|
|
|
5
7
|
module Woods
|
|
@@ -268,7 +270,7 @@ module Woods
|
|
|
268
270
|
|
|
269
271
|
unit
|
|
270
272
|
rescue StandardError => e
|
|
271
|
-
|
|
273
|
+
SourceInputs::ConsumerErrors.log(self, "Failed to extract Rails source #{file_path}: #{e.message}")
|
|
272
274
|
nil
|
|
273
275
|
end
|
|
274
276
|
|
|
@@ -302,7 +304,7 @@ module Woods
|
|
|
302
304
|
|
|
303
305
|
unit
|
|
304
306
|
rescue StandardError => e
|
|
305
|
-
|
|
307
|
+
SourceInputs::ConsumerErrors.log(self, "Failed to extract gem source #{file_path}: #{e.message}")
|
|
306
308
|
nil
|
|
307
309
|
end
|
|
308
310
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../source_inputs/consumer_errors'
|
|
4
|
+
|
|
3
5
|
require_relative 'line_neutralizer'
|
|
4
6
|
require_relative 'shared_utility_methods'
|
|
5
7
|
require_relative 'shared_dependency_scanner'
|
|
@@ -73,7 +75,7 @@ module Woods
|
|
|
73
75
|
build_unit(task_data, file_path, data[:source], sibling_definitions(task_data[:full_name], file_path))
|
|
74
76
|
end
|
|
75
77
|
rescue StandardError => e
|
|
76
|
-
|
|
78
|
+
SourceInputs::ConsumerErrors.log(self, "Failed to extract rake tasks from #{file_path}: #{e.message}")
|
|
77
79
|
[]
|
|
78
80
|
end
|
|
79
81
|
|
|
@@ -100,7 +102,7 @@ module Woods
|
|
|
100
102
|
{ source: source, tasks: parse_tasks(source) }
|
|
101
103
|
end
|
|
102
104
|
rescue StandardError => e
|
|
103
|
-
|
|
105
|
+
SourceInputs::ConsumerErrors.log(self, "Failed to scan rake tasks in #{file}: #{e.message}")
|
|
104
106
|
nil
|
|
105
107
|
end
|
|
106
108
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../source_inputs/consumer_errors'
|
|
4
|
+
|
|
3
5
|
require_relative 'shared_utility_methods'
|
|
4
6
|
require_relative 'shared_dependency_scanner'
|
|
5
7
|
|
|
@@ -76,7 +78,7 @@ module Woods
|
|
|
76
78
|
|
|
77
79
|
unit
|
|
78
80
|
rescue StandardError => e
|
|
79
|
-
|
|
81
|
+
SourceInputs::ConsumerErrors.log(self, "Failed to extract route: #{e.message}")
|
|
80
82
|
nil
|
|
81
83
|
end
|
|
82
84
|
|
|
@@ -23,41 +23,13 @@ module Woods
|
|
|
23
23
|
# end
|
|
24
24
|
#
|
|
25
25
|
module RouteHelperResolver
|
|
26
|
-
# Route helper prefixes that produce non-navigation dependencies.
|
|
27
|
-
# These generate asset URLs or are common false positives from
|
|
28
|
-
# non-route uses of _path/_url suffixes in Ruby code.
|
|
29
|
-
#
|
|
30
|
-
# NOTE: `root` is intentionally excluded — root_path is the most common
|
|
31
|
-
# Rails route helper, but it appears so frequently in non-navigation contexts
|
|
32
|
-
# (path construction, config, tests) that it generates excessive noise.
|
|
33
|
-
# The tradeoff: "what links to the home page?" won't appear in graph queries.
|
|
34
|
-
# Add new prefixes here when false positives are discovered in host apps.
|
|
35
|
-
IGNORED_HELPER_PREFIXES = %w[
|
|
36
|
-
asset
|
|
37
|
-
image
|
|
38
|
-
stylesheet
|
|
39
|
-
javascript
|
|
40
|
-
font
|
|
41
|
-
audio
|
|
42
|
-
video
|
|
43
|
-
turbo_stream
|
|
44
|
-
file
|
|
45
|
-
tmp
|
|
46
|
-
base
|
|
47
|
-
root
|
|
48
|
-
log
|
|
49
|
-
socket
|
|
50
|
-
download
|
|
51
|
-
].freeze
|
|
52
|
-
|
|
53
26
|
# Build the route helper lookup map from Rails named routes.
|
|
54
27
|
# Call this once in your extractor's initialize method.
|
|
55
28
|
#
|
|
56
29
|
# Resilient to partial test doubles: any exception raised while
|
|
57
30
|
# traversing Rails routes (unstubbed `application` on a double,
|
|
58
31
|
# missing `named_routes`, etc.) is swallowed and leaves the map
|
|
59
|
-
# empty — extractors
|
|
60
|
-
# literal as the dependency target.
|
|
32
|
+
# empty — extractors omit unresolved navigation dependencies.
|
|
61
33
|
def build_route_helper_map
|
|
62
34
|
@route_helper_map = {}
|
|
63
35
|
return unless defined?(Rails)
|
|
@@ -65,6 +37,11 @@ module Woods
|
|
|
65
37
|
routes = safe_rails_application_routes
|
|
66
38
|
return unless routes
|
|
67
39
|
|
|
40
|
+
# Rails lazy route sets do not load when named_routes is read. Use the
|
|
41
|
+
# runtime route collection reader before caching helpers, just as the
|
|
42
|
+
# route extractor does. Older route sets and partial doubles still work.
|
|
43
|
+
routes.routes if routes.respond_to?(:routes)
|
|
44
|
+
|
|
68
45
|
routes.named_routes.each do |name, route|
|
|
69
46
|
controller = route.defaults[:controller]
|
|
70
47
|
action = route.defaults[:action]
|
|
@@ -78,8 +55,8 @@ module Woods
|
|
|
78
55
|
}
|
|
79
56
|
end
|
|
80
57
|
rescue StandardError
|
|
81
|
-
# Leave @route_helper_map empty — navigation-edge extractors
|
|
82
|
-
#
|
|
58
|
+
# Leave @route_helper_map empty — navigation-edge extractors omit
|
|
59
|
+
# helpers whose controller/action cannot be resolved.
|
|
83
60
|
@route_helper_map = {}
|
|
84
61
|
end
|
|
85
62
|
|
|
@@ -105,8 +82,8 @@ module Woods
|
|
|
105
82
|
# @return [Hash, nil] { controller:, action:, path:, verb: } or nil if unresolvable
|
|
106
83
|
def resolve_route_helper(helper_name)
|
|
107
84
|
base = helper_name.sub(/_(path|url)\z/, '')
|
|
108
|
-
|
|
109
|
-
|
|
85
|
+
# A real named route is authoritative even when its name resembles
|
|
86
|
+
# a filesystem or asset helper. Unresolved names still produce no edge.
|
|
110
87
|
@route_helper_map&.[](base)
|
|
111
88
|
end
|
|
112
89
|
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../source_inputs/consumer_errors'
|
|
4
|
+
|
|
3
5
|
require 'yaml'
|
|
6
|
+
begin
|
|
7
|
+
require 'active_support/configuration_file'
|
|
8
|
+
rescue LoadError
|
|
9
|
+
# Rails 6.0 predates ConfigurationFile.
|
|
10
|
+
require 'erb'
|
|
11
|
+
end
|
|
4
12
|
|
|
5
13
|
module Woods
|
|
6
14
|
module Extractors
|
|
@@ -38,9 +46,6 @@ module Woods
|
|
|
38
46
|
'0 0 1 1 *' => 'yearly on January 1st'
|
|
39
47
|
}.freeze
|
|
40
48
|
|
|
41
|
-
# Environment keys to unwrap when nested in YAML
|
|
42
|
-
ENVIRONMENT_KEYS = %w[production development test staging].freeze
|
|
43
|
-
|
|
44
49
|
# A quoted Whenever command argument in either style. The body runs to
|
|
45
50
|
# the *matching* delimiter, so the inner quotes of `command "echo 'hi'"`
|
|
46
51
|
# stay part of the command.
|
|
@@ -79,8 +84,8 @@ module Woods
|
|
|
79
84
|
else
|
|
80
85
|
[]
|
|
81
86
|
end
|
|
82
|
-
rescue StandardError => e
|
|
83
|
-
|
|
87
|
+
rescue StandardError, SyntaxError, LoadError => e
|
|
88
|
+
SourceInputs::ConsumerErrors.log(self, "Failed to extract scheduled jobs from #{file_path}: #{e.message}")
|
|
84
89
|
[]
|
|
85
90
|
end
|
|
86
91
|
|
|
@@ -97,12 +102,15 @@ module Woods
|
|
|
97
102
|
# @return [Array<ExtractedUnit>]
|
|
98
103
|
def extract_yaml_schedule(file_path, format)
|
|
99
104
|
source = File.read(file_path)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
data = if format == :solid_queue
|
|
106
|
+
# Match Solid Queue's trusted Rails configuration loader: ERB
|
|
107
|
+
# executes with its real filename (including require_relative).
|
|
108
|
+
load_recurring_configuration(file_path, source)
|
|
109
|
+
else
|
|
110
|
+
# Sidekiq remains safe-loaded; aliases allow shared defaults
|
|
111
|
+
# without permitting additional deserialized classes (#203).
|
|
112
|
+
YAML.safe_load(source, permitted_classes: [Symbol], aliases: true)
|
|
113
|
+
end
|
|
106
114
|
|
|
107
115
|
return [] unless data.is_a?(Hash) && data.any?
|
|
108
116
|
|
|
@@ -116,10 +124,23 @@ module Woods
|
|
|
116
124
|
end
|
|
117
125
|
end
|
|
118
126
|
|
|
127
|
+
def load_recurring_configuration(file_path, source)
|
|
128
|
+
if defined?(ActiveSupport::ConfigurationFile)
|
|
129
|
+
ActiveSupport::ConfigurationFile.parse(file_path)
|
|
130
|
+
else
|
|
131
|
+
# Rails 6.0: preserve the same filename-aware ERB evaluation while
|
|
132
|
+
# retaining the existing scalar/hash YAML policy.
|
|
133
|
+
require 'erb'
|
|
134
|
+
erb = ERB.new(source)
|
|
135
|
+
erb.filename = file_path
|
|
136
|
+
YAML.safe_load(erb.result, permitted_classes: [Symbol], aliases: true)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
119
140
|
# Detect and unwrap environment-nested YAML.
|
|
120
141
|
#
|
|
121
|
-
# If the top
|
|
122
|
-
#
|
|
142
|
+
# If the top level contains maps of tasks, unwrap to the section for
|
|
143
|
+
# the environment being extracted (including custom environment names),
|
|
123
144
|
# falling back to the first section when the current environment has no
|
|
124
145
|
# entry. Taking `values.first` unconditionally meant a file listing
|
|
125
146
|
# `development:` before `production:` indexed the development schedule
|
|
@@ -128,9 +149,14 @@ module Woods
|
|
|
128
149
|
# @param data [Hash] Parsed YAML data
|
|
129
150
|
# @return [Hash] Unwrapped entries
|
|
130
151
|
def unwrap_environment_nesting(data)
|
|
131
|
-
|
|
152
|
+
# Environment sections contain task maps; task entries contain scalar
|
|
153
|
+
# configuration values. Shape matters even for a task called production.
|
|
154
|
+
nested = data.values.all? do |section|
|
|
155
|
+
section.nil? || (section.is_a?(Hash) && section.values.all?(Hash))
|
|
156
|
+
end
|
|
157
|
+
return data unless nested
|
|
132
158
|
|
|
133
|
-
data[current_environment] || data.values.first || {}
|
|
159
|
+
data.key?(current_environment) ? (data[current_environment] || {}) : (data.values.first || {})
|
|
134
160
|
end
|
|
135
161
|
|
|
136
162
|
# The environment name the extraction is running under, as a String.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../source_inputs/consumer_errors'
|
|
4
|
+
|
|
3
5
|
require_relative 'shared_utility_methods'
|
|
4
6
|
require_relative 'shared_dependency_scanner'
|
|
5
7
|
|
|
@@ -106,7 +108,7 @@ module Woods
|
|
|
106
108
|
|
|
107
109
|
unit
|
|
108
110
|
rescue StandardError => e
|
|
109
|
-
|
|
111
|
+
SourceInputs::ConsumerErrors.log(self, "Failed to extract serializer #{file_path}: #{e.message}")
|
|
110
112
|
nil
|
|
111
113
|
end
|
|
112
114
|
|
|
@@ -136,7 +138,7 @@ module Woods
|
|
|
136
138
|
|
|
137
139
|
unit
|
|
138
140
|
rescue StandardError => e
|
|
139
|
-
|
|
141
|
+
SourceInputs::ConsumerErrors.log(self, "Failed to extract serializer #{klass.name}: #{e.message}")
|
|
140
142
|
nil
|
|
141
143
|
end
|
|
142
144
|
|