aidp 0.32.0 → 0.34.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 +35 -0
- data/lib/aidp/analyze/feature_analyzer.rb +322 -320
- data/lib/aidp/analyze/tree_sitter_scan.rb +3 -0
- 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/eval_command.rb +399 -0
- data/lib/aidp/cli/harness_command.rb +1 -1
- data/lib/aidp/cli/security_command.rb +416 -0
- data/lib/aidp/cli/tools_command.rb +6 -4
- data/lib/aidp/cli.rb +172 -4
- data/lib/aidp/comment_consolidator.rb +78 -0
- data/lib/aidp/concurrency/exec.rb +3 -0
- data/lib/aidp/concurrency.rb +0 -3
- data/lib/aidp/config.rb +113 -1
- data/lib/aidp/config_paths.rb +91 -0
- data/lib/aidp/daemon/runner.rb +8 -4
- data/lib/aidp/errors.rb +134 -0
- data/lib/aidp/evaluations/context_capture.rb +205 -0
- data/lib/aidp/evaluations/evaluation_record.rb +114 -0
- data/lib/aidp/evaluations/evaluation_storage.rb +250 -0
- data/lib/aidp/evaluations.rb +23 -0
- data/lib/aidp/execute/async_work_loop_runner.rb +4 -1
- data/lib/aidp/execute/interactive_repl.rb +6 -2
- data/lib/aidp/execute/prompt_evaluator.rb +359 -0
- data/lib/aidp/execute/repl_macros.rb +100 -1
- data/lib/aidp/execute/work_loop_runner.rb +719 -58
- data/lib/aidp/execute/work_loop_state.rb +4 -1
- data/lib/aidp/execute/workflow_selector.rb +3 -0
- data/lib/aidp/harness/ai_decision_engine.rb +79 -0
- data/lib/aidp/harness/ai_filter_factory.rb +285 -0
- data/lib/aidp/harness/capability_registry.rb +2 -0
- data/lib/aidp/harness/condition_detector.rb +3 -0
- data/lib/aidp/harness/config_loader.rb +3 -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/enhanced_runner.rb +14 -11
- data/lib/aidp/harness/error_handler.rb +3 -0
- 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_factory.rb +3 -0
- data/lib/aidp/harness/provider_manager.rb +96 -2
- data/lib/aidp/harness/runner.rb +5 -12
- data/lib/aidp/harness/state/persistence.rb +3 -0
- data/lib/aidp/harness/state_manager.rb +3 -0
- data/lib/aidp/harness/status_display.rb +28 -20
- data/lib/aidp/harness/test_runner.rb +179 -41
- data/lib/aidp/harness/thinking_depth_manager.rb +44 -28
- data/lib/aidp/harness/ui/enhanced_tui.rb +4 -0
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +4 -0
- data/lib/aidp/harness/ui/error_handler.rb +3 -0
- data/lib/aidp/harness/ui/job_monitor.rb +4 -0
- data/lib/aidp/harness/ui/navigation/submenu.rb +2 -2
- data/lib/aidp/harness/ui/navigation/workflow_selector.rb +6 -0
- data/lib/aidp/harness/ui/spinner_helper.rb +3 -0
- data/lib/aidp/harness/ui/workflow_controller.rb +3 -0
- data/lib/aidp/harness/user_interface.rb +3 -0
- data/lib/aidp/loader.rb +195 -0
- data/lib/aidp/logger.rb +3 -0
- data/lib/aidp/message_display.rb +31 -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 +20 -8
- data/lib/aidp/provider_manager.rb +4 -7
- data/lib/aidp/providers/base.rb +2 -0
- data/lib/aidp/security/rule_of_two_enforcer.rb +210 -0
- data/lib/aidp/security/secrets_proxy.rb +328 -0
- data/lib/aidp/security/secrets_registry.rb +227 -0
- data/lib/aidp/security/trifecta_state.rb +220 -0
- data/lib/aidp/security/watch_mode_handler.rb +306 -0
- data/lib/aidp/security/work_loop_adapter.rb +277 -0
- data/lib/aidp/security.rb +56 -0
- data/lib/aidp/setup/wizard.rb +283 -11
- 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/auto_merger.rb +274 -0
- data/lib/aidp/watch/auto_pr_processor.rb +125 -7
- data/lib/aidp/watch/build_processor.rb +16 -1
- data/lib/aidp/watch/change_request_processor.rb +682 -150
- data/lib/aidp/watch/ci_fix_processor.rb +262 -4
- data/lib/aidp/watch/feedback_collector.rb +191 -0
- data/lib/aidp/watch/hierarchical_pr_strategy.rb +256 -0
- data/lib/aidp/watch/implementation_verifier.rb +142 -1
- data/lib/aidp/watch/plan_generator.rb +70 -13
- data/lib/aidp/watch/plan_processor.rb +12 -5
- data/lib/aidp/watch/projects_processor.rb +286 -0
- data/lib/aidp/watch/repository_client.rb +871 -22
- data/lib/aidp/watch/review_processor.rb +33 -6
- data/lib/aidp/watch/runner.rb +80 -29
- data/lib/aidp/watch/state_store.rb +233 -0
- data/lib/aidp/watch/sub_issue_creator.rb +221 -0
- data/lib/aidp/watch.rb +5 -7
- data/lib/aidp/workflows/guided_agent.rb +4 -0
- data/lib/aidp/workstream_cleanup.rb +0 -2
- data/lib/aidp/workstream_executor.rb +3 -4
- data/lib/aidp/worktree.rb +61 -12
- data/lib/aidp/worktree_branch_manager.rb +347 -101
- data/lib/aidp.rb +21 -106
- data/templates/implementation/iterative_implementation.md +46 -3
- metadata +91 -36
- data/lib/aidp/config/paths.rb +0 -131
data/lib/aidp/cli.rb
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
require "optparse"
|
|
4
4
|
require "tty-prompt"
|
|
5
|
+
require "stringio"
|
|
5
6
|
require_relative "harness/runner"
|
|
6
7
|
require_relative "execute/workflow_selector"
|
|
7
8
|
require_relative "harness/ui/enhanced_tui"
|
|
8
9
|
require_relative "harness/ui/enhanced_workflow_selector"
|
|
9
10
|
require_relative "harness/enhanced_runner"
|
|
10
11
|
require_relative "cli/first_run_wizard"
|
|
12
|
+
require_relative "cli/issue_importer"
|
|
11
13
|
require_relative "rescue_logging"
|
|
12
14
|
require_relative "concurrency"
|
|
13
15
|
|
|
@@ -45,6 +47,18 @@ module Aidp
|
|
|
45
47
|
options = parse_options(args)
|
|
46
48
|
self.last_options = options
|
|
47
49
|
|
|
50
|
+
# Validate incompatible options
|
|
51
|
+
if options[:quiet] && options[:verbose]
|
|
52
|
+
display_message("❌ --quiet and --verbose are mutually exclusive", type: :error)
|
|
53
|
+
return 1
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# --quiet is incompatible with default interactive mode (no subcommand)
|
|
57
|
+
if options[:quiet] && !options[:help] && !options[:version]
|
|
58
|
+
display_message("❌ --quiet is not compatible with interactive mode. Use with 'watch' command instead.", type: :error)
|
|
59
|
+
return 1
|
|
60
|
+
end
|
|
61
|
+
|
|
48
62
|
if options[:help]
|
|
49
63
|
display_message(options[:parser].to_s, type: :info)
|
|
50
64
|
return 0
|
|
@@ -55,6 +69,12 @@ module Aidp
|
|
|
55
69
|
return 0
|
|
56
70
|
end
|
|
57
71
|
|
|
72
|
+
# Undocumented: Launch test mode for CI/CD validation
|
|
73
|
+
# Initializes app components and exits cleanly without running full workflows
|
|
74
|
+
if options[:launch_test]
|
|
75
|
+
return run_launch_test(:interactive)
|
|
76
|
+
end
|
|
77
|
+
|
|
58
78
|
# Initialize logger from aidp.yml config
|
|
59
79
|
# Priority: ENV variable > aidp.yml > default (info)
|
|
60
80
|
setup_logging(Dir.pwd)
|
|
@@ -146,6 +166,117 @@ module Aidp
|
|
|
146
166
|
Aidp.logger.warn("cli", "Failed to load logging config, using defaults", error: e.message)
|
|
147
167
|
end
|
|
148
168
|
|
|
169
|
+
# Quick exit launch test for CI/CD validation
|
|
170
|
+
# Initializes app components and exits cleanly without running full workflows
|
|
171
|
+
def run_launch_test(mode)
|
|
172
|
+
Aidp.log_debug("cli", "launch_test_started", mode: mode)
|
|
173
|
+
display_message("Aidp version #{Aidp::VERSION}", type: :info)
|
|
174
|
+
|
|
175
|
+
# Initialize logging
|
|
176
|
+
setup_logging(Dir.pwd)
|
|
177
|
+
|
|
178
|
+
case mode
|
|
179
|
+
when :interactive
|
|
180
|
+
run_interactive_launch_test
|
|
181
|
+
when :watch
|
|
182
|
+
run_watch_launch_test
|
|
183
|
+
else
|
|
184
|
+
display_message("Unknown launch test mode: #{mode}", type: :error)
|
|
185
|
+
return 1
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
Aidp.log_info("cli", "launch_test_completed", mode: mode)
|
|
189
|
+
display_message("Launch test completed successfully", type: :success)
|
|
190
|
+
0
|
|
191
|
+
rescue => e
|
|
192
|
+
log_rescue(e, component: "cli", action: "launch_test", fallback: 1, mode: mode)
|
|
193
|
+
display_message("Launch test failed: #{e.message}", type: :error)
|
|
194
|
+
1
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def run_interactive_launch_test
|
|
198
|
+
Aidp.log_debug("cli", "interactive_launch_test", step: "init_tui")
|
|
199
|
+
|
|
200
|
+
# Initialize TUI components (validates they can be created)
|
|
201
|
+
tui = Aidp::Harness::UI::EnhancedTUI.new
|
|
202
|
+
Aidp.log_debug("cli", "interactive_launch_test", step: "tui_created")
|
|
203
|
+
|
|
204
|
+
# Initialize workflow selector (validates harness loading)
|
|
205
|
+
_selector = Aidp::Harness::UI::EnhancedWorkflowSelector.new(tui, project_dir: Dir.pwd)
|
|
206
|
+
Aidp.log_debug("cli", "interactive_launch_test", step: "workflow_selector_created")
|
|
207
|
+
|
|
208
|
+
# Initialize config manager (validates config loading)
|
|
209
|
+
_config_manager = Aidp::Harness::ConfigManager.new(Dir.pwd)
|
|
210
|
+
Aidp.log_debug("cli", "interactive_launch_test", step: "config_manager_created")
|
|
211
|
+
|
|
212
|
+
# Validate EnhancedRunner can be instantiated (orchestrates workflows)
|
|
213
|
+
Aidp.log_debug("cli", "interactive_launch_test", step: "validate_enhanced_runner")
|
|
214
|
+
require_relative "harness/enhanced_runner"
|
|
215
|
+
_runner = Aidp::Harness::EnhancedRunner.new(Dir.pwd, :execute, {mode: :execute})
|
|
216
|
+
Aidp.log_debug("cli", "interactive_launch_test", step: "enhanced_runner_created")
|
|
217
|
+
display_message("Enhanced Runner instantiation verified", type: :info)
|
|
218
|
+
|
|
219
|
+
# Validate FirstRunWizard can be loaded (critical for setup)
|
|
220
|
+
Aidp.log_debug("cli", "interactive_launch_test", step: "validate_first_run_wizard")
|
|
221
|
+
require_relative "cli/first_run_wizard"
|
|
222
|
+
# Don't instantiate to avoid triggering actual wizard
|
|
223
|
+
Aidp.log_debug("cli", "interactive_launch_test", step: "first_run_wizard_loaded")
|
|
224
|
+
display_message("First Run Wizard loaded", type: :info)
|
|
225
|
+
|
|
226
|
+
# Validate Init::Runner can be instantiated (init command)
|
|
227
|
+
Aidp.log_debug("cli", "interactive_launch_test", step: "validate_init_runner")
|
|
228
|
+
require_relative "init/runner"
|
|
229
|
+
mock_prompt = TTY::Prompt.new(input: StringIO.new, output: StringIO.new)
|
|
230
|
+
_init_runner = Aidp::Init::Runner.new(Dir.pwd, prompt: mock_prompt, options: {dry_run: true})
|
|
231
|
+
Aidp.log_debug("cli", "interactive_launch_test", step: "init_runner_created")
|
|
232
|
+
display_message("Init Runner instantiation verified", type: :info)
|
|
233
|
+
|
|
234
|
+
display_message("Interactive mode initialization verified", type: :info)
|
|
235
|
+
ensure
|
|
236
|
+
tui&.restore_screen
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def run_watch_launch_test
|
|
240
|
+
Aidp.log_debug("cli", "watch_launch_test", step: "init_config")
|
|
241
|
+
|
|
242
|
+
# Load config to validate configuration parsing
|
|
243
|
+
config_manager = Aidp::Harness::ConfigManager.new(Dir.pwd)
|
|
244
|
+
config = config_manager.config || {}
|
|
245
|
+
watch_config = config[:watch] || config["watch"] || {}
|
|
246
|
+
|
|
247
|
+
Aidp.log_debug("cli", "watch_launch_test", step: "config_loaded", has_watch_config: !watch_config.empty?)
|
|
248
|
+
|
|
249
|
+
display_message("Watch mode configuration verified", type: :info)
|
|
250
|
+
|
|
251
|
+
# Instantiate Runner to validate all dependencies are loadable
|
|
252
|
+
# Use mock GitHub client to avoid external API calls
|
|
253
|
+
Aidp.log_debug("cli", "watch_launch_test", step: "validate_runner")
|
|
254
|
+
mock_gh_client = Class.new do
|
|
255
|
+
def available?
|
|
256
|
+
false
|
|
257
|
+
end
|
|
258
|
+
end.new
|
|
259
|
+
|
|
260
|
+
Aidp::Watch::Runner.new(
|
|
261
|
+
issues_url: "https://github.com/test/test/issues",
|
|
262
|
+
interval: 30,
|
|
263
|
+
once: true,
|
|
264
|
+
gh_available: mock_gh_client,
|
|
265
|
+
prompt: TTY::Prompt.new(input: StringIO.new, output: StringIO.new)
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
Aidp.log_debug("cli", "watch_launch_test", step: "runner_instantiated")
|
|
269
|
+
display_message("Watch mode Runner instantiation verified", type: :info)
|
|
270
|
+
|
|
271
|
+
# Validate key Watch mode dependencies are loadable
|
|
272
|
+
Aidp.log_debug("cli", "watch_launch_test", step: "validate_watch_dependencies")
|
|
273
|
+
require_relative "watch/plan_generator"
|
|
274
|
+
require_relative "auto_update"
|
|
275
|
+
require_relative "worktree"
|
|
276
|
+
Aidp.log_debug("cli", "watch_launch_test", step: "watch_dependencies_loaded")
|
|
277
|
+
display_message("Watch mode dependencies loaded", type: :info)
|
|
278
|
+
end
|
|
279
|
+
|
|
149
280
|
def parse_options(args)
|
|
150
281
|
options = {}
|
|
151
282
|
|
|
@@ -216,6 +347,9 @@ module Aidp
|
|
|
216
347
|
opts.on("-v", "--version", "Show version information") { options[:version] = true }
|
|
217
348
|
opts.on("--setup-config", "Setup or reconfigure config file") { options[:setup_config] = true }
|
|
218
349
|
opts.on("--verbose", "Show detailed prompts and raw provider responses during guided workflow") { options[:verbose] = true }
|
|
350
|
+
opts.on("--quiet", "Suppress non-critical output (incompatible with --verbose and --interactive)") { options[:quiet] = true }
|
|
351
|
+
# Undocumented: Quick exit launch test for CI/CD validation
|
|
352
|
+
opts.on("--launch-test", nil) { options[:launch_test] = true }
|
|
219
353
|
|
|
220
354
|
opts.separator ""
|
|
221
355
|
opts.separator "Examples:"
|
|
@@ -256,7 +390,7 @@ module Aidp
|
|
|
256
390
|
# Determine if the invocation is a subcommand style call
|
|
257
391
|
def subcommand?(args)
|
|
258
392
|
return false if args.nil? || args.empty?
|
|
259
|
-
%w[status jobs kb harness providers checkpoint mcp issue config init watch ws work skill settings models tools].include?(args.first)
|
|
393
|
+
%w[status jobs kb harness providers checkpoint eval mcp issue config init watch ws work skill settings models tools security].include?(args.first)
|
|
260
394
|
end
|
|
261
395
|
|
|
262
396
|
def run_subcommand(args)
|
|
@@ -268,6 +402,7 @@ module Aidp
|
|
|
268
402
|
when "harness" then run_harness_command(args)
|
|
269
403
|
when "providers" then run_providers_command(args)
|
|
270
404
|
when "checkpoint" then run_checkpoint_command(args)
|
|
405
|
+
when "eval" then run_eval_command(args)
|
|
271
406
|
when "mcp" then run_mcp_command(args)
|
|
272
407
|
when "issue" then run_issue_command(args)
|
|
273
408
|
when "config" then run_config_command(args)
|
|
@@ -280,6 +415,7 @@ module Aidp
|
|
|
280
415
|
when "settings" then run_settings_command(args)
|
|
281
416
|
when "models" then run_models_command(args)
|
|
282
417
|
when "tools" then run_tools_command(args)
|
|
418
|
+
when "security" then run_security_command(args)
|
|
283
419
|
else
|
|
284
420
|
display_message("Unknown command: #{cmd}", type: :info)
|
|
285
421
|
return 1
|
|
@@ -422,6 +558,13 @@ module Aidp
|
|
|
422
558
|
command.run(args)
|
|
423
559
|
end
|
|
424
560
|
|
|
561
|
+
def run_eval_command(args)
|
|
562
|
+
# Delegate to EvalCommand
|
|
563
|
+
require_relative "cli/eval_command"
|
|
564
|
+
command = EvalCommand.new(prompt: create_prompt)
|
|
565
|
+
command.run(args)
|
|
566
|
+
end
|
|
567
|
+
|
|
425
568
|
def format_time_ago_simple(seconds)
|
|
426
569
|
if seconds < 60
|
|
427
570
|
"#{seconds.to_i}s ago"
|
|
@@ -632,6 +775,12 @@ module Aidp
|
|
|
632
775
|
tools_cmd.run(args)
|
|
633
776
|
end
|
|
634
777
|
|
|
778
|
+
def run_security_command(args)
|
|
779
|
+
require_relative "cli/security_command"
|
|
780
|
+
security_cmd = Aidp::CLI::SecurityCommand.new(project_dir: Dir.pwd, prompt: create_prompt)
|
|
781
|
+
security_cmd.run(args)
|
|
782
|
+
end
|
|
783
|
+
|
|
635
784
|
def run_issue_command(args)
|
|
636
785
|
require_relative "cli/issue_importer"
|
|
637
786
|
|
|
@@ -669,7 +818,7 @@ module Aidp
|
|
|
669
818
|
return
|
|
670
819
|
end
|
|
671
820
|
|
|
672
|
-
importer = IssueImporter.new
|
|
821
|
+
importer = Aidp::IssueImporter.new
|
|
673
822
|
issue_data = importer.import_issue(identifier)
|
|
674
823
|
|
|
675
824
|
if issue_data
|
|
@@ -825,7 +974,7 @@ module Aidp
|
|
|
825
974
|
|
|
826
975
|
def run_watch_command(args)
|
|
827
976
|
if args.empty?
|
|
828
|
-
display_message("Usage: aidp watch <issues_url> [--interval SECONDS] [--provider NAME] [--once] [--no-workstreams] [--force] [--verbose]", type: :info)
|
|
977
|
+
display_message("Usage: aidp watch <issues_url> [--interval SECONDS] [--provider NAME] [--once] [--no-workstreams] [--force] [--verbose] [--quiet]", type: :info)
|
|
829
978
|
return
|
|
830
979
|
end
|
|
831
980
|
|
|
@@ -836,6 +985,8 @@ module Aidp
|
|
|
836
985
|
use_workstreams = true # Default to using workstreams
|
|
837
986
|
force = false
|
|
838
987
|
verbose = false
|
|
988
|
+
quiet = false
|
|
989
|
+
launch_test = false
|
|
839
990
|
|
|
840
991
|
until args.empty?
|
|
841
992
|
token = args.shift
|
|
@@ -853,11 +1004,27 @@ module Aidp
|
|
|
853
1004
|
force = true
|
|
854
1005
|
when "--verbose"
|
|
855
1006
|
verbose = true
|
|
1007
|
+
when "--quiet"
|
|
1008
|
+
quiet = true
|
|
1009
|
+
when "--launch-test"
|
|
1010
|
+
launch_test = true
|
|
856
1011
|
else
|
|
857
1012
|
display_message("⚠️ Unknown watch option: #{token}", type: :warn)
|
|
858
1013
|
end
|
|
859
1014
|
end
|
|
860
1015
|
|
|
1016
|
+
# Validate incompatible options
|
|
1017
|
+
if quiet && verbose
|
|
1018
|
+
display_message("❌ --quiet and --verbose are mutually exclusive", type: :error)
|
|
1019
|
+
return 1
|
|
1020
|
+
end
|
|
1021
|
+
|
|
1022
|
+
# Undocumented: Launch test mode for CI/CD validation
|
|
1023
|
+
# Exits after validating watch mode initialization
|
|
1024
|
+
if launch_test
|
|
1025
|
+
return run_launch_test(:watch)
|
|
1026
|
+
end
|
|
1027
|
+
|
|
861
1028
|
# Initialize logger for watch mode
|
|
862
1029
|
setup_logging(Dir.pwd)
|
|
863
1030
|
|
|
@@ -876,7 +1043,8 @@ module Aidp
|
|
|
876
1043
|
prompt: create_prompt,
|
|
877
1044
|
safety_config: watch_config,
|
|
878
1045
|
force: force,
|
|
879
|
-
verbose: verbose
|
|
1046
|
+
verbose: verbose,
|
|
1047
|
+
quiet: quiet
|
|
880
1048
|
)
|
|
881
1049
|
runner.start
|
|
882
1050
|
rescue ArgumentError => e
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aidp
|
|
4
|
+
# Consolidates comments for GitHub issues and PRs by category
|
|
5
|
+
class CommentConsolidator
|
|
6
|
+
CATEGORY_HEADERS = {
|
|
7
|
+
progress: "## 🔄 Progress Report",
|
|
8
|
+
exceptions: "## 🚨 Exceptions and Errors",
|
|
9
|
+
completion: "## ✅ Completion Summary"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
# @param repository_client [Aidp::Watch::RepositoryClient] GitHub repository client
|
|
13
|
+
# @param number [Integer] Issue or PR number
|
|
14
|
+
def initialize(repository_client:, number:)
|
|
15
|
+
@client = repository_client
|
|
16
|
+
@number = number
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Search for an existing comment by its category header
|
|
20
|
+
# @param category [Symbol] Comment category (:progress, :exceptions, :completion)
|
|
21
|
+
# @return [Hash, nil] Existing comment or nil if not found
|
|
22
|
+
def find_category_comment(category)
|
|
23
|
+
Aidp.log_debug("comment_consolidator", "searching_category_comment",
|
|
24
|
+
number: @number, category: category)
|
|
25
|
+
|
|
26
|
+
header = CATEGORY_HEADERS[category]
|
|
27
|
+
raise ArgumentError, "Invalid category: #{category}" unless header
|
|
28
|
+
|
|
29
|
+
comment = @client.find_comment(@number, header)
|
|
30
|
+
Aidp.log_debug("comment_consolidator", "find_category_comment_result",
|
|
31
|
+
found: !comment.nil?)
|
|
32
|
+
comment
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Update an existing category comment or create a new one
|
|
36
|
+
# @param category [Symbol] Comment category (:progress, :exceptions, :completion)
|
|
37
|
+
# @param new_content [String] New content to add to the comment
|
|
38
|
+
# @param append [Boolean] Whether to append or replace existing content
|
|
39
|
+
# @return [String] Result of comment operation (comment ID or response body)
|
|
40
|
+
def consolidate_comment(category:, new_content:, append: true)
|
|
41
|
+
Aidp.log_debug("comment_consolidator", "consolidating_comment", number: @number, category: category, append: append)
|
|
42
|
+
|
|
43
|
+
header = CATEGORY_HEADERS[category]
|
|
44
|
+
raise ArgumentError, "Invalid category: #{category}" unless header
|
|
45
|
+
|
|
46
|
+
existing_comment = find_category_comment(category)
|
|
47
|
+
|
|
48
|
+
content = if existing_comment && append
|
|
49
|
+
# Append new content with timestamp
|
|
50
|
+
existing_body = existing_comment[:body]
|
|
51
|
+
updated_body = if existing_body.include?(header)
|
|
52
|
+
existing_body.lines.first(1).join +
|
|
53
|
+
"### #{Time.now.strftime("%Y-%m-%d %H:%M:%S")}\n\n" +
|
|
54
|
+
new_content + "\n\n" +
|
|
55
|
+
existing_body.lines[1..]&.join
|
|
56
|
+
else
|
|
57
|
+
# Reconstruct comment if header is missing
|
|
58
|
+
"#{header}\n\n### #{Time.now.strftime("%Y-%m-%d %H:%M:%S")}\n\n" +
|
|
59
|
+
new_content + "\n\n" +
|
|
60
|
+
existing_body
|
|
61
|
+
end
|
|
62
|
+
updated_body
|
|
63
|
+
else
|
|
64
|
+
# Create new or replace content
|
|
65
|
+
"#{header}\n\n### #{Time.now.strftime("%Y-%m-%d %H:%M:%S")}\n\n" + new_content
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Update or create comment
|
|
69
|
+
if existing_comment
|
|
70
|
+
Aidp.log_debug("comment_consolidator", "updating_existing_comment", comment_id: existing_comment[:id])
|
|
71
|
+
@client.update_comment(existing_comment[:id], content)
|
|
72
|
+
else
|
|
73
|
+
Aidp.log_debug("comment_consolidator", "creating_new_comment")
|
|
74
|
+
@client.post_comment(@number, content)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -21,6 +21,9 @@ module Aidp
|
|
|
21
21
|
# Exec.shutdown_all
|
|
22
22
|
module Exec
|
|
23
23
|
class << self
|
|
24
|
+
# Expose for testability - reset pool cache between tests
|
|
25
|
+
attr_writer :pools, :default_pool
|
|
26
|
+
|
|
24
27
|
# Get or create a named thread pool.
|
|
25
28
|
#
|
|
26
29
|
# Pools are cached by name. Calling this method multiple times with the
|
data/lib/aidp/concurrency.rb
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "concurrent-ruby"
|
|
4
|
-
require_relative "concurrency/wait"
|
|
5
|
-
require_relative "concurrency/backoff"
|
|
6
|
-
require_relative "concurrency/exec"
|
|
7
4
|
|
|
8
5
|
module Aidp
|
|
9
6
|
# Concurrency utilities for deterministic waiting, retry/backoff, and executor management.
|
data/lib/aidp/config.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "yaml"
|
|
4
|
-
require_relative "config/paths"
|
|
5
4
|
|
|
6
5
|
module Aidp
|
|
7
6
|
# Configuration management for both execute and analyze modes
|
|
@@ -171,6 +170,64 @@ module Aidp
|
|
|
171
170
|
method: "zfc_automatic",
|
|
172
171
|
allow_parallel: true
|
|
173
172
|
}
|
|
173
|
+
},
|
|
174
|
+
evaluations: {
|
|
175
|
+
enabled: true,
|
|
176
|
+
prompt_after_work_loop: false,
|
|
177
|
+
capture_full_context: true,
|
|
178
|
+
directory: ".aidp/evaluations"
|
|
179
|
+
},
|
|
180
|
+
security: {
|
|
181
|
+
rule_of_two: {
|
|
182
|
+
enabled: true,
|
|
183
|
+
policy: "strict" # strict or relaxed
|
|
184
|
+
},
|
|
185
|
+
secrets_proxy: {
|
|
186
|
+
enabled: true,
|
|
187
|
+
token_ttl: 300 # seconds
|
|
188
|
+
},
|
|
189
|
+
watch_mode: {
|
|
190
|
+
max_retry_attempts: 3,
|
|
191
|
+
fail_forward_enabled: true,
|
|
192
|
+
needs_input_label: "aidp-needs-input"
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
watch: {
|
|
196
|
+
enabled: false,
|
|
197
|
+
polling_interval: 30,
|
|
198
|
+
labels: {
|
|
199
|
+
plan_trigger: "aidp-plan",
|
|
200
|
+
build_trigger: "aidp-build",
|
|
201
|
+
review_trigger: "aidp-review",
|
|
202
|
+
fix_ci_trigger: "aidp-fix-ci",
|
|
203
|
+
change_request_trigger: "aidp-request-changes",
|
|
204
|
+
auto_trigger: "aidp-auto",
|
|
205
|
+
parent_pr: "aidp-parent-pr",
|
|
206
|
+
sub_pr: "aidp-sub-pr"
|
|
207
|
+
},
|
|
208
|
+
projects: {
|
|
209
|
+
enabled: false,
|
|
210
|
+
default_project_id: nil,
|
|
211
|
+
field_mappings: {
|
|
212
|
+
status: "Status",
|
|
213
|
+
priority: "Priority",
|
|
214
|
+
skills: "Skills",
|
|
215
|
+
personas: "Personas",
|
|
216
|
+
blocking: "Blocking"
|
|
217
|
+
},
|
|
218
|
+
auto_create_fields: true,
|
|
219
|
+
sync_interval: 60,
|
|
220
|
+
default_status_values: ["Backlog", "Todo", "In Progress", "In Review", "Done"],
|
|
221
|
+
default_priority_values: ["Low", "Medium", "High", "Critical"]
|
|
222
|
+
},
|
|
223
|
+
auto_merge: {
|
|
224
|
+
enabled: true,
|
|
225
|
+
sub_issue_prs_only: true,
|
|
226
|
+
require_ci_success: true,
|
|
227
|
+
require_reviews: 0,
|
|
228
|
+
merge_method: "squash",
|
|
229
|
+
delete_branch: true
|
|
230
|
+
}
|
|
174
231
|
}
|
|
175
232
|
}.freeze
|
|
176
233
|
|
|
@@ -288,6 +345,38 @@ module Aidp
|
|
|
288
345
|
symbolize_keys(tool_metadata_section)
|
|
289
346
|
end
|
|
290
347
|
|
|
348
|
+
# Get evaluations configuration
|
|
349
|
+
def self.evaluations_config(project_dir = Dir.pwd)
|
|
350
|
+
config = load_harness_config(project_dir)
|
|
351
|
+
evaluations_section = config[:evaluations] || config["evaluations"] || {}
|
|
352
|
+
|
|
353
|
+
# Convert string keys to symbols for consistency
|
|
354
|
+
symbolize_keys(evaluations_section)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# Get security configuration
|
|
358
|
+
def self.security_config(project_dir = Dir.pwd)
|
|
359
|
+
config = load_harness_config(project_dir)
|
|
360
|
+
security_section = config[:security] || config["security"] || {}
|
|
361
|
+
|
|
362
|
+
# Convert string keys to symbols for consistency
|
|
363
|
+
symbolize_keys(security_section)
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
# Check if Rule of Two enforcement is enabled
|
|
367
|
+
def self.rule_of_two_enabled?(project_dir = Dir.pwd)
|
|
368
|
+
sec_config = security_config(project_dir)
|
|
369
|
+
rule_of_two = sec_config[:rule_of_two] || {}
|
|
370
|
+
rule_of_two.fetch(:enabled, true)
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# Check if Secrets Proxy is enabled
|
|
374
|
+
def self.secrets_proxy_enabled?(project_dir = Dir.pwd)
|
|
375
|
+
sec_config = security_config(project_dir)
|
|
376
|
+
proxy_config = sec_config[:secrets_proxy] || {}
|
|
377
|
+
proxy_config.fetch(:enabled, true)
|
|
378
|
+
end
|
|
379
|
+
|
|
291
380
|
# Check if configuration file exists
|
|
292
381
|
def self.config_exists?(project_dir = Dir.pwd)
|
|
293
382
|
ConfigPaths.config_exists?(project_dir)
|
|
@@ -394,9 +483,32 @@ module Aidp
|
|
|
394
483
|
merged[:waterfall] = merged[:waterfall].merge(symbolize_keys(waterfall_section))
|
|
395
484
|
end
|
|
396
485
|
|
|
486
|
+
# Deep merge evaluations config
|
|
487
|
+
if config[:evaluations] || config["evaluations"]
|
|
488
|
+
evaluations_section = config[:evaluations] || config["evaluations"]
|
|
489
|
+
merged[:evaluations] = merged[:evaluations].merge(symbolize_keys(evaluations_section))
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# Deep merge security config
|
|
493
|
+
if config[:security] || config["security"]
|
|
494
|
+
security_section = config[:security] || config["security"]
|
|
495
|
+
merged[:security] = deep_merge_hash(merged[:security], symbolize_keys(security_section))
|
|
496
|
+
end
|
|
497
|
+
|
|
397
498
|
merged
|
|
398
499
|
end
|
|
399
500
|
|
|
501
|
+
# Deep merge for nested hashes (preserves nested defaults)
|
|
502
|
+
private_class_method def self.deep_merge_hash(base, override)
|
|
503
|
+
base.merge(override) do |_key, base_val, override_val|
|
|
504
|
+
if base_val.is_a?(Hash) && override_val.is_a?(Hash)
|
|
505
|
+
deep_merge_hash(base_val, override_val)
|
|
506
|
+
else
|
|
507
|
+
override_val
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
|
|
400
512
|
private_class_method def self.symbolize_keys(hash)
|
|
401
513
|
return hash unless hash.is_a?(Hash)
|
|
402
514
|
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
# Centralized path management for all AIDP internal files
|
|
7
|
+
# Ensures consistent file locations and prevents path-related bugs
|
|
8
|
+
module ConfigPaths
|
|
9
|
+
def self.aidp_dir(project_dir = Dir.pwd) = File.join(project_dir, ".aidp")
|
|
10
|
+
def self.config_file(project_dir = Dir.pwd) = File.join(aidp_dir(project_dir), "aidp.yml")
|
|
11
|
+
def self.config_dir(project_dir = Dir.pwd) = aidp_dir(project_dir)
|
|
12
|
+
def self.progress_dir(project_dir = Dir.pwd) = File.join(aidp_dir(project_dir), "progress")
|
|
13
|
+
def self.execute_progress_file(project_dir = Dir.pwd) = File.join(progress_dir(project_dir), "execute.yml")
|
|
14
|
+
def self.analyze_progress_file(project_dir = Dir.pwd) = File.join(progress_dir(project_dir), "analyze.yml")
|
|
15
|
+
def self.harness_state_dir(project_dir = Dir.pwd) = File.join(aidp_dir(project_dir), "harness")
|
|
16
|
+
def self.harness_state_file(mode, project_dir = Dir.pwd) = File.join(harness_state_dir(project_dir), "#{mode}_state.json")
|
|
17
|
+
def self.providers_dir(project_dir = Dir.pwd) = File.join(aidp_dir(project_dir), "providers")
|
|
18
|
+
def self.provider_info_file(provider_name, project_dir = Dir.pwd) = File.join(providers_dir(project_dir), "#{provider_name}_info.yml")
|
|
19
|
+
def self.jobs_dir(project_dir = Dir.pwd) = File.join(aidp_dir(project_dir), "jobs")
|
|
20
|
+
def self.checkpoint_file(project_dir = Dir.pwd) = File.join(aidp_dir(project_dir), "checkpoint.yml")
|
|
21
|
+
def self.checkpoint_history_file(project_dir = Dir.pwd) = File.join(aidp_dir(project_dir), "checkpoint_history.jsonl")
|
|
22
|
+
def self.json_storage_dir(project_dir = Dir.pwd) = File.join(aidp_dir(project_dir), "json")
|
|
23
|
+
def self.model_cache_dir(project_dir = Dir.pwd) = File.join(aidp_dir(project_dir), "model_cache")
|
|
24
|
+
def self.work_loop_dir(project_dir = Dir.pwd) = File.join(aidp_dir(project_dir), "work_loop")
|
|
25
|
+
def self.logs_dir(project_dir = Dir.pwd) = File.join(aidp_dir(project_dir), "logs")
|
|
26
|
+
def self.evaluations_dir(project_dir = Dir.pwd) = File.join(aidp_dir(project_dir), "evaluations")
|
|
27
|
+
def self.evaluations_index_file(project_dir = Dir.pwd) = File.join(evaluations_dir(project_dir), "index.json")
|
|
28
|
+
|
|
29
|
+
# Security module paths
|
|
30
|
+
def self.security_dir(project_dir = Dir.pwd) = File.join(aidp_dir(project_dir), "security")
|
|
31
|
+
def self.secrets_registry_file(project_dir = Dir.pwd) = File.join(security_dir(project_dir), "secrets_registry.json")
|
|
32
|
+
def self.security_audit_log_file(project_dir = Dir.pwd) = File.join(security_dir(project_dir), "audit.jsonl")
|
|
33
|
+
def self.mcp_risk_profile_file(project_dir = Dir.pwd) = File.join(security_dir(project_dir), "mcp_risk_profile.yml")
|
|
34
|
+
|
|
35
|
+
def self.config_exists?(project_dir = Dir.pwd)
|
|
36
|
+
File.exist?(config_file(project_dir))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.ensure_aidp_dir(project_dir = Dir.pwd)
|
|
40
|
+
dir = aidp_dir(project_dir)
|
|
41
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
42
|
+
dir
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.ensure_config_dir(project_dir = Dir.pwd)
|
|
46
|
+
ensure_aidp_dir(project_dir)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.ensure_progress_dir(project_dir = Dir.pwd)
|
|
50
|
+
dir = progress_dir(project_dir)
|
|
51
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
52
|
+
dir
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.ensure_harness_state_dir(project_dir = Dir.pwd)
|
|
56
|
+
dir = harness_state_dir(project_dir)
|
|
57
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
58
|
+
dir
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.ensure_providers_dir(project_dir = Dir.pwd)
|
|
62
|
+
dir = providers_dir(project_dir)
|
|
63
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
64
|
+
dir
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def self.ensure_jobs_dir(project_dir = Dir.pwd)
|
|
68
|
+
dir = jobs_dir(project_dir)
|
|
69
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
70
|
+
dir
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def self.ensure_json_storage_dir(project_dir = Dir.pwd)
|
|
74
|
+
dir = json_storage_dir(project_dir)
|
|
75
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
76
|
+
dir
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def self.ensure_evaluations_dir(project_dir = Dir.pwd)
|
|
80
|
+
dir = evaluations_dir(project_dir)
|
|
81
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
82
|
+
dir
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def self.ensure_security_dir(project_dir = Dir.pwd)
|
|
86
|
+
dir = security_dir(project_dir)
|
|
87
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
88
|
+
dir
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
data/lib/aidp/daemon/runner.rb
CHANGED
|
@@ -10,15 +10,19 @@ module Aidp
|
|
|
10
10
|
# Main daemon runner for background mode execution
|
|
11
11
|
# Manages work loops, watch mode, and IPC communication
|
|
12
12
|
class Runner
|
|
13
|
-
|
|
13
|
+
# Allow reading/writing @running for testability
|
|
14
|
+
attr_accessor :running
|
|
15
|
+
|
|
16
|
+
def initialize(project_dir, config, options = {}, process_manager: nil,
|
|
17
|
+
work_loop_runner: nil, watch_runner: nil, ipc_server: nil)
|
|
14
18
|
@project_dir = project_dir
|
|
15
19
|
@config = config
|
|
16
20
|
@options = options
|
|
17
21
|
@process_manager = process_manager || ProcessManager.new(project_dir)
|
|
18
22
|
@running = false
|
|
19
|
-
@work_loop_runner =
|
|
20
|
-
@watch_runner =
|
|
21
|
-
@ipc_server =
|
|
23
|
+
@work_loop_runner = work_loop_runner
|
|
24
|
+
@watch_runner = watch_runner
|
|
25
|
+
@ipc_server = ipc_server
|
|
22
26
|
end
|
|
23
27
|
|
|
24
28
|
# Start daemon in background
|