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
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "filter_definition"
|
|
5
|
+
require_relative "provider_factory"
|
|
6
|
+
require_relative "thinking_depth_manager"
|
|
7
|
+
|
|
8
|
+
module Aidp
|
|
9
|
+
module Harness
|
|
10
|
+
# AI-powered factory for generating deterministic filter definitions
|
|
11
|
+
#
|
|
12
|
+
# Uses AI ONCE during configuration to analyze a tool and generate
|
|
13
|
+
# regex patterns and extraction rules. The generated FilterDefinition
|
|
14
|
+
# is then applied deterministically at runtime without any AI calls.
|
|
15
|
+
#
|
|
16
|
+
# @example Generate a filter for pytest
|
|
17
|
+
# factory = AIFilterFactory.new(config)
|
|
18
|
+
# definition = factory.generate_filter(
|
|
19
|
+
# tool_name: "pytest",
|
|
20
|
+
# tool_command: "pytest -v",
|
|
21
|
+
# sample_output: "... actual pytest output ..."
|
|
22
|
+
# )
|
|
23
|
+
# # Save definition to config, use deterministically at runtime
|
|
24
|
+
#
|
|
25
|
+
# @see FilterDefinition for the generated output format
|
|
26
|
+
# @see GeneratedFilterStrategy for runtime application
|
|
27
|
+
class AIFilterFactory
|
|
28
|
+
GENERATION_PROMPT = <<~PROMPT
|
|
29
|
+
Analyze the following tool output and generate regex patterns for filtering.
|
|
30
|
+
The goal is to extract ONLY the important information (failures, errors, locations)
|
|
31
|
+
and filter out noise, so that AI assistants receive concise, actionable output.
|
|
32
|
+
|
|
33
|
+
Tool: {{tool_name}}
|
|
34
|
+
Command: {{tool_command}}
|
|
35
|
+
|
|
36
|
+
Sample output:
|
|
37
|
+
```
|
|
38
|
+
{{sample_output}}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Generate a filter definition with these components:
|
|
42
|
+
|
|
43
|
+
1. summary_patterns: Regex patterns that match summary/result lines (e.g., "5 passed, 2 failed")
|
|
44
|
+
2. failure_section_start: Regex pattern marking where failures section begins (if applicable)
|
|
45
|
+
3. failure_section_end: Regex pattern marking where failures section ends (if applicable)
|
|
46
|
+
4. error_section_start: Regex pattern for errors section start (if different from failures)
|
|
47
|
+
5. error_section_end: Regex pattern for errors section end
|
|
48
|
+
6. error_patterns: Regex patterns that identify error/failure indicator lines
|
|
49
|
+
7. location_patterns: Regex patterns to extract file:line locations from output
|
|
50
|
+
8. noise_patterns: Regex patterns for lines that should be filtered out (timestamps, progress bars, etc.)
|
|
51
|
+
9. important_patterns: Regex patterns for lines that should ALWAYS be kept
|
|
52
|
+
|
|
53
|
+
Important guidelines for patterns:
|
|
54
|
+
- Use simple, portable regex syntax
|
|
55
|
+
- Escape special characters properly (dots, brackets, etc.)
|
|
56
|
+
- Make patterns case-insensitive where appropriate
|
|
57
|
+
- For location patterns, use capture groups to extract the file:line portion
|
|
58
|
+
- Leave fields as null/empty if not applicable to this tool
|
|
59
|
+
|
|
60
|
+
Respond with ONLY valid JSON matching this structure:
|
|
61
|
+
{
|
|
62
|
+
"tool_name": "string",
|
|
63
|
+
"summary_patterns": ["pattern1", "pattern2"],
|
|
64
|
+
"failure_section_start": "pattern or null",
|
|
65
|
+
"failure_section_end": "pattern or null",
|
|
66
|
+
"error_section_start": "pattern or null",
|
|
67
|
+
"error_section_end": "pattern or null",
|
|
68
|
+
"error_patterns": ["pattern1"],
|
|
69
|
+
"location_patterns": ["pattern with (capture) group"],
|
|
70
|
+
"noise_patterns": ["pattern1"],
|
|
71
|
+
"important_patterns": ["pattern1"]
|
|
72
|
+
}
|
|
73
|
+
PROMPT
|
|
74
|
+
|
|
75
|
+
# JSON schema for validating AI response
|
|
76
|
+
RESPONSE_SCHEMA = {
|
|
77
|
+
type: "object",
|
|
78
|
+
properties: {
|
|
79
|
+
tool_name: {type: "string"},
|
|
80
|
+
summary_patterns: {type: "array", items: {type: "string"}},
|
|
81
|
+
failure_section_start: {type: ["string", "null"]},
|
|
82
|
+
failure_section_end: {type: ["string", "null"]},
|
|
83
|
+
error_section_start: {type: ["string", "null"]},
|
|
84
|
+
error_section_end: {type: ["string", "null"]},
|
|
85
|
+
error_patterns: {type: "array", items: {type: "string"}},
|
|
86
|
+
location_patterns: {type: "array", items: {type: "string"}},
|
|
87
|
+
noise_patterns: {type: "array", items: {type: "string"}},
|
|
88
|
+
important_patterns: {type: "array", items: {type: "string"}}
|
|
89
|
+
},
|
|
90
|
+
required: ["tool_name", "summary_patterns"]
|
|
91
|
+
}.freeze
|
|
92
|
+
|
|
93
|
+
attr_reader :config, :provider_factory
|
|
94
|
+
|
|
95
|
+
# Initialize the AI filter factory
|
|
96
|
+
#
|
|
97
|
+
# @param config [Configuration] AIDP configuration
|
|
98
|
+
# @param provider_factory [ProviderFactory, nil] Optional factory for AI providers
|
|
99
|
+
def initialize(config, provider_factory: nil)
|
|
100
|
+
@config = config
|
|
101
|
+
@provider_factory = provider_factory || ProviderFactory.new(config)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Generate a filter definition for a tool
|
|
105
|
+
#
|
|
106
|
+
# @param tool_name [String] Human-readable tool name
|
|
107
|
+
# @param tool_command [String] The command used to run the tool
|
|
108
|
+
# @param sample_output [String, nil] Sample output from the tool (for better patterns)
|
|
109
|
+
# @param tier [String] AI tier to use ("mini", "standard", "advanced")
|
|
110
|
+
# @return [FilterDefinition] Generated filter definition
|
|
111
|
+
# @raise [GenerationError] If AI fails to generate valid patterns
|
|
112
|
+
def generate_filter(tool_name:, tool_command:, sample_output: nil, tier: "mini")
|
|
113
|
+
Aidp.log_info("ai_filter_factory", "Generating filter definition",
|
|
114
|
+
tool_name: tool_name, tool_command: tool_command, tier: tier)
|
|
115
|
+
|
|
116
|
+
# Build prompt with context
|
|
117
|
+
prompt = build_prompt(tool_name, tool_command, sample_output)
|
|
118
|
+
|
|
119
|
+
# Get AI model for the tier
|
|
120
|
+
thinking_manager = ThinkingDepthManager.new(config)
|
|
121
|
+
provider_name, model_name, _model_data = thinking_manager.select_model_for_tier(
|
|
122
|
+
tier,
|
|
123
|
+
provider: config.respond_to?(:default_provider) ? config.default_provider : nil
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
Aidp.log_debug("ai_filter_factory", "Using AI model",
|
|
127
|
+
provider: provider_name, model: model_name)
|
|
128
|
+
|
|
129
|
+
# Call AI
|
|
130
|
+
response = call_ai(provider_name, model_name, prompt)
|
|
131
|
+
|
|
132
|
+
# Parse and validate response
|
|
133
|
+
definition_data = parse_response(response)
|
|
134
|
+
validate_patterns(definition_data)
|
|
135
|
+
|
|
136
|
+
# Create FilterDefinition
|
|
137
|
+
definition = FilterDefinition.new(
|
|
138
|
+
tool_name: definition_data[:tool_name] || tool_name,
|
|
139
|
+
tool_command: tool_command,
|
|
140
|
+
summary_patterns: definition_data[:summary_patterns] || [],
|
|
141
|
+
failure_section_start: definition_data[:failure_section_start],
|
|
142
|
+
failure_section_end: definition_data[:failure_section_end],
|
|
143
|
+
error_section_start: definition_data[:error_section_start],
|
|
144
|
+
error_section_end: definition_data[:error_section_end],
|
|
145
|
+
error_patterns: definition_data[:error_patterns] || [],
|
|
146
|
+
location_patterns: definition_data[:location_patterns] || [],
|
|
147
|
+
noise_patterns: definition_data[:noise_patterns] || [],
|
|
148
|
+
important_patterns: definition_data[:important_patterns] || [],
|
|
149
|
+
context_lines: 3
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
Aidp.log_info("ai_filter_factory", "Filter definition generated",
|
|
153
|
+
tool_name: definition.tool_name,
|
|
154
|
+
summary_pattern_count: definition.summary_patterns.size,
|
|
155
|
+
location_pattern_count: definition.location_patterns.size)
|
|
156
|
+
|
|
157
|
+
definition
|
|
158
|
+
rescue => e
|
|
159
|
+
Aidp.log_error("ai_filter_factory", "Failed to generate filter",
|
|
160
|
+
tool_name: tool_name, error: e.message, error_class: e.class.name)
|
|
161
|
+
raise GenerationError, "Failed to generate filter for #{tool_name}: #{e.message}"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Generate filter from tool command by running it and capturing output
|
|
165
|
+
#
|
|
166
|
+
# @param tool_command [String] Command to run
|
|
167
|
+
# @param project_dir [String] Directory to run command in
|
|
168
|
+
# @param tier [String] AI tier to use
|
|
169
|
+
# @return [FilterDefinition] Generated filter definition
|
|
170
|
+
def generate_from_command(tool_command:, project_dir: Dir.pwd, tier: "mini")
|
|
171
|
+
tool_name = extract_tool_name(tool_command)
|
|
172
|
+
|
|
173
|
+
# Try to get sample output by running the command
|
|
174
|
+
sample_output = capture_sample_output(tool_command, project_dir)
|
|
175
|
+
|
|
176
|
+
generate_filter(
|
|
177
|
+
tool_name: tool_name,
|
|
178
|
+
tool_command: tool_command,
|
|
179
|
+
sample_output: sample_output,
|
|
180
|
+
tier: tier
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
private
|
|
185
|
+
|
|
186
|
+
def build_prompt(tool_name, tool_command, sample_output)
|
|
187
|
+
prompt = GENERATION_PROMPT.dup
|
|
188
|
+
prompt.gsub!("{{tool_name}}", tool_name)
|
|
189
|
+
prompt.gsub!("{{tool_command}}", tool_command)
|
|
190
|
+
|
|
191
|
+
if sample_output && !sample_output.empty?
|
|
192
|
+
# Truncate very long output
|
|
193
|
+
truncated = (sample_output.length > 5000) ? sample_output[0..5000] + "\n...[truncated]" : sample_output
|
|
194
|
+
prompt.gsub!("{{sample_output}}", truncated)
|
|
195
|
+
else
|
|
196
|
+
prompt.gsub!("{{sample_output}}", "[No sample output provided - generate common patterns for #{tool_name}]")
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
prompt
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def call_ai(provider_name, model_name, prompt)
|
|
203
|
+
provider_options = {
|
|
204
|
+
model: model_name,
|
|
205
|
+
output: nil,
|
|
206
|
+
prompt: nil
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
provider = @provider_factory.create_provider(provider_name, provider_options)
|
|
210
|
+
provider.send_message(prompt: prompt, session: nil)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def parse_response(response)
|
|
214
|
+
response_text = response.is_a?(String) ? response : response.to_s
|
|
215
|
+
|
|
216
|
+
# Extract JSON from response
|
|
217
|
+
json_match = response_text.match(/\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/m) ||
|
|
218
|
+
response_text.match(/\{.*\}/m)
|
|
219
|
+
|
|
220
|
+
raise GenerationError, "No JSON found in AI response" unless json_match
|
|
221
|
+
|
|
222
|
+
JSON.parse(json_match[0], symbolize_names: true)
|
|
223
|
+
rescue JSON::ParserError => e
|
|
224
|
+
raise GenerationError, "Invalid JSON in AI response: #{e.message}"
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def validate_patterns(data)
|
|
228
|
+
# Validate that required patterns are present
|
|
229
|
+
unless data[:summary_patterns]&.any?
|
|
230
|
+
raise GenerationError, "No summary patterns generated"
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Test that patterns compile
|
|
234
|
+
test_patterns(data[:summary_patterns], "summary")
|
|
235
|
+
test_patterns(data[:error_patterns], "error") if data[:error_patterns]
|
|
236
|
+
test_patterns(data[:location_patterns], "location") if data[:location_patterns]
|
|
237
|
+
test_patterns(data[:noise_patterns], "noise") if data[:noise_patterns]
|
|
238
|
+
|
|
239
|
+
test_pattern(data[:failure_section_start], "failure_section_start") if data[:failure_section_start]
|
|
240
|
+
test_pattern(data[:failure_section_end], "failure_section_end") if data[:failure_section_end]
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def test_patterns(patterns, name)
|
|
244
|
+
Array(patterns).each_with_index do |pattern, i|
|
|
245
|
+
test_pattern(pattern, "#{name}[#{i}]")
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def test_pattern(pattern, name)
|
|
250
|
+
return if pattern.nil? || pattern.empty?
|
|
251
|
+
Regexp.new(pattern)
|
|
252
|
+
rescue RegexpError => e
|
|
253
|
+
raise GenerationError, "Invalid regex for #{name}: #{pattern} - #{e.message}"
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def extract_tool_name(command)
|
|
257
|
+
# Extract tool name from command (first word after common prefixes)
|
|
258
|
+
cleaned = command
|
|
259
|
+
.sub(/^(bundle exec|npm run|yarn|npx|python -m)\s+/, "")
|
|
260
|
+
.split(/\s+/)
|
|
261
|
+
.first
|
|
262
|
+
|
|
263
|
+
cleaned || "unknown"
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def capture_sample_output(command, project_dir)
|
|
267
|
+
# Run command and capture output (with timeout)
|
|
268
|
+
require "open3"
|
|
269
|
+
|
|
270
|
+
stdout, stderr, _ = Open3.capture3(command, chdir: project_dir)
|
|
271
|
+
|
|
272
|
+
# Combine stdout and stderr for analysis
|
|
273
|
+
output = stdout + stderr
|
|
274
|
+
output.empty? ? nil : output
|
|
275
|
+
rescue => e
|
|
276
|
+
Aidp.log_debug("ai_filter_factory", "Failed to capture sample output",
|
|
277
|
+
command: command, error: e.message)
|
|
278
|
+
nil
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Error raised when filter generation fails
|
|
283
|
+
class GenerationError < StandardError; end
|
|
284
|
+
end
|
|
285
|
+
end
|
|
@@ -383,7 +383,16 @@ module Aidp
|
|
|
383
383
|
guards: {enabled: false},
|
|
384
384
|
version_control: {tool: "git", behavior: "nothing", conventional_commits: false},
|
|
385
385
|
coverage: {enabled: false},
|
|
386
|
-
interactive_testing: {enabled: false, app_type: "web"}
|
|
386
|
+
interactive_testing: {enabled: false, app_type: "web"},
|
|
387
|
+
output_filtering: {
|
|
388
|
+
enabled: true,
|
|
389
|
+
test_mode: "full",
|
|
390
|
+
lint_mode: "full",
|
|
391
|
+
test_max_lines: 500,
|
|
392
|
+
lint_max_lines: 300,
|
|
393
|
+
include_context: true,
|
|
394
|
+
context_lines: 3
|
|
395
|
+
}
|
|
387
396
|
},
|
|
388
397
|
properties: {
|
|
389
398
|
enabled: {
|
|
@@ -796,6 +805,93 @@ module Aidp
|
|
|
796
805
|
}
|
|
797
806
|
}
|
|
798
807
|
}
|
|
808
|
+
},
|
|
809
|
+
output_filtering: {
|
|
810
|
+
type: :hash,
|
|
811
|
+
required: false,
|
|
812
|
+
default: {
|
|
813
|
+
enabled: true,
|
|
814
|
+
test_mode: "full",
|
|
815
|
+
lint_mode: "full",
|
|
816
|
+
test_max_lines: 500,
|
|
817
|
+
lint_max_lines: 300,
|
|
818
|
+
include_context: true,
|
|
819
|
+
context_lines: 3,
|
|
820
|
+
filter_definitions: {}
|
|
821
|
+
},
|
|
822
|
+
properties: {
|
|
823
|
+
enabled: {
|
|
824
|
+
type: :boolean,
|
|
825
|
+
required: false,
|
|
826
|
+
default: true
|
|
827
|
+
},
|
|
828
|
+
test_mode: {
|
|
829
|
+
type: :string,
|
|
830
|
+
required: false,
|
|
831
|
+
default: "full",
|
|
832
|
+
enum: ["full", "failures_only", "minimal"]
|
|
833
|
+
},
|
|
834
|
+
lint_mode: {
|
|
835
|
+
type: :string,
|
|
836
|
+
required: false,
|
|
837
|
+
default: "full",
|
|
838
|
+
enum: ["full", "failures_only", "minimal"]
|
|
839
|
+
},
|
|
840
|
+
test_max_lines: {
|
|
841
|
+
type: :integer,
|
|
842
|
+
required: false,
|
|
843
|
+
default: 500,
|
|
844
|
+
min: 10,
|
|
845
|
+
max: 10000
|
|
846
|
+
},
|
|
847
|
+
lint_max_lines: {
|
|
848
|
+
type: :integer,
|
|
849
|
+
required: false,
|
|
850
|
+
default: 300,
|
|
851
|
+
min: 10,
|
|
852
|
+
max: 10000
|
|
853
|
+
},
|
|
854
|
+
include_context: {
|
|
855
|
+
type: :boolean,
|
|
856
|
+
required: false,
|
|
857
|
+
default: true
|
|
858
|
+
},
|
|
859
|
+
context_lines: {
|
|
860
|
+
type: :integer,
|
|
861
|
+
required: false,
|
|
862
|
+
default: 3,
|
|
863
|
+
min: 0,
|
|
864
|
+
max: 20
|
|
865
|
+
},
|
|
866
|
+
filter_definitions: {
|
|
867
|
+
type: :hash,
|
|
868
|
+
required: false,
|
|
869
|
+
default: {},
|
|
870
|
+
# AI-generated filter definitions for specific tools
|
|
871
|
+
# Keys are tool names, values are FilterDefinition data
|
|
872
|
+
# Generated by AIFilterFactory during configuration
|
|
873
|
+
pattern_properties: {
|
|
874
|
+
/^[a-zA-Z0-9_-]+$/ => {
|
|
875
|
+
type: :hash,
|
|
876
|
+
properties: {
|
|
877
|
+
tool_name: {type: :string, required: true},
|
|
878
|
+
tool_command: {type: :string, required: false},
|
|
879
|
+
summary_patterns: {type: :array, required: false, default: []},
|
|
880
|
+
failure_section_start: {type: :string, required: false},
|
|
881
|
+
failure_section_end: {type: :string, required: false},
|
|
882
|
+
error_section_start: {type: :string, required: false},
|
|
883
|
+
error_section_end: {type: :string, required: false},
|
|
884
|
+
error_patterns: {type: :array, required: false, default: []},
|
|
885
|
+
location_patterns: {type: :array, required: false, default: []},
|
|
886
|
+
noise_patterns: {type: :array, required: false, default: []},
|
|
887
|
+
important_patterns: {type: :array, required: false, default: []},
|
|
888
|
+
context_lines: {type: :integer, required: false, default: 3},
|
|
889
|
+
created_at: {type: :string, required: false}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
799
895
|
}
|
|
800
896
|
}
|
|
801
897
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../config"
|
|
4
|
-
require_relative "../
|
|
4
|
+
require_relative "../config_paths"
|
|
5
5
|
|
|
6
6
|
module Aidp
|
|
7
7
|
module Harness
|
|
@@ -196,24 +196,68 @@ module Aidp
|
|
|
196
196
|
normalize_commands(work_loop_config[:documentation_commands] || [])
|
|
197
197
|
end
|
|
198
198
|
|
|
199
|
+
# Get output filtering configuration
|
|
200
|
+
def output_filtering_config
|
|
201
|
+
work_loop_config[:output_filtering] || default_output_filtering_config
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Check if output filtering is enabled
|
|
205
|
+
def output_filtering_enabled?
|
|
206
|
+
output_filtering_config[:enabled] != false
|
|
207
|
+
end
|
|
208
|
+
|
|
199
209
|
# Get test output mode
|
|
200
210
|
def test_output_mode
|
|
201
|
-
|
|
211
|
+
mode = output_filtering_config[:test_mode]
|
|
212
|
+
mode ? mode.to_sym : :full
|
|
202
213
|
end
|
|
203
214
|
|
|
204
215
|
# Get max output lines for tests
|
|
205
216
|
def test_max_output_lines
|
|
206
|
-
|
|
217
|
+
output_filtering_config[:test_max_lines] || 500
|
|
207
218
|
end
|
|
208
219
|
|
|
209
220
|
# Get lint output mode
|
|
210
221
|
def lint_output_mode
|
|
211
|
-
|
|
222
|
+
mode = output_filtering_config[:lint_mode]
|
|
223
|
+
mode ? mode.to_sym : :full
|
|
212
224
|
end
|
|
213
225
|
|
|
214
226
|
# Get max output lines for linters
|
|
215
227
|
def lint_max_output_lines
|
|
216
|
-
|
|
228
|
+
output_filtering_config[:lint_max_lines] || 300
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Get include_context setting for output filtering
|
|
232
|
+
def output_filtering_include_context
|
|
233
|
+
output_filtering_config.fetch(:include_context, true)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Get context_lines setting for output filtering
|
|
237
|
+
def output_filtering_context_lines
|
|
238
|
+
output_filtering_config[:context_lines] || 3
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Get all filter definitions from config
|
|
242
|
+
# @return [Hash] Map of tool keys to filter definition hashes
|
|
243
|
+
def filter_definitions
|
|
244
|
+
output_filtering_config[:filter_definitions] || {}
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Get filter definition for a specific tool/category
|
|
248
|
+
# @param key [String, Symbol] Tool key (e.g., "unit_test", "lint")
|
|
249
|
+
# @return [FilterDefinition, nil] Loaded filter definition or nil
|
|
250
|
+
def filter_definition_for(key)
|
|
251
|
+
definitions = filter_definitions
|
|
252
|
+
definition_hash = definitions[key.to_s] || definitions[key.to_sym]
|
|
253
|
+
return nil unless definition_hash
|
|
254
|
+
|
|
255
|
+
require_relative "filter_definition"
|
|
256
|
+
FilterDefinition.from_hash(definition_hash)
|
|
257
|
+
rescue => e
|
|
258
|
+
Aidp.log_warn("configuration", "failed_to_load_filter_definition",
|
|
259
|
+
key: key, error: e.message)
|
|
260
|
+
nil
|
|
217
261
|
end
|
|
218
262
|
|
|
219
263
|
# Get guards configuration
|
|
@@ -1054,6 +1098,18 @@ module Aidp
|
|
|
1054
1098
|
}
|
|
1055
1099
|
end
|
|
1056
1100
|
|
|
1101
|
+
def default_output_filtering_config
|
|
1102
|
+
{
|
|
1103
|
+
enabled: true,
|
|
1104
|
+
test_mode: "full",
|
|
1105
|
+
lint_mode: "full",
|
|
1106
|
+
test_max_lines: 500,
|
|
1107
|
+
lint_max_lines: 300,
|
|
1108
|
+
include_context: true,
|
|
1109
|
+
context_lines: 3
|
|
1110
|
+
}
|
|
1111
|
+
end
|
|
1112
|
+
|
|
1057
1113
|
def default_thinking_config
|
|
1058
1114
|
{
|
|
1059
1115
|
default_tier: "mini", # Use mini tier by default for cost optimization
|