aidp 0.22.0 → 0.23.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 +145 -31
- data/lib/aidp/cli.rb +16 -2
- data/lib/aidp/harness/runner.rb +20 -6
- data/lib/aidp/setup/devcontainer/backup_manager.rb +11 -4
- data/lib/aidp/setup/wizard.rb +74 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +94 -4
- data/lib/aidp/watch/plan_generator.rb +16 -1
- data/lib/aidp/watch/plan_processor.rb +54 -3
- data/lib/aidp/watch/repository_client.rb +74 -0
- data/lib/aidp/watch/repository_safety_checker.rb +12 -3
- data/lib/aidp/watch/runner.rb +17 -7
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1dedfe324bb5255c43e42a4ac3f716bb99f9db453ae436a1e2aa57b68754a28f
|
|
4
|
+
data.tar.gz: 24b5a225e474c14dabad326455e4d5286e175dc6a929a1bffab4ffb29c1c063c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b842c15a0169d3b3c7361cbdf633b5bb66d0189f40fb16e12099436b3db7eda3e983fb624326b2160dd018d63090feae2b92042f46758990c31cef9172252d36
|
|
7
|
+
data.tar.gz: 1c614a680d74d56fff4561a62eee471ab1d8f7b8b2cc801786da25a63c12b142948f8e418477a4b479b3b119abe2275b954d9202662f7acbb36cdd82401a68eb
|
data/README.md
CHANGED
|
@@ -48,8 +48,8 @@ AIDP provides first-class devcontainer support for sandboxed, secure AI agent ex
|
|
|
48
48
|
|
|
49
49
|
- **Network Security**: Strict firewall with allowlisted domains only
|
|
50
50
|
- **Sandboxed Environment**: Isolated from your host system
|
|
51
|
-
- **Elevated Permissions**: AI agents can run with full permissions inside the container
|
|
52
51
|
- **Consistent Setup**: Same environment across all developers
|
|
52
|
+
- **Automatic Management**: AIDP can generate and update your devcontainer configuration
|
|
53
53
|
|
|
54
54
|
### For AIDP Development
|
|
55
55
|
|
|
@@ -73,43 +73,54 @@ See [.devcontainer/README.md](.devcontainer/README.md) for complete documentatio
|
|
|
73
73
|
|
|
74
74
|
### Generating Devcontainers for Your Projects
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
AIDP can automatically generate and manage devcontainer configurations through the interactive wizard:
|
|
77
77
|
|
|
78
78
|
```bash
|
|
79
|
-
#
|
|
80
|
-
aidp
|
|
79
|
+
# Launch the interactive configuration wizard
|
|
80
|
+
aidp config --interactive
|
|
81
81
|
|
|
82
|
-
#
|
|
83
|
-
#
|
|
82
|
+
# During the wizard, you'll be asked:
|
|
83
|
+
# - Whether you want AIDP to manage your devcontainer configuration
|
|
84
|
+
# - If you want to add custom ports beyond auto-detected ones
|
|
84
85
|
|
|
85
|
-
#
|
|
86
|
-
|
|
86
|
+
# The wizard will detect ports based on your project type and generate
|
|
87
|
+
# a complete devcontainer.json configuration
|
|
87
88
|
```
|
|
88
89
|
|
|
89
|
-
|
|
90
|
+
You can also manage devcontainer configuration manually:
|
|
91
|
+
|
|
92
|
+
```yaml
|
|
93
|
+
# .aidp/aidp.yml
|
|
94
|
+
devcontainer:
|
|
95
|
+
manage: true
|
|
96
|
+
custom_ports:
|
|
97
|
+
- number: 3000
|
|
98
|
+
label: "Application Server"
|
|
99
|
+
- number: 5432
|
|
100
|
+
label: "PostgreSQL"
|
|
101
|
+
```
|
|
90
102
|
|
|
91
|
-
|
|
92
|
-
- `.devcontainer/devcontainer.json` - VS Code configuration and extensions
|
|
93
|
-
- `.devcontainer/init-firewall.sh` - Network security rules
|
|
94
|
-
- `.devcontainer/README.md` - Setup and usage documentation
|
|
103
|
+
Then apply the configuration:
|
|
95
104
|
|
|
96
|
-
|
|
105
|
+
```bash
|
|
106
|
+
# Preview changes
|
|
107
|
+
aidp devcontainer diff
|
|
97
108
|
|
|
98
|
-
|
|
109
|
+
# Apply configuration
|
|
110
|
+
aidp devcontainer apply
|
|
99
111
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
devcontainer:
|
|
103
|
-
enabled: true
|
|
104
|
-
full_permissions_when_in_devcontainer: true # Run all providers with full permissions
|
|
112
|
+
# List backups
|
|
113
|
+
aidp devcontainer list-backups
|
|
105
114
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
skip_permission_checks:
|
|
109
|
-
- claude # Adds --dangerously-skip-permissions for Claude Code
|
|
115
|
+
# Restore from backup
|
|
116
|
+
aidp devcontainer restore 0
|
|
110
117
|
```
|
|
111
118
|
|
|
112
|
-
|
|
119
|
+
See [docs/DEVELOPMENT_CONTAINER.md](docs/DEVELOPMENT_CONTAINER.md) for complete devcontainer management documentation.
|
|
120
|
+
|
|
121
|
+
### Devcontainer Detection
|
|
122
|
+
|
|
123
|
+
AIDP automatically detects when it's running inside a devcontainer and adjusts its behavior accordingly. This detection uses multiple heuristics including environment variables, filesystem markers, and cgroup information. See [DevcontainerDetector](lib/aidp/utils/devcontainer_detector.rb) for implementation details.
|
|
113
124
|
|
|
114
125
|
## Core Features
|
|
115
126
|
|
|
@@ -190,6 +201,101 @@ aidp ws rm issue-123-fix-auth --delete-branch
|
|
|
190
201
|
|
|
191
202
|
See [Workstreams Guide](docs/WORKSTREAMS.md) for detailed usage.
|
|
192
203
|
|
|
204
|
+
### Watch Mode (Automated GitHub Integration)
|
|
205
|
+
|
|
206
|
+
AIDP can automatically monitor GitHub repositories and respond to labeled issues, creating plans and executing implementations autonomously:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
# Start watch mode for a repository
|
|
210
|
+
aidp watch https://github.com/owner/repo/issues
|
|
211
|
+
|
|
212
|
+
# Optional: specify polling interval, provider, and verbose output
|
|
213
|
+
aidp watch owner/repo --interval 60 --provider claude --verbose
|
|
214
|
+
|
|
215
|
+
# Run a single cycle (useful for CI/testing)
|
|
216
|
+
aidp watch owner/repo --once
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Label Workflow:**
|
|
220
|
+
|
|
221
|
+
AIDP uses a smart label-based workflow to manage the lifecycle of automated issue resolution:
|
|
222
|
+
|
|
223
|
+
1. **Planning Phase** (`aidp-plan` label):
|
|
224
|
+
- Add this label to an issue to trigger plan generation
|
|
225
|
+
- AIDP generates an implementation plan with task breakdown and clarifying questions
|
|
226
|
+
- Posts the plan as a comment on the issue
|
|
227
|
+
- Automatically removes the `aidp-plan` label
|
|
228
|
+
|
|
229
|
+
2. **Review & Clarification**:
|
|
230
|
+
- **If questions exist**: AIDP adds `aidp-needs-input` label and waits for user response
|
|
231
|
+
- User responds to questions in a comment
|
|
232
|
+
- User manually removes `aidp-needs-input` and adds `aidp-build` to proceed
|
|
233
|
+
- **If no questions**: AIDP adds `aidp-ready` label, indicating it's ready to build
|
|
234
|
+
- User can review the plan before proceeding
|
|
235
|
+
- User manually adds `aidp-build` label when ready
|
|
236
|
+
|
|
237
|
+
3. **Implementation Phase** (`aidp-build` label):
|
|
238
|
+
- Triggers autonomous implementation via work loops
|
|
239
|
+
- Creates a feature branch and commits changes
|
|
240
|
+
- Runs tests and linters with automatic fixes
|
|
241
|
+
- **If clarification needed during implementation**:
|
|
242
|
+
- Posts clarification questions as a comment
|
|
243
|
+
- Automatically removes `aidp-build` label and adds `aidp-needs-input`
|
|
244
|
+
- Preserves work-in-progress for later resumption
|
|
245
|
+
- User responds to questions, then manually removes `aidp-needs-input` and re-adds `aidp-build`
|
|
246
|
+
- **On success**:
|
|
247
|
+
- Posts completion comment with summary
|
|
248
|
+
- Automatically removes the `aidp-build` label
|
|
249
|
+
|
|
250
|
+
**Customizable Labels:**
|
|
251
|
+
|
|
252
|
+
All label names are configurable to match your repository's existing label scheme. Configure via the interactive wizard or manually in `aidp.yml`:
|
|
253
|
+
|
|
254
|
+
```yaml
|
|
255
|
+
# .aidp/aidp.yml
|
|
256
|
+
watch:
|
|
257
|
+
labels:
|
|
258
|
+
plan_trigger: aidp-plan # Label to trigger plan generation
|
|
259
|
+
needs_input: aidp-needs-input # Label when plan needs user input
|
|
260
|
+
ready_to_build: aidp-ready # Label when plan is ready to build
|
|
261
|
+
build_trigger: aidp-build # Label to trigger implementation
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Run `aidp config --interactive` and enable watch mode to configure labels interactively.
|
|
265
|
+
|
|
266
|
+
**Safety Features:**
|
|
267
|
+
|
|
268
|
+
- **Public Repository Protection**: Disabled by default for public repos (require explicit opt-in)
|
|
269
|
+
- **Author Allowlist**: Restrict automation to trusted GitHub users only
|
|
270
|
+
- **Container Requirement**: Optionally require sandboxed environment
|
|
271
|
+
- **Force Override**: `--force` flag to bypass safety checks (dangerous!)
|
|
272
|
+
|
|
273
|
+
**Safety Configuration:**
|
|
274
|
+
|
|
275
|
+
```yaml
|
|
276
|
+
# .aidp/aidp.yml
|
|
277
|
+
watch:
|
|
278
|
+
safety:
|
|
279
|
+
allow_public_repos: true # Required for public repositories
|
|
280
|
+
author_allowlist: # Only these users can trigger automation
|
|
281
|
+
- trusted-maintainer
|
|
282
|
+
- team-member
|
|
283
|
+
require_container: true # Require devcontainer/Docker environment
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Run `aidp config --interactive` and enable watch mode to configure safety settings interactively.
|
|
287
|
+
|
|
288
|
+
**Clarification Requests:**
|
|
289
|
+
|
|
290
|
+
AIDP can automatically request clarification when it needs more information during implementation. This works in both watch mode and interactive mode:
|
|
291
|
+
|
|
292
|
+
- **Watch Mode**: Posts clarification questions as a GitHub comment, updates labels to `aidp-needs-input`, and waits for user response
|
|
293
|
+
- **Interactive Mode**: Prompts the user directly in the terminal to answer questions before continuing
|
|
294
|
+
|
|
295
|
+
This ensures AIDP never gets stuck - if it needs more information, it will ask for it rather than making incorrect assumptions or failing silently.
|
|
296
|
+
|
|
297
|
+
See [Watch Mode Guide](docs/FULLY_AUTOMATIC_MODE.md) and [Watch Mode Safety](docs/WATCH_MODE_SAFETY.md) for complete documentation.
|
|
298
|
+
|
|
193
299
|
## Command Reference
|
|
194
300
|
|
|
195
301
|
### Copilot Mode
|
|
@@ -263,6 +369,20 @@ aidp ws rm <slug> --delete-branch # Also delete git branch
|
|
|
263
369
|
aidp ws rm <slug> --force # Skip confirmation
|
|
264
370
|
```
|
|
265
371
|
|
|
372
|
+
### Configuration Commands
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
# Interactive configuration wizard (recommended)
|
|
376
|
+
aidp config --interactive # Configure all settings including watch mode
|
|
377
|
+
|
|
378
|
+
# Legacy setup wizard
|
|
379
|
+
aidp --setup-config # Re-run basic setup wizard
|
|
380
|
+
|
|
381
|
+
# Help and version
|
|
382
|
+
aidp --help # Show all commands
|
|
383
|
+
aidp --version # Show version
|
|
384
|
+
```
|
|
385
|
+
|
|
266
386
|
### System Commands
|
|
267
387
|
|
|
268
388
|
```bash
|
|
@@ -275,11 +395,6 @@ aidp providers
|
|
|
275
395
|
# Harness state management
|
|
276
396
|
aidp harness status
|
|
277
397
|
aidp harness reset
|
|
278
|
-
|
|
279
|
-
# Configuration
|
|
280
|
-
aidp --setup-config # Re-run setup wizard
|
|
281
|
-
aidp --help # Show all commands
|
|
282
|
-
aidp --version # Show version
|
|
283
398
|
```
|
|
284
399
|
|
|
285
400
|
## AI Providers
|
|
@@ -291,7 +406,6 @@ AIDP intelligently manages multiple providers with automatic switching:
|
|
|
291
406
|
- **Cursor CLI** - IDE-integrated provider for code-specific tasks
|
|
292
407
|
- **Gemini CLI** - Google's Gemini command-line interface for general tasks
|
|
293
408
|
- **GitHub Copilot CLI** - GitHub's AI pair programmer command-line interface
|
|
294
|
-
- **macOS UI** - macOS-specific UI automation provider
|
|
295
409
|
- **OpenCode** - Alternative open-source code generation provider
|
|
296
410
|
|
|
297
411
|
The system automatically switches providers when:
|
data/lib/aidp/cli.rb
CHANGED
|
@@ -1231,7 +1231,7 @@ module Aidp
|
|
|
1231
1231
|
|
|
1232
1232
|
def run_watch_command(args)
|
|
1233
1233
|
if args.empty?
|
|
1234
|
-
display_message("Usage: aidp watch <issues_url> [--interval SECONDS] [--provider NAME] [--once] [--no-workstreams]", type: :info)
|
|
1234
|
+
display_message("Usage: aidp watch <issues_url> [--interval SECONDS] [--provider NAME] [--once] [--no-workstreams] [--force] [--verbose]", type: :info)
|
|
1235
1235
|
return
|
|
1236
1236
|
end
|
|
1237
1237
|
|
|
@@ -1240,6 +1240,8 @@ module Aidp
|
|
|
1240
1240
|
provider_name = nil
|
|
1241
1241
|
once = false
|
|
1242
1242
|
use_workstreams = true # Default to using workstreams
|
|
1243
|
+
force = false
|
|
1244
|
+
verbose = false
|
|
1243
1245
|
|
|
1244
1246
|
until args.empty?
|
|
1245
1247
|
token = args.shift
|
|
@@ -1253,11 +1255,20 @@ module Aidp
|
|
|
1253
1255
|
once = true
|
|
1254
1256
|
when "--no-workstreams"
|
|
1255
1257
|
use_workstreams = false
|
|
1258
|
+
when "--force"
|
|
1259
|
+
force = true
|
|
1260
|
+
when "--verbose"
|
|
1261
|
+
verbose = true
|
|
1256
1262
|
else
|
|
1257
1263
|
display_message("⚠️ Unknown watch option: #{token}", type: :warn)
|
|
1258
1264
|
end
|
|
1259
1265
|
end
|
|
1260
1266
|
|
|
1267
|
+
# Load watch safety configuration
|
|
1268
|
+
config_manager = Aidp::Harness::ConfigManager.new(Dir.pwd)
|
|
1269
|
+
config = config_manager.config || {}
|
|
1270
|
+
watch_config = config[:watch] || config["watch"] || {}
|
|
1271
|
+
|
|
1261
1272
|
runner = Aidp::Watch::Runner.new(
|
|
1262
1273
|
issues_url: issues_url,
|
|
1263
1274
|
interval: interval.positive? ? interval : Aidp::Watch::Runner::DEFAULT_INTERVAL,
|
|
@@ -1265,7 +1276,10 @@ module Aidp
|
|
|
1265
1276
|
project_dir: Dir.pwd,
|
|
1266
1277
|
once: once,
|
|
1267
1278
|
use_workstreams: use_workstreams,
|
|
1268
|
-
prompt: create_prompt
|
|
1279
|
+
prompt: create_prompt,
|
|
1280
|
+
safety_config: watch_config,
|
|
1281
|
+
force: force,
|
|
1282
|
+
verbose: verbose
|
|
1269
1283
|
)
|
|
1270
1284
|
runner.start
|
|
1271
1285
|
rescue ArgumentError => e
|
data/lib/aidp/harness/runner.rb
CHANGED
|
@@ -27,11 +27,12 @@ module Aidp
|
|
|
27
27
|
waiting_for_rate_limit: "waiting_for_rate_limit",
|
|
28
28
|
stopped: "stopped",
|
|
29
29
|
completed: "completed",
|
|
30
|
-
error: "error"
|
|
30
|
+
error: "error",
|
|
31
|
+
needs_clarification: "needs_clarification"
|
|
31
32
|
}.freeze
|
|
32
33
|
|
|
33
34
|
# Public accessors for testing and integration
|
|
34
|
-
attr_reader :current_provider, :current_step, :user_input, :execution_log, :provider_manager
|
|
35
|
+
attr_reader :current_provider, :current_step, :user_input, :execution_log, :provider_manager, :clarification_questions
|
|
35
36
|
|
|
36
37
|
def initialize(project_dir, mode = :analyze, options = {})
|
|
37
38
|
@project_dir = project_dir
|
|
@@ -132,7 +133,9 @@ module Aidp
|
|
|
132
133
|
cleanup
|
|
133
134
|
end
|
|
134
135
|
|
|
135
|
-
{status: @state, message: get_completion_message}
|
|
136
|
+
result = {status: @state, message: get_completion_message}
|
|
137
|
+
result[:clarification_questions] = @clarification_questions if @clarification_questions
|
|
138
|
+
result
|
|
136
139
|
end
|
|
137
140
|
|
|
138
141
|
# Pause the harness execution
|
|
@@ -248,12 +251,23 @@ module Aidp
|
|
|
248
251
|
end
|
|
249
252
|
|
|
250
253
|
def handle_user_feedback_request(result)
|
|
251
|
-
@state = STATES[:waiting_for_user]
|
|
252
|
-
log_execution("Waiting for user feedback")
|
|
253
|
-
|
|
254
254
|
# Extract questions from result
|
|
255
255
|
questions = @condition_detector.extract_questions(result)
|
|
256
256
|
|
|
257
|
+
# Check if we're in watch mode (non-interactive)
|
|
258
|
+
if @options[:workflow_type] == :watch_mode
|
|
259
|
+
# Store questions for later retrieval and set state to needs_clarification
|
|
260
|
+
@clarification_questions = questions
|
|
261
|
+
@state = STATES[:needs_clarification]
|
|
262
|
+
log_execution("Clarification needed in watch mode", {question_count: questions.size})
|
|
263
|
+
# Don't continue - exit the loop so we can return this status
|
|
264
|
+
return
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Interactive mode: collect feedback from user
|
|
268
|
+
@state = STATES[:waiting_for_user]
|
|
269
|
+
log_execution("Waiting for user feedback")
|
|
270
|
+
|
|
257
271
|
# Collect user input
|
|
258
272
|
user_responses = @user_interface.collect_feedback(questions)
|
|
259
273
|
|
|
@@ -10,9 +10,10 @@ module Aidp
|
|
|
10
10
|
class BackupManager
|
|
11
11
|
class BackupError < StandardError; end
|
|
12
12
|
|
|
13
|
-
def initialize(project_dir)
|
|
13
|
+
def initialize(project_dir, clock: Time)
|
|
14
14
|
@project_dir = project_dir
|
|
15
15
|
@backup_dir = File.join(project_dir, ".aidp", "backups", "devcontainer")
|
|
16
|
+
@clock = clock
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
# Create a backup of the devcontainer file
|
|
@@ -26,7 +27,7 @@ module Aidp
|
|
|
26
27
|
|
|
27
28
|
ensure_backup_directory_exists
|
|
28
29
|
|
|
29
|
-
timestamp =
|
|
30
|
+
timestamp = current_time.utc.strftime("%Y%m%d_%H%M%S")
|
|
30
31
|
backup_filename = "devcontainer-#{timestamp}.json"
|
|
31
32
|
backup_path = File.join(@backup_dir, backup_filename)
|
|
32
33
|
|
|
@@ -163,11 +164,17 @@ module Aidp
|
|
|
163
164
|
end
|
|
164
165
|
|
|
165
166
|
def parse_timestamp(timestamp_str)
|
|
166
|
-
return
|
|
167
|
+
return current_time if timestamp_str.nil?
|
|
167
168
|
|
|
168
169
|
Time.strptime(timestamp_str, "%Y%m%d_%H%M%S")
|
|
169
170
|
rescue ArgumentError
|
|
170
|
-
|
|
171
|
+
current_time
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
attr_reader :clock
|
|
175
|
+
|
|
176
|
+
def current_time
|
|
177
|
+
clock.respond_to?(:call) ? clock.call : clock.now
|
|
171
178
|
end
|
|
172
179
|
end
|
|
173
180
|
end
|
data/lib/aidp/setup/wizard.rb
CHANGED
|
@@ -786,6 +786,79 @@ module Aidp
|
|
|
786
786
|
watch_enabled: watch,
|
|
787
787
|
quick_mode_default: quick_mode
|
|
788
788
|
})
|
|
789
|
+
|
|
790
|
+
# Configure watch mode settings if enabled
|
|
791
|
+
configure_watch_mode if watch
|
|
792
|
+
end
|
|
793
|
+
|
|
794
|
+
def configure_watch_mode
|
|
795
|
+
prompt.say("\n👀 Watch Mode Configuration")
|
|
796
|
+
prompt.say("-" * 40)
|
|
797
|
+
|
|
798
|
+
configure_watch_safety
|
|
799
|
+
configure_watch_labels
|
|
800
|
+
end
|
|
801
|
+
|
|
802
|
+
def configure_watch_safety
|
|
803
|
+
prompt.say("\n🔒 Watch mode safety settings")
|
|
804
|
+
existing = get([:watch, :safety]) || {}
|
|
805
|
+
|
|
806
|
+
allow_public_repos = prompt.yes?(
|
|
807
|
+
"Allow watch mode on public repositories?",
|
|
808
|
+
default: existing.fetch(:allow_public_repos, false)
|
|
809
|
+
)
|
|
810
|
+
|
|
811
|
+
prompt.say("\n📝 Author allowlist (GitHub usernames allowed to trigger watch mode)")
|
|
812
|
+
prompt.say(" Leave empty to allow all authors (not recommended for public repos)")
|
|
813
|
+
author_allowlist = ask_list(
|
|
814
|
+
"Author allowlist (comma-separated GitHub usernames)",
|
|
815
|
+
existing[:author_allowlist] || [],
|
|
816
|
+
allow_empty: true
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
require_container = prompt.yes?(
|
|
820
|
+
"Require watch mode to run in a container?",
|
|
821
|
+
default: existing.fetch(:require_container, true)
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
set([:watch, :safety], {
|
|
825
|
+
allow_public_repos: allow_public_repos,
|
|
826
|
+
author_allowlist: author_allowlist,
|
|
827
|
+
require_container: require_container
|
|
828
|
+
})
|
|
829
|
+
end
|
|
830
|
+
|
|
831
|
+
def configure_watch_labels
|
|
832
|
+
prompt.say("\n🏷️ Watch mode label configuration")
|
|
833
|
+
prompt.say(" Configure GitHub issue labels that trigger watch mode actions")
|
|
834
|
+
existing = get([:watch, :labels]) || {}
|
|
835
|
+
|
|
836
|
+
plan_trigger = ask_with_default(
|
|
837
|
+
"Label to trigger plan generation",
|
|
838
|
+
existing[:plan_trigger] || "aidp-plan"
|
|
839
|
+
)
|
|
840
|
+
|
|
841
|
+
needs_input = ask_with_default(
|
|
842
|
+
"Label for plans needing user input",
|
|
843
|
+
existing[:needs_input] || "aidp-needs-input"
|
|
844
|
+
)
|
|
845
|
+
|
|
846
|
+
ready_to_build = ask_with_default(
|
|
847
|
+
"Label for plans ready to build",
|
|
848
|
+
existing[:ready_to_build] || "aidp-ready"
|
|
849
|
+
)
|
|
850
|
+
|
|
851
|
+
build_trigger = ask_with_default(
|
|
852
|
+
"Label to trigger implementation",
|
|
853
|
+
existing[:build_trigger] || "aidp-build"
|
|
854
|
+
)
|
|
855
|
+
|
|
856
|
+
set([:watch, :labels], {
|
|
857
|
+
plan_trigger: plan_trigger,
|
|
858
|
+
needs_input: needs_input,
|
|
859
|
+
ready_to_build: ready_to_build,
|
|
860
|
+
build_trigger: build_trigger
|
|
861
|
+
})
|
|
789
862
|
end
|
|
790
863
|
|
|
791
864
|
# -------------------------------------------
|
|
@@ -816,6 +889,7 @@ module Aidp
|
|
|
816
889
|
.sub(/^nfrs:/, "# Non-functional requirements to reference during planning\nnfrs:")
|
|
817
890
|
.sub(/^logging:/, "# Logging configuration\nlogging:")
|
|
818
891
|
.sub(/^modes:/, "# Defaults for background/watch/quick modes\nmodes:")
|
|
892
|
+
.sub(/^watch:/, "# Watch mode safety and label configuration\nwatch:")
|
|
819
893
|
end
|
|
820
894
|
|
|
821
895
|
def display_preview(yaml_content)
|
data/lib/aidp/version.rb
CHANGED
|
@@ -15,14 +15,22 @@ module Aidp
|
|
|
15
15
|
class BuildProcessor
|
|
16
16
|
include Aidp::MessageDisplay
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
DEFAULT_BUILD_LABEL = "aidp-build"
|
|
19
|
+
DEFAULT_NEEDS_INPUT_LABEL = "aidp-needs-input"
|
|
19
20
|
IMPLEMENTATION_STEP = "16_IMPLEMENTATION"
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
attr_reader :build_label, :needs_input_label
|
|
23
|
+
|
|
24
|
+
def initialize(repository_client:, state_store:, project_dir: Dir.pwd, use_workstreams: true, verbose: false, label_config: {})
|
|
22
25
|
@repository_client = repository_client
|
|
23
26
|
@state_store = state_store
|
|
24
27
|
@project_dir = project_dir
|
|
25
28
|
@use_workstreams = use_workstreams
|
|
29
|
+
@verbose = verbose
|
|
30
|
+
|
|
31
|
+
# Load label configuration
|
|
32
|
+
@build_label = label_config[:build_trigger] || label_config["build_trigger"] || DEFAULT_BUILD_LABEL
|
|
33
|
+
@needs_input_label = label_config[:needs_input] || label_config["needs_input"] || DEFAULT_NEEDS_INPUT_LABEL
|
|
26
34
|
end
|
|
27
35
|
|
|
28
36
|
def process(issue)
|
|
@@ -55,6 +63,8 @@ module Aidp
|
|
|
55
63
|
|
|
56
64
|
if result[:status] == "completed"
|
|
57
65
|
handle_success(issue: issue, slug: slug, branch_name: branch_name, base_branch: base_branch, plan_data: plan_data, working_dir: working_dir)
|
|
66
|
+
elsif result[:status] == "needs_clarification"
|
|
67
|
+
handle_clarification_request(issue: issue, slug: slug, result: result)
|
|
58
68
|
else
|
|
59
69
|
handle_failure(issue: issue, slug: slug, result: result)
|
|
60
70
|
end
|
|
@@ -203,15 +213,33 @@ module Aidp
|
|
|
203
213
|
prompt_manager = Aidp::Execute::PromptManager.new(working_dir)
|
|
204
214
|
prompt_manager.write(content)
|
|
205
215
|
display_message("📝 Wrote PROMPT.md with implementation contract", type: :info)
|
|
216
|
+
|
|
217
|
+
if @verbose
|
|
218
|
+
display_message("\n--- Implementation Prompt ---", type: :muted)
|
|
219
|
+
display_message(content.strip, type: :muted)
|
|
220
|
+
display_message("--- End Prompt ---\n", type: :muted)
|
|
221
|
+
end
|
|
206
222
|
end
|
|
207
223
|
|
|
208
224
|
def build_user_input(issue:, plan_data:)
|
|
209
225
|
tasks = Array(plan_value(plan_data, "tasks"))
|
|
210
|
-
{
|
|
226
|
+
user_input = {
|
|
211
227
|
"Implementation Contract" => plan_value(plan_data, "summary").to_s,
|
|
212
228
|
"Tasks" => tasks.map { |task| "- #{task}" }.join("\n"),
|
|
213
229
|
"Issue URL" => issue[:url]
|
|
214
230
|
}.delete_if { |_k, v| v.nil? || v.empty? }
|
|
231
|
+
|
|
232
|
+
if @verbose
|
|
233
|
+
display_message("\n--- User Input for Harness ---", type: :muted)
|
|
234
|
+
user_input.each do |key, value|
|
|
235
|
+
display_message("#{key}:", type: :muted)
|
|
236
|
+
display_message(value, type: :muted)
|
|
237
|
+
display_message("", type: :muted)
|
|
238
|
+
end
|
|
239
|
+
display_message("--- End User Input ---\n", type: :muted)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
user_input
|
|
215
243
|
end
|
|
216
244
|
|
|
217
245
|
def run_harness(user_input:, working_dir: @project_dir)
|
|
@@ -220,8 +248,20 @@ module Aidp
|
|
|
220
248
|
workflow_type: :watch_mode,
|
|
221
249
|
user_input: user_input
|
|
222
250
|
}
|
|
251
|
+
|
|
252
|
+
display_message("🚀 Running harness in execute mode...", type: :info) if @verbose
|
|
253
|
+
|
|
223
254
|
runner = Aidp::Harness::Runner.new(working_dir, :execute, options)
|
|
224
|
-
runner.run
|
|
255
|
+
result = runner.run
|
|
256
|
+
|
|
257
|
+
if @verbose
|
|
258
|
+
display_message("\n--- Harness Result ---", type: :muted)
|
|
259
|
+
display_message("Status: #{result[:status]}", type: :muted)
|
|
260
|
+
display_message("Message: #{result[:message]}", type: :muted) if result[:message]
|
|
261
|
+
display_message("--- End Result ---\n", type: :muted)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
result
|
|
225
265
|
end
|
|
226
266
|
|
|
227
267
|
def handle_success(issue:, slug:, branch_name:, base_branch:, plan_data:, working_dir:)
|
|
@@ -257,12 +297,62 @@ module Aidp
|
|
|
257
297
|
)
|
|
258
298
|
display_message("🎉 Posted completion comment for issue ##{issue[:number]}", type: :success)
|
|
259
299
|
|
|
300
|
+
# Remove build label after successful completion
|
|
301
|
+
begin
|
|
302
|
+
@repository_client.remove_labels(issue[:number], @build_label)
|
|
303
|
+
display_message("🏷️ Removed '#{@build_label}' label after completion", type: :info)
|
|
304
|
+
rescue => e
|
|
305
|
+
display_message("⚠️ Failed to remove build label: #{e.message}", type: :warn)
|
|
306
|
+
# Don't fail the process if label removal fails
|
|
307
|
+
end
|
|
308
|
+
|
|
260
309
|
# Keep workstream for review - don't auto-cleanup on success
|
|
261
310
|
if @use_workstreams
|
|
262
311
|
display_message("ℹ️ Workstream #{slug} preserved for review. Remove with: aidp ws rm #{slug}", type: :muted)
|
|
263
312
|
end
|
|
264
313
|
end
|
|
265
314
|
|
|
315
|
+
def handle_clarification_request(issue:, slug:, result:)
|
|
316
|
+
questions = result[:clarification_questions] || []
|
|
317
|
+
workstream_note = @use_workstreams ? " The workstream `#{slug}` has been preserved." : " The branch has been preserved."
|
|
318
|
+
|
|
319
|
+
# Build comment with questions
|
|
320
|
+
comment_parts = []
|
|
321
|
+
comment_parts << "❓ Implementation needs clarification for ##{issue[:number]}."
|
|
322
|
+
comment_parts << ""
|
|
323
|
+
comment_parts << "The AI agent needs additional information to proceed with implementation:"
|
|
324
|
+
comment_parts << ""
|
|
325
|
+
questions.each_with_index do |question, index|
|
|
326
|
+
comment_parts << "#{index + 1}. #{question}"
|
|
327
|
+
end
|
|
328
|
+
comment_parts << ""
|
|
329
|
+
comment_parts << "**Next Steps**: Please reply with answers to the questions above. Once resolved, remove the `#{@needs_input_label}` label and add the `#{@build_label}` label to resume implementation."
|
|
330
|
+
comment_parts << ""
|
|
331
|
+
comment_parts << workstream_note.to_s
|
|
332
|
+
|
|
333
|
+
comment = comment_parts.join("\n")
|
|
334
|
+
@repository_client.post_comment(issue[:number], comment)
|
|
335
|
+
|
|
336
|
+
# Update labels: remove build trigger, add needs input
|
|
337
|
+
begin
|
|
338
|
+
@repository_client.replace_labels(
|
|
339
|
+
issue[:number],
|
|
340
|
+
old_labels: [@build_label],
|
|
341
|
+
new_labels: [@needs_input_label]
|
|
342
|
+
)
|
|
343
|
+
display_message("🏷️ Updated labels: removed '#{@build_label}', added '#{@needs_input_label}' (needs clarification)", type: :info)
|
|
344
|
+
rescue => e
|
|
345
|
+
display_message("⚠️ Failed to update labels for issue ##{issue[:number]}: #{e.message}", type: :warn)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
@state_store.record_build_status(
|
|
349
|
+
issue[:number],
|
|
350
|
+
status: "needs_clarification",
|
|
351
|
+
details: {questions: questions, workstream: slug}
|
|
352
|
+
)
|
|
353
|
+
display_message("💬 Posted clarification request for issue ##{issue[:number]}", type: :success)
|
|
354
|
+
end
|
|
355
|
+
|
|
266
356
|
def handle_failure(issue:, slug:, result:)
|
|
267
357
|
message = result[:message] || "Unknown failure"
|
|
268
358
|
workstream_note = @use_workstreams ? " The workstream `#{slug}` has been left intact for debugging." : " The branch has been left intact for debugging."
|
|
@@ -26,8 +26,9 @@ module Aidp
|
|
|
26
26
|
Focus on concrete engineering tasks. Ensure questions are actionable.
|
|
27
27
|
PROMPT
|
|
28
28
|
|
|
29
|
-
def initialize(provider_name: nil)
|
|
29
|
+
def initialize(provider_name: nil, verbose: false)
|
|
30
30
|
@provider_name = provider_name
|
|
31
|
+
@verbose = verbose
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
def generate(issue)
|
|
@@ -67,7 +68,21 @@ module Aidp
|
|
|
67
68
|
|
|
68
69
|
def generate_with_provider(provider, issue)
|
|
69
70
|
payload = build_prompt(issue)
|
|
71
|
+
|
|
72
|
+
if @verbose
|
|
73
|
+
display_message("\n--- Plan Generation Prompt ---", type: :muted)
|
|
74
|
+
display_message(payload.strip, type: :muted)
|
|
75
|
+
display_message("--- End Prompt ---\n", type: :muted)
|
|
76
|
+
end
|
|
77
|
+
|
|
70
78
|
response = provider.send_message(prompt: payload)
|
|
79
|
+
|
|
80
|
+
if @verbose
|
|
81
|
+
display_message("\n--- Provider Response ---", type: :muted)
|
|
82
|
+
display_message(response.strip, type: :muted)
|
|
83
|
+
display_message("--- End Response ---\n", type: :muted)
|
|
84
|
+
end
|
|
85
|
+
|
|
71
86
|
parsed = parse_structured_response(response)
|
|
72
87
|
|
|
73
88
|
return parsed if parsed
|
|
@@ -11,13 +11,32 @@ module Aidp
|
|
|
11
11
|
class PlanProcessor
|
|
12
12
|
include Aidp::MessageDisplay
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
# Default label names
|
|
15
|
+
DEFAULT_PLAN_LABEL = "aidp-plan"
|
|
16
|
+
DEFAULT_NEEDS_INPUT_LABEL = "aidp-needs-input"
|
|
17
|
+
DEFAULT_READY_LABEL = "aidp-ready"
|
|
18
|
+
DEFAULT_BUILD_LABEL = "aidp-build"
|
|
19
|
+
|
|
15
20
|
COMMENT_HEADER = "## 🤖 AIDP Plan Proposal"
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
attr_reader :plan_label, :needs_input_label, :ready_label, :build_label
|
|
23
|
+
|
|
24
|
+
def initialize(repository_client:, state_store:, plan_generator:, label_config: {})
|
|
18
25
|
@repository_client = repository_client
|
|
19
26
|
@state_store = state_store
|
|
20
27
|
@plan_generator = plan_generator
|
|
28
|
+
|
|
29
|
+
# Load label configuration with defaults
|
|
30
|
+
@plan_label = label_config[:plan_trigger] || label_config["plan_trigger"] || DEFAULT_PLAN_LABEL
|
|
31
|
+
@needs_input_label = label_config[:needs_input] || label_config["needs_input"] || DEFAULT_NEEDS_INPUT_LABEL
|
|
32
|
+
@ready_label = label_config[:ready_to_build] || label_config["ready_to_build"] || DEFAULT_READY_LABEL
|
|
33
|
+
@build_label = label_config[:build_trigger] || label_config["build_trigger"] || DEFAULT_BUILD_LABEL
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# For backward compatibility
|
|
37
|
+
def self.plan_label_from_config(config)
|
|
38
|
+
labels = config[:labels] || config["labels"] || {}
|
|
39
|
+
labels[:plan_trigger] || labels["plan_trigger"] || DEFAULT_PLAN_LABEL
|
|
21
40
|
end
|
|
22
41
|
|
|
23
42
|
def process(issue)
|
|
@@ -35,14 +54,39 @@ module Aidp
|
|
|
35
54
|
|
|
36
55
|
display_message("💬 Posted plan comment for issue ##{number}", type: :success)
|
|
37
56
|
@state_store.record_plan(number, plan_data.merge(comment_body: comment_body, comment_hint: COMMENT_HEADER))
|
|
57
|
+
|
|
58
|
+
# Update labels: remove plan trigger, add appropriate status label
|
|
59
|
+
update_labels_after_plan(number, plan_data)
|
|
38
60
|
end
|
|
39
61
|
|
|
40
62
|
private
|
|
41
63
|
|
|
64
|
+
def update_labels_after_plan(number, plan_data)
|
|
65
|
+
questions = Array(plan_data[:questions])
|
|
66
|
+
has_questions = questions.any? && !questions.all? { |q| q.to_s.strip.empty? }
|
|
67
|
+
|
|
68
|
+
# Determine which label to add based on whether there are questions
|
|
69
|
+
new_label = has_questions ? @needs_input_label : @ready_label
|
|
70
|
+
status_text = has_questions ? "needs input" : "ready to build"
|
|
71
|
+
|
|
72
|
+
begin
|
|
73
|
+
@repository_client.replace_labels(
|
|
74
|
+
number,
|
|
75
|
+
old_labels: [@plan_label],
|
|
76
|
+
new_labels: [new_label]
|
|
77
|
+
)
|
|
78
|
+
display_message("🏷️ Updated labels: removed '#{@plan_label}', added '#{new_label}' (#{status_text})", type: :info)
|
|
79
|
+
rescue => e
|
|
80
|
+
display_message("⚠️ Failed to update labels for issue ##{number}: #{e.message}", type: :warn)
|
|
81
|
+
# Don't fail the whole process if label update fails
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
42
85
|
def build_comment(issue:, plan:)
|
|
43
86
|
summary = plan[:summary].to_s.strip
|
|
44
87
|
tasks = Array(plan[:tasks])
|
|
45
88
|
questions = Array(plan[:questions])
|
|
89
|
+
has_questions = questions.any? && !questions.all? { |q| q.to_s.strip.empty? }
|
|
46
90
|
|
|
47
91
|
parts = []
|
|
48
92
|
parts << COMMENT_HEADER
|
|
@@ -59,7 +103,14 @@ module Aidp
|
|
|
59
103
|
parts << "### Clarifying Questions"
|
|
60
104
|
parts << format_numbered(questions, placeholder: "_No questions identified_")
|
|
61
105
|
parts << ""
|
|
62
|
-
|
|
106
|
+
|
|
107
|
+
# Add instructions based on whether there are questions
|
|
108
|
+
parts << if has_questions
|
|
109
|
+
"**Next Steps**: Please reply with answers to the questions above. Once resolved, remove the `#{@needs_input_label}` label and add the `#{@build_label}` label to begin implementation."
|
|
110
|
+
else
|
|
111
|
+
"**Next Steps**: This plan is ready for implementation. Add the `#{@build_label}` label to begin."
|
|
112
|
+
end
|
|
113
|
+
|
|
63
114
|
parts.join("\n")
|
|
64
115
|
end
|
|
65
116
|
|
|
@@ -65,6 +65,20 @@ module Aidp
|
|
|
65
65
|
gh_available? ? create_pull_request_via_gh(title: title, body: body, head: head, base: base, issue_number: issue_number) : raise("GitHub CLI not available - cannot create PR")
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
+
def add_labels(number, *labels)
|
|
69
|
+
gh_available? ? add_labels_via_gh(number, labels.flatten) : add_labels_via_api(number, labels.flatten)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def remove_labels(number, *labels)
|
|
73
|
+
gh_available? ? remove_labels_via_gh(number, labels.flatten) : remove_labels_via_api(number, labels.flatten)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def replace_labels(number, old_labels:, new_labels:)
|
|
77
|
+
# Remove old labels and add new ones atomically where possible
|
|
78
|
+
remove_labels(number, *old_labels) unless old_labels.empty?
|
|
79
|
+
add_labels(number, *new_labels) unless new_labels.empty?
|
|
80
|
+
end
|
|
81
|
+
|
|
68
82
|
private
|
|
69
83
|
|
|
70
84
|
def list_issues_via_gh(labels:, state:)
|
|
@@ -180,6 +194,66 @@ module Aidp
|
|
|
180
194
|
stdout.strip
|
|
181
195
|
end
|
|
182
196
|
|
|
197
|
+
def add_labels_via_gh(number, labels)
|
|
198
|
+
return if labels.empty?
|
|
199
|
+
|
|
200
|
+
cmd = ["gh", "issue", "edit", number.to_s, "--repo", full_repo]
|
|
201
|
+
labels.each { |label| cmd += ["--add-label", label] }
|
|
202
|
+
|
|
203
|
+
stdout, stderr, status = Open3.capture3(*cmd)
|
|
204
|
+
raise "Failed to add labels via gh: #{stderr.strip}" unless status.success?
|
|
205
|
+
|
|
206
|
+
stdout.strip
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def add_labels_via_api(number, labels)
|
|
210
|
+
return if labels.empty?
|
|
211
|
+
|
|
212
|
+
uri = URI("https://api.github.com/repos/#{full_repo}/issues/#{number}/labels")
|
|
213
|
+
request = Net::HTTP::Post.new(uri)
|
|
214
|
+
request["Content-Type"] = "application/json"
|
|
215
|
+
request.body = JSON.dump({labels: labels})
|
|
216
|
+
|
|
217
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
|
218
|
+
http.request(request)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
raise "Failed to add labels via API (#{response.code})" unless response.code.start_with?("2")
|
|
222
|
+
response.body
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def remove_labels_via_gh(number, labels)
|
|
226
|
+
return if labels.empty?
|
|
227
|
+
|
|
228
|
+
cmd = ["gh", "issue", "edit", number.to_s, "--repo", full_repo]
|
|
229
|
+
labels.each { |label| cmd += ["--remove-label", label] }
|
|
230
|
+
|
|
231
|
+
stdout, stderr, status = Open3.capture3(*cmd)
|
|
232
|
+
raise "Failed to remove labels via gh: #{stderr.strip}" unless status.success?
|
|
233
|
+
|
|
234
|
+
stdout.strip
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def remove_labels_via_api(number, labels)
|
|
238
|
+
return if labels.empty?
|
|
239
|
+
|
|
240
|
+
labels.each do |label|
|
|
241
|
+
# URL encode the label name
|
|
242
|
+
encoded_label = URI.encode_www_form_component(label)
|
|
243
|
+
uri = URI("https://api.github.com/repos/#{full_repo}/issues/#{number}/labels/#{encoded_label}")
|
|
244
|
+
request = Net::HTTP::Delete.new(uri)
|
|
245
|
+
|
|
246
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
|
247
|
+
http.request(request)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# 404 is OK - label didn't exist
|
|
251
|
+
unless response.code.start_with?("2") || response.code == "404"
|
|
252
|
+
raise "Failed to remove label '#{label}' via API (#{response.code})"
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
183
257
|
def normalize_issue(raw)
|
|
184
258
|
{
|
|
185
259
|
number: raw["number"],
|
|
@@ -163,16 +163,25 @@ module Aidp
|
|
|
163
163
|
end
|
|
164
164
|
|
|
165
165
|
def public_repos_allowed?
|
|
166
|
-
|
|
166
|
+
# Support both string and symbol keys
|
|
167
|
+
safety_config = @config[:safety] || @config["safety"] || {}
|
|
168
|
+
(safety_config[:allow_public_repos] || safety_config["allow_public_repos"]) == true
|
|
167
169
|
end
|
|
168
170
|
|
|
169
171
|
def author_allowlist
|
|
170
|
-
|
|
172
|
+
# Support both string and symbol keys
|
|
173
|
+
safety_config = @config[:safety] || @config["safety"] || {}
|
|
174
|
+
@author_allowlist ||= Array(
|
|
175
|
+
safety_config[:author_allowlist] || safety_config["author_allowlist"]
|
|
176
|
+
).compact.map(&:to_s)
|
|
171
177
|
end
|
|
172
178
|
|
|
173
179
|
def safe_environment?
|
|
174
180
|
# Check if running in a container
|
|
175
|
-
|
|
181
|
+
# Support both string and symbol keys
|
|
182
|
+
safety_config = @config[:safety] || @config["safety"] || {}
|
|
183
|
+
require_container = safety_config[:require_container] || safety_config["require_container"]
|
|
184
|
+
in_container? || require_container == false
|
|
176
185
|
end
|
|
177
186
|
|
|
178
187
|
def in_container?
|
data/lib/aidp/watch/runner.rb
CHANGED
|
@@ -19,12 +19,13 @@ module Aidp
|
|
|
19
19
|
|
|
20
20
|
DEFAULT_INTERVAL = 30
|
|
21
21
|
|
|
22
|
-
def initialize(issues_url:, interval: DEFAULT_INTERVAL, provider_name: nil, gh_available: nil, project_dir: Dir.pwd, once: false, use_workstreams: true, prompt: TTY::Prompt.new, safety_config: {}, force: false)
|
|
22
|
+
def initialize(issues_url:, interval: DEFAULT_INTERVAL, provider_name: nil, gh_available: nil, project_dir: Dir.pwd, once: false, use_workstreams: true, prompt: TTY::Prompt.new, safety_config: {}, force: false, verbose: false)
|
|
23
23
|
@prompt = prompt
|
|
24
24
|
@interval = interval
|
|
25
25
|
@once = once
|
|
26
26
|
@project_dir = project_dir
|
|
27
27
|
@force = force
|
|
28
|
+
@verbose = verbose
|
|
28
29
|
|
|
29
30
|
owner, repo = RepositoryClient.parse_issues_url(issues_url)
|
|
30
31
|
@repository_client = RepositoryClient.new(owner: owner, repo: repo, gh_available: gh_available)
|
|
@@ -33,16 +34,23 @@ module Aidp
|
|
|
33
34
|
config: safety_config
|
|
34
35
|
)
|
|
35
36
|
@state_store = StateStore.new(project_dir: project_dir, repository: "#{owner}/#{repo}")
|
|
37
|
+
|
|
38
|
+
# Extract label configuration from safety_config (it's actually the full watch config)
|
|
39
|
+
label_config = safety_config[:labels] || safety_config["labels"] || {}
|
|
40
|
+
|
|
36
41
|
@plan_processor = PlanProcessor.new(
|
|
37
42
|
repository_client: @repository_client,
|
|
38
43
|
state_store: @state_store,
|
|
39
|
-
plan_generator: PlanGenerator.new(provider_name: provider_name)
|
|
44
|
+
plan_generator: PlanGenerator.new(provider_name: provider_name, verbose: verbose),
|
|
45
|
+
label_config: label_config
|
|
40
46
|
)
|
|
41
47
|
@build_processor = BuildProcessor.new(
|
|
42
48
|
repository_client: @repository_client,
|
|
43
49
|
state_store: @state_store,
|
|
44
50
|
project_dir: project_dir,
|
|
45
|
-
use_workstreams: use_workstreams
|
|
51
|
+
use_workstreams: use_workstreams,
|
|
52
|
+
verbose: verbose,
|
|
53
|
+
label_config: label_config
|
|
46
54
|
)
|
|
47
55
|
end
|
|
48
56
|
|
|
@@ -73,9 +81,10 @@ module Aidp
|
|
|
73
81
|
end
|
|
74
82
|
|
|
75
83
|
def process_plan_triggers
|
|
76
|
-
|
|
84
|
+
plan_label = @plan_processor.plan_label
|
|
85
|
+
issues = @repository_client.list_issues(labels: [plan_label], state: "open")
|
|
77
86
|
issues.each do |issue|
|
|
78
|
-
next unless issue_has_label?(issue,
|
|
87
|
+
next unless issue_has_label?(issue, plan_label)
|
|
79
88
|
|
|
80
89
|
detailed = @repository_client.fetch_issue(issue[:number])
|
|
81
90
|
|
|
@@ -89,9 +98,10 @@ module Aidp
|
|
|
89
98
|
end
|
|
90
99
|
|
|
91
100
|
def process_build_triggers
|
|
92
|
-
|
|
101
|
+
build_label = @build_processor.build_label
|
|
102
|
+
issues = @repository_client.list_issues(labels: [build_label], state: "open")
|
|
93
103
|
issues.each do |issue|
|
|
94
|
-
next unless issue_has_label?(issue,
|
|
104
|
+
next unless issue_has_label?(issue, build_label)
|
|
95
105
|
|
|
96
106
|
status = @state_store.build_status(issue[:number])
|
|
97
107
|
next if status["status"] == "completed"
|