aidp 0.16.0 → 0.17.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/lib/aidp/analyze/error_handler.rb +32 -13
- data/lib/aidp/analyze/kb_inspector.rb +2 -3
- data/lib/aidp/analyze/progress.rb +6 -11
- data/lib/aidp/cli/mcp_dashboard.rb +1 -1
- data/lib/aidp/cli.rb +300 -33
- data/lib/aidp/config.rb +1 -1
- data/lib/aidp/execute/async_work_loop_runner.rb +2 -1
- data/lib/aidp/execute/checkpoint.rb +1 -1
- data/lib/aidp/execute/future_work_backlog.rb +1 -1
- data/lib/aidp/execute/progress.rb +6 -9
- data/lib/aidp/execute/repl_macros.rb +79 -10
- data/lib/aidp/harness/config_loader.rb +2 -2
- data/lib/aidp/harness/config_validator.rb +1 -1
- data/lib/aidp/harness/enhanced_runner.rb +16 -7
- data/lib/aidp/harness/error_handler.rb +12 -5
- data/lib/aidp/harness/provider_manager.rb +4 -19
- data/lib/aidp/harness/runner.rb +2 -2
- data/lib/aidp/harness/state/persistence.rb +9 -10
- data/lib/aidp/harness/state/workflow_state.rb +3 -2
- data/lib/aidp/harness/state_manager.rb +33 -97
- data/lib/aidp/harness/status_display.rb +22 -12
- data/lib/aidp/harness/ui/enhanced_tui.rb +3 -4
- data/lib/aidp/harness/user_interface.rb +11 -6
- data/lib/aidp/jobs/background_runner.rb +8 -2
- data/lib/aidp/logger.rb +1 -1
- data/lib/aidp/message_display.rb +9 -2
- data/lib/aidp/providers/anthropic.rb +1 -1
- data/lib/aidp/providers/base.rb +4 -4
- data/lib/aidp/providers/codex.rb +1 -1
- data/lib/aidp/providers/cursor.rb +1 -1
- data/lib/aidp/providers/gemini.rb +1 -1
- data/lib/aidp/providers/github_copilot.rb +1 -1
- data/lib/aidp/providers/macos_ui.rb +1 -1
- data/lib/aidp/providers/opencode.rb +1 -1
- data/lib/aidp/skills/registry.rb +31 -29
- data/lib/aidp/skills/router.rb +178 -0
- data/lib/aidp/skills/wizard/builder.rb +141 -0
- data/lib/aidp/skills/wizard/controller.rb +145 -0
- data/lib/aidp/skills/wizard/differ.rb +232 -0
- data/lib/aidp/skills/wizard/prompter.rb +317 -0
- data/lib/aidp/skills/wizard/template_library.rb +164 -0
- data/lib/aidp/skills/wizard/writer.rb +105 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/plan_generator.rb +1 -1
- data/lib/aidp/watch/repository_client.rb +13 -9
- data/lib/aidp/workflows/guided_agent.rb +2 -312
- data/lib/aidp/workstream_executor.rb +8 -2
- data/templates/skills/README.md +334 -0
- data/templates/skills/architecture_analyst/SKILL.md +173 -0
- data/templates/skills/product_strategist/SKILL.md +141 -0
- data/templates/skills/repository_analyst/SKILL.md +117 -0
- data/templates/skills/test_analyzer/SKILL.md +213 -0
- metadata +13 -1
|
@@ -11,19 +11,20 @@ module Aidp
|
|
|
11
11
|
module Harness
|
|
12
12
|
# Manages harness-specific state and persistence, extending existing progress tracking
|
|
13
13
|
class StateManager
|
|
14
|
-
def initialize(project_dir, mode)
|
|
14
|
+
def initialize(project_dir, mode, skip_persistence: false)
|
|
15
15
|
@project_dir = project_dir
|
|
16
16
|
@mode = mode
|
|
17
17
|
@state_dir = File.join(project_dir, ".aidp", "harness")
|
|
18
18
|
@state_file = File.join(@state_dir, "#{mode}_state.json")
|
|
19
19
|
@lock_file = File.join(@state_dir, "#{mode}_state.lock")
|
|
20
|
+
@skip_persistence = skip_persistence
|
|
21
|
+
@memory_state = {} if @skip_persistence # In-memory state for tests
|
|
20
22
|
|
|
21
|
-
# Initialize the appropriate progress tracker
|
|
22
23
|
case mode
|
|
23
24
|
when :analyze
|
|
24
|
-
@progress_tracker = Aidp::Analyze::Progress.new(project_dir)
|
|
25
|
+
@progress_tracker = Aidp::Analyze::Progress.new(project_dir, skip_persistence: @skip_persistence)
|
|
25
26
|
when :execute
|
|
26
|
-
@progress_tracker = Aidp::Execute::Progress.new(project_dir)
|
|
27
|
+
@progress_tracker = Aidp::Execute::Progress.new(project_dir, skip_persistence: @skip_persistence)
|
|
27
28
|
else
|
|
28
29
|
raise ArgumentError, "Unsupported mode: #{mode}"
|
|
29
30
|
end
|
|
@@ -33,21 +34,14 @@ module Aidp
|
|
|
33
34
|
|
|
34
35
|
# Check if state exists
|
|
35
36
|
def has_state?
|
|
36
|
-
|
|
37
|
-
return false if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
38
|
-
|
|
37
|
+
return false if @skip_persistence
|
|
39
38
|
File.exist?(@state_file)
|
|
40
39
|
end
|
|
41
40
|
|
|
42
41
|
# Load existing state
|
|
43
42
|
def load_state
|
|
44
|
-
|
|
45
|
-
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
46
|
-
return {}
|
|
47
|
-
end
|
|
48
|
-
|
|
43
|
+
return @memory_state if @skip_persistence
|
|
49
44
|
return {} unless has_state?
|
|
50
|
-
|
|
51
45
|
with_lock do
|
|
52
46
|
content = File.read(@state_file)
|
|
53
47
|
JSON.parse(content, symbolize_names: true)
|
|
@@ -59,20 +53,16 @@ module Aidp
|
|
|
59
53
|
|
|
60
54
|
# Save current state
|
|
61
55
|
def save_state(state_data)
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
if @skip_persistence
|
|
57
|
+
@memory_state = state_data
|
|
64
58
|
return
|
|
65
59
|
end
|
|
66
|
-
|
|
67
60
|
with_lock do
|
|
68
|
-
# Add metadata
|
|
69
61
|
state_with_metadata = state_data.merge(
|
|
70
62
|
mode: @mode,
|
|
71
63
|
project_dir: @project_dir,
|
|
72
64
|
saved_at: Time.now.iso8601
|
|
73
65
|
)
|
|
74
|
-
|
|
75
|
-
# Write to temporary file first, then rename (atomic operation)
|
|
76
66
|
temp_file = "#{@state_file}.tmp"
|
|
77
67
|
File.write(temp_file, JSON.pretty_generate(state_with_metadata))
|
|
78
68
|
File.rename(temp_file, @state_file)
|
|
@@ -81,9 +71,10 @@ module Aidp
|
|
|
81
71
|
|
|
82
72
|
# Clear state (for fresh start)
|
|
83
73
|
def clear_state
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
74
|
+
if @skip_persistence
|
|
75
|
+
@memory_state = {}
|
|
76
|
+
return
|
|
77
|
+
end
|
|
87
78
|
with_lock do
|
|
88
79
|
File.delete(@state_file) if File.exist?(@state_file)
|
|
89
80
|
end
|
|
@@ -91,11 +82,7 @@ module Aidp
|
|
|
91
82
|
|
|
92
83
|
# Get state metadata
|
|
93
84
|
def state_metadata
|
|
94
|
-
# In test mode, return empty metadata to avoid file operations
|
|
95
|
-
return {} if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
96
|
-
|
|
97
85
|
return {} unless has_state?
|
|
98
|
-
|
|
99
86
|
state = load_state
|
|
100
87
|
{
|
|
101
88
|
mode: state[:mode],
|
|
@@ -216,21 +203,6 @@ module Aidp
|
|
|
216
203
|
|
|
217
204
|
# Export state for debugging
|
|
218
205
|
def export_state
|
|
219
|
-
# In test mode, include test variables
|
|
220
|
-
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
221
|
-
test_state = {
|
|
222
|
-
current_workstream: @test_workstream,
|
|
223
|
-
workstream_path: @test_workstream_path,
|
|
224
|
-
workstream_branch: @test_workstream_branch
|
|
225
|
-
}
|
|
226
|
-
return {
|
|
227
|
-
state_file: @state_file,
|
|
228
|
-
has_state: false,
|
|
229
|
-
metadata: {},
|
|
230
|
-
state: test_state
|
|
231
|
-
}
|
|
232
|
-
end
|
|
233
|
-
|
|
234
206
|
{
|
|
235
207
|
state_file: @state_file,
|
|
236
208
|
has_state: has_state?,
|
|
@@ -309,11 +281,7 @@ module Aidp
|
|
|
309
281
|
@progress_tracker.reset
|
|
310
282
|
clear_state
|
|
311
283
|
# Also clear test workstream variables
|
|
312
|
-
|
|
313
|
-
@test_workstream = nil
|
|
314
|
-
@test_workstream_path = nil
|
|
315
|
-
@test_workstream_branch = nil
|
|
316
|
-
end
|
|
284
|
+
# Test-only instance vars removed; rely on persistence skip flag for isolation
|
|
317
285
|
end
|
|
318
286
|
|
|
319
287
|
# Get progress summary
|
|
@@ -472,11 +440,6 @@ module Aidp
|
|
|
472
440
|
|
|
473
441
|
# Get current workstream slug
|
|
474
442
|
def current_workstream
|
|
475
|
-
# In test mode, use instance variable
|
|
476
|
-
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
477
|
-
return @test_workstream
|
|
478
|
-
end
|
|
479
|
-
|
|
480
443
|
state = load_state
|
|
481
444
|
state[:current_workstream]
|
|
482
445
|
end
|
|
@@ -498,14 +461,6 @@ module Aidp
|
|
|
498
461
|
ws = Aidp::Worktree.info(slug: slug, project_dir: @project_dir)
|
|
499
462
|
return false unless ws
|
|
500
463
|
|
|
501
|
-
# In test mode, use instance variables
|
|
502
|
-
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
503
|
-
@test_workstream = slug
|
|
504
|
-
@test_workstream_path = ws[:path]
|
|
505
|
-
@test_workstream_branch = ws[:branch]
|
|
506
|
-
return true
|
|
507
|
-
end
|
|
508
|
-
|
|
509
464
|
update_state(
|
|
510
465
|
current_workstream: slug,
|
|
511
466
|
workstream_path: ws[:path],
|
|
@@ -516,14 +471,6 @@ module Aidp
|
|
|
516
471
|
|
|
517
472
|
# Clear current workstream (switch back to main project)
|
|
518
473
|
def clear_workstream
|
|
519
|
-
# In test mode, use instance variables
|
|
520
|
-
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
521
|
-
@test_workstream = nil
|
|
522
|
-
@test_workstream_path = nil
|
|
523
|
-
@test_workstream_branch = nil
|
|
524
|
-
return
|
|
525
|
-
end
|
|
526
|
-
|
|
527
474
|
update_state(
|
|
528
475
|
current_workstream: nil,
|
|
529
476
|
workstream_path: nil,
|
|
@@ -533,15 +480,6 @@ module Aidp
|
|
|
533
480
|
|
|
534
481
|
# Get workstream metadata
|
|
535
482
|
def workstream_metadata
|
|
536
|
-
# In test mode, use instance variables
|
|
537
|
-
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
538
|
-
return {
|
|
539
|
-
slug: @test_workstream,
|
|
540
|
-
path: @test_workstream_path,
|
|
541
|
-
branch: @test_workstream_branch
|
|
542
|
-
}
|
|
543
|
-
end
|
|
544
|
-
|
|
545
483
|
state = load_state
|
|
546
484
|
{
|
|
547
485
|
slug: state[:current_workstream],
|
|
@@ -642,34 +580,31 @@ module Aidp
|
|
|
642
580
|
end
|
|
643
581
|
|
|
644
582
|
def with_lock(&_block)
|
|
645
|
-
#
|
|
646
|
-
if
|
|
583
|
+
# Skip locking entirely when persistence disabled
|
|
584
|
+
if @skip_persistence
|
|
647
585
|
yield
|
|
648
586
|
return
|
|
649
587
|
end
|
|
650
588
|
|
|
651
|
-
# Improved file-based locking
|
|
589
|
+
# Improved file-based locking using exponential backoff
|
|
590
|
+
require_relative "../concurrency"
|
|
652
591
|
lock_acquired = false
|
|
653
|
-
timeout = 30 # 30 seconds in production
|
|
654
|
-
|
|
655
|
-
start_time = Time.now
|
|
656
|
-
while (Time.now - start_time) < timeout
|
|
657
|
-
begin
|
|
658
|
-
# Try to acquire lock
|
|
659
|
-
File.open(@lock_file, File::CREAT | File::EXCL | File::WRONLY) do |_lock|
|
|
660
|
-
lock_acquired = true
|
|
661
|
-
yield
|
|
662
|
-
break
|
|
663
|
-
end
|
|
664
|
-
rescue Errno::EEXIST
|
|
665
|
-
# Lock file exists, wait briefly and retry
|
|
666
|
-
sleep(0.1)
|
|
667
|
-
end
|
|
668
|
-
end
|
|
669
592
|
|
|
670
|
-
|
|
671
|
-
|
|
593
|
+
Aidp::Concurrency::Backoff.retry(
|
|
594
|
+
max_attempts: 300, # 300 attempts * ~0.1s = ~30s max
|
|
595
|
+
base: 0.1,
|
|
596
|
+
max_delay: 1.0,
|
|
597
|
+
strategy: :exponential,
|
|
598
|
+
on: [Errno::EEXIST]
|
|
599
|
+
) do
|
|
600
|
+
# Try to acquire lock
|
|
601
|
+
File.open(@lock_file, File::CREAT | File::EXCL | File::WRONLY) do |_lock|
|
|
602
|
+
lock_acquired = true
|
|
603
|
+
yield
|
|
604
|
+
end
|
|
672
605
|
end
|
|
606
|
+
rescue Aidp::Concurrency::MaxAttemptsExceededError
|
|
607
|
+
raise "Could not acquire state lock within timeout"
|
|
673
608
|
ensure
|
|
674
609
|
# Clean up lock file
|
|
675
610
|
File.delete(@lock_file) if lock_acquired && File.exist?(@lock_file)
|
|
@@ -677,6 +612,7 @@ module Aidp
|
|
|
677
612
|
|
|
678
613
|
# Clean up stale lock files (older than 30 seconds)
|
|
679
614
|
def cleanup_stale_lock
|
|
615
|
+
return if @skip_persistence
|
|
680
616
|
return unless File.exist?(@lock_file)
|
|
681
617
|
|
|
682
618
|
begin
|
|
@@ -46,7 +46,7 @@ module Aidp
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
# Start real-time status updates
|
|
49
|
-
def start_status_updates(display_mode = :compact)
|
|
49
|
+
def start_status_updates(display_mode = :compact, async_updates: true)
|
|
50
50
|
return if @running
|
|
51
51
|
|
|
52
52
|
@running = true
|
|
@@ -54,20 +54,30 @@ module Aidp
|
|
|
54
54
|
@display_mode = display_mode
|
|
55
55
|
@last_update = Time.now
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
57
|
+
if async_updates
|
|
58
|
+
begin
|
|
59
|
+
require "concurrent"
|
|
60
|
+
@status_future = Concurrent::Future.execute do
|
|
61
|
+
while @running
|
|
62
|
+
begin
|
|
63
|
+
collect_status_data
|
|
64
|
+
display_status
|
|
65
|
+
check_alerts
|
|
66
|
+
sleep(@update_interval)
|
|
67
|
+
rescue => e
|
|
68
|
+
handle_display_error(e)
|
|
69
|
+
end
|
|
68
70
|
end
|
|
69
71
|
end
|
|
72
|
+
rescue LoadError
|
|
73
|
+
# Fallback: perform single synchronous update if concurrent not available
|
|
74
|
+
collect_status_data
|
|
75
|
+
display_status
|
|
70
76
|
end
|
|
77
|
+
else
|
|
78
|
+
# Synchronous single update mode (useful for tests)
|
|
79
|
+
collect_status_data
|
|
80
|
+
display_status
|
|
71
81
|
end
|
|
72
82
|
end
|
|
73
83
|
|
|
@@ -20,16 +20,15 @@ module Aidp
|
|
|
20
20
|
|
|
21
21
|
class DisplayError < TUIError; end
|
|
22
22
|
|
|
23
|
-
def initialize(prompt: TTY::Prompt.new)
|
|
23
|
+
def initialize(prompt: TTY::Prompt.new, tty: $stdin)
|
|
24
24
|
@cursor = TTY::Cursor
|
|
25
25
|
@screen = TTY::Screen
|
|
26
26
|
@pastel = Pastel.new
|
|
27
27
|
@prompt = prompt
|
|
28
28
|
|
|
29
29
|
# Headless (non-interactive) detection for test/CI environments:
|
|
30
|
-
# -
|
|
31
|
-
|
|
32
|
-
@headless = !!(defined?(RSpec) || ENV["RSPEC_RUNNING"] || $stdin.nil? || !$stdin.tty?)
|
|
30
|
+
# - STDIN not a TTY (captured by PTY/tmux harness or test environment)
|
|
31
|
+
@headless = !!(tty.nil? || !tty.tty?)
|
|
33
32
|
|
|
34
33
|
@current_mode = nil
|
|
35
34
|
@workflow_active = false
|
|
@@ -2052,17 +2052,22 @@ module Aidp
|
|
|
2052
2052
|
# ============================================================================
|
|
2053
2053
|
|
|
2054
2054
|
# Start the control interface
|
|
2055
|
-
def start_control_interface
|
|
2055
|
+
def start_control_interface(async_control: true)
|
|
2056
2056
|
return unless @control_interface_enabled
|
|
2057
2057
|
|
|
2058
2058
|
@control_mutex.synchronize do
|
|
2059
2059
|
return if @control_future&.pending?
|
|
2060
2060
|
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2061
|
+
if async_control
|
|
2062
|
+
begin
|
|
2063
|
+
require "concurrent"
|
|
2064
|
+
@control_future = Concurrent::Future.execute { control_interface_loop }
|
|
2065
|
+
rescue LoadError
|
|
2066
|
+
# Fallback: run a single synchronous loop iteration if concurrent not available
|
|
2067
|
+
control_interface_loop_iteration
|
|
2068
|
+
end
|
|
2069
|
+
else
|
|
2070
|
+
control_interface_loop_iteration
|
|
2066
2071
|
end
|
|
2067
2072
|
end
|
|
2068
2073
|
|
|
@@ -17,12 +17,18 @@ module Aidp
|
|
|
17
17
|
|
|
18
18
|
attr_reader :project_dir, :jobs_dir
|
|
19
19
|
|
|
20
|
-
def initialize(project_dir = Dir.pwd)
|
|
20
|
+
def initialize(project_dir = Dir.pwd, suppress_display: false)
|
|
21
21
|
@project_dir = project_dir
|
|
22
22
|
@jobs_dir = File.join(project_dir, ".aidp", "jobs")
|
|
23
|
+
@suppress_display = suppress_display
|
|
23
24
|
ensure_jobs_directory
|
|
24
25
|
end
|
|
25
26
|
|
|
27
|
+
def display_message(msg, type: :info)
|
|
28
|
+
return if @suppress_display
|
|
29
|
+
super
|
|
30
|
+
end
|
|
31
|
+
|
|
26
32
|
# Start a background job
|
|
27
33
|
# Returns job_id
|
|
28
34
|
def start(mode, options = {})
|
|
@@ -213,7 +219,7 @@ module Aidp
|
|
|
213
219
|
return nil unless File.exist?(metadata_file)
|
|
214
220
|
|
|
215
221
|
# Return raw metadata with times as ISO8601 strings to avoid unsafe class loading
|
|
216
|
-
YAML.
|
|
222
|
+
YAML.safe_load_file(metadata_file, permitted_classes: [Date, Time, Symbol], aliases: true)
|
|
217
223
|
rescue
|
|
218
224
|
nil
|
|
219
225
|
end
|
data/lib/aidp/logger.rb
CHANGED
|
@@ -114,7 +114,7 @@ module Aidp
|
|
|
114
114
|
logger
|
|
115
115
|
rescue => e
|
|
116
116
|
# Fall back to STDERR if file logging fails
|
|
117
|
-
warn "[AIDP Logger] Failed to create log file at #{path}: #{e.message}. Falling back to STDERR."
|
|
117
|
+
Kernel.warn "[AIDP Logger] Failed to create log file at #{path}: #{e.message}. Falling back to STDERR."
|
|
118
118
|
logger = ::Logger.new($stderr)
|
|
119
119
|
logger.level = ::Logger::DEBUG
|
|
120
120
|
logger.formatter = proc { |severity, datetime, progname, msg| "#{msg}\n" }
|
data/lib/aidp/message_display.rb
CHANGED
|
@@ -39,9 +39,16 @@ module Aidp
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
module ClassMethods
|
|
42
|
-
# Class-level display helper (
|
|
42
|
+
# Class-level display helper (uses fresh prompt to respect $stdout changes)
|
|
43
43
|
def display_message(message, type: :info)
|
|
44
|
-
|
|
44
|
+
class_message_display_prompt.say(message, color: COLOR_MAP.fetch(type, :white))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
# Don't memoize - create fresh prompt each time to respect $stdout redirection in tests
|
|
50
|
+
def class_message_display_prompt
|
|
51
|
+
TTY::Prompt.new
|
|
45
52
|
end
|
|
46
53
|
end
|
|
47
54
|
end
|
data/lib/aidp/providers/base.rb
CHANGED
|
@@ -71,8 +71,8 @@ module Aidp
|
|
|
71
71
|
name
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
def
|
|
75
|
-
raise NotImplementedError, "#{self.class} must implement #
|
|
74
|
+
def send_message(prompt:, session: nil)
|
|
75
|
+
raise NotImplementedError, "#{self.class} must implement #send_message"
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
# Fetch MCP servers configured for this provider
|
|
@@ -279,8 +279,8 @@ module Aidp
|
|
|
279
279
|
error_message = nil
|
|
280
280
|
|
|
281
281
|
begin
|
|
282
|
-
# Call the original
|
|
283
|
-
result =
|
|
282
|
+
# Call the original send_message method
|
|
283
|
+
result = send_message(prompt: prompt, session: session)
|
|
284
284
|
success = true
|
|
285
285
|
|
|
286
286
|
# Extract token usage and cost if available
|
data/lib/aidp/providers/codex.rb
CHANGED
|
@@ -16,7 +16,7 @@ module Aidp
|
|
|
16
16
|
"macos"
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
def
|
|
19
|
+
def send_message(prompt:, session: nil)
|
|
20
20
|
raise "macOS UI not available on this platform" unless self.class.available?
|
|
21
21
|
|
|
22
22
|
debug_provider("macos", "Starting Cursor interaction", {prompt_length: prompt.length})
|
data/lib/aidp/skills/registry.rb
CHANGED
|
@@ -8,10 +8,10 @@ module Aidp
|
|
|
8
8
|
# lookup, filtering, and management capabilities.
|
|
9
9
|
#
|
|
10
10
|
# Skills are loaded from:
|
|
11
|
-
# 1.
|
|
12
|
-
# 2.
|
|
11
|
+
# 1. Template skills directory (gem templates/skills/) - built-in templates
|
|
12
|
+
# 2. Project skills directory (.aidp/skills/) - project-specific skills
|
|
13
13
|
#
|
|
14
|
-
#
|
|
14
|
+
# Project skills with matching IDs override template skills.
|
|
15
15
|
#
|
|
16
16
|
# @example Basic usage
|
|
17
17
|
# registry = Registry.new(project_dir: "/path/to/project")
|
|
@@ -39,8 +39,8 @@ module Aidp
|
|
|
39
39
|
# Load skills from all search paths
|
|
40
40
|
#
|
|
41
41
|
# Skills are loaded in order:
|
|
42
|
-
# 1.
|
|
43
|
-
# 2.
|
|
42
|
+
# 1. Template skills (gem templates/skills/) - built-in templates
|
|
43
|
+
# 2. Project skills (.aidp/skills/) - override templates
|
|
44
44
|
#
|
|
45
45
|
# @return [Integer] Number of skills loaded
|
|
46
46
|
def load_skills
|
|
@@ -48,13 +48,13 @@ module Aidp
|
|
|
48
48
|
|
|
49
49
|
@skills = {}
|
|
50
50
|
|
|
51
|
-
# Load
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
# Load template skills first
|
|
52
|
+
template_skills = load_from_path(template_skills_path)
|
|
53
|
+
template_skills.each { |skill| register_skill(skill, source: :template) }
|
|
54
54
|
|
|
55
|
-
# Load
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
# Load project skills (override templates if IDs match)
|
|
56
|
+
project_skills = load_from_path(project_skills_path)
|
|
57
|
+
project_skills.each { |skill| register_skill(skill, source: :project) }
|
|
58
58
|
|
|
59
59
|
@loaded = true
|
|
60
60
|
|
|
@@ -139,13 +139,13 @@ module Aidp
|
|
|
139
139
|
|
|
140
140
|
# Get skill IDs grouped by source
|
|
141
141
|
#
|
|
142
|
-
# @return [Hash] Hash with :
|
|
142
|
+
# @return [Hash] Hash with :template and :project arrays
|
|
143
143
|
def by_source
|
|
144
144
|
load_skills unless loaded?
|
|
145
145
|
|
|
146
146
|
{
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
template: @skills.values.select { |s| template_skill?(s) }.map(&:id),
|
|
148
|
+
project: @skills.values.select { |s| project_skill?(s) }.map(&:id)
|
|
149
149
|
}
|
|
150
150
|
end
|
|
151
151
|
|
|
@@ -186,34 +186,36 @@ module Aidp
|
|
|
186
186
|
Loader.load_from_directory(path, provider: provider)
|
|
187
187
|
end
|
|
188
188
|
|
|
189
|
-
# Get
|
|
189
|
+
# Get template skills path (from gem)
|
|
190
190
|
#
|
|
191
|
-
# @return [String] Path to
|
|
192
|
-
def
|
|
193
|
-
|
|
191
|
+
# @return [String] Path to template skills directory
|
|
192
|
+
def template_skills_path
|
|
193
|
+
# Get the gem root directory (go up from lib/aidp/skills/registry.rb)
|
|
194
|
+
gem_root = File.expand_path("../../../..", __dir__)
|
|
195
|
+
File.join(gem_root, "templates", "skills")
|
|
194
196
|
end
|
|
195
197
|
|
|
196
|
-
# Get
|
|
198
|
+
# Get project skills path
|
|
197
199
|
#
|
|
198
|
-
# @return [String] Path to
|
|
199
|
-
def
|
|
200
|
+
# @return [String] Path to project-specific skills directory
|
|
201
|
+
def project_skills_path
|
|
200
202
|
File.join(project_dir, ".aidp", "skills")
|
|
201
203
|
end
|
|
202
204
|
|
|
203
|
-
# Check if skill is from
|
|
205
|
+
# Check if skill is from template directory
|
|
204
206
|
#
|
|
205
207
|
# @param skill [Skill] Skill to check
|
|
206
|
-
# @return [Boolean] True if
|
|
207
|
-
def
|
|
208
|
-
skill.source_path.start_with?(
|
|
208
|
+
# @return [Boolean] True if template
|
|
209
|
+
def template_skill?(skill)
|
|
210
|
+
skill.source_path.start_with?(template_skills_path)
|
|
209
211
|
end
|
|
210
212
|
|
|
211
|
-
# Check if skill is from
|
|
213
|
+
# Check if skill is from project directory
|
|
212
214
|
#
|
|
213
215
|
# @param skill [Skill] Skill to check
|
|
214
|
-
# @return [Boolean] True if
|
|
215
|
-
def
|
|
216
|
-
skill.source_path.start_with?(
|
|
216
|
+
# @return [Boolean] True if project
|
|
217
|
+
def project_skill?(skill)
|
|
218
|
+
skill.source_path.start_with?(project_skills_path)
|
|
217
219
|
end
|
|
218
220
|
end
|
|
219
221
|
end
|