woods 2.0.0.beta2 → 2.0.0.beta4
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 +339 -1
- data/CONTRIBUTING.md +188 -12
- data/README.md +93 -174
- data/SECURITY.md +9 -6
- data/docs/AGENT_GUIDE.md +109 -8
- data/docs/AGENT_SETUP.md +98 -7
- data/docs/BACKEND_MATRIX.md +25 -0
- data/docs/CLIENT_HOOKS.md +111 -0
- data/docs/CONFIGURATION_REFERENCE.md +267 -16
- data/docs/CONSOLE_MCP_SETUP.md +80 -7
- data/docs/DOCKER_SETUP.md +22 -3
- data/docs/EVALUATION.md +464 -1
- data/docs/EXTRACTOR_REFERENCE.md +45 -6
- data/docs/FAQ.md +11 -12
- data/docs/GETTING_STARTED.md +17 -5
- data/docs/INCREMENTAL_EXTRACTION.md +147 -7
- data/docs/INDEX_LAYOUT.md +382 -0
- data/docs/INTERNALS.md +7 -2
- data/docs/MCP_SERVERS.md +276 -5
- data/docs/MCP_TOOL_COOKBOOK.md +37 -22
- data/docs/MCP_WORKTREE_SETUP.md +43 -83
- data/docs/NOTION_INTEGRATION.md +13 -0
- data/docs/OBSIDIAN_INTEGRATION.md +57 -9
- data/docs/PUBLISHED_INDEX.md +72 -0
- data/docs/README.md +7 -0
- data/docs/RETRIEVAL_GUIDE.md +273 -12
- data/docs/RUNTIME_TRACING.md +71 -0
- data/docs/SOURCE_FRESHNESS.md +143 -0
- data/docs/TROUBLESHOOTING.md +129 -18
- data/docs/UNBLOCKED_INTEGRATION.md +25 -0
- data/docs/UPGRADING_TO_2.md +48 -22
- data/docs/WATCH_DAEMON.md +277 -67
- data/exe/woods-agent-config +6 -0
- data/exe/woods-extract +5 -0
- data/exe/woods-hook-context +6 -0
- data/exe/woods-mcp-start +14 -9
- data/lib/generators/woods/pgvector_generator.rb +8 -2
- data/lib/generators/woods/templates/woods.rb.tt +1 -3
- data/lib/tasks/woods.rake +47 -397
- data/lib/woods/agent_configuration/applier.rb +135 -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 +72 -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 +18 -17
- data/lib/woods/console/credential_scanner_registry.rb +36 -0
- data/lib/woods/console/dispatch_pipeline.rb +7 -0
- data/lib/woods/console/embedded_executor.rb +32 -10
- 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/console/sql_noise_stripper.rb +9 -7
- data/lib/woods/console/sql_table_scanner.rb +47 -7
- data/lib/woods/console/sql_validator.rb +49 -9
- data/lib/woods/console/sqlite_read_guard.rb +46 -0
- data/lib/woods/coordination/pipeline_lock.rb +3 -2
- data/lib/woods/dependency_graph.rb +65 -13
- data/lib/woods/embedding/corpus.rb +94 -0
- data/lib/woods/embedding/indexer.rb +114 -60
- 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 +277 -149
- 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/declared_parent.rb +55 -0
- 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 +10 -13
- 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 +13 -9
- data/lib/woods/extractors/mailer_extractor.rb +26 -15
- 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 +26 -34
- 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 +13 -9
- 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 +48 -19
- 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 +22 -13
- data/lib/woods/mcp/bootstrapper.rb +79 -4
- data/lib/woods/mcp/config_resolver.rb +2 -1
- data/lib/woods/mcp/index_reader.rb +334 -162
- 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 +22 -9
- data/lib/woods/mcp/renderers/plain_renderer.rb +18 -8
- data/lib/woods/mcp/search_results.rb +74 -0
- data/lib/woods/mcp/server.rb +178 -63
- data/lib/woods/mcp/tool_contract.rb +3 -1
- data/lib/woods/mcp/tool_response_renderer.rb +41 -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/mcp/traversal_response.rb +22 -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 +13 -6
- data/lib/woods/payload_store.rb +27 -26
- data/lib/woods/published_index/typed_unit_reader.rb +40 -3
- data/lib/woods/published_index.rb +2 -2
- data/lib/woods/railtie.rb +3 -3
- data/lib/woods/railtie_support.rb +12 -12
- data/lib/woods/rake_helpers.rb +382 -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 +84 -0
- data/lib/woods/retrieval/lexical_index.rb +120 -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/file_store.rb +6 -1
- 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 +31 -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 +35 -10
- 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 +58 -9
- 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 +154 -32
- 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 +19 -0
- data/plugin/skills/woods-diagnose/SKILL.md +319 -1
- data/plugin/skills/woods-investigate/SKILL.md +145 -0
- data/plugin/skills/woods-mcp-config/SKILL.md +90 -2
- data/plugin/skills/woods-setup/SKILL.md +110 -6
- metadata +87 -5
|
@@ -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_source_extraction'
|
|
5
7
|
require_relative 'shared_utility_methods'
|
|
@@ -27,7 +29,9 @@ module Woods
|
|
|
27
29
|
include RouteHelperResolver
|
|
28
30
|
|
|
29
31
|
def initialize
|
|
30
|
-
|
|
32
|
+
if defined?(ActionMailer::Base)
|
|
33
|
+
@mailer_base = defined?(ApplicationMailer) ? ApplicationMailer : ActionMailer::Base
|
|
34
|
+
end
|
|
31
35
|
build_route_helper_map
|
|
32
36
|
end
|
|
33
37
|
|
|
@@ -45,7 +49,9 @@ module Woods
|
|
|
45
49
|
#
|
|
46
50
|
# @return [Array<Class>]
|
|
47
51
|
def discoverable_classes
|
|
48
|
-
@mailer_base
|
|
52
|
+
return [] unless @mailer_base
|
|
53
|
+
|
|
54
|
+
@mailer_base.descendants.select { |mailer| app_defined_mailer?(mailer) }
|
|
49
55
|
end
|
|
50
56
|
|
|
51
57
|
# Extract a single mailer
|
|
@@ -53,8 +59,7 @@ module Woods
|
|
|
53
59
|
# @param mailer [Class] The mailer class
|
|
54
60
|
# @return [ExtractedUnit] The extracted unit
|
|
55
61
|
def extract_mailer(mailer)
|
|
56
|
-
return nil
|
|
57
|
-
return nil if mailer == ActionMailer::Base
|
|
62
|
+
return nil unless app_defined_mailer?(mailer)
|
|
58
63
|
|
|
59
64
|
file_path = source_file_for(mailer)
|
|
60
65
|
|
|
@@ -76,12 +81,22 @@ module Woods
|
|
|
76
81
|
|
|
77
82
|
unit
|
|
78
83
|
rescue StandardError => e
|
|
79
|
-
|
|
84
|
+
SourceInputs::ConsumerErrors.log(self, "Failed to extract mailer #{mailer.name}: #{e.message}")
|
|
80
85
|
nil
|
|
81
86
|
end
|
|
82
87
|
|
|
83
88
|
private
|
|
84
89
|
|
|
90
|
+
# Discovery and direct incremental extraction must agree on ownership;
|
|
91
|
+
# otherwise a gem class with a fabricated path becomes a phantom unit.
|
|
92
|
+
def app_defined_mailer?(mailer)
|
|
93
|
+
return false unless @mailer_base && mailer.name
|
|
94
|
+
return false if mailer == ActionMailer::Base
|
|
95
|
+
|
|
96
|
+
path = source_file_for(mailer)
|
|
97
|
+
path && File.file?(path) && app_source?(path, Rails.root.to_s)
|
|
98
|
+
end
|
|
99
|
+
|
|
85
100
|
# Locate the source file for a mailer class.
|
|
86
101
|
#
|
|
87
102
|
# Convention path first, then introspection via {#resolve_source_location}
|
|
@@ -101,12 +116,8 @@ module Woods
|
|
|
101
116
|
# ──────────────────────────────────────────────────────────────────────
|
|
102
117
|
|
|
103
118
|
def annotate_source(source, mailer)
|
|
104
|
-
actions = mailer.action_methods.
|
|
105
|
-
default_from =
|
|
106
|
-
mailer.default[:from]
|
|
107
|
-
rescue StandardError
|
|
108
|
-
nil
|
|
109
|
-
end
|
|
119
|
+
actions = mailer.action_methods.sort
|
|
120
|
+
default_from = extract_defaults(mailer)[:from]
|
|
110
121
|
|
|
111
122
|
<<~ANNOTATION
|
|
112
123
|
# ╔═══════════════════════════════════════════════════════════════════════╗
|
|
@@ -124,7 +135,7 @@ module Woods
|
|
|
124
135
|
# ──────────────────────────────────────────────────────────────────────
|
|
125
136
|
|
|
126
137
|
def extract_metadata(mailer, source)
|
|
127
|
-
actions = mailer.action_methods.
|
|
138
|
+
actions = mailer.action_methods.sort
|
|
128
139
|
|
|
129
140
|
{
|
|
130
141
|
# Actions (mail methods)
|
|
@@ -156,7 +167,7 @@ module Woods
|
|
|
156
167
|
|
|
157
168
|
def extract_defaults(mailer)
|
|
158
169
|
mailer_defaults = mailer.default
|
|
159
|
-
mailer_defaults.slice(:from, :reply_to, :cc, :bcc).compact
|
|
170
|
+
mailer_defaults.slice(:from, :reply_to, :cc, :bcc).compact.transform_values { |value| stable_filter(value) }
|
|
160
171
|
rescue StandardError
|
|
161
172
|
{}
|
|
162
173
|
end
|
|
@@ -167,7 +178,7 @@ module Woods
|
|
|
167
178
|
|
|
168
179
|
result = {
|
|
169
180
|
type: :"#{cb.kind}_action",
|
|
170
|
-
filter: cb
|
|
181
|
+
filter: callback_filter_label(cb)
|
|
171
182
|
}
|
|
172
183
|
result[:only] = only if only.any?
|
|
173
184
|
result[:except] = except if except.any?
|
|
@@ -259,7 +270,7 @@ module Woods
|
|
|
259
270
|
# ──────────────────────────────────────────────────────────────────────
|
|
260
271
|
|
|
261
272
|
def build_action_chunks(mailer, _source)
|
|
262
|
-
mailer.action_methods.filter_map do |action|
|
|
273
|
+
mailer.action_methods.sort.filter_map do |action|
|
|
263
274
|
action_source = extract_action_source(mailer, action)
|
|
264
275
|
next if action_source.nil? || action_source.strip.empty?
|
|
265
276
|
|
|
@@ -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 manager #{file_path}: #{e.message}")
|
|
73
75
|
nil
|
|
74
76
|
end
|
|
75
77
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'prism'
|
|
4
|
+
|
|
5
|
+
module Woods
|
|
6
|
+
module Extractors
|
|
7
|
+
# Syntactic method signatures only: never evaluate a default expression or
|
|
8
|
+
# mistake names inside one for additional parameters. Consumers retain their
|
|
9
|
+
# existing metadata shapes by selecting the fields they expose.
|
|
10
|
+
module MethodParameters
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def extract(source, method_name)
|
|
14
|
+
parsed = Prism.parse(source)
|
|
15
|
+
return [] unless parsed.success?
|
|
16
|
+
|
|
17
|
+
method = find_method(parsed.value, method_name.to_sym)
|
|
18
|
+
return [] unless method&.parameters
|
|
19
|
+
|
|
20
|
+
method.parameters.compact_child_nodes.flat_map { |node| describe(node) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def find_method(node, name)
|
|
24
|
+
return nil if node.is_a?(Prism::SingletonClassNode)
|
|
25
|
+
return node if node.is_a?(Prism::DefNode) && node.receiver.nil? && node.name == name
|
|
26
|
+
|
|
27
|
+
node.compact_child_nodes.each do |child|
|
|
28
|
+
found = find_method(child, name)
|
|
29
|
+
return found if found
|
|
30
|
+
end
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def describe(node)
|
|
35
|
+
# Destructured positional arguments contain declarations, while a
|
|
36
|
+
# default's subtree contains values and must never be walked.
|
|
37
|
+
return node.compact_child_nodes.flat_map { |child| describe(child) } if node.is_a?(Prism::MultiTargetNode)
|
|
38
|
+
return [] unless node.respond_to?(:name) && node.name
|
|
39
|
+
|
|
40
|
+
optional = node.is_a?(Prism::OptionalParameterNode) || node.is_a?(Prism::OptionalKeywordParameterNode)
|
|
41
|
+
keyword = node.is_a?(Prism::RequiredKeywordParameterNode) ||
|
|
42
|
+
node.is_a?(Prism::OptionalKeywordParameterNode) || node.is_a?(Prism::KeywordRestParameterNode)
|
|
43
|
+
splat = if node.is_a?(Prism::KeywordRestParameterNode)
|
|
44
|
+
:double
|
|
45
|
+
elsif node.is_a?(Prism::RestParameterNode)
|
|
46
|
+
:single
|
|
47
|
+
end
|
|
48
|
+
[{ name: node.name.to_s, has_default: optional, keyword: keyword, splat: splat }]
|
|
49
|
+
end
|
|
50
|
+
private_class_method :find_method, :describe
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -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).
|
|
@@ -605,7 +619,7 @@ module Woods
|
|
|
605
619
|
entry = {
|
|
606
620
|
attribute: attribute,
|
|
607
621
|
type: v.class.name.demodulize.underscore.sub(/_validator$/, ''),
|
|
608
|
-
options: v.options.except(:if, :unless, :on),
|
|
622
|
+
options: v.options.except(:if, :unless, :on).transform_values { |value| stable_filter(value) },
|
|
609
623
|
conditions: format_validation_conditions(v)
|
|
610
624
|
}
|
|
611
625
|
entry[:implicit_belongs_to] = true if implicit_belongs_to_validator?(model, v)
|
|
@@ -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
|
}
|
|
@@ -1221,17 +1224,6 @@ module Woods
|
|
|
1221
1224
|
# Helper methods
|
|
1222
1225
|
# ──────────────────────────────────────────────────────────────────────
|
|
1223
1226
|
|
|
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
1227
|
def count_loc(model, source = nil)
|
|
1236
1228
|
if source
|
|
1237
1229
|
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'
|
|
@@ -80,14 +82,16 @@ module Woods
|
|
|
80
82
|
file_path: file_path
|
|
81
83
|
)
|
|
82
84
|
|
|
85
|
+
parent_class = extract_parent_class(source, class_name)
|
|
86
|
+
|
|
83
87
|
unit.namespace = extract_namespace(class_name)
|
|
84
|
-
unit.source_code = annotate_source(source, class_name)
|
|
85
|
-
unit.metadata = extract_metadata(source,
|
|
88
|
+
unit.source_code = annotate_source(source, class_name, parent_class)
|
|
89
|
+
unit.metadata = extract_metadata(source, parent_class)
|
|
86
90
|
unit.dependencies = extract_dependencies(source)
|
|
87
91
|
|
|
88
92
|
unit
|
|
89
93
|
rescue StandardError => e
|
|
90
|
-
|
|
94
|
+
SourceInputs::ConsumerErrors.log(self, "Failed to extract PORO #{file_path}: #{e.message}")
|
|
91
95
|
nil
|
|
92
96
|
end
|
|
93
97
|
|
|
@@ -175,10 +179,10 @@ module Woods
|
|
|
175
179
|
#
|
|
176
180
|
# @param source [String] Ruby source code
|
|
177
181
|
# @param class_name [String] The class name
|
|
182
|
+
# @param parent_class [String, nil] Selected explicit parent
|
|
178
183
|
# @return [String] Annotated source
|
|
179
|
-
def annotate_source(source, class_name)
|
|
180
|
-
|
|
181
|
-
parent_label = parent || 'none'
|
|
184
|
+
def annotate_source(source, class_name, parent_class)
|
|
185
|
+
parent_label = parent_class || 'none'
|
|
182
186
|
|
|
183
187
|
annotation = <<~ANNOTATION
|
|
184
188
|
# ╔═══════════════════════════════════════════════════════════════════════╗
|
|
@@ -198,14 +202,14 @@ module Woods
|
|
|
198
202
|
# Build the metadata hash for a PORO unit.
|
|
199
203
|
#
|
|
200
204
|
# @param source [String] Ruby source code
|
|
201
|
-
# @param
|
|
205
|
+
# @param parent_class [String, nil] Selected explicit parent
|
|
202
206
|
# @return [Hash] PORO metadata
|
|
203
|
-
def extract_metadata(source,
|
|
207
|
+
def extract_metadata(source, parent_class)
|
|
204
208
|
{
|
|
205
209
|
public_methods: extract_public_methods(source),
|
|
206
210
|
class_methods: extract_class_methods(source),
|
|
207
211
|
initialize_params: extract_initialize_params(source),
|
|
208
|
-
parent_class:
|
|
212
|
+
parent_class: parent_class,
|
|
209
213
|
loc: count_loc(source),
|
|
210
214
|
method_count: source.scan(/def\s+(?:self\.)?\w+/).size
|
|
211
215
|
}
|
|
@@ -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
|
|