aidp 0.33.0 → 0.34.1
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/tree_sitter_scan.rb +3 -0
- 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 +170 -3
- data/lib/aidp/concurrency/exec.rb +3 -0
- data/lib/aidp/config.rb +113 -0
- data/lib/aidp/config_paths.rb +20 -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 +399 -47
- 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/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/enhanced_runner.rb +14 -11
- data/lib/aidp/harness/error_handler.rb +3 -0
- data/lib/aidp/harness/provider_factory.rb +3 -0
- data/lib/aidp/harness/provider_manager.rb +6 -0
- data/lib/aidp/harness/runner.rb +5 -1
- 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/thinking_depth_manager.rb +32 -32
- 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 -0
- 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/ui.rb +11 -0
- data/lib/aidp/harness/user_interface.rb +3 -0
- data/lib/aidp/loader.rb +2 -2
- data/lib/aidp/logger.rb +3 -0
- data/lib/aidp/message_display.rb +31 -0
- data/lib/aidp/pr_worktree_manager.rb +18 -6
- data/lib/aidp/provider_manager.rb +3 -0
- 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 +4 -2
- 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 +680 -286
- 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 +861 -53
- data/lib/aidp/watch/review_processor.rb +33 -6
- data/lib/aidp/watch/runner.rb +51 -11
- data/lib/aidp/watch/state_store.rb +233 -0
- data/lib/aidp/watch/sub_issue_creator.rb +221 -0
- data/lib/aidp/workflows/guided_agent.rb +4 -0
- data/lib/aidp/workstream_executor.rb +3 -0
- data/lib/aidp/worktree.rb +61 -11
- data/lib/aidp/worktree_branch_manager.rb +347 -101
- data/templates/implementation/iterative_implementation.md +46 -3
- metadata +21 -1
data/lib/aidp/cli.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
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"
|
|
@@ -46,6 +47,18 @@ module Aidp
|
|
|
46
47
|
options = parse_options(args)
|
|
47
48
|
self.last_options = options
|
|
48
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
|
+
|
|
49
62
|
if options[:help]
|
|
50
63
|
display_message(options[:parser].to_s, type: :info)
|
|
51
64
|
return 0
|
|
@@ -56,6 +69,12 @@ module Aidp
|
|
|
56
69
|
return 0
|
|
57
70
|
end
|
|
58
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
|
+
|
|
59
78
|
# Initialize logger from aidp.yml config
|
|
60
79
|
# Priority: ENV variable > aidp.yml > default (info)
|
|
61
80
|
setup_logging(Dir.pwd)
|
|
@@ -147,6 +166,117 @@ module Aidp
|
|
|
147
166
|
Aidp.logger.warn("cli", "Failed to load logging config, using defaults", error: e.message)
|
|
148
167
|
end
|
|
149
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
|
+
|
|
150
280
|
def parse_options(args)
|
|
151
281
|
options = {}
|
|
152
282
|
|
|
@@ -217,6 +347,9 @@ module Aidp
|
|
|
217
347
|
opts.on("-v", "--version", "Show version information") { options[:version] = true }
|
|
218
348
|
opts.on("--setup-config", "Setup or reconfigure config file") { options[:setup_config] = true }
|
|
219
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 }
|
|
220
353
|
|
|
221
354
|
opts.separator ""
|
|
222
355
|
opts.separator "Examples:"
|
|
@@ -257,7 +390,7 @@ module Aidp
|
|
|
257
390
|
# Determine if the invocation is a subcommand style call
|
|
258
391
|
def subcommand?(args)
|
|
259
392
|
return false if args.nil? || args.empty?
|
|
260
|
-
%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)
|
|
261
394
|
end
|
|
262
395
|
|
|
263
396
|
def run_subcommand(args)
|
|
@@ -269,6 +402,7 @@ module Aidp
|
|
|
269
402
|
when "harness" then run_harness_command(args)
|
|
270
403
|
when "providers" then run_providers_command(args)
|
|
271
404
|
when "checkpoint" then run_checkpoint_command(args)
|
|
405
|
+
when "eval" then run_eval_command(args)
|
|
272
406
|
when "mcp" then run_mcp_command(args)
|
|
273
407
|
when "issue" then run_issue_command(args)
|
|
274
408
|
when "config" then run_config_command(args)
|
|
@@ -281,6 +415,7 @@ module Aidp
|
|
|
281
415
|
when "settings" then run_settings_command(args)
|
|
282
416
|
when "models" then run_models_command(args)
|
|
283
417
|
when "tools" then run_tools_command(args)
|
|
418
|
+
when "security" then run_security_command(args)
|
|
284
419
|
else
|
|
285
420
|
display_message("Unknown command: #{cmd}", type: :info)
|
|
286
421
|
return 1
|
|
@@ -423,6 +558,13 @@ module Aidp
|
|
|
423
558
|
command.run(args)
|
|
424
559
|
end
|
|
425
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
|
+
|
|
426
568
|
def format_time_ago_simple(seconds)
|
|
427
569
|
if seconds < 60
|
|
428
570
|
"#{seconds.to_i}s ago"
|
|
@@ -633,6 +775,12 @@ module Aidp
|
|
|
633
775
|
tools_cmd.run(args)
|
|
634
776
|
end
|
|
635
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
|
+
|
|
636
784
|
def run_issue_command(args)
|
|
637
785
|
require_relative "cli/issue_importer"
|
|
638
786
|
|
|
@@ -826,7 +974,7 @@ module Aidp
|
|
|
826
974
|
|
|
827
975
|
def run_watch_command(args)
|
|
828
976
|
if args.empty?
|
|
829
|
-
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)
|
|
830
978
|
return
|
|
831
979
|
end
|
|
832
980
|
|
|
@@ -837,6 +985,8 @@ module Aidp
|
|
|
837
985
|
use_workstreams = true # Default to using workstreams
|
|
838
986
|
force = false
|
|
839
987
|
verbose = false
|
|
988
|
+
quiet = false
|
|
989
|
+
launch_test = false
|
|
840
990
|
|
|
841
991
|
until args.empty?
|
|
842
992
|
token = args.shift
|
|
@@ -854,11 +1004,27 @@ module Aidp
|
|
|
854
1004
|
force = true
|
|
855
1005
|
when "--verbose"
|
|
856
1006
|
verbose = true
|
|
1007
|
+
when "--quiet"
|
|
1008
|
+
quiet = true
|
|
1009
|
+
when "--launch-test"
|
|
1010
|
+
launch_test = true
|
|
857
1011
|
else
|
|
858
1012
|
display_message("⚠️ Unknown watch option: #{token}", type: :warn)
|
|
859
1013
|
end
|
|
860
1014
|
end
|
|
861
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
|
+
|
|
862
1028
|
# Initialize logger for watch mode
|
|
863
1029
|
setup_logging(Dir.pwd)
|
|
864
1030
|
|
|
@@ -877,7 +1043,8 @@ module Aidp
|
|
|
877
1043
|
prompt: create_prompt,
|
|
878
1044
|
safety_config: watch_config,
|
|
879
1045
|
force: force,
|
|
880
|
-
verbose: verbose
|
|
1046
|
+
verbose: verbose,
|
|
1047
|
+
quiet: quiet
|
|
881
1048
|
)
|
|
882
1049
|
runner.start
|
|
883
1050
|
rescue ArgumentError => e
|
|
@@ -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/config.rb
CHANGED
|
@@ -170,6 +170,64 @@ module Aidp
|
|
|
170
170
|
method: "zfc_automatic",
|
|
171
171
|
allow_parallel: true
|
|
172
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
|
+
}
|
|
173
231
|
}
|
|
174
232
|
}.freeze
|
|
175
233
|
|
|
@@ -287,6 +345,38 @@ module Aidp
|
|
|
287
345
|
symbolize_keys(tool_metadata_section)
|
|
288
346
|
end
|
|
289
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
|
+
|
|
290
380
|
# Check if configuration file exists
|
|
291
381
|
def self.config_exists?(project_dir = Dir.pwd)
|
|
292
382
|
ConfigPaths.config_exists?(project_dir)
|
|
@@ -393,9 +483,32 @@ module Aidp
|
|
|
393
483
|
merged[:waterfall] = merged[:waterfall].merge(symbolize_keys(waterfall_section))
|
|
394
484
|
end
|
|
395
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
|
+
|
|
396
498
|
merged
|
|
397
499
|
end
|
|
398
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
|
+
|
|
399
512
|
private_class_method def self.symbolize_keys(hash)
|
|
400
513
|
return hash unless hash.is_a?(Hash)
|
|
401
514
|
|
data/lib/aidp/config_paths.rb
CHANGED
|
@@ -23,6 +23,14 @@ module Aidp
|
|
|
23
23
|
def self.model_cache_dir(project_dir = Dir.pwd) = File.join(aidp_dir(project_dir), "model_cache")
|
|
24
24
|
def self.work_loop_dir(project_dir = Dir.pwd) = File.join(aidp_dir(project_dir), "work_loop")
|
|
25
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")
|
|
26
34
|
|
|
27
35
|
def self.config_exists?(project_dir = Dir.pwd)
|
|
28
36
|
File.exist?(config_file(project_dir))
|
|
@@ -67,5 +75,17 @@ module Aidp
|
|
|
67
75
|
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
68
76
|
dir
|
|
69
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
|
|
70
90
|
end
|
|
71
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
|
data/lib/aidp/errors.rb
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "time"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
3
6
|
module Aidp
|
|
4
7
|
# Error classes for AIDP
|
|
5
8
|
module Errors
|
|
@@ -8,5 +11,136 @@ module Aidp
|
|
|
8
11
|
class ValidationError < StandardError; end
|
|
9
12
|
class StateError < StandardError; end
|
|
10
13
|
class UserError < StandardError; end
|
|
14
|
+
|
|
15
|
+
# Exception raised when a Rule of Two policy violation occurs
|
|
16
|
+
# Contains detailed context about the violation for logging and debugging
|
|
17
|
+
class PolicyViolation < StandardError
|
|
18
|
+
attr_reader :flag, :source, :current_state, :suggested_mitigations
|
|
19
|
+
|
|
20
|
+
def initialize(flag:, source:, current_state:, message: nil)
|
|
21
|
+
@flag = flag
|
|
22
|
+
@source = source
|
|
23
|
+
@current_state = current_state
|
|
24
|
+
@suggested_mitigations = build_suggested_mitigations(flag, current_state)
|
|
25
|
+
|
|
26
|
+
super(message || default_message)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Export violation details for logging
|
|
30
|
+
def to_h
|
|
31
|
+
{
|
|
32
|
+
type: "rule_of_two_violation",
|
|
33
|
+
flag_attempted: @flag,
|
|
34
|
+
source: @source,
|
|
35
|
+
current_state: @current_state,
|
|
36
|
+
suggested_mitigations: @suggested_mitigations,
|
|
37
|
+
timestamp: Time.now.iso8601
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# JSON representation for structured logging
|
|
42
|
+
def to_json(*args)
|
|
43
|
+
to_h.to_json(*args)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def default_message
|
|
49
|
+
"Rule of Two violation: Cannot enable '#{@flag}' - would create lethal trifecta"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Build contextual mitigation suggestions based on which flags are enabled
|
|
53
|
+
def build_suggested_mitigations(flag, state)
|
|
54
|
+
mitigations = []
|
|
55
|
+
|
|
56
|
+
# Suggest based on which flags would form the trifecta
|
|
57
|
+
if state[:untrusted_input]
|
|
58
|
+
mitigations << {
|
|
59
|
+
action: "sanitize_input",
|
|
60
|
+
description: "Sanitize the untrusted input before processing to remove the untrusted_input flag",
|
|
61
|
+
command: "aidp security sanitize-input --source <source>"
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if state[:private_data]
|
|
66
|
+
mitigations << {
|
|
67
|
+
action: "use_secrets_proxy",
|
|
68
|
+
description: "Route credential access through the Secrets Proxy to get short-lived tokens",
|
|
69
|
+
command: "aidp security proxy-request --secret <name>"
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
if state[:egress]
|
|
74
|
+
mitigations << {
|
|
75
|
+
action: "disable_egress",
|
|
76
|
+
description: "Disable external communication for this operation",
|
|
77
|
+
command: "Use deterministic unit or sandbox the operation"
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Add flag-specific suggestion for the attempted flag
|
|
82
|
+
case flag
|
|
83
|
+
when :untrusted_input
|
|
84
|
+
mitigations << {
|
|
85
|
+
action: "validate_source",
|
|
86
|
+
description: "Validate and trust the input source (e.g., from trusted author allowlist)",
|
|
87
|
+
command: "Add author to watch.safety.author_allowlist in aidp.yml"
|
|
88
|
+
}
|
|
89
|
+
when :private_data
|
|
90
|
+
mitigations << {
|
|
91
|
+
action: "scope_credentials",
|
|
92
|
+
description: "Use capability-scoped tokens instead of full credentials",
|
|
93
|
+
command: "aidp security register-secret --scoped"
|
|
94
|
+
}
|
|
95
|
+
when :egress
|
|
96
|
+
mitigations << {
|
|
97
|
+
action: "queue_for_approval",
|
|
98
|
+
description: "Queue the operation for manual execution outside the agent context",
|
|
99
|
+
command: "Operation will be logged for manual review"
|
|
100
|
+
}
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
mitigations.uniq { |m| m[:action] }
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Exception raised when secrets proxy cannot fulfill a request
|
|
108
|
+
class SecretsProxyError < StandardError
|
|
109
|
+
attr_reader :secret_name, :reason
|
|
110
|
+
|
|
111
|
+
def initialize(secret_name:, reason:)
|
|
112
|
+
@secret_name = secret_name
|
|
113
|
+
@reason = reason
|
|
114
|
+
super("Secrets proxy error for '#{secret_name}': #{reason}")
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Exception raised when attempting to access an unregistered secret
|
|
119
|
+
class UnregisteredSecretError < SecretsProxyError
|
|
120
|
+
def initialize(secret_name:)
|
|
121
|
+
super(
|
|
122
|
+
secret_name: secret_name,
|
|
123
|
+
reason: "Secret not registered. Use 'aidp security register-secret #{secret_name}' to register."
|
|
124
|
+
)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Exception raised when a token has expired
|
|
129
|
+
class TokenExpiredError < SecretsProxyError
|
|
130
|
+
def initialize(secret_name:, expired_at:)
|
|
131
|
+
super(
|
|
132
|
+
secret_name: secret_name,
|
|
133
|
+
reason: "Token expired at #{expired_at}. Request a new token via the secrets proxy."
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Convenience aliases in Security module for backward compatibility
|
|
140
|
+
module Security
|
|
141
|
+
PolicyViolation = Aidp::Errors::PolicyViolation
|
|
142
|
+
SecretsProxyError = Aidp::Errors::SecretsProxyError
|
|
143
|
+
UnregisteredSecretError = Aidp::Errors::UnregisteredSecretError
|
|
144
|
+
TokenExpiredError = Aidp::Errors::TokenExpiredError
|
|
11
145
|
end
|
|
12
146
|
end
|