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,651 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aidp
|
4
|
+
module Execute
|
5
|
+
# REPL macros for fine-grained human control during work loops
|
6
|
+
# Provides commands for:
|
7
|
+
# - /pin <file> - Mark files as read-only
|
8
|
+
# - /focus <dir|glob> - Restrict work scope
|
9
|
+
# - /split - Divide work into smaller contracts
|
10
|
+
# - /halt-on <pattern> - Pause on specific test failures
|
11
|
+
class ReplMacros
|
12
|
+
attr_reader :pinned_files, :focus_patterns, :halt_patterns, :split_mode
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@pinned_files = Set.new
|
16
|
+
@focus_patterns = []
|
17
|
+
@halt_patterns = []
|
18
|
+
@split_mode = false
|
19
|
+
@commands = register_commands
|
20
|
+
end
|
21
|
+
|
22
|
+
# Parse and execute a REPL command
|
23
|
+
# Returns { success: boolean, message: string, action: symbol }
|
24
|
+
def execute(command_line)
|
25
|
+
return {success: false, message: "Empty command", action: :none} if command_line.nil? || command_line.strip.empty?
|
26
|
+
|
27
|
+
parts = command_line.strip.split(/\s+/)
|
28
|
+
command = parts[0]
|
29
|
+
args = parts[1..]
|
30
|
+
|
31
|
+
if command.start_with?("/")
|
32
|
+
execute_macro(command, args)
|
33
|
+
else
|
34
|
+
{success: false, message: "Unknown command: #{command}", action: :none}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Check if a file is pinned (read-only)
|
39
|
+
def pinned?(file_path)
|
40
|
+
@pinned_files.include?(normalize_path(file_path))
|
41
|
+
end
|
42
|
+
|
43
|
+
# Check if a file matches focus scope
|
44
|
+
def in_focus?(file_path)
|
45
|
+
return true if @focus_patterns.empty? # No focus = all files in scope
|
46
|
+
|
47
|
+
normalized = normalize_path(file_path)
|
48
|
+
@focus_patterns.any? { |pattern| matches_pattern?(normalized, pattern) }
|
49
|
+
end
|
50
|
+
|
51
|
+
# Check if a test failure should trigger a halt
|
52
|
+
def should_halt?(failure_message)
|
53
|
+
return false if @halt_patterns.empty?
|
54
|
+
|
55
|
+
@halt_patterns.any? { |pattern| failure_message.match?(Regexp.new(pattern, Regexp::IGNORECASE)) }
|
56
|
+
end
|
57
|
+
|
58
|
+
# Get summary of current REPL state
|
59
|
+
def summary
|
60
|
+
{
|
61
|
+
pinned_files: @pinned_files.to_a,
|
62
|
+
focus_patterns: @focus_patterns,
|
63
|
+
halt_patterns: @halt_patterns,
|
64
|
+
split_mode: @split_mode,
|
65
|
+
active_constraints: active_constraints_count
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
# Clear all macros
|
70
|
+
def reset!
|
71
|
+
@pinned_files.clear
|
72
|
+
@focus_patterns.clear
|
73
|
+
@halt_patterns.clear
|
74
|
+
@split_mode = false
|
75
|
+
end
|
76
|
+
|
77
|
+
# List all available commands
|
78
|
+
def list_commands
|
79
|
+
@commands.keys.sort
|
80
|
+
end
|
81
|
+
|
82
|
+
# Get help for a specific command
|
83
|
+
def help(command = nil)
|
84
|
+
if command.nil?
|
85
|
+
@commands.map { |cmd, info| "#{cmd}: #{info[:description]}" }.join("\n")
|
86
|
+
elsif @commands.key?(command)
|
87
|
+
info = @commands[command]
|
88
|
+
"#{command}: #{info[:description]}\nUsage: #{info[:usage]}\nExample: #{info[:example]}"
|
89
|
+
else
|
90
|
+
"Unknown command: #{command}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
# Register all available REPL commands
|
97
|
+
def register_commands
|
98
|
+
{
|
99
|
+
"/pin" => {
|
100
|
+
description: "Mark file(s) as read-only (do not modify)",
|
101
|
+
usage: "/pin <file|glob>",
|
102
|
+
example: "/pin config/database.yml",
|
103
|
+
handler: method(:cmd_pin)
|
104
|
+
},
|
105
|
+
"/unpin" => {
|
106
|
+
description: "Remove read-only protection from file(s)",
|
107
|
+
usage: "/unpin <file|glob>",
|
108
|
+
example: "/unpin config/database.yml",
|
109
|
+
handler: method(:cmd_unpin)
|
110
|
+
},
|
111
|
+
"/focus" => {
|
112
|
+
description: "Restrict work scope to specific files/directories",
|
113
|
+
usage: "/focus <dir|glob>",
|
114
|
+
example: "/focus lib/features/auth/**/*",
|
115
|
+
handler: method(:cmd_focus)
|
116
|
+
},
|
117
|
+
"/unfocus" => {
|
118
|
+
description: "Remove focus restriction",
|
119
|
+
usage: "/unfocus",
|
120
|
+
example: "/unfocus",
|
121
|
+
handler: method(:cmd_unfocus)
|
122
|
+
},
|
123
|
+
"/split" => {
|
124
|
+
description: "Divide current work into smaller contracts",
|
125
|
+
usage: "/split",
|
126
|
+
example: "/split",
|
127
|
+
handler: method(:cmd_split)
|
128
|
+
},
|
129
|
+
"/halt-on" => {
|
130
|
+
description: "Pause work loop on specific test failure pattern",
|
131
|
+
usage: "/halt-on <pattern>",
|
132
|
+
example: "/halt-on 'authentication.*failed'",
|
133
|
+
handler: method(:cmd_halt_on)
|
134
|
+
},
|
135
|
+
"/unhalt" => {
|
136
|
+
description: "Remove halt-on pattern",
|
137
|
+
usage: "/unhalt <pattern>",
|
138
|
+
example: "/unhalt 'authentication.*failed'",
|
139
|
+
handler: method(:cmd_unhalt)
|
140
|
+
},
|
141
|
+
"/pause" => {
|
142
|
+
description: "Pause the running work loop",
|
143
|
+
usage: "/pause",
|
144
|
+
example: "/pause",
|
145
|
+
handler: method(:cmd_pause)
|
146
|
+
},
|
147
|
+
"/resume" => {
|
148
|
+
description: "Resume a paused work loop",
|
149
|
+
usage: "/resume",
|
150
|
+
example: "/resume",
|
151
|
+
handler: method(:cmd_resume)
|
152
|
+
},
|
153
|
+
"/cancel" => {
|
154
|
+
description: "Cancel the work loop and save checkpoint",
|
155
|
+
usage: "/cancel [--no-checkpoint]",
|
156
|
+
example: "/cancel",
|
157
|
+
handler: method(:cmd_cancel)
|
158
|
+
},
|
159
|
+
"/inject" => {
|
160
|
+
description: "Add instruction to be merged in next iteration",
|
161
|
+
usage: "/inject <instruction> [--priority high|normal|low]",
|
162
|
+
example: "/inject 'Add error handling for edge case X'",
|
163
|
+
handler: method(:cmd_inject)
|
164
|
+
},
|
165
|
+
"/merge" => {
|
166
|
+
description: "Update plan/contract for next iteration",
|
167
|
+
usage: "/merge <plan_update>",
|
168
|
+
example: "/merge 'Add acceptance criteria: handle timeouts'",
|
169
|
+
handler: method(:cmd_merge)
|
170
|
+
},
|
171
|
+
"/update" => {
|
172
|
+
description: "Update guard rail configuration",
|
173
|
+
usage: "/update guard <key>=<value>",
|
174
|
+
example: "/update guard max_lines=500",
|
175
|
+
handler: method(:cmd_update)
|
176
|
+
},
|
177
|
+
"/reload" => {
|
178
|
+
description: "Reload configuration from file",
|
179
|
+
usage: "/reload config",
|
180
|
+
example: "/reload config",
|
181
|
+
handler: method(:cmd_reload)
|
182
|
+
},
|
183
|
+
"/rollback" => {
|
184
|
+
description: "Rollback n commits on current branch",
|
185
|
+
usage: "/rollback <n>",
|
186
|
+
example: "/rollback 2",
|
187
|
+
handler: method(:cmd_rollback)
|
188
|
+
},
|
189
|
+
"/undo" => {
|
190
|
+
description: "Undo last commit",
|
191
|
+
usage: "/undo last",
|
192
|
+
example: "/undo last",
|
193
|
+
handler: method(:cmd_undo)
|
194
|
+
},
|
195
|
+
"/background" => {
|
196
|
+
description: "Detach REPL and enter background daemon mode",
|
197
|
+
usage: "/background",
|
198
|
+
example: "/background",
|
199
|
+
handler: method(:cmd_background)
|
200
|
+
},
|
201
|
+
"/status" => {
|
202
|
+
description: "Show current REPL macro state",
|
203
|
+
usage: "/status",
|
204
|
+
example: "/status",
|
205
|
+
handler: method(:cmd_status)
|
206
|
+
},
|
207
|
+
"/reset" => {
|
208
|
+
description: "Clear all REPL macros",
|
209
|
+
usage: "/reset",
|
210
|
+
example: "/reset",
|
211
|
+
handler: method(:cmd_reset)
|
212
|
+
},
|
213
|
+
"/help" => {
|
214
|
+
description: "Show help for commands",
|
215
|
+
usage: "/help [command]",
|
216
|
+
example: "/help pin",
|
217
|
+
handler: method(:cmd_help)
|
218
|
+
}
|
219
|
+
}
|
220
|
+
end
|
221
|
+
|
222
|
+
# Execute a macro command
|
223
|
+
def execute_macro(command, args)
|
224
|
+
command_info = @commands[command]
|
225
|
+
|
226
|
+
unless command_info
|
227
|
+
return {
|
228
|
+
success: false,
|
229
|
+
message: "Unknown command: #{command}. Type /help for available commands.",
|
230
|
+
action: :none
|
231
|
+
}
|
232
|
+
end
|
233
|
+
|
234
|
+
command_info[:handler].call(args)
|
235
|
+
end
|
236
|
+
|
237
|
+
# Command: /pin <file|glob>
|
238
|
+
def cmd_pin(args)
|
239
|
+
return {success: false, message: "Usage: /pin <file|glob>", action: :none} if args.empty?
|
240
|
+
|
241
|
+
pattern = args.join(" ")
|
242
|
+
files = expand_pattern(pattern)
|
243
|
+
|
244
|
+
if files.empty?
|
245
|
+
@pinned_files.add(normalize_path(pattern))
|
246
|
+
files_added = [pattern]
|
247
|
+
else
|
248
|
+
files.each { |f| @pinned_files.add(f) }
|
249
|
+
files_added = files
|
250
|
+
end
|
251
|
+
|
252
|
+
{
|
253
|
+
success: true,
|
254
|
+
message: "Pinned #{files_added.size} file(s): #{files_added.join(", ")}",
|
255
|
+
action: :update_constraints,
|
256
|
+
data: {pinned: files_added}
|
257
|
+
}
|
258
|
+
end
|
259
|
+
|
260
|
+
# Command: /unpin <file|glob>
|
261
|
+
def cmd_unpin(args)
|
262
|
+
return {success: false, message: "Usage: /unpin <file|glob>", action: :none} if args.empty?
|
263
|
+
|
264
|
+
pattern = args.join(" ")
|
265
|
+
files = expand_pattern(pattern)
|
266
|
+
|
267
|
+
removed = []
|
268
|
+
if files.empty?
|
269
|
+
normalized = normalize_path(pattern)
|
270
|
+
# Check if file is pinned before attempting to remove
|
271
|
+
if @pinned_files.include?(normalized)
|
272
|
+
@pinned_files.delete(normalized)
|
273
|
+
removed << pattern
|
274
|
+
end
|
275
|
+
else
|
276
|
+
files.each do |f|
|
277
|
+
if @pinned_files.include?(f)
|
278
|
+
@pinned_files.delete(f)
|
279
|
+
removed << f
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
if removed.any?
|
285
|
+
{
|
286
|
+
success: true,
|
287
|
+
message: "Unpinned #{removed.size} file(s): #{removed.join(", ")}",
|
288
|
+
action: :update_constraints,
|
289
|
+
data: {unpinned: removed}
|
290
|
+
}
|
291
|
+
else
|
292
|
+
{success: false, message: "No matching pinned files found", action: :none}
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
# Command: /focus <dir|glob>
|
297
|
+
def cmd_focus(args)
|
298
|
+
return {success: false, message: "Usage: /focus <dir|glob>", action: :none} if args.empty?
|
299
|
+
|
300
|
+
pattern = args.join(" ")
|
301
|
+
@focus_patterns << pattern
|
302
|
+
|
303
|
+
{
|
304
|
+
success: true,
|
305
|
+
message: "Focus set to: #{pattern}",
|
306
|
+
action: :update_constraints,
|
307
|
+
data: {focus: pattern}
|
308
|
+
}
|
309
|
+
end
|
310
|
+
|
311
|
+
# Command: /unfocus
|
312
|
+
def cmd_unfocus(args)
|
313
|
+
@focus_patterns.clear
|
314
|
+
|
315
|
+
{
|
316
|
+
success: true,
|
317
|
+
message: "Focus removed - all files in scope",
|
318
|
+
action: :update_constraints
|
319
|
+
}
|
320
|
+
end
|
321
|
+
|
322
|
+
# Command: /split
|
323
|
+
def cmd_split(args)
|
324
|
+
@split_mode = true
|
325
|
+
|
326
|
+
{
|
327
|
+
success: true,
|
328
|
+
message: "Split mode enabled - work will be divided into smaller contracts",
|
329
|
+
action: :split_work,
|
330
|
+
data: {split_mode: true}
|
331
|
+
}
|
332
|
+
end
|
333
|
+
|
334
|
+
# Command: /halt-on <pattern>
|
335
|
+
def cmd_halt_on(args)
|
336
|
+
return {success: false, message: "Usage: /halt-on <pattern>", action: :none} if args.empty?
|
337
|
+
|
338
|
+
pattern = args.join(" ").gsub(/^['"]|['"]$/, "") # Remove quotes
|
339
|
+
@halt_patterns << pattern
|
340
|
+
|
341
|
+
{
|
342
|
+
success: true,
|
343
|
+
message: "Will halt on test failures matching: #{pattern}",
|
344
|
+
action: :update_constraints,
|
345
|
+
data: {halt_pattern: pattern}
|
346
|
+
}
|
347
|
+
end
|
348
|
+
|
349
|
+
# Command: /unhalt <pattern>
|
350
|
+
def cmd_unhalt(args)
|
351
|
+
if args.empty?
|
352
|
+
@halt_patterns.clear
|
353
|
+
message = "All halt patterns removed"
|
354
|
+
else
|
355
|
+
pattern = args.join(" ").gsub(/^['"]|['"]$/, "")
|
356
|
+
if @halt_patterns.delete(pattern)
|
357
|
+
message = "Removed halt pattern: #{pattern}"
|
358
|
+
else
|
359
|
+
return {success: false, message: "Halt pattern not found: #{pattern}", action: :none}
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
{
|
364
|
+
success: true,
|
365
|
+
message: message,
|
366
|
+
action: :update_constraints
|
367
|
+
}
|
368
|
+
end
|
369
|
+
|
370
|
+
# Command: /status
|
371
|
+
def cmd_status(args)
|
372
|
+
lines = []
|
373
|
+
lines << "REPL Macro Status:"
|
374
|
+
lines << ""
|
375
|
+
|
376
|
+
if @pinned_files.any?
|
377
|
+
lines << "Pinned Files (#{@pinned_files.size}):"
|
378
|
+
@pinned_files.to_a.sort.each { |f| lines << " - #{f}" }
|
379
|
+
else
|
380
|
+
lines << "Pinned Files: (none)"
|
381
|
+
end
|
382
|
+
|
383
|
+
lines << ""
|
384
|
+
|
385
|
+
if @focus_patterns.any?
|
386
|
+
lines << "Focus Patterns (#{@focus_patterns.size}):"
|
387
|
+
@focus_patterns.each { |p| lines << " - #{p}" }
|
388
|
+
else
|
389
|
+
lines << "Focus: All files in scope"
|
390
|
+
end
|
391
|
+
|
392
|
+
lines << ""
|
393
|
+
|
394
|
+
if @halt_patterns.any?
|
395
|
+
lines << "Halt Patterns (#{@halt_patterns.size}):"
|
396
|
+
@halt_patterns.each { |p| lines << " - #{p}" }
|
397
|
+
else
|
398
|
+
lines << "Halt Patterns: (none)"
|
399
|
+
end
|
400
|
+
|
401
|
+
lines << ""
|
402
|
+
lines << "Split Mode: #{@split_mode ? "enabled" : "disabled"}"
|
403
|
+
|
404
|
+
{
|
405
|
+
success: true,
|
406
|
+
message: lines.join("\n"),
|
407
|
+
action: :display
|
408
|
+
}
|
409
|
+
end
|
410
|
+
|
411
|
+
# Command: /reset
|
412
|
+
def cmd_reset(args)
|
413
|
+
reset!
|
414
|
+
|
415
|
+
{
|
416
|
+
success: true,
|
417
|
+
message: "All REPL macros cleared",
|
418
|
+
action: :update_constraints
|
419
|
+
}
|
420
|
+
end
|
421
|
+
|
422
|
+
# Command: /help
|
423
|
+
def cmd_help(args)
|
424
|
+
message = if args.empty?
|
425
|
+
help_text = ["Available REPL Commands:", ""]
|
426
|
+
@commands.each do |cmd, info|
|
427
|
+
help_text << "#{cmd} - #{info[:description]}"
|
428
|
+
end
|
429
|
+
help_text << ""
|
430
|
+
help_text << "Type /help <command> for detailed help"
|
431
|
+
help_text.join("\n")
|
432
|
+
else
|
433
|
+
help(args.first)
|
434
|
+
end
|
435
|
+
|
436
|
+
{
|
437
|
+
success: true,
|
438
|
+
message: message,
|
439
|
+
action: :display
|
440
|
+
}
|
441
|
+
end
|
442
|
+
|
443
|
+
# Normalize file path
|
444
|
+
def normalize_path(path)
|
445
|
+
path.to_s.strip.gsub(%r{^./}, "")
|
446
|
+
end
|
447
|
+
|
448
|
+
# Expand glob pattern to actual files
|
449
|
+
def expand_pattern(pattern)
|
450
|
+
return [] unless pattern.include?("*") || pattern.include?("?")
|
451
|
+
|
452
|
+
Dir.glob(pattern, File::FNM_DOTMATCH).map { |f| normalize_path(f) }.reject { |f| File.directory?(f) }
|
453
|
+
end
|
454
|
+
|
455
|
+
# Check if path matches glob pattern
|
456
|
+
# Uses File.fnmatch for safe, efficient pattern matching without ReDoS risk
|
457
|
+
def matches_pattern?(path, pattern)
|
458
|
+
# Ruby's File.fnmatch with FNM_EXTGLOB handles most patterns safely
|
459
|
+
# For ** patterns, we need to handle them specially as fnmatch doesn't support ** natively
|
460
|
+
|
461
|
+
if pattern.include?("**")
|
462
|
+
# Convert ** to * for fnmatch compatibility and check if path contains the pattern parts
|
463
|
+
# Pattern like "lib/**/*.rb" should match "lib/foo/bar.rb"
|
464
|
+
pattern_parts = pattern.split("**").map(&:strip).reject(&:empty?)
|
465
|
+
|
466
|
+
if pattern_parts.empty?
|
467
|
+
# Pattern is just "**" - matches everything
|
468
|
+
true
|
469
|
+
elsif pattern_parts.size == 1
|
470
|
+
# Pattern like "**/file.rb" or "lib/**"
|
471
|
+
part = pattern_parts[0].sub(%r{^/}, "").sub(%r{/$}, "")
|
472
|
+
if pattern.start_with?("**")
|
473
|
+
# Matches if any part of the path matches
|
474
|
+
File.fnmatch(part, path, File::FNM_EXTGLOB) ||
|
475
|
+
File.fnmatch("**/#{part}", path, File::FNM_EXTGLOB) ||
|
476
|
+
path.end_with?(part) ||
|
477
|
+
path.include?("/#{part}")
|
478
|
+
else
|
479
|
+
# Pattern ends with **: match prefix
|
480
|
+
path.start_with?(part)
|
481
|
+
end
|
482
|
+
else
|
483
|
+
# Pattern like "lib/**/*.rb" - has prefix and suffix
|
484
|
+
prefix = pattern_parts[0].sub(%r{/$}, "")
|
485
|
+
suffix = pattern_parts[1].sub(%r{^/}, "")
|
486
|
+
|
487
|
+
path.start_with?(prefix) && File.fnmatch(suffix, path.sub(/^#{Regexp.escape(prefix)}\//, ""), File::FNM_EXTGLOB)
|
488
|
+
end
|
489
|
+
else
|
490
|
+
# Standard glob pattern - use File.fnmatch which is safe from ReDoS
|
491
|
+
# FNM_DOTMATCH allows * to match files starting with .
|
492
|
+
File.fnmatch(pattern, path, File::FNM_EXTGLOB | File::FNM_DOTMATCH)
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
# Count active constraints
|
497
|
+
def active_constraints_count
|
498
|
+
count = 0
|
499
|
+
count += @pinned_files.size
|
500
|
+
count += @focus_patterns.size
|
501
|
+
count += @halt_patterns.size
|
502
|
+
count += 1 if @split_mode
|
503
|
+
count
|
504
|
+
end
|
505
|
+
|
506
|
+
# Command: /pause
|
507
|
+
def cmd_pause(args)
|
508
|
+
{
|
509
|
+
success: true,
|
510
|
+
message: "Pause signal sent to work loop",
|
511
|
+
action: :pause_work_loop
|
512
|
+
}
|
513
|
+
end
|
514
|
+
|
515
|
+
# Command: /resume
|
516
|
+
def cmd_resume(args)
|
517
|
+
{
|
518
|
+
success: true,
|
519
|
+
message: "Resume signal sent to work loop",
|
520
|
+
action: :resume_work_loop
|
521
|
+
}
|
522
|
+
end
|
523
|
+
|
524
|
+
# Command: /cancel
|
525
|
+
def cmd_cancel(args)
|
526
|
+
save_checkpoint = !args.include?("--no-checkpoint")
|
527
|
+
|
528
|
+
{
|
529
|
+
success: true,
|
530
|
+
message: save_checkpoint ? "Cancelling with checkpoint save..." : "Cancelling without checkpoint...",
|
531
|
+
action: :cancel_work_loop,
|
532
|
+
data: {save_checkpoint: save_checkpoint}
|
533
|
+
}
|
534
|
+
end
|
535
|
+
|
536
|
+
# Command: /inject <instruction>
|
537
|
+
def cmd_inject(args)
|
538
|
+
return {success: false, message: "Usage: /inject <instruction> [--priority high|normal|low]", action: :none} if args.empty?
|
539
|
+
|
540
|
+
# Extract priority flag if present
|
541
|
+
priority = :normal
|
542
|
+
if (idx = args.index("--priority"))
|
543
|
+
priority = args[idx + 1]&.to_sym || :normal
|
544
|
+
args.delete_at(idx) # Remove --priority
|
545
|
+
args.delete_at(idx) # Remove priority value
|
546
|
+
end
|
547
|
+
|
548
|
+
instruction = args.join(" ")
|
549
|
+
|
550
|
+
{
|
551
|
+
success: true,
|
552
|
+
message: "Instruction queued for next iteration (priority: #{priority})",
|
553
|
+
action: :enqueue_instruction,
|
554
|
+
data: {
|
555
|
+
instruction: instruction,
|
556
|
+
type: :user_input,
|
557
|
+
priority: priority
|
558
|
+
}
|
559
|
+
}
|
560
|
+
end
|
561
|
+
|
562
|
+
# Command: /merge <plan_update>
|
563
|
+
def cmd_merge(args)
|
564
|
+
return {success: false, message: "Usage: /merge <plan_update>", action: :none} if args.empty?
|
565
|
+
|
566
|
+
plan_update = args.join(" ")
|
567
|
+
|
568
|
+
{
|
569
|
+
success: true,
|
570
|
+
message: "Plan update queued for next iteration",
|
571
|
+
action: :enqueue_instruction,
|
572
|
+
data: {
|
573
|
+
instruction: plan_update,
|
574
|
+
type: :plan_update,
|
575
|
+
priority: :high
|
576
|
+
}
|
577
|
+
}
|
578
|
+
end
|
579
|
+
|
580
|
+
# Command: /update guard <key>=<value>
|
581
|
+
def cmd_update(args)
|
582
|
+
return {success: false, message: "Usage: /update guard <key>=<value>", action: :none} if args.size < 2
|
583
|
+
|
584
|
+
category = args[0]
|
585
|
+
return {success: false, message: "Only 'guard' updates supported currently", action: :none} unless category == "guard"
|
586
|
+
|
587
|
+
key_value = args[1]
|
588
|
+
return {success: false, message: "Invalid format. Use: key=value", action: :none} unless key_value.include?("=")
|
589
|
+
|
590
|
+
key, value = key_value.split("=", 2)
|
591
|
+
|
592
|
+
{
|
593
|
+
success: true,
|
594
|
+
message: "Guard update queued: #{key} = #{value}",
|
595
|
+
action: :update_guard,
|
596
|
+
data: {key: key, value: value}
|
597
|
+
}
|
598
|
+
end
|
599
|
+
|
600
|
+
# Command: /reload config
|
601
|
+
def cmd_reload(args)
|
602
|
+
return {success: false, message: "Usage: /reload config", action: :none} if args.empty?
|
603
|
+
|
604
|
+
category = args[0]
|
605
|
+
return {success: false, message: "Only 'config' reload supported", action: :none} unless category == "config"
|
606
|
+
|
607
|
+
{
|
608
|
+
success: true,
|
609
|
+
message: "Configuration reload requested for next iteration",
|
610
|
+
action: :reload_config
|
611
|
+
}
|
612
|
+
end
|
613
|
+
|
614
|
+
# Command: /rollback <n>
|
615
|
+
def cmd_rollback(args)
|
616
|
+
return {success: false, message: "Usage: /rollback <n>", action: :none} if args.empty?
|
617
|
+
|
618
|
+
n = args[0].to_i
|
619
|
+
return {success: false, message: "Invalid number: #{args[0]}", action: :none} if n <= 0
|
620
|
+
|
621
|
+
{
|
622
|
+
success: true,
|
623
|
+
message: "Rollback #{n} commit(s) requested - will execute at next safe point",
|
624
|
+
action: :rollback_commits,
|
625
|
+
data: {count: n}
|
626
|
+
}
|
627
|
+
end
|
628
|
+
|
629
|
+
# Command: /undo last
|
630
|
+
def cmd_undo(args)
|
631
|
+
return {success: false, message: "Usage: /undo last", action: :none} unless args[0] == "last"
|
632
|
+
|
633
|
+
{
|
634
|
+
success: true,
|
635
|
+
message: "Undo last commit requested - will execute at next safe point",
|
636
|
+
action: :rollback_commits,
|
637
|
+
data: {count: 1}
|
638
|
+
}
|
639
|
+
end
|
640
|
+
|
641
|
+
# Command: /background
|
642
|
+
def cmd_background(args)
|
643
|
+
{
|
644
|
+
success: true,
|
645
|
+
message: "Detaching REPL and entering background daemon mode...",
|
646
|
+
action: :enter_background_mode
|
647
|
+
}
|
648
|
+
end
|
649
|
+
end
|
650
|
+
end
|
651
|
+
end
|
data/lib/aidp/execute/steps.rb
CHANGED
@@ -124,6 +124,14 @@ module Aidp
|
|
124
124
|
"outs" => ["implementation_log.md"],
|
125
125
|
"gate" => false,
|
126
126
|
"implementation" => true # Special step that runs development tasks
|
127
|
+
},
|
128
|
+
# Simple task execution - for one-off commands and simple fixes
|
129
|
+
"99_SIMPLE_TASK" => {
|
130
|
+
"templates" => ["implementation/simple_task.md"],
|
131
|
+
"description" => "Execute Simple Task (one-off commands, quick fixes, linting)",
|
132
|
+
"outs" => [],
|
133
|
+
"gate" => false,
|
134
|
+
"simple" => true # Special step for simple, focused tasks
|
127
135
|
}
|
128
136
|
}.freeze
|
129
137
|
end
|