aidp 0.7.0 → 0.8.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 +60 -214
- data/bin/aidp +1 -1
- data/lib/aidp/analysis/kb_inspector.rb +38 -23
- data/lib/aidp/analysis/seams.rb +2 -31
- data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +0 -13
- data/lib/aidp/analysis/tree_sitter_scan.rb +3 -20
- data/lib/aidp/analyze/error_handler.rb +2 -75
- data/lib/aidp/analyze/json_file_storage.rb +292 -0
- data/lib/aidp/analyze/progress.rb +12 -0
- data/lib/aidp/analyze/progress_visualizer.rb +12 -17
- data/lib/aidp/analyze/ruby_maat_integration.rb +13 -31
- data/lib/aidp/analyze/runner.rb +256 -87
- data/lib/aidp/cli/jobs_command.rb +100 -432
- data/lib/aidp/cli.rb +309 -239
- data/lib/aidp/config.rb +298 -10
- data/lib/aidp/debug_logger.rb +195 -0
- data/lib/aidp/debug_mixin.rb +187 -0
- data/lib/aidp/execute/progress.rb +9 -0
- data/lib/aidp/execute/runner.rb +221 -40
- data/lib/aidp/execute/steps.rb +17 -7
- data/lib/aidp/execute/workflow_selector.rb +211 -0
- data/lib/aidp/harness/completion_checker.rb +268 -0
- data/lib/aidp/harness/condition_detector.rb +1526 -0
- data/lib/aidp/harness/config_loader.rb +373 -0
- data/lib/aidp/harness/config_manager.rb +382 -0
- data/lib/aidp/harness/config_schema.rb +1006 -0
- data/lib/aidp/harness/config_validator.rb +355 -0
- data/lib/aidp/harness/configuration.rb +477 -0
- data/lib/aidp/harness/enhanced_runner.rb +494 -0
- data/lib/aidp/harness/error_handler.rb +616 -0
- data/lib/aidp/harness/provider_config.rb +423 -0
- data/lib/aidp/harness/provider_factory.rb +306 -0
- data/lib/aidp/harness/provider_manager.rb +1269 -0
- data/lib/aidp/harness/provider_type_checker.rb +88 -0
- data/lib/aidp/harness/runner.rb +411 -0
- data/lib/aidp/harness/state/errors.rb +28 -0
- data/lib/aidp/harness/state/metrics.rb +219 -0
- data/lib/aidp/harness/state/persistence.rb +128 -0
- data/lib/aidp/harness/state/provider_state.rb +132 -0
- data/lib/aidp/harness/state/ui_state.rb +68 -0
- data/lib/aidp/harness/state/workflow_state.rb +123 -0
- data/lib/aidp/harness/state_manager.rb +586 -0
- data/lib/aidp/harness/status_display.rb +888 -0
- data/lib/aidp/harness/ui/base.rb +16 -0
- data/lib/aidp/harness/ui/enhanced_tui.rb +545 -0
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +252 -0
- data/lib/aidp/harness/ui/error_handler.rb +132 -0
- data/lib/aidp/harness/ui/frame_manager.rb +361 -0
- data/lib/aidp/harness/ui/job_monitor.rb +500 -0
- data/lib/aidp/harness/ui/navigation/main_menu.rb +311 -0
- data/lib/aidp/harness/ui/navigation/menu_formatter.rb +120 -0
- data/lib/aidp/harness/ui/navigation/menu_item.rb +142 -0
- data/lib/aidp/harness/ui/navigation/menu_state.rb +139 -0
- data/lib/aidp/harness/ui/navigation/submenu.rb +202 -0
- data/lib/aidp/harness/ui/navigation/workflow_selector.rb +176 -0
- data/lib/aidp/harness/ui/progress_display.rb +280 -0
- data/lib/aidp/harness/ui/question_collector.rb +141 -0
- data/lib/aidp/harness/ui/spinner_group.rb +184 -0
- data/lib/aidp/harness/ui/spinner_helper.rb +152 -0
- data/lib/aidp/harness/ui/status_manager.rb +312 -0
- data/lib/aidp/harness/ui/status_widget.rb +280 -0
- data/lib/aidp/harness/ui/workflow_controller.rb +312 -0
- data/lib/aidp/harness/user_interface.rb +2381 -0
- data/lib/aidp/provider_manager.rb +131 -7
- data/lib/aidp/providers/anthropic.rb +28 -103
- data/lib/aidp/providers/base.rb +170 -0
- data/lib/aidp/providers/cursor.rb +52 -181
- data/lib/aidp/providers/gemini.rb +24 -107
- data/lib/aidp/providers/macos_ui.rb +99 -5
- data/lib/aidp/providers/opencode.rb +194 -0
- data/lib/aidp/storage/csv_storage.rb +172 -0
- data/lib/aidp/storage/file_manager.rb +214 -0
- data/lib/aidp/storage/json_storage.rb +140 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp.rb +54 -39
- data/templates/COMMON/AGENT_BASE.md +11 -0
- data/templates/EXECUTE/00_PRD.md +4 -4
- data/templates/EXECUTE/02_ARCHITECTURE.md +5 -4
- data/templates/EXECUTE/07_TEST_PLAN.md +4 -1
- data/templates/EXECUTE/08_TASKS.md +4 -4
- data/templates/EXECUTE/10_IMPLEMENTATION_AGENT.md +4 -4
- data/templates/README.md +279 -0
- data/templates/aidp-development.yml.example +373 -0
- data/templates/aidp-minimal.yml.example +48 -0
- data/templates/aidp-production.yml.example +475 -0
- data/templates/aidp.yml.example +598 -0
- metadata +93 -69
- data/lib/aidp/analyze/agent_personas.rb +0 -71
- data/lib/aidp/analyze/agent_tool_executor.rb +0 -439
- data/lib/aidp/analyze/data_retention_manager.rb +0 -421
- data/lib/aidp/analyze/database.rb +0 -260
- data/lib/aidp/analyze/dependencies.rb +0 -335
- data/lib/aidp/analyze/export_manager.rb +0 -418
- data/lib/aidp/analyze/focus_guidance.rb +0 -517
- data/lib/aidp/analyze/incremental_analyzer.rb +0 -533
- data/lib/aidp/analyze/language_analysis_strategies.rb +0 -897
- data/lib/aidp/analyze/large_analysis_progress.rb +0 -499
- data/lib/aidp/analyze/memory_manager.rb +0 -339
- data/lib/aidp/analyze/metrics_storage.rb +0 -336
- data/lib/aidp/analyze/parallel_processor.rb +0 -454
- data/lib/aidp/analyze/performance_optimizer.rb +0 -691
- data/lib/aidp/analyze/repository_chunker.rb +0 -697
- data/lib/aidp/analyze/static_analysis_detector.rb +0 -577
- data/lib/aidp/analyze/storage.rb +0 -655
- data/lib/aidp/analyze/tool_configuration.rb +0 -441
- data/lib/aidp/analyze/tool_modernization.rb +0 -750
- data/lib/aidp/database/pg_adapter.rb +0 -148
- data/lib/aidp/database_config.rb +0 -69
- data/lib/aidp/database_connection.rb +0 -72
- data/lib/aidp/job_manager.rb +0 -41
- data/lib/aidp/jobs/base_job.rb +0 -45
- data/lib/aidp/jobs/provider_execution_job.rb +0 -83
- data/lib/aidp/project_detector.rb +0 -117
- data/lib/aidp/providers/agent_supervisor.rb +0 -348
- data/lib/aidp/providers/supervised_base.rb +0 -317
- data/lib/aidp/providers/supervised_cursor.rb +0 -22
- data/lib/aidp/sync.rb +0 -13
- data/lib/aidp/workspace.rb +0 -19
@@ -0,0 +1,219 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aidp
|
4
|
+
module Harness
|
5
|
+
module State
|
6
|
+
# Manages metrics, analytics, and performance calculations
|
7
|
+
class Metrics
|
8
|
+
def initialize(persistence, workflow_state)
|
9
|
+
@persistence = persistence
|
10
|
+
@workflow_state = workflow_state
|
11
|
+
end
|
12
|
+
|
13
|
+
def record_provider_switch(from_provider, to_provider)
|
14
|
+
current_state = load_state
|
15
|
+
provider_switches = (current_state[:provider_switches] || 0) + 1
|
16
|
+
|
17
|
+
update_state(
|
18
|
+
provider_switches: provider_switches,
|
19
|
+
last_provider_switch: create_switch_record(from_provider, to_provider)
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def record_rate_limit_event(provider_name, reset_time)
|
24
|
+
current_state = load_state
|
25
|
+
rate_limit_events = (current_state[:rate_limit_events] || 0) + 1
|
26
|
+
|
27
|
+
update_state(
|
28
|
+
rate_limit_events: rate_limit_events,
|
29
|
+
last_rate_limit: create_rate_limit_record(provider_name, reset_time)
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
def record_user_feedback_request(step_name, questions_count)
|
34
|
+
current_state = load_state
|
35
|
+
user_feedback_requests = (current_state[:user_feedback_requests] || 0) + 1
|
36
|
+
|
37
|
+
update_state(
|
38
|
+
user_feedback_requests: user_feedback_requests,
|
39
|
+
last_user_feedback: create_feedback_record(step_name, questions_count)
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def record_error_event(step_name, error_type, provider_name = nil)
|
44
|
+
current_state = load_state
|
45
|
+
error_events = (current_state[:error_events] || 0) + 1
|
46
|
+
|
47
|
+
update_state(
|
48
|
+
error_events: error_events,
|
49
|
+
last_error: create_error_record(step_name, error_type, provider_name)
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def record_retry_attempt(step_name, provider_name, attempt_number)
|
54
|
+
current_state = load_state
|
55
|
+
retry_attempts = (current_state[:retry_attempts] || 0) + 1
|
56
|
+
|
57
|
+
update_state(
|
58
|
+
retry_attempts: retry_attempts,
|
59
|
+
last_retry: create_retry_record(step_name, provider_name, attempt_number)
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def harness_metrics
|
64
|
+
state = load_state
|
65
|
+
{
|
66
|
+
provider_switches: state[:provider_switches] || 0,
|
67
|
+
rate_limit_events: state[:rate_limit_events] || 0,
|
68
|
+
user_feedback_requests: state[:user_feedback_requests] || 0,
|
69
|
+
error_events: state[:error_events] || 0,
|
70
|
+
retry_attempts: state[:retry_attempts] || 0,
|
71
|
+
current_provider: state[:current_provider],
|
72
|
+
harness_state: state[:state],
|
73
|
+
last_activity: state[:last_updated]
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
def performance_metrics
|
78
|
+
{
|
79
|
+
efficiency: calculate_efficiency_metrics,
|
80
|
+
reliability: calculate_reliability_metrics,
|
81
|
+
performance: calculate_performance_metrics
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def load_state
|
88
|
+
@persistence.load_state
|
89
|
+
end
|
90
|
+
|
91
|
+
def update_state(updates)
|
92
|
+
current_state = load_state
|
93
|
+
updated_state = current_state.merge(updates)
|
94
|
+
updated_state[:last_updated] = Time.now
|
95
|
+
@persistence.save_state(updated_state)
|
96
|
+
end
|
97
|
+
|
98
|
+
def create_switch_record(from_provider, to_provider)
|
99
|
+
{
|
100
|
+
from: from_provider,
|
101
|
+
to: to_provider,
|
102
|
+
timestamp: Time.now
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
106
|
+
def create_rate_limit_record(provider_name, reset_time)
|
107
|
+
{
|
108
|
+
provider: provider_name,
|
109
|
+
reset_time: reset_time,
|
110
|
+
timestamp: Time.now
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
def create_feedback_record(step_name, questions_count)
|
115
|
+
{
|
116
|
+
step: step_name,
|
117
|
+
questions_count: questions_count,
|
118
|
+
timestamp: Time.now
|
119
|
+
}
|
120
|
+
end
|
121
|
+
|
122
|
+
def create_error_record(step_name, error_type, provider_name)
|
123
|
+
{
|
124
|
+
step: step_name,
|
125
|
+
error_type: error_type,
|
126
|
+
provider: provider_name,
|
127
|
+
timestamp: Time.now
|
128
|
+
}
|
129
|
+
end
|
130
|
+
|
131
|
+
def create_retry_record(step_name, provider_name, attempt_number)
|
132
|
+
{
|
133
|
+
step: step_name,
|
134
|
+
provider: provider_name,
|
135
|
+
attempt: attempt_number,
|
136
|
+
timestamp: Time.now
|
137
|
+
}
|
138
|
+
end
|
139
|
+
|
140
|
+
def calculate_efficiency_metrics
|
141
|
+
{
|
142
|
+
provider_switches_per_step: calculate_switches_per_step,
|
143
|
+
average_retries_per_step: calculate_retries_per_step,
|
144
|
+
user_feedback_ratio: calculate_feedback_ratio
|
145
|
+
}
|
146
|
+
end
|
147
|
+
|
148
|
+
def calculate_reliability_metrics
|
149
|
+
{
|
150
|
+
error_rate: calculate_error_rate,
|
151
|
+
rate_limit_frequency: calculate_rate_limit_frequency,
|
152
|
+
success_rate: calculate_success_rate
|
153
|
+
}
|
154
|
+
end
|
155
|
+
|
156
|
+
def calculate_performance_metrics
|
157
|
+
{
|
158
|
+
session_duration: @workflow_state.session_duration,
|
159
|
+
steps_per_hour: calculate_steps_per_hour,
|
160
|
+
average_step_duration: calculate_average_step_duration
|
161
|
+
}
|
162
|
+
end
|
163
|
+
|
164
|
+
def calculate_switches_per_step
|
165
|
+
provider_switches = load_state[:provider_switches] || 0
|
166
|
+
completed_steps_count = @workflow_state.completed_steps.size
|
167
|
+
return 0 if completed_steps_count == 0
|
168
|
+
(provider_switches.to_f / completed_steps_count).round(2)
|
169
|
+
end
|
170
|
+
|
171
|
+
def calculate_retries_per_step
|
172
|
+
retry_attempts = load_state[:retry_attempts] || 0
|
173
|
+
completed_steps_count = @workflow_state.completed_steps.size
|
174
|
+
return 0 if completed_steps_count == 0
|
175
|
+
(retry_attempts.to_f / completed_steps_count).round(2)
|
176
|
+
end
|
177
|
+
|
178
|
+
def calculate_feedback_ratio
|
179
|
+
user_feedback_requests = load_state[:user_feedback_requests] || 0
|
180
|
+
completed_steps_count = @workflow_state.completed_steps.size
|
181
|
+
return 0 if completed_steps_count == 0
|
182
|
+
(user_feedback_requests.to_f / completed_steps_count).round(2)
|
183
|
+
end
|
184
|
+
|
185
|
+
def calculate_error_rate
|
186
|
+
error_events = load_state[:error_events] || 0
|
187
|
+
total_events = error_events + @workflow_state.completed_steps.size
|
188
|
+
return 0 if total_events == 0
|
189
|
+
(error_events.to_f / total_events * 100).round(2)
|
190
|
+
end
|
191
|
+
|
192
|
+
def calculate_rate_limit_frequency
|
193
|
+
rate_limit_events = load_state[:rate_limit_events] || 0
|
194
|
+
session_duration_hours = @workflow_state.session_duration / 3600.0
|
195
|
+
return 0 if session_duration_hours == 0
|
196
|
+
(rate_limit_events / session_duration_hours).round(2)
|
197
|
+
end
|
198
|
+
|
199
|
+
def calculate_success_rate
|
200
|
+
error_events = load_state[:error_events] || 0
|
201
|
+
total_attempts = @workflow_state.completed_steps.size + error_events
|
202
|
+
return 100 if total_attempts == 0
|
203
|
+
((@workflow_state.completed_steps.size.to_f / total_attempts) * 100).round(2)
|
204
|
+
end
|
205
|
+
|
206
|
+
def calculate_steps_per_hour
|
207
|
+
session_duration_hours = @workflow_state.session_duration / 3600.0
|
208
|
+
return 0 if session_duration_hours == 0
|
209
|
+
(@workflow_state.completed_steps.size / session_duration_hours).round(2)
|
210
|
+
end
|
211
|
+
|
212
|
+
def calculate_average_step_duration
|
213
|
+
return 0 if @workflow_state.completed_steps.size == 0
|
214
|
+
(@workflow_state.session_duration / @workflow_state.completed_steps.size).round(2)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "fileutils"
|
5
|
+
|
6
|
+
module Aidp
|
7
|
+
module Harness
|
8
|
+
module State
|
9
|
+
# Handles file I/O and persistence for state management
|
10
|
+
class Persistence
|
11
|
+
def initialize(project_dir, mode)
|
12
|
+
@project_dir = project_dir
|
13
|
+
@mode = mode
|
14
|
+
@state_dir = File.join(project_dir, ".aidp", "harness")
|
15
|
+
@state_file = File.join(@state_dir, "#{mode}_state.json")
|
16
|
+
@lock_file = File.join(@state_dir, "#{mode}_state.lock")
|
17
|
+
ensure_state_directory
|
18
|
+
end
|
19
|
+
|
20
|
+
def has_state?
|
21
|
+
return false if test_mode?
|
22
|
+
File.exist?(@state_file)
|
23
|
+
end
|
24
|
+
|
25
|
+
def load_state
|
26
|
+
return {} if test_mode? || !has_state?
|
27
|
+
|
28
|
+
with_lock do
|
29
|
+
content = File.read(@state_file)
|
30
|
+
JSON.parse(content, symbolize_names: true)
|
31
|
+
rescue JSON::ParserError => e
|
32
|
+
warn "Failed to parse state file: #{e.message}"
|
33
|
+
{}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def save_state(state_data)
|
38
|
+
return if test_mode?
|
39
|
+
|
40
|
+
with_lock do
|
41
|
+
state_with_metadata = add_metadata(state_data)
|
42
|
+
write_atomically(state_with_metadata)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def clear_state
|
47
|
+
return if test_mode?
|
48
|
+
|
49
|
+
with_lock do
|
50
|
+
File.delete(@state_file) if File.exist?(@state_file)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def test_mode?
|
57
|
+
ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
58
|
+
end
|
59
|
+
|
60
|
+
def add_metadata(state_data)
|
61
|
+
state_data.merge(
|
62
|
+
mode: @mode,
|
63
|
+
project_dir: @project_dir,
|
64
|
+
saved_at: Time.now.iso8601
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
def write_atomically(state_with_metadata)
|
69
|
+
temp_file = "#{@state_file}.tmp"
|
70
|
+
File.write(temp_file, JSON.pretty_generate(state_with_metadata))
|
71
|
+
File.rename(temp_file, @state_file)
|
72
|
+
end
|
73
|
+
|
74
|
+
def ensure_state_directory
|
75
|
+
FileUtils.mkdir_p(@state_dir) unless Dir.exist?(@state_dir)
|
76
|
+
end
|
77
|
+
|
78
|
+
def with_lock(&block)
|
79
|
+
return yield if test_mode?
|
80
|
+
|
81
|
+
acquire_lock_with_timeout(&block)
|
82
|
+
ensure
|
83
|
+
cleanup_lock_file
|
84
|
+
end
|
85
|
+
|
86
|
+
def acquire_lock_with_timeout(&block)
|
87
|
+
lock_acquired = false
|
88
|
+
timeout = 30
|
89
|
+
start_time = Time.now
|
90
|
+
|
91
|
+
while (Time.now - start_time) < timeout
|
92
|
+
lock_acquired = try_acquire_lock(&block)
|
93
|
+
break if lock_acquired
|
94
|
+
sleep_briefly
|
95
|
+
end
|
96
|
+
|
97
|
+
raise_lock_timeout_error unless lock_acquired
|
98
|
+
end
|
99
|
+
|
100
|
+
def try_acquire_lock(&block)
|
101
|
+
File.open(@lock_file, File::CREAT | File::EXCL | File::WRONLY) do |_lock|
|
102
|
+
yield
|
103
|
+
true
|
104
|
+
end
|
105
|
+
rescue Errno::EEXIST
|
106
|
+
false
|
107
|
+
end
|
108
|
+
|
109
|
+
def sleep_briefly
|
110
|
+
require "async"
|
111
|
+
if Async::Task.current?
|
112
|
+
Async::Task.current.sleep(0.1)
|
113
|
+
else
|
114
|
+
sleep(0.1)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def raise_lock_timeout_error
|
119
|
+
raise "Could not acquire state lock within 30 seconds"
|
120
|
+
end
|
121
|
+
|
122
|
+
def cleanup_lock_file
|
123
|
+
File.delete(@lock_file) if File.exist?(@lock_file)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aidp
|
4
|
+
module Harness
|
5
|
+
module State
|
6
|
+
# Manages provider-specific state and rate limiting
|
7
|
+
class ProviderState
|
8
|
+
def initialize(persistence)
|
9
|
+
@persistence = persistence
|
10
|
+
end
|
11
|
+
|
12
|
+
def provider_state
|
13
|
+
state[:provider_state] || {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def update_provider_state(provider_name, provider_data)
|
17
|
+
current_provider_state = provider_state
|
18
|
+
current_provider_state[provider_name] = provider_data
|
19
|
+
update_state(provider_state: current_provider_state)
|
20
|
+
end
|
21
|
+
|
22
|
+
def rate_limit_info
|
23
|
+
state[:rate_limit_info] || {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def update_rate_limit_info(provider_name, reset_time, error_count = 0)
|
27
|
+
current_info = rate_limit_info
|
28
|
+
current_info[provider_name] = create_rate_limit_entry(reset_time, error_count)
|
29
|
+
update_state(rate_limit_info: current_info)
|
30
|
+
end
|
31
|
+
|
32
|
+
def provider_rate_limited?(provider_name)
|
33
|
+
info = rate_limit_info[provider_name]
|
34
|
+
return false unless info
|
35
|
+
|
36
|
+
reset_time = parse_reset_time(info[:reset_time])
|
37
|
+
reset_time && Time.now < reset_time
|
38
|
+
end
|
39
|
+
|
40
|
+
def next_provider_reset_time
|
41
|
+
rate_limit_info.map do |_provider, info|
|
42
|
+
parse_reset_time(info[:reset_time])
|
43
|
+
end.compact.min
|
44
|
+
end
|
45
|
+
|
46
|
+
def token_usage
|
47
|
+
state[:token_usage] || {}
|
48
|
+
end
|
49
|
+
|
50
|
+
def record_token_usage(provider_name, model_name, input_tokens, output_tokens, cost = nil)
|
51
|
+
current_usage = token_usage
|
52
|
+
key = "#{provider_name}:#{model_name}"
|
53
|
+
|
54
|
+
current_usage[key] = update_token_usage_entry(
|
55
|
+
current_usage[key], input_tokens, output_tokens, cost
|
56
|
+
)
|
57
|
+
|
58
|
+
update_state(token_usage: current_usage)
|
59
|
+
end
|
60
|
+
|
61
|
+
def token_usage_summary
|
62
|
+
usage = token_usage
|
63
|
+
{
|
64
|
+
total_tokens: calculate_total_tokens(usage),
|
65
|
+
total_cost: calculate_total_cost(usage),
|
66
|
+
total_requests: calculate_total_requests(usage),
|
67
|
+
by_provider_model: usage
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def state
|
74
|
+
@persistence.load_state
|
75
|
+
end
|
76
|
+
|
77
|
+
def update_state(updates)
|
78
|
+
current_state = state
|
79
|
+
updated_state = current_state.merge(updates)
|
80
|
+
updated_state[:last_updated] = Time.now
|
81
|
+
@persistence.save_state(updated_state)
|
82
|
+
end
|
83
|
+
|
84
|
+
def create_rate_limit_entry(reset_time, error_count)
|
85
|
+
{
|
86
|
+
reset_time: reset_time&.iso8601,
|
87
|
+
error_count: error_count,
|
88
|
+
last_updated: Time.now.iso8601
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
def parse_reset_time(reset_time_string)
|
93
|
+
Time.parse(reset_time_string) if reset_time_string
|
94
|
+
end
|
95
|
+
|
96
|
+
def update_token_usage_entry(existing_entry, input_tokens, output_tokens, cost)
|
97
|
+
existing_entry ||= create_empty_token_usage_entry
|
98
|
+
|
99
|
+
existing_entry[:input_tokens] += input_tokens
|
100
|
+
existing_entry[:output_tokens] += output_tokens
|
101
|
+
existing_entry[:total_tokens] += (input_tokens + output_tokens)
|
102
|
+
existing_entry[:cost] += cost if cost
|
103
|
+
existing_entry[:requests] += 1
|
104
|
+
|
105
|
+
existing_entry
|
106
|
+
end
|
107
|
+
|
108
|
+
def create_empty_token_usage_entry
|
109
|
+
{
|
110
|
+
input_tokens: 0,
|
111
|
+
output_tokens: 0,
|
112
|
+
total_tokens: 0,
|
113
|
+
cost: 0.0,
|
114
|
+
requests: 0
|
115
|
+
}
|
116
|
+
end
|
117
|
+
|
118
|
+
def calculate_total_tokens(usage)
|
119
|
+
usage.values.sum { |entry| entry[:total_tokens] }
|
120
|
+
end
|
121
|
+
|
122
|
+
def calculate_total_cost(usage)
|
123
|
+
usage.values.sum { |entry| entry[:cost] }
|
124
|
+
end
|
125
|
+
|
126
|
+
def calculate_total_requests(usage)
|
127
|
+
usage.values.sum { |entry| entry[:requests] }
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aidp
|
4
|
+
module Harness
|
5
|
+
module State
|
6
|
+
# Manages UI-specific state and user interactions
|
7
|
+
class UIState
|
8
|
+
def initialize(persistence)
|
9
|
+
@persistence = persistence
|
10
|
+
end
|
11
|
+
|
12
|
+
def user_input
|
13
|
+
state[:user_input] || {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_user_input(key, value)
|
17
|
+
current_input = user_input
|
18
|
+
current_input[key] = value
|
19
|
+
update_state(user_input: current_input)
|
20
|
+
end
|
21
|
+
|
22
|
+
def execution_log
|
23
|
+
state[:execution_log] || []
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_execution_log(entry)
|
27
|
+
current_log = execution_log
|
28
|
+
current_log << entry
|
29
|
+
update_state(execution_log: current_log)
|
30
|
+
end
|
31
|
+
|
32
|
+
def current_step
|
33
|
+
state[:current_step]
|
34
|
+
end
|
35
|
+
|
36
|
+
def set_current_step(step_name)
|
37
|
+
update_state(current_step: step_name)
|
38
|
+
end
|
39
|
+
|
40
|
+
def state_metadata
|
41
|
+
return {} unless @persistence.has_state?
|
42
|
+
|
43
|
+
state_data = state
|
44
|
+
{
|
45
|
+
mode: state_data[:mode],
|
46
|
+
saved_at: state_data[:saved_at],
|
47
|
+
current_step: state_data[:current_step],
|
48
|
+
state: state_data[:state],
|
49
|
+
last_updated: state_data[:last_updated]
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def state
|
56
|
+
@persistence.load_state
|
57
|
+
end
|
58
|
+
|
59
|
+
def update_state(updates)
|
60
|
+
current_state = state
|
61
|
+
updated_state = current_state.merge(updates)
|
62
|
+
updated_state[:last_updated] = Time.now
|
63
|
+
@persistence.save_state(updated_state)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../execute/progress"
|
4
|
+
require_relative "../../analyze/progress"
|
5
|
+
require_relative "../../execute/steps"
|
6
|
+
require_relative "../../analyze/steps"
|
7
|
+
|
8
|
+
module Aidp
|
9
|
+
module Harness
|
10
|
+
module State
|
11
|
+
# Manages workflow-specific state and progress tracking
|
12
|
+
class WorkflowState
|
13
|
+
def initialize(persistence, project_dir, mode)
|
14
|
+
@persistence = persistence
|
15
|
+
@project_dir = project_dir
|
16
|
+
@mode = mode
|
17
|
+
@progress_tracker = create_progress_tracker
|
18
|
+
end
|
19
|
+
|
20
|
+
def completed_steps
|
21
|
+
@progress_tracker.completed_steps
|
22
|
+
end
|
23
|
+
|
24
|
+
def current_step
|
25
|
+
@progress_tracker.current_step
|
26
|
+
end
|
27
|
+
|
28
|
+
def step_completed?(step_name)
|
29
|
+
@progress_tracker.step_completed?(step_name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def mark_step_completed(step_name)
|
33
|
+
@progress_tracker.mark_step_completed(step_name)
|
34
|
+
update_harness_state(current_step: nil, last_step_completed: step_name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def mark_step_in_progress(step_name)
|
38
|
+
@progress_tracker.mark_step_in_progress(step_name)
|
39
|
+
update_harness_state(current_step: step_name)
|
40
|
+
end
|
41
|
+
|
42
|
+
def next_step
|
43
|
+
@progress_tracker.next_step
|
44
|
+
end
|
45
|
+
|
46
|
+
def total_steps
|
47
|
+
steps_spec.keys.size
|
48
|
+
end
|
49
|
+
|
50
|
+
def all_steps_completed?
|
51
|
+
completed_steps.size == total_steps
|
52
|
+
end
|
53
|
+
|
54
|
+
def progress_percentage
|
55
|
+
return 100.0 if all_steps_completed?
|
56
|
+
(completed_steps.size.to_f / total_steps * 100).round(2)
|
57
|
+
end
|
58
|
+
|
59
|
+
def session_duration
|
60
|
+
return 0 unless @progress_tracker.started_at
|
61
|
+
Time.now - @progress_tracker.started_at
|
62
|
+
end
|
63
|
+
|
64
|
+
def reset_all
|
65
|
+
@progress_tracker.reset
|
66
|
+
@persistence.clear_state
|
67
|
+
end
|
68
|
+
|
69
|
+
def progress_summary
|
70
|
+
{
|
71
|
+
mode: @mode,
|
72
|
+
completed_steps: completed_steps.size,
|
73
|
+
total_steps: total_steps,
|
74
|
+
current_step: current_step,
|
75
|
+
next_step: next_step,
|
76
|
+
all_completed: all_steps_completed?,
|
77
|
+
started_at: @progress_tracker.started_at,
|
78
|
+
harness_state: harness_state,
|
79
|
+
progress_percentage: progress_percentage,
|
80
|
+
session_duration: session_duration
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
attr_reader :progress_tracker
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def create_progress_tracker
|
89
|
+
case @mode
|
90
|
+
when :analyze
|
91
|
+
Aidp::Analyze::Progress.new(@project_dir)
|
92
|
+
when :execute
|
93
|
+
Aidp::Execute::Progress.new(@project_dir)
|
94
|
+
else
|
95
|
+
raise ArgumentError, "Unsupported mode: #{@mode}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def steps_spec
|
100
|
+
case @mode
|
101
|
+
when :analyze
|
102
|
+
Aidp::Analyze::Steps::SPEC
|
103
|
+
when :execute
|
104
|
+
Aidp::Execute::Steps::SPEC
|
105
|
+
else
|
106
|
+
{}
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def harness_state
|
111
|
+
@persistence.has_state? ? @persistence.load_state : {}
|
112
|
+
end
|
113
|
+
|
114
|
+
def update_harness_state(updates)
|
115
|
+
current_state = @persistence.load_state
|
116
|
+
updated_state = current_state.merge(updates)
|
117
|
+
updated_state[:last_updated] = Time.now
|
118
|
+
@persistence.save_state(updated_state)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|