aidp 0.15.1 → 0.16.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/README.md +47 -0
- data/lib/aidp/analyze/error_handler.rb +14 -15
- data/lib/aidp/analyze/runner.rb +27 -5
- data/lib/aidp/analyze/steps.rb +4 -0
- data/lib/aidp/cli/jobs_command.rb +2 -1
- data/lib/aidp/cli.rb +853 -6
- data/lib/aidp/concurrency/backoff.rb +148 -0
- data/lib/aidp/concurrency/exec.rb +192 -0
- data/lib/aidp/concurrency/wait.rb +148 -0
- data/lib/aidp/concurrency.rb +71 -0
- data/lib/aidp/config.rb +20 -0
- data/lib/aidp/daemon/runner.rb +9 -8
- data/lib/aidp/debug_mixin.rb +1 -0
- data/lib/aidp/errors.rb +12 -0
- data/lib/aidp/execute/interactive_repl.rb +102 -11
- data/lib/aidp/execute/repl_macros.rb +776 -2
- data/lib/aidp/execute/runner.rb +27 -5
- data/lib/aidp/execute/steps.rb +2 -0
- data/lib/aidp/harness/config_loader.rb +24 -2
- data/lib/aidp/harness/enhanced_runner.rb +16 -2
- data/lib/aidp/harness/error_handler.rb +1 -1
- data/lib/aidp/harness/provider_info.rb +20 -16
- data/lib/aidp/harness/provider_manager.rb +56 -49
- data/lib/aidp/harness/runner.rb +3 -11
- data/lib/aidp/harness/state/persistence.rb +1 -6
- data/lib/aidp/harness/state_manager.rb +115 -7
- data/lib/aidp/harness/status_display.rb +11 -18
- data/lib/aidp/harness/ui/navigation/submenu.rb +1 -0
- data/lib/aidp/harness/ui/workflow_controller.rb +1 -1
- data/lib/aidp/harness/user_interface.rb +12 -15
- data/lib/aidp/init/doc_generator.rb +75 -10
- data/lib/aidp/init/project_analyzer.rb +154 -26
- data/lib/aidp/init/runner.rb +263 -10
- data/lib/aidp/jobs/background_runner.rb +15 -5
- data/lib/aidp/logger.rb +11 -0
- data/lib/aidp/providers/codex.rb +0 -1
- data/lib/aidp/providers/cursor.rb +0 -1
- data/lib/aidp/providers/github_copilot.rb +0 -1
- data/lib/aidp/providers/opencode.rb +0 -1
- data/lib/aidp/skills/composer.rb +178 -0
- data/lib/aidp/skills/loader.rb +205 -0
- data/lib/aidp/skills/registry.rb +220 -0
- data/lib/aidp/skills/skill.rb +174 -0
- data/lib/aidp/skills.rb +30 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +93 -28
- data/lib/aidp/watch/runner.rb +3 -2
- data/lib/aidp/workstream_executor.rb +244 -0
- data/lib/aidp/workstream_state.rb +212 -0
- data/lib/aidp/worktree.rb +208 -0
- data/lib/aidp.rb +6 -0
- metadata +17 -7
- data/lib/aidp/analyze/prioritizer.rb +0 -403
- data/lib/aidp/analyze/report_generator.rb +0 -582
- data/lib/aidp/cli/checkpoint_command.rb +0 -98
|
@@ -54,24 +54,17 @@ module Aidp
|
|
|
54
54
|
@display_mode = display_mode
|
|
55
55
|
@last_update = Time.now
|
|
56
56
|
|
|
57
|
-
# Start status display using Async (skip in test mode)
|
|
58
57
|
unless ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
59
|
-
require "
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
else
|
|
70
|
-
Async::Task.current.sleep(@update_interval)
|
|
71
|
-
end
|
|
72
|
-
rescue => e
|
|
73
|
-
handle_display_error(e)
|
|
74
|
-
end
|
|
58
|
+
require "concurrent"
|
|
59
|
+
@status_future = Concurrent::Future.execute do
|
|
60
|
+
while @running
|
|
61
|
+
begin
|
|
62
|
+
collect_status_data
|
|
63
|
+
display_status
|
|
64
|
+
check_alerts
|
|
65
|
+
sleep(@update_interval)
|
|
66
|
+
rescue => e
|
|
67
|
+
handle_display_error(e)
|
|
75
68
|
end
|
|
76
69
|
end
|
|
77
70
|
end
|
|
@@ -81,7 +74,7 @@ module Aidp
|
|
|
81
74
|
# Stop status updates
|
|
82
75
|
def stop_status_updates
|
|
83
76
|
@running = false
|
|
84
|
-
@
|
|
77
|
+
@status_future&.wait(5)
|
|
85
78
|
clear_display
|
|
86
79
|
end
|
|
87
80
|
|
|
@@ -265,7 +265,7 @@ module Aidp
|
|
|
265
265
|
def control_interface_loop
|
|
266
266
|
loop do
|
|
267
267
|
handle_control_input
|
|
268
|
-
sleep(0.1)
|
|
268
|
+
sleep(0.1)
|
|
269
269
|
rescue => e
|
|
270
270
|
@status_manager.show_error_status("Control interface error: #{e.message}")
|
|
271
271
|
end
|
|
@@ -2056,14 +2056,13 @@ module Aidp
|
|
|
2056
2056
|
return unless @control_interface_enabled
|
|
2057
2057
|
|
|
2058
2058
|
@control_mutex.synchronize do
|
|
2059
|
-
return if @
|
|
2059
|
+
return if @control_future&.pending?
|
|
2060
2060
|
|
|
2061
|
-
# Start control interface using
|
|
2061
|
+
# Start control interface using concurrent-ruby (skip in test mode)
|
|
2062
|
+
# Using Concurrent::Future for background execution with proper thread pool management
|
|
2062
2063
|
unless ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
2063
|
-
require "
|
|
2064
|
-
|
|
2065
|
-
task.async { control_interface_loop }
|
|
2066
|
-
end
|
|
2064
|
+
require "concurrent"
|
|
2065
|
+
@control_future = Concurrent::Future.execute { control_interface_loop }
|
|
2067
2066
|
end
|
|
2068
2067
|
end
|
|
2069
2068
|
|
|
@@ -2079,9 +2078,9 @@ module Aidp
|
|
|
2079
2078
|
# Stop the control interface
|
|
2080
2079
|
def stop_control_interface
|
|
2081
2080
|
@control_mutex.synchronize do
|
|
2082
|
-
if @
|
|
2083
|
-
@
|
|
2084
|
-
@
|
|
2081
|
+
if @control_future
|
|
2082
|
+
@control_future.cancel
|
|
2083
|
+
@control_future = nil
|
|
2085
2084
|
end
|
|
2086
2085
|
end
|
|
2087
2086
|
|
|
@@ -2153,10 +2152,9 @@ module Aidp
|
|
|
2153
2152
|
elsif resume_requested?
|
|
2154
2153
|
handle_resume_state
|
|
2155
2154
|
break
|
|
2156
|
-
elsif ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
2157
|
-
sleep(0.1)
|
|
2158
2155
|
else
|
|
2159
|
-
|
|
2156
|
+
# Periodic check for user input/state changes
|
|
2157
|
+
sleep(0.1)
|
|
2160
2158
|
end
|
|
2161
2159
|
end
|
|
2162
2160
|
end
|
|
@@ -2400,10 +2398,9 @@ module Aidp
|
|
|
2400
2398
|
elsif Time.now - start_time > timeout_seconds
|
|
2401
2399
|
display_message("\n⏰ Control interface timeout reached. Continuing execution...", type: :warning)
|
|
2402
2400
|
break
|
|
2403
|
-
elsif ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
2404
|
-
sleep(0.1)
|
|
2405
2401
|
else
|
|
2406
|
-
|
|
2402
|
+
# Periodic check for user confirmation
|
|
2403
|
+
sleep(0.1)
|
|
2407
2404
|
end
|
|
2408
2405
|
end
|
|
2409
2406
|
end
|
|
@@ -32,10 +32,11 @@ module Aidp
|
|
|
32
32
|
|
|
33
33
|
def write_style_guide(analysis, preferences)
|
|
34
34
|
languages = format_list(analysis[:languages].keys)
|
|
35
|
-
frameworks
|
|
36
|
-
|
|
35
|
+
# Extract high-confidence frameworks (>= 0.7)
|
|
36
|
+
frameworks = extract_confident_names(analysis[:frameworks], threshold: 0.7)
|
|
37
|
+
test_frameworks = extract_confident_names(analysis[:test_frameworks], threshold: 0.7)
|
|
37
38
|
key_dirs = format_list(analysis[:key_directories])
|
|
38
|
-
tooling = analysis[:tooling].
|
|
39
|
+
tooling = extract_confident_names(analysis[:tooling], threshold: 0.7, key: :tool).map { |tool| format_tool(tool) }.sort
|
|
39
40
|
|
|
40
41
|
adoption_note = if truthy?(preferences[:adopt_new_conventions])
|
|
41
42
|
"This project has opted to adopt new conventions recommended by aidp init. When in doubt, prefer the rules below over legacy patterns."
|
|
@@ -43,14 +44,17 @@ module Aidp
|
|
|
43
44
|
"Retain existing conventions when they do not conflict with the guidance below."
|
|
44
45
|
end
|
|
45
46
|
|
|
47
|
+
frameworks_text = frameworks.empty? ? "None detected" : frameworks.join(", ")
|
|
48
|
+
test_frameworks_text = test_frameworks.empty? ? "Unknown" : test_frameworks.join(", ")
|
|
49
|
+
|
|
46
50
|
content = <<~GUIDE
|
|
47
51
|
# Project LLM Style Guide
|
|
48
52
|
|
|
49
53
|
> Generated automatically by `aidp init` on #{Time.now.utc.iso8601}.
|
|
50
54
|
>
|
|
51
55
|
> Detected languages: #{languages}
|
|
52
|
-
> Framework hints: #{
|
|
53
|
-
> Primary test frameworks: #{
|
|
56
|
+
> Framework hints: #{frameworks_text}
|
|
57
|
+
> Primary test frameworks: #{test_frameworks_text}
|
|
54
58
|
> Key directories: #{key_dirs.empty? ? "Standard structure" : key_dirs}
|
|
55
59
|
|
|
56
60
|
#{adoption_note}
|
|
@@ -115,9 +119,10 @@ module Aidp
|
|
|
115
119
|
|
|
116
120
|
def write_project_analysis(analysis)
|
|
117
121
|
languages = format_language_breakdown(analysis[:languages])
|
|
118
|
-
frameworks =
|
|
122
|
+
frameworks = format_framework_detection(analysis[:frameworks])
|
|
123
|
+
test_frameworks = format_framework_detection(analysis[:test_frameworks])
|
|
119
124
|
config_files = bullet_list(analysis[:config_files], default: "_No dedicated configuration files discovered_")
|
|
120
|
-
tooling =
|
|
125
|
+
tooling = format_tooling_detection(analysis[:tooling])
|
|
121
126
|
|
|
122
127
|
stats = analysis[:repo_stats]
|
|
123
128
|
stats_lines = [
|
|
@@ -146,7 +151,7 @@ module Aidp
|
|
|
146
151
|
#{config_files}
|
|
147
152
|
|
|
148
153
|
## Test & Quality Signals
|
|
149
|
-
#{
|
|
154
|
+
#{test_frameworks}
|
|
150
155
|
|
|
151
156
|
## Local Quality Toolchain
|
|
152
157
|
#{tooling}
|
|
@@ -175,13 +180,19 @@ module Aidp
|
|
|
175
180
|
"- Keep legacy style deviations documented until dedicated refactors are scheduled.\n"
|
|
176
181
|
end
|
|
177
182
|
|
|
183
|
+
tooling_section = if tooling.empty?
|
|
184
|
+
"_No linting/formatting tools detected. Consider adding RuboCop, ESLint, or Prettier based on the primary language._"
|
|
185
|
+
else
|
|
186
|
+
format_tooling_detection_table(tooling)
|
|
187
|
+
end
|
|
188
|
+
|
|
178
189
|
content = <<~PLAN
|
|
179
190
|
# Code Quality Plan
|
|
180
191
|
|
|
181
192
|
This plan captures the current tooling landscape and proposes next steps for keeping the codebase healthy. Generated by `aidp init` on #{Time.now.utc.iso8601}.
|
|
182
193
|
|
|
183
194
|
## Local Quality Toolchain
|
|
184
|
-
#{
|
|
195
|
+
#{tooling_section}
|
|
185
196
|
|
|
186
197
|
## Immediate Actions
|
|
187
198
|
#{proactive}#{migration}- Document onboarding steps in `docs/` to ensure future contributors follow the agreed workflow.
|
|
@@ -189,7 +200,7 @@ module Aidp
|
|
|
189
200
|
## Long-Term Improvements
|
|
190
201
|
- Keep the style guide in sync with real-world code changes; regenerate with `aidp init` after major rewrites.
|
|
191
202
|
- Automate test and lint runs via CI (detected: #{analysis.dig(:repo_stats, :has_ci_config) ? "yes" : "no"}).
|
|
192
|
-
- Track flaky tests or unstable tooling in `PROJECT_ANALYSIS.md` under a
|
|
203
|
+
- Track flaky tests or unstable tooling in `PROJECT_ANALYSIS.md` under a "Health Log" section.
|
|
193
204
|
|
|
194
205
|
---
|
|
195
206
|
Based on templates: `analysis/analyze_static_code.md`, `analysis/analyze_tests.md`.
|
|
@@ -251,6 +262,60 @@ module Aidp
|
|
|
251
262
|
rescue
|
|
252
263
|
false
|
|
253
264
|
end
|
|
265
|
+
|
|
266
|
+
# Extract confident names from detection results
|
|
267
|
+
def extract_confident_names(detections, threshold: 0.7, key: :name)
|
|
268
|
+
return [] if detections.nil? || detections.empty?
|
|
269
|
+
|
|
270
|
+
detections.select { |d| d[:confidence] >= threshold }.map { |d| d[key] }
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Format framework detection with confidence levels
|
|
274
|
+
def format_framework_detection(detections)
|
|
275
|
+
return "_None detected_" if detections.nil? || detections.empty?
|
|
276
|
+
|
|
277
|
+
lines = detections.map do |detection|
|
|
278
|
+
name = detection[:name]
|
|
279
|
+
confidence = (detection[:confidence] * 100).round
|
|
280
|
+
evidence = detection[:evidence].join("; ")
|
|
281
|
+
"- **#{name}** (#{confidence}% confidence)\n - Evidence: #{evidence}"
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
lines.join("\n")
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Format tooling detection with confidence levels
|
|
288
|
+
def format_tooling_detection(tooling)
|
|
289
|
+
return "_No tooling detected._" if tooling.nil? || tooling.empty?
|
|
290
|
+
|
|
291
|
+
header = "| Tool | Confidence | Evidence |\n|------|------------|----------|"
|
|
292
|
+
rows = tooling.map do |tool_data|
|
|
293
|
+
tool_name = format_tool(tool_data[:tool])
|
|
294
|
+
confidence = "#{(tool_data[:confidence] * 100).round}%"
|
|
295
|
+
evidence = tool_data[:evidence].join(", ")
|
|
296
|
+
"| #{tool_name} | #{confidence} | #{evidence} |"
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
([header] + rows).join("\n")
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Format tooling detection table for quality plan
|
|
303
|
+
def format_tooling_detection_table(tooling)
|
|
304
|
+
return "_No tooling detected._" if tooling.nil? || tooling.empty?
|
|
305
|
+
|
|
306
|
+
header = "| Tool | Evidence |\n|------|----------|"
|
|
307
|
+
rows = tooling.select { |t| t[:confidence] >= 0.7 }.map do |tool_data|
|
|
308
|
+
tool_name = format_tool(tool_data[:tool])
|
|
309
|
+
evidence = tool_data[:evidence].join(", ")
|
|
310
|
+
"| #{tool_name} | #{evidence} |"
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
if rows.empty?
|
|
314
|
+
"_No high-confidence tooling detected. Consider adding linting and formatting tools._"
|
|
315
|
+
else
|
|
316
|
+
([header] + rows).join("\n")
|
|
317
|
+
end
|
|
318
|
+
end
|
|
254
319
|
end
|
|
255
320
|
end
|
|
256
321
|
end
|
|
@@ -155,7 +155,9 @@ module Aidp
|
|
|
155
155
|
@project_dir = project_dir
|
|
156
156
|
end
|
|
157
157
|
|
|
158
|
-
def analyze
|
|
158
|
+
def analyze(options = {})
|
|
159
|
+
@explain_detection = options[:explain_detection] || false
|
|
160
|
+
|
|
159
161
|
{
|
|
160
162
|
languages: detect_languages,
|
|
161
163
|
frameworks: detect_frameworks,
|
|
@@ -185,21 +187,57 @@ module Aidp
|
|
|
185
187
|
end
|
|
186
188
|
|
|
187
189
|
def detect_frameworks
|
|
188
|
-
|
|
190
|
+
results = []
|
|
189
191
|
|
|
190
192
|
FRAMEWORK_HINTS.each do |framework, rules|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
193
|
+
evidence = []
|
|
194
|
+
confidence = 0.0
|
|
195
|
+
|
|
196
|
+
# Check for required files
|
|
197
|
+
matched_files = []
|
|
198
|
+
if rules[:files]
|
|
199
|
+
matched_files = rules[:files].select { |file| project_glob?(file) }
|
|
200
|
+
if matched_files.any?
|
|
201
|
+
evidence << "Found files: #{matched_files.join(", ")}"
|
|
202
|
+
confidence += 0.3
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Check for content patterns in specific files or across project
|
|
207
|
+
if rules[:contents]
|
|
208
|
+
rules[:contents].each do |pattern|
|
|
209
|
+
if matched_files.any?
|
|
210
|
+
# Search only in the matched files
|
|
211
|
+
if search_files_for_pattern(matched_files, pattern)
|
|
212
|
+
evidence << "Found pattern '#{pattern.inspect}' in #{matched_files.join(", ")}"
|
|
213
|
+
confidence += 0.7
|
|
214
|
+
end
|
|
215
|
+
elsif search_project_for_pattern(pattern)
|
|
216
|
+
# Search across all project files (less confident)
|
|
217
|
+
evidence << "Found pattern '#{pattern.inspect}' in project files"
|
|
218
|
+
confidence += 0.4
|
|
219
|
+
end
|
|
196
220
|
end
|
|
197
|
-
elsif
|
|
198
|
-
|
|
221
|
+
elsif matched_files.any?
|
|
222
|
+
# Files exist but no content check needed - moderately confident
|
|
223
|
+
confidence = 0.6
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Only include frameworks with evidence
|
|
227
|
+
if evidence.any? && confidence > 0.0
|
|
228
|
+
# Cap confidence at 1.0
|
|
229
|
+
confidence = [confidence, 1.0].min
|
|
230
|
+
|
|
231
|
+
results << {
|
|
232
|
+
name: framework,
|
|
233
|
+
confidence: confidence,
|
|
234
|
+
evidence: evidence
|
|
235
|
+
}
|
|
199
236
|
end
|
|
200
237
|
end
|
|
201
238
|
|
|
202
|
-
|
|
239
|
+
# Sort by confidence (descending) then name
|
|
240
|
+
results.sort_by { |r| [-r[:confidence], r[:name]] }
|
|
203
241
|
end
|
|
204
242
|
|
|
205
243
|
def detect_key_directories
|
|
@@ -212,28 +250,82 @@ module Aidp
|
|
|
212
250
|
|
|
213
251
|
def detect_test_frameworks
|
|
214
252
|
results = []
|
|
253
|
+
dependency_files = ["Gemfile", "Gemfile.lock", "package.json", "pyproject.toml", "requirements.txt", "go.mod", "mix.exs", "composer.json", "Cargo.toml"]
|
|
215
254
|
|
|
216
255
|
TEST_FRAMEWORK_HINTS.each do |framework, hints|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
256
|
+
evidence = []
|
|
257
|
+
confidence = 0.0
|
|
258
|
+
|
|
259
|
+
# Check for test directories
|
|
260
|
+
if hints[:directories]
|
|
261
|
+
found_dirs = hints[:directories].select { |dir| Dir.exist?(File.join(project_dir, dir)) }
|
|
262
|
+
if found_dirs.any?
|
|
263
|
+
evidence << "Found directories: #{found_dirs.join(", ")}"
|
|
264
|
+
confidence += 0.5
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Check for dependencies in lockfiles/manifests
|
|
269
|
+
hints[:dependencies]&.each do |pattern|
|
|
270
|
+
matched_files = []
|
|
271
|
+
dependency_files.each do |dep_file|
|
|
272
|
+
path = File.join(project_dir, dep_file)
|
|
273
|
+
next unless File.exist?(path)
|
|
274
|
+
|
|
275
|
+
begin
|
|
276
|
+
if File.read(path).match?(pattern)
|
|
277
|
+
matched_files << dep_file
|
|
278
|
+
end
|
|
279
|
+
rescue Errno::ENOENT, ArgumentError, Encoding::InvalidByteSequenceError
|
|
280
|
+
next
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
if matched_files.any?
|
|
285
|
+
evidence << "Found dependency pattern '#{pattern.inspect}' in #{matched_files.join(", ")}"
|
|
286
|
+
confidence += 0.6
|
|
287
|
+
end
|
|
288
|
+
end
|
|
220
289
|
|
|
221
|
-
|
|
290
|
+
# Check for test files
|
|
291
|
+
if hints[:files]
|
|
292
|
+
matched_globs = hints[:files].select { |glob| project_glob?(glob) }
|
|
293
|
+
if matched_globs.any?
|
|
294
|
+
evidence << "Found test files matching: #{matched_globs.join(", ")}"
|
|
295
|
+
confidence += 0.4
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Only include test frameworks with evidence
|
|
300
|
+
if evidence.any? && confidence > 0.0
|
|
301
|
+
confidence = [confidence, 1.0].min
|
|
302
|
+
|
|
303
|
+
results << {
|
|
304
|
+
name: framework,
|
|
305
|
+
confidence: confidence,
|
|
306
|
+
evidence: evidence
|
|
307
|
+
}
|
|
308
|
+
end
|
|
222
309
|
end
|
|
223
310
|
|
|
224
|
-
|
|
311
|
+
# Sort by confidence (descending) then name
|
|
312
|
+
results.sort_by { |r| [-r[:confidence], r[:name]] }
|
|
225
313
|
end
|
|
226
314
|
|
|
227
315
|
def detect_tooling
|
|
228
|
-
|
|
316
|
+
results = []
|
|
229
317
|
|
|
230
318
|
TOOLING_HINTS.each do |tool, indicators|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
319
|
+
evidence = []
|
|
320
|
+
confidence = 0.0
|
|
321
|
+
|
|
322
|
+
matched_indicators = indicators.select { |indicator| project_glob?(indicator) }
|
|
323
|
+
if matched_indicators.any?
|
|
324
|
+
evidence << "Found config files: #{matched_indicators.join(", ")}"
|
|
325
|
+
confidence += 0.8
|
|
235
326
|
end
|
|
236
|
-
|
|
327
|
+
|
|
328
|
+
results << {tool: tool, evidence: evidence, confidence: confidence} if evidence.any?
|
|
237
329
|
end
|
|
238
330
|
|
|
239
331
|
# Post-process for package.json to extract scripts referencing linters
|
|
@@ -242,15 +334,31 @@ module Aidp
|
|
|
242
334
|
begin
|
|
243
335
|
json = JSON.parse(File.read(package_json_path))
|
|
244
336
|
scripts = json.fetch("scripts", {})
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
337
|
+
|
|
338
|
+
[:eslint, :prettier, :jest].each do |tool|
|
|
339
|
+
tool_str = tool.to_s
|
|
340
|
+
if scripts.values.any? { |cmd| cmd.include?(tool_str) }
|
|
341
|
+
# Check if we already have this tool from config detection
|
|
342
|
+
existing = results.find { |r| r[:tool] == tool }
|
|
343
|
+
if existing
|
|
344
|
+
existing[:evidence] << "Referenced in package.json scripts"
|
|
345
|
+
existing[:confidence] = [existing[:confidence] + 0.3, 1.0].min
|
|
346
|
+
else
|
|
347
|
+
results << {
|
|
348
|
+
tool: tool,
|
|
349
|
+
evidence: ["Referenced in package.json scripts"],
|
|
350
|
+
confidence: 0.6
|
|
351
|
+
}
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|
|
248
355
|
rescue JSON::ParserError
|
|
249
356
|
# ignore malformed package.json
|
|
250
357
|
end
|
|
251
358
|
end
|
|
252
359
|
|
|
253
|
-
|
|
360
|
+
# Sort by confidence (descending) then tool name
|
|
361
|
+
results.sort_by { |r| [-r[:confidence], r[:tool].to_s] }
|
|
254
362
|
end
|
|
255
363
|
|
|
256
364
|
def collect_repo_stats
|
|
@@ -306,13 +414,33 @@ module Aidp
|
|
|
306
414
|
def search_files_for_pattern(files, pattern)
|
|
307
415
|
files.any? do |file|
|
|
308
416
|
Dir.glob(File.join(project_dir, file)).any? do |path|
|
|
309
|
-
|
|
417
|
+
# Special handling for package.json - only search in dependencies
|
|
418
|
+
if File.basename(path) == "package.json"
|
|
419
|
+
check_package_json_dependency(path, pattern)
|
|
420
|
+
else
|
|
421
|
+
File.read(path).match?(pattern)
|
|
422
|
+
end
|
|
310
423
|
rescue Errno::ENOENT, Errno::EISDIR
|
|
311
424
|
false
|
|
312
425
|
end
|
|
313
426
|
end
|
|
314
427
|
end
|
|
315
428
|
|
|
429
|
+
def check_package_json_dependency(path, pattern)
|
|
430
|
+
json = JSON.parse(File.read(path))
|
|
431
|
+
deps = json.fetch("dependencies", {})
|
|
432
|
+
dev_deps = json.fetch("devDependencies", {})
|
|
433
|
+
peer_deps = json.fetch("peerDependencies", {})
|
|
434
|
+
|
|
435
|
+
all_deps = deps.keys + dev_deps.keys + peer_deps.keys
|
|
436
|
+
all_deps.any? { |dep| dep.match?(pattern) }
|
|
437
|
+
rescue JSON::ParserError, Errno::ENOENT
|
|
438
|
+
# Fallback to simple text search if JSON parsing fails
|
|
439
|
+
File.read(path).match?(pattern)
|
|
440
|
+
rescue
|
|
441
|
+
false
|
|
442
|
+
end
|
|
443
|
+
|
|
316
444
|
def search_project_for_pattern(pattern, limit_files: nil)
|
|
317
445
|
if limit_files
|
|
318
446
|
limit_files.any? do |file|
|