aidp 0.22.0 → 0.24.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +145 -31
  3. data/lib/aidp/cli.rb +19 -2
  4. data/lib/aidp/execute/work_loop_runner.rb +252 -45
  5. data/lib/aidp/execute/work_loop_unit_scheduler.rb +27 -2
  6. data/lib/aidp/harness/condition_detector.rb +42 -8
  7. data/lib/aidp/harness/config_manager.rb +7 -0
  8. data/lib/aidp/harness/config_schema.rb +25 -0
  9. data/lib/aidp/harness/configuration.rb +69 -6
  10. data/lib/aidp/harness/error_handler.rb +117 -44
  11. data/lib/aidp/harness/provider_manager.rb +64 -0
  12. data/lib/aidp/harness/provider_metrics.rb +138 -0
  13. data/lib/aidp/harness/runner.rb +110 -35
  14. data/lib/aidp/harness/simple_user_interface.rb +4 -0
  15. data/lib/aidp/harness/state/ui_state.rb +0 -10
  16. data/lib/aidp/harness/state_manager.rb +1 -15
  17. data/lib/aidp/harness/test_runner.rb +39 -2
  18. data/lib/aidp/logger.rb +34 -4
  19. data/lib/aidp/providers/adapter.rb +241 -0
  20. data/lib/aidp/providers/anthropic.rb +75 -7
  21. data/lib/aidp/providers/base.rb +29 -1
  22. data/lib/aidp/providers/capability_registry.rb +205 -0
  23. data/lib/aidp/providers/codex.rb +14 -0
  24. data/lib/aidp/providers/error_taxonomy.rb +195 -0
  25. data/lib/aidp/providers/gemini.rb +3 -2
  26. data/lib/aidp/setup/devcontainer/backup_manager.rb +11 -4
  27. data/lib/aidp/setup/provider_registry.rb +107 -0
  28. data/lib/aidp/setup/wizard.rb +189 -31
  29. data/lib/aidp/version.rb +1 -1
  30. data/lib/aidp/watch/build_processor.rb +357 -27
  31. data/lib/aidp/watch/plan_generator.rb +16 -1
  32. data/lib/aidp/watch/plan_processor.rb +54 -3
  33. data/lib/aidp/watch/repository_client.rb +78 -4
  34. data/lib/aidp/watch/repository_safety_checker.rb +12 -3
  35. data/lib/aidp/watch/runner.rb +52 -10
  36. data/lib/aidp/workflows/guided_agent.rb +53 -0
  37. data/lib/aidp/worktree.rb +67 -10
  38. data/templates/work_loop/decide_whats_next.md +21 -0
  39. data/templates/work_loop/diagnose_failures.md +21 -0
  40. metadata +10 -3
  41. /data/{bin → exe}/aidp +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e07daf05fddb6301340e9a01b1c6d8eea60ddcf53b2ec2d56e914be610cd67a7
4
- data.tar.gz: 21f26ed81af8f996cbb50c4a4dfe7603230f29499399c134c7a83492042a39e2
3
+ metadata.gz: e2ec07f1c36212b7ace8e467e098fc3bcd917dc0738e27125bbddcef9bbcc30e
4
+ data.tar.gz: edbc40f6c50b581729d185186eae3510e8c23885f12655c46868117af361f31a
5
5
  SHA512:
6
- metadata.gz: 45a1c8c35dbf2f3f672f1990d652c238dd050b3dae3a96edf4d69960475a9e9ee27f1f4ced5ed4a6b0639de6256f56c78d25931e08adacf05ed2aa444e23c9fe
7
- data.tar.gz: 85560929fa2c4a1b51a862e8c541ed92d85e51a3c025eede695a5211c7b30b0a2115c098969fbfbb3a7e4237d5d0c074d194f0df0c50b7cdb025a75e7fc9249a
6
+ metadata.gz: f6472e33b88cf45f0c0abc5131dd608ba20d73480074552275d6d886222357c435023845fa8762b7f527268ba34fc2d3841d504309c8c068adbc38d6ac41f12b
7
+ data.tar.gz: 4283ba15ee02985603adbdba0be476adec35efc9fa6730a01beef1eac50d068647f3560aa03d75fcdb24ffc102e742bc1c3d9ff53fe95662a20241c711b54ae7
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
- Use `aidp init` to generate a devcontainer for any project:
76
+ AIDP can automatically generate and manage devcontainer configurations through the interactive wizard:
77
77
 
78
78
  ```bash
79
- # Initialize project with devcontainer
80
- aidp init
79
+ # Launch the interactive configuration wizard
80
+ aidp config --interactive
81
81
 
82
- # When prompted:
83
- # "Generate devcontainer configuration for sandboxed development?" Yes
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
- # Or use the flag directly
86
- aidp init --with-devcontainer
86
+ # The wizard will detect ports based on your project type and generate
87
+ # a complete devcontainer.json configuration
87
88
  ```
88
89
 
89
- This creates:
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
- - `.devcontainer/Dockerfile` - Customized for your project's language/framework
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
- ### Elevated Permissions in Devcontainers
105
+ ```bash
106
+ # Preview changes
107
+ aidp devcontainer diff
97
108
 
98
- When running inside a devcontainer, you can enable elevated permissions for AI agents:
109
+ # Apply configuration
110
+ aidp devcontainer apply
99
111
 
100
- ```yaml
101
- # aidp.yml
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
- # Or enable per-provider
107
- permissions:
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
- AIDP automatically detects when it's running in a devcontainer and adjusts agent permissions accordingly. This is safe because the container is sandboxed from your host system.
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,23 @@ 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
+ # Initialize logger for watch mode
1268
+ setup_logging(Dir.pwd)
1269
+
1270
+ # Load watch safety configuration
1271
+ config_manager = Aidp::Harness::ConfigManager.new(Dir.pwd)
1272
+ config = config_manager.config || {}
1273
+ watch_config = config[:watch] || config["watch"] || {}
1274
+
1261
1275
  runner = Aidp::Watch::Runner.new(
1262
1276
  issues_url: issues_url,
1263
1277
  interval: interval.positive? ? interval : Aidp::Watch::Runner::DEFAULT_INTERVAL,
@@ -1265,7 +1279,10 @@ module Aidp
1265
1279
  project_dir: Dir.pwd,
1266
1280
  once: once,
1267
1281
  use_workstreams: use_workstreams,
1268
- prompt: create_prompt
1282
+ prompt: create_prompt,
1283
+ safety_config: watch_config,
1284
+ force: force,
1285
+ verbose: verbose
1269
1286
  )
1270
1287
  runner.start
1271
1288
  rescue ArgumentError => e
@@ -77,7 +77,7 @@ module Aidp
77
77
  display_guard_policy_status
78
78
  display_pending_tasks
79
79
 
80
- @unit_scheduler = WorkLoopUnitScheduler.new(units_config)
80
+ @unit_scheduler = WorkLoopUnitScheduler.new(units_config, project_dir: @project_dir)
81
81
  base_context = context.dup
82
82
 
83
83
  loop do
@@ -97,6 +97,8 @@ module Aidp
97
97
 
98
98
  agentic_payload = if unit.name == :decide_whats_next
99
99
  run_decider_agentic_unit(enriched_context)
100
+ elsif unit.name == :diagnose_failures
101
+ run_diagnose_agentic_unit(enriched_context)
100
102
  else
101
103
  run_primary_agentic_unit(step_spec, enriched_context)
102
104
  end
@@ -148,7 +150,27 @@ module Aidp
148
150
  transition_to(:ready) unless @current_state == :ready
149
151
 
150
152
  transition_to(:apply_patch)
151
- agent_result = apply_patch
153
+
154
+ # Wrap agent call in exception handling for true fix-forward
155
+ begin
156
+ agent_result = apply_patch
157
+ rescue => e
158
+ # Convert exception to error result for fix-forward handling
159
+ Aidp.logger.error("work_loop", "Exception during agent call",
160
+ step: @step_name,
161
+ iteration: @iteration_count,
162
+ error: e.message,
163
+ error_class: e.class.name,
164
+ backtrace: e.backtrace&.first(5))
165
+
166
+ display_message(" ⚠️ Exception during agent call: #{e.class.name}: #{e.message}", type: :error)
167
+
168
+ # Append exception to PROMPT.md so agent can see and fix it
169
+ append_exception_to_prompt(e)
170
+
171
+ # Continue to next iteration with fix-forward pattern
172
+ next
173
+ end
152
174
 
153
175
  # Process agent output for task filing signals
154
176
  process_task_filing(agent_result)
@@ -223,6 +245,34 @@ module Aidp
223
245
  )
224
246
  end
225
247
 
248
+ def run_diagnose_agentic_unit(context)
249
+ Aidp.logger.info("work_loop", "Running diagnose_failures agentic unit", step: @step_name)
250
+
251
+ prompt = build_diagnose_prompt(context)
252
+
253
+ agent_result = @provider_manager.execute_with_provider(
254
+ @provider_manager.current_provider,
255
+ prompt,
256
+ {
257
+ step_name: @step_name,
258
+ iteration: @iteration_count,
259
+ project_dir: @project_dir,
260
+ mode: :diagnose_failures
261
+ }
262
+ )
263
+
264
+ requested = AgentSignalParser.extract_next_unit(agent_result[:output])
265
+
266
+ build_agentic_payload(
267
+ agent_result: agent_result,
268
+ response: agent_result,
269
+ summary: agent_result[:output],
270
+ completed: false,
271
+ terminate: false,
272
+ requested_next: requested
273
+ )
274
+ end
275
+
226
276
  def units_config
227
277
  if @config.respond_to?(:work_loop_units_config)
228
278
  @config.work_loop_units_config
@@ -243,41 +293,94 @@ module Aidp
243
293
  end
244
294
 
245
295
  def build_decider_prompt(context)
246
- outputs = Array(context[:deterministic_outputs])
247
- summary = context[:previous_agent_summary]
248
-
249
- sections = []
250
- sections << "# Decide Next Work Loop Unit"
251
- sections << ""
252
- sections << "You are operating in the Aidp work loop. Determine what should happen next."
253
- sections << ""
254
- sections << "## Recent Deterministic Outputs"
255
-
256
- if outputs.empty?
257
- sections << "- None recorded yet."
258
- else
259
- outputs.each do |entry|
260
- sections << "- #{entry[:name]} (status: #{entry[:status]}, finished_at: #{entry[:finished_at]})"
261
- sections << " Output: #{entry[:output_path] || "n/a"}"
262
- end
263
- end
296
+ template = load_work_loop_template("decide_whats_next.md", default_decider_template)
297
+ replacements = {
298
+ "{{DETERMINISTIC_OUTPUTS}}" => format_deterministic_outputs(context[:deterministic_outputs]),
299
+ "{{PREVIOUS_AGENT_SUMMARY}}" => format_previous_agent_summary(context[:previous_agent_summary])
300
+ }
301
+ replacements.reduce(template) { |body, (token, value)| body.gsub(token, value) }
302
+ end
264
303
 
265
- if summary
266
- sections << ""
267
- sections << "## Previous Agent Summary"
268
- sections << summary
269
- end
304
+ def build_diagnose_prompt(context)
305
+ template = load_work_loop_template("diagnose_failures.md", default_diagnose_template)
306
+ replacements = {
307
+ "{{DETERMINISTIC_OUTPUTS}}" => format_deterministic_outputs(context[:deterministic_outputs]),
308
+ "{{PREVIOUS_AGENT_SUMMARY}}" => format_previous_agent_summary(context[:previous_agent_summary])
309
+ }
310
+ replacements.reduce(template) { |body, (token, value)| body.gsub(token, value) }
311
+ end
312
+
313
+ def load_work_loop_template(relative_path, fallback)
314
+ template_path = File.join(@project_dir, "templates", "work_loop", relative_path)
315
+ return File.read(template_path) if File.exist?(template_path)
270
316
 
271
- sections << ""
272
- sections << "## Instructions"
273
- sections << "- Decide whether to run another deterministic unit or resume agentic editing."
274
- sections << "- Announce your decision with `NEXT_UNIT: <unit_name>`."
275
- sections << "- Valid values: names defined in configuration, `agentic`, or `wait_for_github`."
276
- sections << "- Provide a concise rationale below."
277
- sections << ""
278
- sections << "## Rationale"
317
+ fallback
318
+ rescue => e
319
+ Aidp.logger.warn("work_loop", "Unable to load #{relative_path}", error: e.message)
320
+ fallback
321
+ end
322
+
323
+ def default_decider_template
324
+ <<~TEMPLATE
325
+ # Decide Next Work Loop Unit
326
+
327
+ ## Deterministic Outputs
328
+
329
+ {{DETERMINISTIC_OUTPUTS}}
330
+
331
+ ## Previous Agent Summary
332
+
333
+ {{PREVIOUS_AGENT_SUMMARY}}
334
+
335
+ ## Guidance
336
+ - Decide whether to run another deterministic unit or resume agentic editing.
337
+ - Announce your decision with `NEXT_UNIT: <unit_name>`.
338
+ - Valid values: names defined in configuration, `agentic`, or `wait_for_github`.
339
+ - Provide a concise rationale below.
279
340
 
280
- sections.join("\n")
341
+ ## Rationale
342
+ TEMPLATE
343
+ end
344
+
345
+ def default_diagnose_template
346
+ <<~TEMPLATE
347
+ # Diagnose Failures
348
+
349
+ ## Recent Deterministic Outputs
350
+
351
+ {{DETERMINISTIC_OUTPUTS}}
352
+
353
+ ## Previous Agent Summary
354
+
355
+ {{PREVIOUS_AGENT_SUMMARY}}
356
+
357
+ ## Instructions
358
+ - Identify the root cause of the failures above.
359
+ - Recommend the next concrete action (another deterministic unit, agentic editing, or waiting).
360
+ - Emit `NEXT_UNIT: <unit_name>` on its own line.
361
+
362
+ ## Analysis
363
+ TEMPLATE
364
+ end
365
+
366
+ def format_deterministic_outputs(entries)
367
+ data = Array(entries)
368
+ return "- None recorded yet." if data.empty?
369
+
370
+ data.map do |entry|
371
+ name = entry[:name] || "unknown_unit"
372
+ status = entry[:status] || "unknown"
373
+ finished_at = entry[:finished_at]&.to_s || "unknown"
374
+ output = entry[:output_path] || "n/a"
375
+ "- #{name} (status: #{status}, finished_at: #{finished_at})\n Output: #{output}"
376
+ end.join("\n")
377
+ end
378
+
379
+ def format_previous_agent_summary(summary)
380
+ content = summary.to_s.strip
381
+ return "_No previous agent summary._" if content.empty?
382
+
383
+ content
281
384
  end
282
385
 
283
386
  # Transition to a new state in the fix-forward state machine
@@ -544,16 +647,50 @@ module Aidp
544
647
  prompt_content = @prompt_manager.read
545
648
  return {status: "error", message: "PROMPT.md not found"} unless prompt_content
546
649
 
547
- # Send to provider via provider_manager
548
- @provider_manager.execute_with_provider(
549
- @provider_manager.current_provider,
550
- prompt_content,
551
- {
552
- step_name: @step_name,
553
- iteration: @iteration_count,
554
- project_dir: @project_dir
555
- }
556
- )
650
+ # Prepend work loop instructions to every iteration
651
+ full_prompt = build_work_loop_header(@step_name, @iteration_count) + "\n\n" + prompt_content
652
+
653
+ # CRITICAL: Change to project directory before calling provider
654
+ # This ensures Claude CLI runs in the correct directory and can create files
655
+ Dir.chdir(@project_dir) do
656
+ # Send to provider via provider_manager
657
+ @provider_manager.execute_with_provider(
658
+ @provider_manager.current_provider,
659
+ full_prompt,
660
+ {
661
+ step_name: @step_name,
662
+ iteration: @iteration_count,
663
+ project_dir: @project_dir
664
+ }
665
+ )
666
+ end
667
+ end
668
+
669
+ def build_work_loop_header(step_name, iteration)
670
+ parts = []
671
+ parts << "# Work Loop: #{step_name} (Iteration #{iteration})"
672
+ parts << ""
673
+ parts << "## Instructions"
674
+ parts << "You are working in a work loop. Your responsibilities:"
675
+ parts << "1. Read the task description below to understand what needs to be done"
676
+ parts << "2. **Write/edit code files** to implement the required changes"
677
+ parts << "3. Run tests to verify your changes work correctly"
678
+ parts << "4. Update the task list in PROMPT.md as you complete items"
679
+ parts << "5. When ALL tasks are complete and tests pass, mark the step COMPLETE"
680
+ parts << ""
681
+ parts << "## Important Notes"
682
+ parts << "- You have full file system access - create and edit files as needed"
683
+ parts << "- The working directory is: #{@project_dir}"
684
+ parts << "- After you finish, tests and linters will run automatically"
685
+ parts << "- If tests/linters fail, you'll see the errors in the next iteration and can fix them"
686
+ parts << ""
687
+ parts << "## Completion Criteria"
688
+ parts << "Mark this step COMPLETE by adding this line to PROMPT.md:"
689
+ parts << "```"
690
+ parts << "STATUS: COMPLETE"
691
+ parts << "```"
692
+ parts << ""
693
+ parts.join("\n")
557
694
  end
558
695
 
559
696
  def prompt_marked_complete?
@@ -598,6 +735,9 @@ module Aidp
598
735
  failures << ""
599
736
  end
600
737
 
738
+ strategy = build_failure_strategy(test_results, lint_results)
739
+ failures.concat(strategy) unless strategy.empty?
740
+
601
741
  failures << "**Fix-forward instructions**: Do not rollback changes. Build on what exists and fix the failures above."
602
742
  failures << ""
603
743
 
@@ -608,7 +748,48 @@ module Aidp
608
748
  updated_prompt = current_prompt + "\n\n---\n\n" + failures.join("\n")
609
749
  @prompt_manager.write(updated_prompt, step_name: @step_name)
610
750
 
611
- display_message(" [NEXT_PATCH] Added failure reports and diagnostic to PROMPT.md", type: :warning)
751
+ display_message(" [NEXT_PATCH] Added failure reports, strategy, and diagnostic to PROMPT.md", type: :warning)
752
+ end
753
+
754
+ # Append exception details to PROMPT.md for fix-forward handling
755
+ # This allows the agent to see and fix errors that occur during execution
756
+ def append_exception_to_prompt(exception)
757
+ error_report = []
758
+
759
+ error_report << "## Fix-Forward Exception in Iteration #{@iteration_count}"
760
+ error_report << ""
761
+ error_report << "**CRITICAL**: An exception occurred during this iteration. Please analyze and fix the underlying issue."
762
+ error_report << ""
763
+ error_report << "### Exception Details"
764
+ error_report << "- **Type**: `#{exception.class.name}`"
765
+ error_report << "- **Message**: #{exception.message}"
766
+ error_report << ""
767
+
768
+ if exception.backtrace && !exception.backtrace.empty?
769
+ error_report << "### Stack Trace (First 10 lines)"
770
+ error_report << "```"
771
+ exception.backtrace.first(10).each do |line|
772
+ error_report << line
773
+ end
774
+ error_report << "```"
775
+ error_report << ""
776
+ end
777
+
778
+ error_report << "### Required Action"
779
+ error_report << "1. Analyze the exception type and message"
780
+ error_report << "2. Review the stack trace to identify the source"
781
+ error_report << "3. Fix the underlying code issue"
782
+ error_report << "4. Ensure the fix doesn't break existing functionality"
783
+ error_report << ""
784
+ error_report << "**Fix-forward instructions**: Do not rollback changes. Identify the root cause and fix it in the next iteration."
785
+ error_report << ""
786
+
787
+ # Append to PROMPT.md
788
+ current_prompt = @prompt_manager.read
789
+ updated_prompt = current_prompt + "\n\n---\n\n" + error_report.join("\n")
790
+ @prompt_manager.write(updated_prompt, step_name: @step_name)
791
+
792
+ display_message(" [EXCEPTION] Added exception details to PROMPT.md for fix-forward", type: :error)
612
793
  end
613
794
 
614
795
  # Check if we should reinject the style guide at this iteration
@@ -651,6 +832,32 @@ module Aidp
651
832
  reminder.join("\n")
652
833
  end
653
834
 
835
+ def build_failure_strategy(test_results, lint_results)
836
+ return [] if test_results[:success] && lint_results[:success]
837
+
838
+ lines = ["### Recovery Strategy", ""]
839
+
840
+ unless test_results[:success]
841
+ commands = format_command_list(test_results[:failures])
842
+ lines << "- Re-run #{commands} locally to reproduce the failing specs listed above."
843
+ lines << "- Triage the exact failures before moving on to new work."
844
+ end
845
+
846
+ unless lint_results[:success]
847
+ commands = format_command_list(lint_results[:failures])
848
+ lines << "- Execute #{commands} and fix each reported offense."
849
+ end
850
+
851
+ lines << ""
852
+ lines
853
+ end
854
+
855
+ def format_command_list(failures)
856
+ commands = Array(failures).map { |failure| failure[:command] }.compact
857
+ commands = ["the configured command"] if commands.empty?
858
+ commands.map { |cmd| "`#{cmd}`" }.join(" or ")
859
+ end
860
+
654
861
  # Load current step's template content
655
862
  def load_current_template
656
863
  return nil unless @step_name