aidp 0.32.0 → 0.33.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.
- checksums.yaml +4 -4
- data/lib/aidp/analyze/feature_analyzer.rb +322 -320
- data/lib/aidp/auto_update/coordinator.rb +97 -7
- data/lib/aidp/auto_update.rb +0 -12
- data/lib/aidp/cli/devcontainer_commands.rb +0 -5
- data/lib/aidp/cli.rb +2 -1
- data/lib/aidp/comment_consolidator.rb +78 -0
- data/lib/aidp/concurrency.rb +0 -3
- data/lib/aidp/config.rb +0 -1
- data/lib/aidp/config_paths.rb +71 -0
- data/lib/aidp/execute/work_loop_runner.rb +324 -15
- data/lib/aidp/harness/ai_filter_factory.rb +285 -0
- data/lib/aidp/harness/config_schema.rb +97 -1
- data/lib/aidp/harness/config_validator.rb +1 -1
- data/lib/aidp/harness/configuration.rb +61 -5
- data/lib/aidp/harness/filter_definition.rb +212 -0
- data/lib/aidp/harness/generated_filter_strategy.rb +197 -0
- data/lib/aidp/harness/output_filter.rb +50 -25
- data/lib/aidp/harness/output_filter_config.rb +129 -0
- data/lib/aidp/harness/provider_manager.rb +90 -2
- data/lib/aidp/harness/runner.rb +0 -11
- data/lib/aidp/harness/test_runner.rb +179 -41
- data/lib/aidp/harness/thinking_depth_manager.rb +16 -0
- data/lib/aidp/harness/ui/navigation/submenu.rb +0 -2
- data/lib/aidp/loader.rb +195 -0
- data/lib/aidp/metadata/compiler.rb +29 -17
- data/lib/aidp/metadata/query.rb +1 -1
- data/lib/aidp/metadata/scanner.rb +8 -1
- data/lib/aidp/metadata/tool_metadata.rb +13 -13
- data/lib/aidp/metadata/validator.rb +10 -0
- data/lib/aidp/metadata.rb +16 -0
- data/lib/aidp/pr_worktree_manager.rb +2 -2
- data/lib/aidp/provider_manager.rb +1 -7
- data/lib/aidp/setup/wizard.rb +279 -9
- data/lib/aidp/skills.rb +0 -5
- data/lib/aidp/storage/csv_storage.rb +3 -0
- data/lib/aidp/style_guide/selector.rb +360 -0
- data/lib/aidp/tooling_detector.rb +283 -16
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/change_request_processor.rb +152 -14
- data/lib/aidp/watch/repository_client.rb +41 -0
- data/lib/aidp/watch/runner.rb +29 -18
- data/lib/aidp/watch.rb +5 -7
- data/lib/aidp/workstream_cleanup.rb +0 -2
- data/lib/aidp/workstream_executor.rb +0 -4
- data/lib/aidp/worktree.rb +0 -1
- data/lib/aidp.rb +21 -106
- metadata +72 -36
- data/lib/aidp/config/paths.rb +0 -131
data/lib/aidp/loader.rb
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "zeitwerk"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
# Zeitwerk-based class loader with hot code reloading support
|
|
7
|
+
#
|
|
8
|
+
# This module configures Zeitwerk for autoloading AIDP classes and provides
|
|
9
|
+
# a reload capability similar to Rails development mode. When files change
|
|
10
|
+
# (e.g., after git pull in watch mode), calling Aidp::Loader.reload! will
|
|
11
|
+
# unload all classes and allow them to be reloaded on next reference.
|
|
12
|
+
#
|
|
13
|
+
# @example Enable reloading in development
|
|
14
|
+
# Aidp::Loader.setup(enable_reloading: true)
|
|
15
|
+
# # ... code changes on disk ...
|
|
16
|
+
# Aidp::Loader.reload!
|
|
17
|
+
#
|
|
18
|
+
# @example Production mode (no reloading)
|
|
19
|
+
# Aidp::Loader.setup(enable_reloading: false)
|
|
20
|
+
# Aidp::Loader.eager_load!
|
|
21
|
+
module Loader
|
|
22
|
+
class << self
|
|
23
|
+
# @return [Zeitwerk::Loader, nil] The configured loader instance
|
|
24
|
+
attr_reader :loader
|
|
25
|
+
|
|
26
|
+
# @return [Boolean] Whether reloading is enabled
|
|
27
|
+
attr_reader :reloading_enabled
|
|
28
|
+
|
|
29
|
+
# Set up the Zeitwerk loader for AIDP
|
|
30
|
+
#
|
|
31
|
+
# @param enable_reloading [Boolean] Whether to enable hot reloading
|
|
32
|
+
# @param eager_load [Boolean] Whether to eager load all classes
|
|
33
|
+
# @return [Zeitwerk::Loader] The configured loader
|
|
34
|
+
def setup(enable_reloading: false, eager_load: false)
|
|
35
|
+
return @loader if @loader
|
|
36
|
+
|
|
37
|
+
Aidp.log_debug("loader", "setup_started",
|
|
38
|
+
enable_reloading: enable_reloading,
|
|
39
|
+
eager_load: eager_load)
|
|
40
|
+
|
|
41
|
+
@reloading_enabled = enable_reloading
|
|
42
|
+
@loader = create_loader
|
|
43
|
+
configure_inflections(@loader)
|
|
44
|
+
configure_ignores(@loader)
|
|
45
|
+
|
|
46
|
+
if enable_reloading
|
|
47
|
+
@loader.enable_reloading
|
|
48
|
+
Aidp.log_debug("loader", "reloading_enabled")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
@loader.setup
|
|
52
|
+
|
|
53
|
+
if eager_load && !enable_reloading
|
|
54
|
+
@loader.eager_load
|
|
55
|
+
Aidp.log_debug("loader", "eager_load_complete")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
Aidp.log_info("loader", "setup_complete",
|
|
59
|
+
reloading: enable_reloading,
|
|
60
|
+
eager_loaded: eager_load && !enable_reloading)
|
|
61
|
+
|
|
62
|
+
@loader
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Reload all autoloaded classes
|
|
66
|
+
#
|
|
67
|
+
# This unloads all classes managed by Zeitwerk and allows them to be
|
|
68
|
+
# reloaded on next reference. Only works if enable_reloading was true
|
|
69
|
+
# during setup.
|
|
70
|
+
#
|
|
71
|
+
# @return [Boolean] Whether reload was performed
|
|
72
|
+
def reload!
|
|
73
|
+
unless @loader
|
|
74
|
+
Aidp.log_warn("loader", "reload_skipped", reason: "loader_not_setup")
|
|
75
|
+
return false
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
unless @reloading_enabled
|
|
79
|
+
Aidp.log_warn("loader", "reload_skipped", reason: "reloading_disabled")
|
|
80
|
+
return false
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
Aidp.log_info("loader", "reload_started")
|
|
84
|
+
|
|
85
|
+
begin
|
|
86
|
+
@loader.reload
|
|
87
|
+
Aidp.log_info("loader", "reload_complete")
|
|
88
|
+
true
|
|
89
|
+
rescue => e
|
|
90
|
+
Aidp.log_error("loader", "reload_failed", error: e.message)
|
|
91
|
+
false
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Check if loader is set up for reloading
|
|
96
|
+
#
|
|
97
|
+
# @return [Boolean]
|
|
98
|
+
def reloading?
|
|
99
|
+
@reloading_enabled == true
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Check if loader is set up
|
|
103
|
+
#
|
|
104
|
+
# @return [Boolean]
|
|
105
|
+
def setup?
|
|
106
|
+
!@loader.nil?
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Eager load all classes (production mode)
|
|
110
|
+
#
|
|
111
|
+
# @return [void]
|
|
112
|
+
def eager_load!
|
|
113
|
+
@loader&.eager_load
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Reset the loader (mainly for testing)
|
|
117
|
+
#
|
|
118
|
+
# @return [void]
|
|
119
|
+
def reset!
|
|
120
|
+
if @loader
|
|
121
|
+
begin
|
|
122
|
+
@loader.unload if @reloading_enabled
|
|
123
|
+
rescue Zeitwerk::SetupRequired
|
|
124
|
+
# If loader was never set up, skip unload
|
|
125
|
+
end
|
|
126
|
+
@loader.unregister
|
|
127
|
+
end
|
|
128
|
+
@loader = nil
|
|
129
|
+
@reloading_enabled = false
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
private
|
|
133
|
+
|
|
134
|
+
def create_loader
|
|
135
|
+
loader = Zeitwerk::Loader.new
|
|
136
|
+
loader.tag = "aidp"
|
|
137
|
+
|
|
138
|
+
# Set the root directory for autoloading
|
|
139
|
+
lib_path = File.expand_path("..", __dir__)
|
|
140
|
+
loader.push_dir(lib_path, namespace: Object)
|
|
141
|
+
|
|
142
|
+
loader
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Configure inflections for non-standard class names
|
|
146
|
+
def configure_inflections(loader)
|
|
147
|
+
loader.inflector.inflect(
|
|
148
|
+
# AI-prefixed classes
|
|
149
|
+
"ai_decision_engine" => "AIDecisionEngine",
|
|
150
|
+
"ai_filter_factory" => "AIFilterFactory",
|
|
151
|
+
|
|
152
|
+
# Acronym-based names
|
|
153
|
+
"kb_inspector" => "KBInspector",
|
|
154
|
+
"ui_state" => "UIState",
|
|
155
|
+
"ui_error" => "UIError",
|
|
156
|
+
"rspec_filter_strategy" => "RSpecFilterStrategy",
|
|
157
|
+
|
|
158
|
+
# TTY-related
|
|
159
|
+
"tui" => "TUI",
|
|
160
|
+
"enhanced_tui" => "EnhancedTUI",
|
|
161
|
+
|
|
162
|
+
# Other acronyms
|
|
163
|
+
"pr_worktree_manager" => "PRWorktreeManager",
|
|
164
|
+
"csv_storage" => "CSVStorage",
|
|
165
|
+
"cli" => "CLI",
|
|
166
|
+
"ruby_llm_registry" => "RubyLLMRegistry",
|
|
167
|
+
"rubygems_api_adapter" => "RubyGemsAPIAdapter",
|
|
168
|
+
"submenu" => "SubMenu",
|
|
169
|
+
"terminal_io" => "TerminalIO",
|
|
170
|
+
|
|
171
|
+
# Module folders
|
|
172
|
+
"ui" => "UI"
|
|
173
|
+
)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Configure files/directories to ignore
|
|
177
|
+
def configure_ignores(loader)
|
|
178
|
+
# Ignore files that are manually required before Zeitwerk
|
|
179
|
+
loader.ignore(File.expand_path("version.rb", __dir__))
|
|
180
|
+
loader.ignore(File.expand_path("core_ext", __dir__))
|
|
181
|
+
loader.ignore(File.expand_path("logger.rb", __dir__))
|
|
182
|
+
loader.ignore(File.expand_path("cli/issue_importer.rb", __dir__))
|
|
183
|
+
loader.ignore(File.expand_path("config/paths.rb", __dir__))
|
|
184
|
+
|
|
185
|
+
# Ignore the loader itself
|
|
186
|
+
loader.ignore(__FILE__)
|
|
187
|
+
|
|
188
|
+
# Ignore files with multiple constants (require manually after setup)
|
|
189
|
+
loader.ignore(File.expand_path("auto_update/errors.rb", __dir__))
|
|
190
|
+
loader.ignore(File.expand_path("errors.rb", __dir__))
|
|
191
|
+
loader.ignore(File.expand_path("harness/state/errors.rb", __dir__))
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -39,7 +39,7 @@ module Aidp
|
|
|
39
39
|
Aidp.log_info("metadata", "Compiling tool directory", directories: @directories, output: output_path)
|
|
40
40
|
|
|
41
41
|
# Scan all directories
|
|
42
|
-
scanner = Scanner.new(@directories)
|
|
42
|
+
scanner = Scanner.new(@directories, strict: @strict)
|
|
43
43
|
@tools = scanner.scan_all
|
|
44
44
|
|
|
45
45
|
# Validate tools
|
|
@@ -47,14 +47,24 @@ module Aidp
|
|
|
47
47
|
validation_results = validator.validate_all
|
|
48
48
|
|
|
49
49
|
# Handle validation failures
|
|
50
|
-
|
|
50
|
+
parse_error_results = scanner.parse_errors.map do |err|
|
|
51
|
+
Validator::ValidationResult.new(
|
|
52
|
+
tool_id: "(unknown)",
|
|
53
|
+
file_path: err[:file],
|
|
54
|
+
valid: false,
|
|
55
|
+
errors: [err[:error]],
|
|
56
|
+
warnings: []
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
invalid_results = handle_validation_results(validation_results + parse_error_results)
|
|
51
61
|
|
|
52
62
|
# Build indexes and graphs
|
|
53
63
|
build_indexes
|
|
54
64
|
build_dependency_graph
|
|
55
65
|
|
|
56
66
|
# Create directory structure
|
|
57
|
-
directory = create_directory_structure
|
|
67
|
+
directory = create_directory_structure(invalid_results: invalid_results)
|
|
58
68
|
|
|
59
69
|
# Write to file
|
|
60
70
|
write_directory(directory, output_path)
|
|
@@ -142,22 +152,23 @@ module Aidp
|
|
|
142
152
|
# Create directory structure for serialization
|
|
143
153
|
#
|
|
144
154
|
# @return [Hash] Directory structure
|
|
145
|
-
def create_directory_structure
|
|
155
|
+
def create_directory_structure(invalid_results: [])
|
|
146
156
|
{
|
|
147
|
-
version
|
|
148
|
-
compiled_at
|
|
149
|
-
tools
|
|
150
|
-
indexes
|
|
151
|
-
by_type
|
|
152
|
-
|
|
153
|
-
by_work_unit_type
|
|
157
|
+
"version" => "1.0.0",
|
|
158
|
+
"compiled_at" => Time.now.iso8601,
|
|
159
|
+
"tools" => @tools.map(&:to_h),
|
|
160
|
+
"indexes" => {
|
|
161
|
+
"by_type" => @indexes[:by_type].transform_values { |tools| tools.map(&:id) },
|
|
162
|
+
"by_tags" => @indexes[:by_tag].transform_values { |tools| tools.map(&:id) },
|
|
163
|
+
"by_work_unit_type" => @indexes[:by_work_unit_type].transform_values { |tools| tools.map(&:id) }
|
|
154
164
|
},
|
|
155
|
-
dependency_graph
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
165
|
+
"dependency_graph" => @dependency_graph,
|
|
166
|
+
"errors" => invalid_results.map { |res| {"tool_id" => res.tool_id, "errors" => res.errors, "warnings" => res.warnings} },
|
|
167
|
+
"statistics" => {
|
|
168
|
+
"total_tools" => @tools.size,
|
|
169
|
+
"by_type" => @tools.group_by(&:type).transform_values(&:size),
|
|
170
|
+
"total_tags" => @indexes[:by_tag].size,
|
|
171
|
+
"total_work_unit_types" => @indexes[:by_work_unit_type].size
|
|
161
172
|
}
|
|
162
173
|
}
|
|
163
174
|
end
|
|
@@ -223,6 +234,7 @@ module Aidp
|
|
|
223
234
|
warnings: result.warnings
|
|
224
235
|
)
|
|
225
236
|
end
|
|
237
|
+
invalid_results
|
|
226
238
|
end
|
|
227
239
|
end
|
|
228
240
|
end
|
data/lib/aidp/metadata/query.rb
CHANGED
|
@@ -64,7 +64,7 @@ module Aidp
|
|
|
64
64
|
Aidp.log_debug("metadata", "Finding by tags", tags: tags, match_all: match_all)
|
|
65
65
|
|
|
66
66
|
tags = Array(tags).map(&:downcase)
|
|
67
|
-
indexes = directory["indexes"]["
|
|
67
|
+
indexes = directory["indexes"]["by_tags"] || {}
|
|
68
68
|
|
|
69
69
|
if match_all
|
|
70
70
|
# Find tools that have ALL specified tags
|
|
@@ -17,16 +17,21 @@ module Aidp
|
|
|
17
17
|
# Initialize scanner with directory paths
|
|
18
18
|
#
|
|
19
19
|
# @param directories [Array<String>] Directories to scan
|
|
20
|
-
def initialize(directories = [])
|
|
20
|
+
def initialize(directories = [], strict: false)
|
|
21
21
|
@directories = Array(directories)
|
|
22
|
+
@strict = strict
|
|
23
|
+
@parse_errors = []
|
|
22
24
|
end
|
|
23
25
|
|
|
26
|
+
attr_reader :parse_errors
|
|
27
|
+
|
|
24
28
|
# Scan all configured directories
|
|
25
29
|
#
|
|
26
30
|
# @return [Array<ToolMetadata>] All discovered tool metadata
|
|
27
31
|
def scan_all
|
|
28
32
|
Aidp.log_debug("metadata", "Scanning directories", directories: @directories)
|
|
29
33
|
|
|
34
|
+
@parse_errors = []
|
|
30
35
|
all_tools = []
|
|
31
36
|
@directories.each do |dir|
|
|
32
37
|
tools = scan_directory(dir)
|
|
@@ -69,6 +74,8 @@ module Aidp
|
|
|
69
74
|
file: file_path,
|
|
70
75
|
error: e.message
|
|
71
76
|
)
|
|
77
|
+
@parse_errors << {file: file_path, error: e.message}
|
|
78
|
+
raise if @strict
|
|
72
79
|
end
|
|
73
80
|
|
|
74
81
|
Aidp.log_debug(
|
|
@@ -146,19 +146,19 @@ module Aidp
|
|
|
146
146
|
# @return [Hash] Metadata as hash (for JSON)
|
|
147
147
|
def to_h
|
|
148
148
|
{
|
|
149
|
-
type
|
|
150
|
-
id
|
|
151
|
-
title
|
|
152
|
-
summary
|
|
153
|
-
version
|
|
154
|
-
applies_to
|
|
155
|
-
work_unit_types
|
|
156
|
-
priority
|
|
157
|
-
capabilities
|
|
158
|
-
dependencies
|
|
159
|
-
experimental
|
|
160
|
-
source_path
|
|
161
|
-
file_hash
|
|
149
|
+
"type" => type,
|
|
150
|
+
"id" => id,
|
|
151
|
+
"title" => title,
|
|
152
|
+
"summary" => summary,
|
|
153
|
+
"version" => version,
|
|
154
|
+
"applies_to" => applies_to,
|
|
155
|
+
"work_unit_types" => work_unit_types,
|
|
156
|
+
"priority" => priority,
|
|
157
|
+
"capabilities" => capabilities,
|
|
158
|
+
"dependencies" => dependencies,
|
|
159
|
+
"experimental" => experimental,
|
|
160
|
+
"source_path" => source_path,
|
|
161
|
+
"file_hash" => file_hash
|
|
162
162
|
}
|
|
163
163
|
end
|
|
164
164
|
|
|
@@ -84,6 +84,10 @@ module Aidp
|
|
|
84
84
|
warnings << "Tool content is very short (#{tool.content.length} characters)"
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
+
unless valid_version_format?(tool.version)
|
|
88
|
+
warnings << "Version '#{tool.version}' is not in semver format (MAJOR.MINOR.PATCH)"
|
|
89
|
+
end
|
|
90
|
+
|
|
87
91
|
ValidationResult.new(
|
|
88
92
|
tool_id: tool.id,
|
|
89
93
|
file_path: tool.source_path,
|
|
@@ -156,6 +160,12 @@ module Aidp
|
|
|
156
160
|
# For now, this is a placeholder for future deprecation warnings
|
|
157
161
|
end
|
|
158
162
|
|
|
163
|
+
def valid_version_format?(version)
|
|
164
|
+
version.to_s.match?(/\A\d+\.\d+\.\d+\z/)
|
|
165
|
+
rescue
|
|
166
|
+
false
|
|
167
|
+
end
|
|
168
|
+
|
|
159
169
|
# Write validation errors to log file
|
|
160
170
|
#
|
|
161
171
|
# @param results [Array<ValidationResult>] Validation results
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aidp
|
|
4
|
+
# Namespace marker for metadata components
|
|
5
|
+
module Metadata
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Load key metadata components
|
|
10
|
+
require_relative "metadata/parser"
|
|
11
|
+
require_relative "metadata/tool_metadata"
|
|
12
|
+
require_relative "metadata/scanner"
|
|
13
|
+
require_relative "metadata/validator"
|
|
14
|
+
require_relative "metadata/compiler"
|
|
15
|
+
require_relative "metadata/cache"
|
|
16
|
+
require_relative "metadata/query"
|
|
@@ -4,7 +4,7 @@ require "shellwords"
|
|
|
4
4
|
|
|
5
5
|
module Aidp
|
|
6
6
|
# Manages worktrees specifically for Pull Request branches
|
|
7
|
-
class
|
|
7
|
+
class PRWorktreeManager
|
|
8
8
|
def initialize(base_repo_path: nil, project_dir: nil, worktree_registry_path: nil)
|
|
9
9
|
@base_repo_path = base_repo_path || project_dir || Dir.pwd
|
|
10
10
|
@project_dir = project_dir
|
|
@@ -258,7 +258,7 @@ module Aidp
|
|
|
258
258
|
|
|
259
259
|
# Advanced change detection patterns
|
|
260
260
|
file_patterns = [
|
|
261
|
-
/(
|
|
261
|
+
/(modify|update|add|delete)\s+file:\s*([^\n]+)/i,
|
|
262
262
|
/\[(\w+)\]\s*([^\n]+)/, # GitHub-style change indicators
|
|
263
263
|
/(?:Action:\s*(\w+))\s*File:\s*([^\n]+)/i
|
|
264
264
|
]
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "tty-prompt"
|
|
4
|
-
require_relative "harness/provider_factory"
|
|
5
4
|
|
|
6
5
|
module Aidp
|
|
7
6
|
class ProviderManager
|
|
@@ -20,12 +19,7 @@ module Aidp
|
|
|
20
19
|
|
|
21
20
|
# Get harness factory instance
|
|
22
21
|
def get_harness_factory
|
|
23
|
-
@harness_factory ||=
|
|
24
|
-
require_relative "harness/config_manager"
|
|
25
|
-
Aidp::Harness::ProviderFactory.new
|
|
26
|
-
rescue LoadError
|
|
27
|
-
nil
|
|
28
|
-
end
|
|
22
|
+
@harness_factory ||= Aidp::Harness::ProviderFactory.new
|
|
29
23
|
end
|
|
30
24
|
|
|
31
25
|
# Create provider using harness configuration
|