aidp 0.24.0 → 0.26.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 +72 -7
- data/lib/aidp/analyze/error_handler.rb +11 -0
- data/lib/aidp/auto_update/bundler_adapter.rb +66 -0
- data/lib/aidp/auto_update/checkpoint.rb +178 -0
- data/lib/aidp/auto_update/checkpoint_store.rb +182 -0
- data/lib/aidp/auto_update/coordinator.rb +204 -0
- data/lib/aidp/auto_update/errors.rb +17 -0
- data/lib/aidp/auto_update/failure_tracker.rb +162 -0
- data/lib/aidp/auto_update/rubygems_api_adapter.rb +95 -0
- data/lib/aidp/auto_update/update_check.rb +106 -0
- data/lib/aidp/auto_update/update_logger.rb +143 -0
- data/lib/aidp/auto_update/update_policy.rb +109 -0
- data/lib/aidp/auto_update/version_detector.rb +144 -0
- data/lib/aidp/auto_update.rb +52 -0
- data/lib/aidp/cli.rb +165 -1
- data/lib/aidp/execute/work_loop_runner.rb +225 -55
- data/lib/aidp/harness/config_loader.rb +20 -11
- data/lib/aidp/harness/config_schema.rb +80 -8
- data/lib/aidp/harness/configuration.rb +73 -2
- data/lib/aidp/harness/filter_strategy.rb +45 -0
- data/lib/aidp/harness/generic_filter_strategy.rb +63 -0
- data/lib/aidp/harness/output_filter.rb +136 -0
- data/lib/aidp/harness/provider_factory.rb +2 -0
- data/lib/aidp/harness/provider_manager.rb +18 -3
- data/lib/aidp/harness/rspec_filter_strategy.rb +82 -0
- data/lib/aidp/harness/test_runner.rb +165 -27
- data/lib/aidp/harness/ui/enhanced_tui.rb +4 -1
- data/lib/aidp/logger.rb +35 -5
- data/lib/aidp/message_display.rb +56 -2
- data/lib/aidp/prompt_optimization/style_guide_indexer.rb +3 -1
- data/lib/aidp/provider_manager.rb +2 -0
- data/lib/aidp/providers/kilocode.rb +202 -0
- data/lib/aidp/safe_directory.rb +10 -3
- data/lib/aidp/setup/provider_registry.rb +15 -0
- data/lib/aidp/setup/wizard.rb +12 -4
- data/lib/aidp/skills/composer.rb +4 -0
- data/lib/aidp/skills/loader.rb +3 -1
- data/lib/aidp/storage/csv_storage.rb +9 -3
- data/lib/aidp/storage/file_manager.rb +8 -2
- data/lib/aidp/storage/json_storage.rb +9 -3
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +106 -17
- data/lib/aidp/watch/change_request_processor.rb +659 -0
- data/lib/aidp/watch/ci_fix_processor.rb +448 -0
- data/lib/aidp/watch/plan_processor.rb +81 -8
- data/lib/aidp/watch/repository_client.rb +465 -20
- data/lib/aidp/watch/review_processor.rb +266 -0
- data/lib/aidp/watch/reviewers/base_reviewer.rb +164 -0
- data/lib/aidp/watch/reviewers/performance_reviewer.rb +65 -0
- data/lib/aidp/watch/reviewers/security_reviewer.rb +65 -0
- data/lib/aidp/watch/reviewers/senior_dev_reviewer.rb +33 -0
- data/lib/aidp/watch/runner.rb +222 -0
- data/lib/aidp/watch/state_store.rb +99 -1
- data/lib/aidp/workstream_executor.rb +5 -2
- data/lib/aidp.rb +5 -0
- data/templates/aidp.yml.example +53 -0
- metadata +25 -1
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "json"
|
|
5
|
+
require "socket"
|
|
6
|
+
require_relative "../safe_directory"
|
|
7
|
+
|
|
8
|
+
module Aidp
|
|
9
|
+
module AutoUpdate
|
|
10
|
+
# Service for logging update events in JSON Lines format
|
|
11
|
+
class UpdateLogger
|
|
12
|
+
include Aidp::SafeDirectory
|
|
13
|
+
|
|
14
|
+
attr_reader :log_file
|
|
15
|
+
|
|
16
|
+
def initialize(project_dir: Dir.pwd)
|
|
17
|
+
@project_dir = project_dir
|
|
18
|
+
log_dir = File.join(project_dir, ".aidp", "logs")
|
|
19
|
+
actual_dir = safe_mkdir_p(log_dir, component_name: "UpdateLogger")
|
|
20
|
+
@log_file = File.join(actual_dir, "updates.log")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Log an update check
|
|
24
|
+
# @param update_check [UpdateCheck] Update check result
|
|
25
|
+
def log_check(update_check)
|
|
26
|
+
write_log_entry(
|
|
27
|
+
event: "check",
|
|
28
|
+
current_version: update_check.current_version,
|
|
29
|
+
available_version: update_check.available_version,
|
|
30
|
+
update_available: update_check.update_available,
|
|
31
|
+
update_allowed: update_check.update_allowed,
|
|
32
|
+
policy_reason: update_check.policy_reason,
|
|
33
|
+
error: update_check.error
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Log update initiation
|
|
38
|
+
# @param checkpoint [Checkpoint] Checkpoint created for update
|
|
39
|
+
# @param target_version [String] Version updating to
|
|
40
|
+
def log_update_initiated(checkpoint, target_version: nil)
|
|
41
|
+
write_log_entry(
|
|
42
|
+
event: "update_initiated",
|
|
43
|
+
checkpoint_id: checkpoint.checkpoint_id,
|
|
44
|
+
from_version: checkpoint.aidp_version,
|
|
45
|
+
to_version: target_version,
|
|
46
|
+
mode: checkpoint.mode
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Log successful checkpoint restoration
|
|
51
|
+
# @param checkpoint [Checkpoint] Checkpoint that was restored
|
|
52
|
+
def log_restore(checkpoint)
|
|
53
|
+
write_log_entry(
|
|
54
|
+
event: "restore",
|
|
55
|
+
checkpoint_id: checkpoint.checkpoint_id,
|
|
56
|
+
from_version: checkpoint.aidp_version,
|
|
57
|
+
restored_version: Aidp::VERSION,
|
|
58
|
+
mode: checkpoint.mode
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Log update failure
|
|
63
|
+
# @param reason [String] Failure reason
|
|
64
|
+
# @param checkpoint_id [String, nil] Associated checkpoint ID
|
|
65
|
+
def log_failure(reason, checkpoint_id: nil)
|
|
66
|
+
write_log_entry(
|
|
67
|
+
event: "failure",
|
|
68
|
+
reason: reason,
|
|
69
|
+
checkpoint_id: checkpoint_id,
|
|
70
|
+
version: Aidp::VERSION
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Log successful update completion
|
|
75
|
+
# @param from_version [String] Version updated from
|
|
76
|
+
# @param to_version [String] Version updated to
|
|
77
|
+
def log_success(from_version:, to_version:)
|
|
78
|
+
write_log_entry(
|
|
79
|
+
event: "success",
|
|
80
|
+
from_version: from_version,
|
|
81
|
+
to_version: to_version
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Log restart loop detection
|
|
86
|
+
# @param failure_count [Integer] Number of consecutive failures
|
|
87
|
+
def log_restart_loop(failure_count)
|
|
88
|
+
write_log_entry(
|
|
89
|
+
event: "restart_loop_detected",
|
|
90
|
+
failure_count: failure_count,
|
|
91
|
+
version: Aidp::VERSION
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Read recent update log entries
|
|
96
|
+
# @param limit [Integer] Maximum number of entries to return
|
|
97
|
+
# @return [Array<Hash>] Recent log entries
|
|
98
|
+
def recent_entries(limit: 10)
|
|
99
|
+
return [] unless File.exist?(@log_file)
|
|
100
|
+
|
|
101
|
+
entries = []
|
|
102
|
+
File.readlines(@log_file).reverse_each do |line|
|
|
103
|
+
break if entries.size >= limit
|
|
104
|
+
begin
|
|
105
|
+
entries << JSON.parse(line, symbolize_names: true)
|
|
106
|
+
rescue JSON::ParserError
|
|
107
|
+
# Skip malformed lines
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
entries
|
|
112
|
+
rescue => e
|
|
113
|
+
Aidp.log_error("update_logger", "read_entries_failed",
|
|
114
|
+
error: e.message)
|
|
115
|
+
[]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
def write_log_entry(data)
|
|
121
|
+
entry = data.merge(
|
|
122
|
+
timestamp: Time.now.utc.iso8601,
|
|
123
|
+
hostname: Socket.gethostname
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Remove nil values
|
|
127
|
+
entry = entry.compact
|
|
128
|
+
|
|
129
|
+
File.open(@log_file, "a") do |f|
|
|
130
|
+
f.puts(JSON.generate(entry))
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
Aidp.log_debug("update_logger", "log_entry_written",
|
|
134
|
+
event: data[:event])
|
|
135
|
+
rescue => e
|
|
136
|
+
# Log to main logger but don't fail
|
|
137
|
+
Aidp.log_error("update_logger", "write_failed",
|
|
138
|
+
event: data[:event],
|
|
139
|
+
error: e.message)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aidp
|
|
4
|
+
module AutoUpdate
|
|
5
|
+
# Value object representing auto-update configuration policy
|
|
6
|
+
class UpdatePolicy
|
|
7
|
+
attr_reader :enabled, :policy, :allow_prerelease, :check_interval_seconds,
|
|
8
|
+
:supervisor, :max_consecutive_failures
|
|
9
|
+
|
|
10
|
+
VALID_POLICIES = %w[off exact patch minor major].freeze
|
|
11
|
+
VALID_SUPERVISORS = %w[none supervisord s6 runit].freeze
|
|
12
|
+
|
|
13
|
+
def initialize(
|
|
14
|
+
enabled: false,
|
|
15
|
+
policy: "off",
|
|
16
|
+
allow_prerelease: false,
|
|
17
|
+
check_interval_seconds: 3600,
|
|
18
|
+
supervisor: "none",
|
|
19
|
+
max_consecutive_failures: 3
|
|
20
|
+
)
|
|
21
|
+
@enabled = enabled
|
|
22
|
+
@policy = validate_policy(policy)
|
|
23
|
+
@allow_prerelease = allow_prerelease
|
|
24
|
+
@check_interval_seconds = validate_interval(check_interval_seconds)
|
|
25
|
+
@supervisor = validate_supervisor(supervisor)
|
|
26
|
+
@max_consecutive_failures = validate_max_failures(max_consecutive_failures)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Create from configuration hash
|
|
30
|
+
# @param config [Hash] Configuration hash from aidp.yml
|
|
31
|
+
# @return [UpdatePolicy]
|
|
32
|
+
def self.from_config(config)
|
|
33
|
+
return disabled unless config
|
|
34
|
+
|
|
35
|
+
new(
|
|
36
|
+
enabled: config[:enabled] || config["enabled"] || false,
|
|
37
|
+
policy: config[:policy] || config["policy"] || "off",
|
|
38
|
+
allow_prerelease: config[:allow_prerelease] || config["allow_prerelease"] || false,
|
|
39
|
+
check_interval_seconds: config[:check_interval_seconds] || config["check_interval_seconds"] || 3600,
|
|
40
|
+
supervisor: config[:supervisor] || config["supervisor"] || "none",
|
|
41
|
+
max_consecutive_failures: config[:max_consecutive_failures] || config["max_consecutive_failures"] || 3
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Create a disabled policy
|
|
46
|
+
# @return [UpdatePolicy]
|
|
47
|
+
def self.disabled
|
|
48
|
+
new(enabled: false, policy: "off")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Check if updates are completely disabled
|
|
52
|
+
# @return [Boolean]
|
|
53
|
+
def disabled?
|
|
54
|
+
!@enabled || @policy == "off"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Check if a supervisor is configured
|
|
58
|
+
# @return [Boolean]
|
|
59
|
+
def supervised?
|
|
60
|
+
@supervisor != "none"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Convert to hash for serialization
|
|
64
|
+
# @return [Hash]
|
|
65
|
+
def to_h
|
|
66
|
+
{
|
|
67
|
+
enabled: @enabled,
|
|
68
|
+
policy: @policy,
|
|
69
|
+
allow_prerelease: @allow_prerelease,
|
|
70
|
+
check_interval_seconds: @check_interval_seconds,
|
|
71
|
+
supervisor: @supervisor,
|
|
72
|
+
max_consecutive_failures: @max_consecutive_failures
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def validate_policy(policy)
|
|
79
|
+
unless VALID_POLICIES.include?(policy.to_s)
|
|
80
|
+
raise ArgumentError, "Invalid policy: #{policy}. Must be one of: #{VALID_POLICIES.join(", ")}"
|
|
81
|
+
end
|
|
82
|
+
policy.to_s
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def validate_supervisor(supervisor)
|
|
86
|
+
unless VALID_SUPERVISORS.include?(supervisor.to_s)
|
|
87
|
+
raise ArgumentError, "Invalid supervisor: #{supervisor}. Must be one of: #{VALID_SUPERVISORS.join(", ")}"
|
|
88
|
+
end
|
|
89
|
+
supervisor.to_s
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def validate_interval(interval)
|
|
93
|
+
interval = interval.to_i
|
|
94
|
+
if interval < 300 || interval > 86400
|
|
95
|
+
raise ArgumentError, "Invalid check_interval_seconds: #{interval}. Must be between 300 and 86400"
|
|
96
|
+
end
|
|
97
|
+
interval
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def validate_max_failures(max_failures)
|
|
101
|
+
max_failures = max_failures.to_i
|
|
102
|
+
if max_failures < 1 || max_failures > 10
|
|
103
|
+
raise ArgumentError, "Invalid max_consecutive_failures: #{max_failures}. Must be between 1 and 10"
|
|
104
|
+
end
|
|
105
|
+
max_failures
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "update_check"
|
|
4
|
+
require_relative "update_policy"
|
|
5
|
+
require_relative "bundler_adapter"
|
|
6
|
+
require_relative "rubygems_api_adapter"
|
|
7
|
+
|
|
8
|
+
module Aidp
|
|
9
|
+
module AutoUpdate
|
|
10
|
+
# Service for detecting available gem versions and enforcing semver policy
|
|
11
|
+
class VersionDetector
|
|
12
|
+
def initialize(
|
|
13
|
+
policy:, current_version: Aidp::VERSION,
|
|
14
|
+
bundler_adapter: BundlerAdapter.new,
|
|
15
|
+
rubygems_adapter: RubyGemsAPIAdapter.new
|
|
16
|
+
)
|
|
17
|
+
@current_version = Gem::Version.new(current_version)
|
|
18
|
+
@policy = policy
|
|
19
|
+
@bundler_adapter = bundler_adapter
|
|
20
|
+
@rubygems_adapter = rubygems_adapter
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Check for updates according to policy
|
|
24
|
+
# @return [UpdateCheck] Result of update check
|
|
25
|
+
def check_for_update
|
|
26
|
+
Aidp.log_info("version_detector", "checking_for_updates",
|
|
27
|
+
current_version: @current_version.to_s,
|
|
28
|
+
policy: @policy.policy)
|
|
29
|
+
|
|
30
|
+
available_version = fetch_latest_version
|
|
31
|
+
|
|
32
|
+
unless available_version
|
|
33
|
+
Aidp.log_warn("version_detector", "no_version_available")
|
|
34
|
+
return UpdateCheck.unavailable(current_version: @current_version.to_s)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
update_available = available_version > @current_version
|
|
38
|
+
# Check policy even if disabled - we still want to report update_available
|
|
39
|
+
update_allowed = @policy.disabled? ? false : update_allowed_by_policy?(available_version)
|
|
40
|
+
reason = policy_reason(available_version)
|
|
41
|
+
|
|
42
|
+
Aidp.log_info("version_detector", "update_check_complete",
|
|
43
|
+
current: @current_version.to_s,
|
|
44
|
+
available: available_version.to_s,
|
|
45
|
+
update_available: update_available,
|
|
46
|
+
update_allowed: update_allowed,
|
|
47
|
+
reason: reason)
|
|
48
|
+
|
|
49
|
+
UpdateCheck.new(
|
|
50
|
+
current_version: @current_version.to_s,
|
|
51
|
+
available_version: available_version.to_s,
|
|
52
|
+
update_available: update_available,
|
|
53
|
+
update_allowed: update_allowed,
|
|
54
|
+
policy_reason: reason
|
|
55
|
+
)
|
|
56
|
+
rescue => e
|
|
57
|
+
Aidp.log_error("version_detector", "check_failed", error: e.message)
|
|
58
|
+
UpdateCheck.failed(e.message, current_version: @current_version.to_s)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
# Fetch latest version using bundler first, fallback to RubyGems API
|
|
64
|
+
# @return [Gem::Version, nil]
|
|
65
|
+
def fetch_latest_version
|
|
66
|
+
# Try bundler first
|
|
67
|
+
version = @bundler_adapter.latest_version_for("aidp")
|
|
68
|
+
return version if version
|
|
69
|
+
|
|
70
|
+
# Fallback to RubyGems API
|
|
71
|
+
Aidp.log_debug("version_detector", "falling_back_to_rubygems_api")
|
|
72
|
+
@rubygems_adapter.latest_version_for("aidp", allow_prerelease: @policy.allow_prerelease)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Apply semver policy to determine if update is allowed
|
|
76
|
+
# @param available [Gem::Version] Available version
|
|
77
|
+
# @return [Boolean] Whether update is permitted
|
|
78
|
+
def update_allowed_by_policy?(available)
|
|
79
|
+
case @policy.policy
|
|
80
|
+
when "off"
|
|
81
|
+
false
|
|
82
|
+
when "exact"
|
|
83
|
+
# Only update if exact match (essentially disabled)
|
|
84
|
+
available == @current_version
|
|
85
|
+
when "patch"
|
|
86
|
+
# Allow patch updates within same major.minor
|
|
87
|
+
same_major_minor?(available) && available >= @current_version
|
|
88
|
+
when "minor"
|
|
89
|
+
# Allow minor + patch updates within same major
|
|
90
|
+
same_major?(available) && available >= @current_version
|
|
91
|
+
when "major"
|
|
92
|
+
# Allow any update (including major version bumps)
|
|
93
|
+
available >= @current_version
|
|
94
|
+
else
|
|
95
|
+
false
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Generate human-readable reason for policy decision
|
|
100
|
+
# @param available [Gem::Version] Available version
|
|
101
|
+
# @return [String] Explanation of policy decision
|
|
102
|
+
def policy_reason(available)
|
|
103
|
+
return "No update available" if available <= @current_version
|
|
104
|
+
|
|
105
|
+
case @policy.policy
|
|
106
|
+
when "off"
|
|
107
|
+
"Updates disabled by policy"
|
|
108
|
+
when "exact"
|
|
109
|
+
"Policy requires exact version match"
|
|
110
|
+
when "patch"
|
|
111
|
+
if same_major_minor?(available)
|
|
112
|
+
"Patch update allowed by policy"
|
|
113
|
+
else
|
|
114
|
+
"Minor or major version change blocked by patch policy"
|
|
115
|
+
end
|
|
116
|
+
when "minor"
|
|
117
|
+
if same_major?(available)
|
|
118
|
+
"Minor/patch update allowed by policy"
|
|
119
|
+
else
|
|
120
|
+
"Major version change blocked by minor policy"
|
|
121
|
+
end
|
|
122
|
+
when "major"
|
|
123
|
+
"Update allowed by major policy"
|
|
124
|
+
else
|
|
125
|
+
"Unknown policy: #{@policy.policy}"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Check if version has same major version
|
|
130
|
+
# @param version [Gem::Version] Version to compare
|
|
131
|
+
# @return [Boolean]
|
|
132
|
+
def same_major?(version)
|
|
133
|
+
version.segments[0] == @current_version.segments[0]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Check if version has same major.minor version
|
|
137
|
+
# @param version [Gem::Version] Version to compare
|
|
138
|
+
# @return [Boolean]
|
|
139
|
+
def same_major_minor?(version)
|
|
140
|
+
same_major?(version) && version.segments[1] == @current_version.segments[1]
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "auto_update/errors"
|
|
4
|
+
require_relative "auto_update/update_policy"
|
|
5
|
+
require_relative "auto_update/update_check"
|
|
6
|
+
require_relative "auto_update/checkpoint"
|
|
7
|
+
require_relative "auto_update/bundler_adapter"
|
|
8
|
+
require_relative "auto_update/rubygems_api_adapter"
|
|
9
|
+
require_relative "auto_update/version_detector"
|
|
10
|
+
require_relative "auto_update/checkpoint_store"
|
|
11
|
+
require_relative "auto_update/update_logger"
|
|
12
|
+
require_relative "auto_update/failure_tracker"
|
|
13
|
+
require_relative "auto_update/coordinator"
|
|
14
|
+
|
|
15
|
+
module Aidp
|
|
16
|
+
# Auto-update functionality for Aidp in devcontainers
|
|
17
|
+
module AutoUpdate
|
|
18
|
+
# Exit code used to signal supervisor to perform update
|
|
19
|
+
UPDATE_EXIT_CODE = 75
|
|
20
|
+
|
|
21
|
+
# Create coordinator from project configuration
|
|
22
|
+
# @param project_dir [String] Project root directory
|
|
23
|
+
# @return [Coordinator]
|
|
24
|
+
def self.coordinator(project_dir: Dir.pwd)
|
|
25
|
+
config = Aidp::Config.load_harness_config(project_dir)
|
|
26
|
+
auto_update_config = config[:auto_update] || config["auto_update"] || {}
|
|
27
|
+
|
|
28
|
+
Coordinator.from_config(auto_update_config, project_dir: project_dir)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Check if auto-update is enabled in configuration
|
|
32
|
+
# @param project_dir [String] Project root directory
|
|
33
|
+
# @return [Boolean]
|
|
34
|
+
def self.enabled?(project_dir: Dir.pwd)
|
|
35
|
+
config = Aidp::Config.load_harness_config(project_dir)
|
|
36
|
+
auto_update_config = config[:auto_update] || config["auto_update"] || {}
|
|
37
|
+
|
|
38
|
+
policy = UpdatePolicy.from_config(auto_update_config)
|
|
39
|
+
!policy.disabled?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Get auto-update policy from configuration
|
|
43
|
+
# @param project_dir [String] Project root directory
|
|
44
|
+
# @return [UpdatePolicy]
|
|
45
|
+
def self.policy(project_dir: Dir.pwd)
|
|
46
|
+
config = Aidp::Config.load_harness_config(project_dir)
|
|
47
|
+
auto_update_config = config[:auto_update] || config["auto_update"] || {}
|
|
48
|
+
|
|
49
|
+
UpdatePolicy.from_config(auto_update_config)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/aidp/cli.rb
CHANGED
|
@@ -323,6 +323,11 @@ module Aidp
|
|
|
323
323
|
opts.separator " show <id> - Show detailed skill information"
|
|
324
324
|
opts.separator " search <query> - Search skills by keyword"
|
|
325
325
|
opts.separator " validate [path] - Validate skill file format"
|
|
326
|
+
opts.separator " settings Manage runtime settings"
|
|
327
|
+
opts.separator " auto-update status - Show auto-update configuration"
|
|
328
|
+
opts.separator " auto-update on|off - Enable/disable auto-updates"
|
|
329
|
+
opts.separator " auto-update policy <pol> - Set update policy (off/exact/patch/minor/major)"
|
|
330
|
+
opts.separator " auto-update prerelease - Toggle prerelease updates"
|
|
326
331
|
opts.separator " harness Manage harness state"
|
|
327
332
|
opts.separator " config Manage configuration"
|
|
328
333
|
opts.separator " status - Show harness status"
|
|
@@ -376,7 +381,7 @@ module Aidp
|
|
|
376
381
|
# Determine if the invocation is a subcommand style call
|
|
377
382
|
def subcommand?(args)
|
|
378
383
|
return false if args.nil? || args.empty?
|
|
379
|
-
%w[status jobs kb harness providers checkpoint mcp issue config init watch ws work skill].include?(args.first)
|
|
384
|
+
%w[status jobs kb harness providers checkpoint mcp issue config init watch ws work skill settings].include?(args.first)
|
|
380
385
|
end
|
|
381
386
|
|
|
382
387
|
def run_subcommand(args)
|
|
@@ -397,6 +402,7 @@ module Aidp
|
|
|
397
402
|
when "ws" then run_ws_command(args)
|
|
398
403
|
when "work" then run_work_command(args)
|
|
399
404
|
when "skill" then run_skill_command(args)
|
|
405
|
+
when "settings" then run_settings_command(args)
|
|
400
406
|
else
|
|
401
407
|
display_message("Unknown command: #{cmd}", type: :info)
|
|
402
408
|
return 1
|
|
@@ -1854,6 +1860,164 @@ module Aidp
|
|
|
1854
1860
|
display_message("Usage: aidp config --interactive [--dry-run]", type: :info)
|
|
1855
1861
|
end
|
|
1856
1862
|
|
|
1863
|
+
def run_settings_command(args)
|
|
1864
|
+
require_relative "auto_update"
|
|
1865
|
+
require "yaml"
|
|
1866
|
+
require "tty-table"
|
|
1867
|
+
|
|
1868
|
+
subcommand = args.shift
|
|
1869
|
+
|
|
1870
|
+
case subcommand
|
|
1871
|
+
when "auto-update"
|
|
1872
|
+
action = args.shift
|
|
1873
|
+
|
|
1874
|
+
case action
|
|
1875
|
+
when "status", nil
|
|
1876
|
+
# Show current auto-update status
|
|
1877
|
+
coordinator = Aidp::AutoUpdate.coordinator(project_dir: Dir.pwd)
|
|
1878
|
+
status = coordinator.status
|
|
1879
|
+
|
|
1880
|
+
display_message("Auto-Update Configuration", type: :highlight)
|
|
1881
|
+
display_message("=" * 60, type: :muted)
|
|
1882
|
+
display_message("Enabled: #{status[:enabled] ? "Yes" : "No"}", type: status[:enabled] ? :success : :muted)
|
|
1883
|
+
display_message("Policy: #{status[:policy]}", type: :info)
|
|
1884
|
+
display_message("Supervisor: #{status[:supervisor]}", type: :info)
|
|
1885
|
+
display_message("Allow Prerelease: #{coordinator.policy.allow_prerelease}", type: :info)
|
|
1886
|
+
display_message("Check Interval: #{coordinator.policy.check_interval_seconds}s", type: :info)
|
|
1887
|
+
display_message("Max Consecutive Failures: #{coordinator.policy.max_consecutive_failures}", type: :info)
|
|
1888
|
+
display_message("", type: :info)
|
|
1889
|
+
display_message("Current Version: #{status[:current_version]}", type: :info)
|
|
1890
|
+
display_message("Latest Available: #{status[:available_version] || "checking..."}", type: :info)
|
|
1891
|
+
|
|
1892
|
+
if status[:update_available]
|
|
1893
|
+
if status[:update_allowed]
|
|
1894
|
+
display_message("Update Available: Yes (allowed by policy)", type: :success)
|
|
1895
|
+
else
|
|
1896
|
+
display_message("Update Available: Yes (blocked by policy: #{status[:policy_reason]})", type: :warning)
|
|
1897
|
+
end
|
|
1898
|
+
else
|
|
1899
|
+
display_message("Update Available: No", type: :muted)
|
|
1900
|
+
end
|
|
1901
|
+
|
|
1902
|
+
display_message("", type: :info)
|
|
1903
|
+
display_message("Failure Tracker:", type: :highlight)
|
|
1904
|
+
failure_status = status[:failure_tracker]
|
|
1905
|
+
display_message("Consecutive Failures: #{failure_status[:failures]}/#{failure_status[:max_failures]}", type: :info)
|
|
1906
|
+
display_message("Last Success: #{failure_status[:last_success] || "never"}", type: :muted)
|
|
1907
|
+
|
|
1908
|
+
if status[:recent_updates]&.any?
|
|
1909
|
+
display_message("", type: :info)
|
|
1910
|
+
display_message("Recent Updates:", type: :highlight)
|
|
1911
|
+
status[:recent_updates].each do |entry|
|
|
1912
|
+
timestamp = entry["timestamp"] || entry[:timestamp]
|
|
1913
|
+
event = entry["event"] || entry[:event]
|
|
1914
|
+
display_message(" #{timestamp} - #{event}", type: :muted)
|
|
1915
|
+
end
|
|
1916
|
+
end
|
|
1917
|
+
|
|
1918
|
+
when "on"
|
|
1919
|
+
# Enable auto-update
|
|
1920
|
+
update_config_value(:auto_update, :enabled, true)
|
|
1921
|
+
display_message("✓ Auto-update enabled", type: :success)
|
|
1922
|
+
display_message("", type: :info)
|
|
1923
|
+
display_message("Make sure to configure a supervisor for watch mode.", type: :muted)
|
|
1924
|
+
display_message("See: docs/SELF_UPDATE.md", type: :muted)
|
|
1925
|
+
|
|
1926
|
+
when "off"
|
|
1927
|
+
# Disable auto-update
|
|
1928
|
+
update_config_value(:auto_update, :enabled, false)
|
|
1929
|
+
display_message("✓ Auto-update disabled", type: :success)
|
|
1930
|
+
|
|
1931
|
+
when "policy"
|
|
1932
|
+
# Set update policy
|
|
1933
|
+
policy = args.shift
|
|
1934
|
+
unless %w[off exact patch minor major].include?(policy)
|
|
1935
|
+
display_message("❌ Invalid policy. Must be: off, exact, patch, minor, major", type: :error)
|
|
1936
|
+
return
|
|
1937
|
+
end
|
|
1938
|
+
|
|
1939
|
+
update_config_value(:auto_update, :policy, policy)
|
|
1940
|
+
display_message("✓ Auto-update policy set to: #{policy}", type: :success)
|
|
1941
|
+
display_message("", type: :info)
|
|
1942
|
+
case policy
|
|
1943
|
+
when "off"
|
|
1944
|
+
display_message("No automatic updates will be performed", type: :muted)
|
|
1945
|
+
when "exact"
|
|
1946
|
+
display_message("Only exact version matches allowed", type: :muted)
|
|
1947
|
+
when "patch"
|
|
1948
|
+
display_message("Patch updates allowed (e.g., 1.2.3 → 1.2.4)", type: :muted)
|
|
1949
|
+
when "minor"
|
|
1950
|
+
display_message("Minor + patch updates allowed (e.g., 1.2.3 → 1.3.0)", type: :muted)
|
|
1951
|
+
when "major"
|
|
1952
|
+
display_message("All updates allowed (e.g., 1.2.3 → 2.0.0)", type: :muted)
|
|
1953
|
+
end
|
|
1954
|
+
|
|
1955
|
+
when "prerelease"
|
|
1956
|
+
# Toggle prerelease
|
|
1957
|
+
current = load_auto_update_config[:allow_prerelease]
|
|
1958
|
+
new_value = !current
|
|
1959
|
+
update_config_value(:auto_update, :allow_prerelease, new_value)
|
|
1960
|
+
display_message("✓ Prerelease updates: #{new_value ? "enabled" : "disabled"}", type: :success)
|
|
1961
|
+
|
|
1962
|
+
else
|
|
1963
|
+
display_message("Usage: aidp settings auto-update <command>", type: :info)
|
|
1964
|
+
display_message("", type: :info)
|
|
1965
|
+
display_message("Commands:", type: :info)
|
|
1966
|
+
display_message(" status Show current configuration", type: :info)
|
|
1967
|
+
display_message(" on Enable auto-updates", type: :info)
|
|
1968
|
+
display_message(" off Disable auto-updates", type: :info)
|
|
1969
|
+
display_message(" policy <policy> Set update policy", type: :info)
|
|
1970
|
+
display_message(" Policies: off, exact, patch, minor, major", type: :muted)
|
|
1971
|
+
display_message(" prerelease Toggle prerelease updates", type: :info)
|
|
1972
|
+
display_message("", type: :info)
|
|
1973
|
+
display_message("Examples:", type: :info)
|
|
1974
|
+
display_message(" aidp settings auto-update status", type: :info)
|
|
1975
|
+
display_message(" aidp settings auto-update on", type: :info)
|
|
1976
|
+
display_message(" aidp settings auto-update policy minor", type: :info)
|
|
1977
|
+
display_message(" aidp settings auto-update prerelease", type: :info)
|
|
1978
|
+
end
|
|
1979
|
+
|
|
1980
|
+
else
|
|
1981
|
+
display_message("Usage: aidp settings <category> <command>", type: :info)
|
|
1982
|
+
display_message("", type: :info)
|
|
1983
|
+
display_message("Categories:", type: :info)
|
|
1984
|
+
display_message(" auto-update Auto-update configuration", type: :info)
|
|
1985
|
+
display_message("", type: :info)
|
|
1986
|
+
display_message("Examples:", type: :info)
|
|
1987
|
+
display_message(" aidp settings auto-update status", type: :info)
|
|
1988
|
+
end
|
|
1989
|
+
end
|
|
1990
|
+
|
|
1991
|
+
# Load current auto_update config
|
|
1992
|
+
def load_auto_update_config
|
|
1993
|
+
config_path = File.join(Dir.pwd, ".aidp", "aidp.yml")
|
|
1994
|
+
return {} unless File.exist?(config_path)
|
|
1995
|
+
|
|
1996
|
+
full_config = YAML.safe_load_file(config_path, permitted_classes: [Date, Time, Symbol], aliases: true)
|
|
1997
|
+
full_config["auto_update"] || full_config[:auto_update] || {}
|
|
1998
|
+
end
|
|
1999
|
+
|
|
2000
|
+
# Update a specific configuration value
|
|
2001
|
+
def update_config_value(section, key, value)
|
|
2002
|
+
config_path = File.join(Dir.pwd, ".aidp", "aidp.yml")
|
|
2003
|
+
|
|
2004
|
+
# Load existing config
|
|
2005
|
+
config = if File.exist?(config_path)
|
|
2006
|
+
YAML.safe_load_file(config_path, permitted_classes: [Date, Time, Symbol], aliases: true) || {}
|
|
2007
|
+
else
|
|
2008
|
+
{}
|
|
2009
|
+
end
|
|
2010
|
+
|
|
2011
|
+
# Ensure section exists
|
|
2012
|
+
config[section.to_s] ||= {}
|
|
2013
|
+
|
|
2014
|
+
# Update value
|
|
2015
|
+
config[section.to_s][key.to_s] = value
|
|
2016
|
+
|
|
2017
|
+
# Write back
|
|
2018
|
+
File.write(config_path, YAML.dump(config))
|
|
2019
|
+
end
|
|
2020
|
+
|
|
1857
2021
|
def run_skill_command(args)
|
|
1858
2022
|
require_relative "skills"
|
|
1859
2023
|
require "tty-table"
|