aidp 0.13.0 → 0.14.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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -0
  3. data/lib/aidp/cli/first_run_wizard.rb +28 -303
  4. data/lib/aidp/cli/issue_importer.rb +359 -0
  5. data/lib/aidp/cli.rb +151 -3
  6. data/lib/aidp/daemon/process_manager.rb +146 -0
  7. data/lib/aidp/daemon/runner.rb +232 -0
  8. data/lib/aidp/execute/async_work_loop_runner.rb +216 -0
  9. data/lib/aidp/execute/future_work_backlog.rb +411 -0
  10. data/lib/aidp/execute/guard_policy.rb +246 -0
  11. data/lib/aidp/execute/instruction_queue.rb +131 -0
  12. data/lib/aidp/execute/interactive_repl.rb +335 -0
  13. data/lib/aidp/execute/repl_macros.rb +651 -0
  14. data/lib/aidp/execute/steps.rb +8 -0
  15. data/lib/aidp/execute/work_loop_runner.rb +322 -36
  16. data/lib/aidp/execute/work_loop_state.rb +162 -0
  17. data/lib/aidp/harness/config_schema.rb +88 -0
  18. data/lib/aidp/harness/configuration.rb +48 -1
  19. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +2 -0
  20. data/lib/aidp/init/doc_generator.rb +256 -0
  21. data/lib/aidp/init/project_analyzer.rb +343 -0
  22. data/lib/aidp/init/runner.rb +83 -0
  23. data/lib/aidp/init.rb +5 -0
  24. data/lib/aidp/logger.rb +279 -0
  25. data/lib/aidp/setup/wizard.rb +777 -0
  26. data/lib/aidp/tooling_detector.rb +115 -0
  27. data/lib/aidp/version.rb +1 -1
  28. data/lib/aidp/watch/build_processor.rb +282 -0
  29. data/lib/aidp/watch/plan_generator.rb +166 -0
  30. data/lib/aidp/watch/plan_processor.rb +83 -0
  31. data/lib/aidp/watch/repository_client.rb +243 -0
  32. data/lib/aidp/watch/runner.rb +93 -0
  33. data/lib/aidp/watch/state_store.rb +105 -0
  34. data/lib/aidp/watch.rb +9 -0
  35. data/lib/aidp.rb +14 -0
  36. data/templates/implementation/simple_task.md +36 -0
  37. metadata +26 -1
@@ -0,0 +1,359 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "net/http"
5
+ require "uri"
6
+ require "open3"
7
+
8
+ module Aidp
9
+ # Handles importing GitHub issues into AIDP work loops
10
+ class IssueImporter
11
+ include Aidp::MessageDisplay
12
+
13
+ # Initialize the importer
14
+ #
15
+ # @param gh_available [Boolean, nil] (test-only) forcibly sets whether gh CLI is considered
16
+ # available. When nil (default) we auto-detect. This enables deterministic specs without
17
+ # depending on developer environment.
18
+ def initialize(gh_available: nil, enable_bootstrap: true)
19
+ @gh_available = gh_available.nil? ? gh_cli_available? : gh_available
20
+ @enable_bootstrap = enable_bootstrap
21
+ end
22
+
23
+ def import_issue(identifier)
24
+ issue_url = normalize_issue_identifier(identifier)
25
+ return nil unless issue_url
26
+
27
+ issue_data = fetch_issue_data(issue_url)
28
+ return nil unless issue_data
29
+
30
+ display_imported_issue(issue_data)
31
+ create_work_loop_prompt(issue_data)
32
+ perform_bootstrap(issue_data)
33
+
34
+ issue_data
35
+ end
36
+
37
+ private
38
+
39
+ def normalize_issue_identifier(identifier)
40
+ case identifier
41
+ when /^https:\/\/github\.com\/([^\/]+)\/([^\/]+)\/issues\/(\d+)/
42
+ # Full URL: https://github.com/owner/repo/issues/123
43
+ identifier
44
+ when /^(\d+)$/
45
+ # Just issue number - need to detect current repo
46
+ current_repo = detect_current_github_repo
47
+ if current_repo
48
+ "https://github.com/#{current_repo}/issues/#{identifier}"
49
+ else
50
+ display_message("❌ Issue number provided but not in a GitHub repository", type: :error)
51
+ nil
52
+ end
53
+ when /^([^\/]+)\/([^\/]+)#(\d+)$/
54
+ # owner/repo#123 format
55
+ owner, repo, number = $1, $2, $3
56
+ "https://github.com/#{owner}/#{repo}/issues/#{number}"
57
+ else
58
+ display_message("❌ Invalid issue identifier. Use: URL, number, or owner/repo#number", type: :error)
59
+ nil
60
+ end
61
+ end
62
+
63
+ def detect_current_github_repo
64
+ return nil unless File.exist?(".git")
65
+
66
+ # Try to get origin URL
67
+ stdout, _stderr, status = Open3.capture3("git remote get-url origin")
68
+ return nil unless status.success?
69
+
70
+ origin_url = stdout.strip
71
+
72
+ # Parse GitHub URL (both SSH and HTTPS)
73
+ if origin_url =~ %r{github\.com[:/]([^/]+)/([^/\s]+?)(?:\.git)?$}
74
+ "#{$1}/#{$2}"
75
+ end
76
+ end
77
+
78
+ def fetch_issue_data(issue_url)
79
+ # Extract owner, repo, and issue number from URL
80
+ match = issue_url.match(/github\.com\/([^\/]+)\/([^\/]+)\/issues\/(\d+)/)
81
+ return nil unless match
82
+
83
+ owner, repo, number = match[1], match[2], match[3]
84
+
85
+ # First try GitHub CLI if available (works for private repos)
86
+ if @gh_available
87
+ display_message("🔍 Fetching issue via GitHub CLI...", type: :info)
88
+ issue_data = fetch_via_gh_cli(owner, repo, number)
89
+ return issue_data if issue_data
90
+ end
91
+
92
+ # Fallback to public API
93
+ display_message("🔍 Fetching issue via GitHub API...", type: :info)
94
+ fetch_via_api(owner, repo, number)
95
+ end
96
+
97
+ def fetch_via_gh_cli(owner, repo, number)
98
+ cmd = [
99
+ "gh", "issue", "view", number,
100
+ "--repo", "#{owner}/#{repo}",
101
+ "--json", "title,body,labels,milestone,comments,state,assignees,number,url"
102
+ ]
103
+
104
+ stdout, stderr, status = Open3.capture3(*cmd)
105
+
106
+ unless status.success?
107
+ display_message("⚠️ GitHub CLI failed: #{stderr.strip}", type: :warn)
108
+ return nil
109
+ end
110
+
111
+ begin
112
+ data = JSON.parse(stdout)
113
+ normalize_gh_cli_data(data)
114
+ rescue JSON::ParserError => e
115
+ display_message("❌ Failed to parse GitHub CLI response: #{e.message}", type: :error)
116
+ nil
117
+ end
118
+ end
119
+
120
+ def fetch_via_api(owner, repo, number)
121
+ uri = URI("https://api.github.com/repos/#{owner}/#{repo}/issues/#{number}")
122
+
123
+ begin
124
+ response = Net::HTTP.get_response(uri)
125
+
126
+ unless response.code == "200"
127
+ case response.code
128
+ when "404"
129
+ display_message("❌ Issue not found (may be private)", type: :error)
130
+ when "403"
131
+ display_message("❌ API rate limit exceeded", type: :error)
132
+ else
133
+ display_message("❌ GitHub API error: #{response.code}", type: :error)
134
+ end
135
+ return nil
136
+ end
137
+
138
+ data = JSON.parse(response.body)
139
+ normalize_api_data(data)
140
+ rescue => e
141
+ display_message("❌ Failed to fetch issue: #{e.message}", type: :error)
142
+ nil
143
+ end
144
+ end
145
+
146
+ def normalize_gh_cli_data(data)
147
+ {
148
+ number: data["number"],
149
+ title: data["title"],
150
+ body: data["body"] || "",
151
+ state: data["state"],
152
+ url: data["url"],
153
+ labels: data["labels"]&.map { |l| l["name"] } || [],
154
+ milestone: data["milestone"]&.dig("title"),
155
+ assignees: data["assignees"]&.map { |a| a["login"] } || [],
156
+ comments: data["comments"]&.length || 0,
157
+ source: "gh_cli"
158
+ }
159
+ end
160
+
161
+ def normalize_api_data(data)
162
+ {
163
+ number: data["number"],
164
+ title: data["title"],
165
+ body: data["body"] || "",
166
+ state: data["state"],
167
+ url: data["html_url"],
168
+ labels: data["labels"]&.map { |l| l["name"] } || [],
169
+ milestone: data["milestone"]&.dig("title"),
170
+ assignees: data["assignees"]&.map { |a| a["login"] } || [],
171
+ comments: data["comments"] || 0,
172
+ source: "api"
173
+ }
174
+ end
175
+
176
+ def display_imported_issue(issue_data)
177
+ display_message("✅ Successfully imported GitHub issue", type: :success)
178
+ display_message("", type: :info)
179
+ display_message("📋 Issue ##{issue_data[:number]}: #{issue_data[:title]}", type: :highlight)
180
+ display_message("🔗 URL: #{issue_data[:url]}", type: :muted)
181
+ display_message("📊 State: #{issue_data[:state]}", type: :info)
182
+
183
+ unless issue_data[:labels].empty?
184
+ display_message("🏷️ Labels: #{issue_data[:labels].join(", ")}", type: :info)
185
+ end
186
+
187
+ if issue_data[:milestone]
188
+ display_message("🎯 Milestone: #{issue_data[:milestone]}", type: :info)
189
+ end
190
+
191
+ unless issue_data[:assignees].empty?
192
+ display_message("👤 Assignees: #{issue_data[:assignees].join(", ")}", type: :info)
193
+ end
194
+
195
+ if issue_data[:comments] > 0
196
+ display_message("💬 Comments: #{issue_data[:comments]}", type: :info)
197
+ end
198
+
199
+ display_message("", type: :info)
200
+ display_message("📝 Description:", type: :info)
201
+ # Truncate body if too long
202
+ body = issue_data[:body]
203
+ if body.length > 500
204
+ display_message("#{body[0..497]}...", type: :muted)
205
+ display_message("[Truncated - full description available in work loop]", type: :muted)
206
+ else
207
+ display_message(body, type: :muted)
208
+ end
209
+ end
210
+
211
+ def create_work_loop_prompt(issue_data)
212
+ # Create PROMPT.md for work loop
213
+ prompt_content = generate_prompt_content(issue_data)
214
+
215
+ File.write("PROMPT.md", prompt_content)
216
+ display_message("", type: :info)
217
+ display_message("📄 Created PROMPT.md for work loop", type: :success)
218
+ display_message(" You can now run 'aidp execute' to start working on this issue", type: :info)
219
+ end
220
+
221
+ def generate_prompt_content(issue_data)
222
+ <<~PROMPT
223
+ # Work Loop: GitHub Issue ##{issue_data[:number]}
224
+
225
+ ## Instructions
226
+ You are working on a GitHub issue imported into AIDP. Your responsibilities:
227
+ 1. Read this PROMPT.md file to understand what needs to be done
228
+ 2. Complete the work described in the issue below
229
+ 3. **IMPORTANT**: Edit this PROMPT.md file yourself to:
230
+ - Remove completed items
231
+ - Update with current status
232
+ - Keep it concise (remove unnecessary context)
233
+ - Mark the issue COMPLETE when 100% done
234
+ 4. After you finish, tests and linters will run automatically
235
+ 5. If tests/linters fail, you'll see the errors in the next iteration
236
+
237
+ ## Completion Criteria
238
+ Mark this issue COMPLETE by adding this line to PROMPT.md:
239
+ ```
240
+ STATUS: COMPLETE
241
+ ```
242
+
243
+ ## GitHub Issue Details
244
+
245
+ **Issue ##{issue_data[:number]}**: #{issue_data[:title]}
246
+ **URL**: #{issue_data[:url]}
247
+ **State**: #{issue_data[:state]}
248
+ #{"**Labels**: #{issue_data[:labels].join(", ")}" unless issue_data[:labels].empty?}
249
+ #{"**Milestone**: #{issue_data[:milestone]}" if issue_data[:milestone]}
250
+ #{"**Assignees**: #{issue_data[:assignees].join(", ")}" unless issue_data[:assignees].empty?}
251
+
252
+ ## Issue Description
253
+
254
+ #{issue_data[:body]}
255
+
256
+ ## Implementation Plan
257
+
258
+ Based on the issue description above, implement the requested changes:
259
+
260
+ 1. [ ] Analyze the requirements from the issue description
261
+ 2. [ ] Plan the implementation approach
262
+ 3. [ ] Implement the requested functionality
263
+ 4. [ ] Add or update tests as needed
264
+ 5. [ ] Update documentation if required
265
+ 6. [ ] Verify all tests pass
266
+ 7. [ ] Mark STATUS: COMPLETE
267
+
268
+ ## Notes
269
+
270
+ - This issue was imported via AIDP issue import (source: #{issue_data[:source]})
271
+ - Original issue URL: #{issue_data[:url]}
272
+ - If you need clarification, refer back to the original issue
273
+ - Consider any linked PRs or related issues mentioned in the description
274
+ PROMPT
275
+ end
276
+
277
+ def gh_cli_available?
278
+ _stdout, _stderr, status = Open3.capture3("gh", "--version")
279
+ status.success?
280
+ rescue Errno::ENOENT
281
+ false
282
+ end
283
+
284
+ def perform_bootstrap(issue_data)
285
+ return if ENV["AIDP_DISABLE_BOOTSTRAP"] == "1"
286
+ return unless @enable_bootstrap
287
+ return unless git_repo?
288
+
289
+ ensure_initial_commit
290
+
291
+ branch = branch_name(issue_data)
292
+ create_branch(branch)
293
+ create_checkpoint_tag(issue_data)
294
+ detect_and_record_tooling
295
+ rescue => e
296
+ display_message("⚠️ Bootstrap step failed: #{e.message}", type: :warn)
297
+ end
298
+
299
+ def git_repo?
300
+ File.exist?(".git")
301
+ end
302
+
303
+ # Ensure we have an initial commit so that branch and tag creation succeed in fresh repos.
304
+ # In an empty repo without commits, git checkout -b and git tag will fail due to missing HEAD.
305
+ def ensure_initial_commit
306
+ _stdout, _stderr, status = Open3.capture3("git", "rev-parse", "--verify", "HEAD")
307
+ return if status.success? # already have at least one commit
308
+
309
+ # Create a placeholder file if nothing is present so commit has content
310
+ placeholder = ".aidp_bootstrap"
311
+ unless File.exist?(placeholder)
312
+ File.write(placeholder, "Initial commit placeholder for AIDP bootstrap\n")
313
+ end
314
+
315
+ Open3.capture3("git", "add", "-A")
316
+ _c_stdout, c_stderr, c_status = Open3.capture3("git", "commit", "-m", "chore(aidp): initial commit before bootstrap")
317
+ unless c_status.success?
318
+ display_message("⚠️ Could not create initial commit: #{c_stderr.strip}", type: :warn)
319
+ end
320
+ end
321
+
322
+ def branch_name(issue_data)
323
+ slug = issue_data[:title].downcase.gsub(/[^a-z0-9]+/, "-").gsub(/^-|-$/, "")[0, 40]
324
+ "aidp/iss-#{issue_data[:number]}-#{slug}"
325
+ end
326
+
327
+ def create_branch(name)
328
+ _stdout, stderr, status = Open3.capture3("git", "checkout", "-b", name)
329
+ if status.success?
330
+ display_message("🌿 Created branch #{name}", type: :success)
331
+ else
332
+ display_message("⚠️ Could not create branch: #{stderr.strip}", type: :warn)
333
+ end
334
+ end
335
+
336
+ def create_checkpoint_tag(issue_data)
337
+ tag = "aidp-start/#{issue_data[:number]}"
338
+ _stdout, stderr, status = Open3.capture3("git", "tag", tag)
339
+ if status.success?
340
+ display_message("🏷️ Added checkpoint tag #{tag}", type: :success)
341
+ else
342
+ display_message("⚠️ Could not create tag: #{stderr.strip}", type: :warn)
343
+ end
344
+ end
345
+
346
+ def detect_and_record_tooling
347
+ require_relative "../tooling_detector"
348
+ result = Aidp::ToolingDetector.detect
349
+ return if result.test_commands.empty? && result.lint_commands.empty?
350
+
351
+ tooling_info = "# Detected Tooling\n\n" \
352
+ + (result.test_commands.empty? ? "" : "Test Commands:\n#{result.test_commands.map { |c| "- #{c}" }.join("\n")}\n\n") \
353
+ + (result.lint_commands.empty? ? "" : "Lint Commands:\n#{result.lint_commands.map { |c| "- #{c}" }.join("\n")}\n")
354
+
355
+ File.open("PROMPT.md", "a") { |f| f.puts("\n---\n\n#{tooling_info}") }
356
+ display_message("🧪 Detected tooling and appended to PROMPT.md", type: :info)
357
+ end
358
+ end
359
+ end
data/lib/aidp/cli.rb CHANGED
@@ -195,16 +195,20 @@ module Aidp
195
195
  # Get workflow configuration (no spinner - may wait for user input)
196
196
  workflow_config = workflow_selector.select_workflow(harness_mode: false, mode: mode)
197
197
 
198
+ # For guided mode, use the mode determined by the guided workflow selector
199
+ # Otherwise use the initially selected mode
200
+ actual_mode = workflow_config[:mode] || mode
201
+
198
202
  # Pass workflow configuration to harness
199
203
  harness_options = {
200
- mode: mode,
204
+ mode: actual_mode,
201
205
  workflow_type: workflow_config[:workflow_type],
202
206
  selected_steps: workflow_config[:steps],
203
207
  user_input: workflow_config[:user_input]
204
208
  }
205
209
 
206
210
  # Create and run the enhanced harness
207
- harness_runner = Aidp::Harness::EnhancedRunner.new(Dir.pwd, mode, harness_options)
211
+ harness_runner = Aidp::Harness::EnhancedRunner.new(Dir.pwd, actual_mode, harness_options)
208
212
  result = harness_runner.run
209
213
  display_harness_result(result)
210
214
  0
@@ -232,6 +236,8 @@ module Aidp
232
236
  opts.separator "Commands:"
233
237
  opts.separator " analyze [--background] Start analyze mode workflow"
234
238
  opts.separator " execute [--background] Start execute mode workflow"
239
+ opts.separator " init Analyse project and bootstrap quality docs"
240
+ opts.separator " watch <issues_url> Run fully automatic watch mode"
235
241
  opts.separator " status Show current system status"
236
242
  opts.separator " jobs Manage background jobs"
237
243
  opts.separator " list - List all jobs"
@@ -251,6 +257,7 @@ module Aidp
251
257
  opts.separator " dashboard - Show all MCP servers across providers"
252
258
  opts.separator " check <servers...> - Check provider eligibility for servers"
253
259
  opts.separator " harness Manage harness state"
260
+ opts.separator " config Manage configuration"
254
261
  opts.separator " status - Show harness status"
255
262
  opts.separator " reset - Reset harness state"
256
263
  opts.separator " kb Knowledge base commands"
@@ -277,6 +284,14 @@ module Aidp
277
284
  opts.separator " aidp checkpoint summary --watch # Auto-refresh every 5s"
278
285
  opts.separator " aidp checkpoint summary --watch --interval 10"
279
286
  opts.separator ""
287
+ opts.separator " # Project bootstrap"
288
+ opts.separator " aidp init"
289
+ opts.separator " aidp config --interactive"
290
+ opts.separator " # Fully automatic orchestration"
291
+ opts.separator ""
292
+ opts.separator " aidp watch https://github.com/<org>/<repo>/issues"
293
+ opts.separator " aidp watch owner/repo --interval 120 --provider claude"
294
+ opts.separator ""
280
295
  opts.separator " # Other commands"
281
296
  opts.separator " aidp providers # Check provider health"
282
297
  opts.separator " aidp providers info claude # Show detailed provider info"
@@ -296,7 +311,7 @@ module Aidp
296
311
  # Determine if the invocation is a subcommand style call
297
312
  def subcommand?(args)
298
313
  return false if args.nil? || args.empty?
299
- %w[status jobs kb harness execute analyze providers checkpoint mcp].include?(args.first)
314
+ %w[status jobs kb harness execute analyze providers checkpoint mcp issue config init watch].include?(args.first)
300
315
  end
301
316
 
302
317
  def run_subcommand(args)
@@ -311,6 +326,10 @@ module Aidp
311
326
  when "providers" then run_providers_command(args)
312
327
  when "checkpoint" then run_checkpoint_command(args)
313
328
  when "mcp" then run_mcp_command(args)
329
+ when "issue" then run_issue_command(args)
330
+ when "config" then run_config_command(args)
331
+ when "init" then run_init_command
332
+ when "watch" then run_watch_command(args)
314
333
  else
315
334
  display_message("Unknown command: #{cmd}", type: :info)
316
335
  return 1
@@ -907,6 +926,135 @@ module Aidp
907
926
  display_message(" Message: #{result[:message]}", type: :info) if result[:message]
908
927
  end
909
928
  end
929
+
930
+ def run_issue_command(args)
931
+ require_relative "cli/issue_importer"
932
+
933
+ usage = <<~USAGE
934
+ Usage: aidp issue <command> [options]
935
+
936
+ Commands:
937
+ import <identifier> Import a GitHub issue
938
+ Identifier can be:
939
+ - Full URL: https://github.com/owner/repo/issues/123
940
+ - Issue number: 123 (when in a git repo)
941
+ - Shorthand: owner/repo#123
942
+
943
+ Examples:
944
+ aidp issue import https://github.com/rails/rails/issues/12345
945
+ aidp issue import 123
946
+ aidp issue import rails/rails#12345
947
+
948
+ Options:
949
+ -h, --help Show this help message
950
+ USAGE
951
+
952
+ if args.empty? || args.include?("-h") || args.include?("--help")
953
+ display_message(usage, type: :info)
954
+ return
955
+ end
956
+
957
+ command = args.shift
958
+ case command
959
+ when "import"
960
+ identifier = args.shift
961
+ unless identifier
962
+ display_message("❌ Missing issue identifier", type: :error)
963
+ display_message(usage, type: :info)
964
+ return
965
+ end
966
+
967
+ importer = IssueImporter.new
968
+ issue_data = importer.import_issue(identifier)
969
+
970
+ if issue_data
971
+ display_message("", type: :info)
972
+ display_message("🚀 Ready to start work loop!", type: :success)
973
+ display_message(" Run: aidp execute", type: :info)
974
+ end
975
+ else
976
+ display_message("❌ Unknown issue command: #{command}", type: :error)
977
+ display_message(usage, type: :info)
978
+ end
979
+ end
980
+
981
+ def run_config_command(args)
982
+ interactive = false
983
+ dry_run = false
984
+
985
+ until args.empty?
986
+ token = args.shift
987
+ case token
988
+ when "--interactive"
989
+ interactive = true
990
+ when "--dry-run"
991
+ dry_run = true
992
+ when "-h", "--help"
993
+ display_config_usage
994
+ return
995
+ else
996
+ display_message("Unknown option: #{token}", type: :error)
997
+ display_config_usage
998
+ return
999
+ end
1000
+ end
1001
+
1002
+ unless interactive
1003
+ display_config_usage
1004
+ return
1005
+ end
1006
+
1007
+ wizard = Aidp::Setup::Wizard.new(Dir.pwd, prompt: TTY::Prompt.new, dry_run: dry_run)
1008
+ wizard.run
1009
+ end
1010
+
1011
+ def run_init_command
1012
+ runner = Aidp::Init::Runner.new(Dir.pwd, prompt: TTY::Prompt.new)
1013
+ runner.run
1014
+ end
1015
+
1016
+ def run_watch_command(args)
1017
+ if args.empty?
1018
+ display_message("Usage: aidp watch <issues_url> [--interval SECONDS] [--provider NAME] [--once]", type: :info)
1019
+ return
1020
+ end
1021
+
1022
+ issues_url = args.shift
1023
+ interval = Aidp::Watch::Runner::DEFAULT_INTERVAL
1024
+ provider_name = nil
1025
+ once = false
1026
+
1027
+ until args.empty?
1028
+ token = args.shift
1029
+ case token
1030
+ when "--interval"
1031
+ interval_value = args.shift
1032
+ interval = interval_value.to_i if interval_value
1033
+ when "--provider"
1034
+ provider_name = args.shift
1035
+ when "--once"
1036
+ once = true
1037
+ else
1038
+ display_message("⚠️ Unknown watch option: #{token}", type: :warn)
1039
+ end
1040
+ end
1041
+
1042
+ runner = Aidp::Watch::Runner.new(
1043
+ issues_url: issues_url,
1044
+ interval: interval.positive? ? interval : Aidp::Watch::Runner::DEFAULT_INTERVAL,
1045
+ provider_name: provider_name,
1046
+ project_dir: Dir.pwd,
1047
+ once: once,
1048
+ prompt: TTY::Prompt.new
1049
+ )
1050
+ runner.start
1051
+ rescue ArgumentError => e
1052
+ display_message("❌ #{e.message}", type: :error)
1053
+ end
1054
+
1055
+ def display_config_usage
1056
+ display_message("Usage: aidp config --interactive [--dry-run]", type: :info)
1057
+ end
910
1058
  end # class << self
911
1059
  end
912
1060
  end