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.
- checksums.yaml +4 -4
- data/README.md +7 -0
- data/lib/aidp/cli/first_run_wizard.rb +28 -303
- data/lib/aidp/cli/issue_importer.rb +359 -0
- data/lib/aidp/cli.rb +151 -3
- data/lib/aidp/daemon/process_manager.rb +146 -0
- data/lib/aidp/daemon/runner.rb +232 -0
- data/lib/aidp/execute/async_work_loop_runner.rb +216 -0
- data/lib/aidp/execute/future_work_backlog.rb +411 -0
- data/lib/aidp/execute/guard_policy.rb +246 -0
- data/lib/aidp/execute/instruction_queue.rb +131 -0
- data/lib/aidp/execute/interactive_repl.rb +335 -0
- data/lib/aidp/execute/repl_macros.rb +651 -0
- data/lib/aidp/execute/steps.rb +8 -0
- data/lib/aidp/execute/work_loop_runner.rb +322 -36
- data/lib/aidp/execute/work_loop_state.rb +162 -0
- data/lib/aidp/harness/config_schema.rb +88 -0
- data/lib/aidp/harness/configuration.rb +48 -1
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +2 -0
- data/lib/aidp/init/doc_generator.rb +256 -0
- data/lib/aidp/init/project_analyzer.rb +343 -0
- data/lib/aidp/init/runner.rb +83 -0
- data/lib/aidp/init.rb +5 -0
- data/lib/aidp/logger.rb +279 -0
- data/lib/aidp/setup/wizard.rb +777 -0
- data/lib/aidp/tooling_detector.rb +115 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +282 -0
- data/lib/aidp/watch/plan_generator.rb +166 -0
- data/lib/aidp/watch/plan_processor.rb +83 -0
- data/lib/aidp/watch/repository_client.rb +243 -0
- data/lib/aidp/watch/runner.rb +93 -0
- data/lib/aidp/watch/state_store.rb +105 -0
- data/lib/aidp/watch.rb +9 -0
- data/lib/aidp.rb +14 -0
- data/templates/implementation/simple_task.md +36 -0
- 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:
|
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,
|
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
|