aidp 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -0
  3. data/lib/aidp/cli/first_run_wizard.rb +28 -303
  4. data/lib/aidp/cli/issue_importer.rb +359 -0
  5. data/lib/aidp/cli.rb +151 -3
  6. data/lib/aidp/daemon/process_manager.rb +146 -0
  7. data/lib/aidp/daemon/runner.rb +232 -0
  8. data/lib/aidp/execute/async_work_loop_runner.rb +216 -0
  9. data/lib/aidp/execute/future_work_backlog.rb +411 -0
  10. data/lib/aidp/execute/guard_policy.rb +246 -0
  11. data/lib/aidp/execute/instruction_queue.rb +131 -0
  12. data/lib/aidp/execute/interactive_repl.rb +335 -0
  13. data/lib/aidp/execute/repl_macros.rb +651 -0
  14. data/lib/aidp/execute/steps.rb +8 -0
  15. data/lib/aidp/execute/work_loop_runner.rb +322 -36
  16. data/lib/aidp/execute/work_loop_state.rb +162 -0
  17. data/lib/aidp/harness/config_schema.rb +88 -0
  18. data/lib/aidp/harness/configuration.rb +48 -1
  19. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +2 -0
  20. data/lib/aidp/init/doc_generator.rb +256 -0
  21. data/lib/aidp/init/project_analyzer.rb +343 -0
  22. data/lib/aidp/init/runner.rb +83 -0
  23. data/lib/aidp/init.rb +5 -0
  24. data/lib/aidp/logger.rb +279 -0
  25. data/lib/aidp/setup/wizard.rb +777 -0
  26. data/lib/aidp/tooling_detector.rb +115 -0
  27. data/lib/aidp/version.rb +1 -1
  28. data/lib/aidp/watch/build_processor.rb +282 -0
  29. data/lib/aidp/watch/plan_generator.rb +166 -0
  30. data/lib/aidp/watch/plan_processor.rb +83 -0
  31. data/lib/aidp/watch/repository_client.rb +243 -0
  32. data/lib/aidp/watch/runner.rb +93 -0
  33. data/lib/aidp/watch/state_store.rb +105 -0
  34. data/lib/aidp/watch.rb +9 -0
  35. data/lib/aidp.rb +14 -0
  36. data/templates/implementation/simple_task.md +36 -0
  37. metadata +26 -1
@@ -404,6 +404,55 @@ module Aidp
404
404
  items: {
405
405
  type: :string
406
406
  }
407
+ },
408
+ guards: {
409
+ type: :hash,
410
+ required: false,
411
+ default: {
412
+ enabled: false
413
+ },
414
+ properties: {
415
+ enabled: {
416
+ type: :boolean,
417
+ required: false,
418
+ default: false
419
+ },
420
+ include_files: {
421
+ type: :array,
422
+ required: false,
423
+ default: [],
424
+ items: {
425
+ type: :string
426
+ }
427
+ },
428
+ exclude_files: {
429
+ type: :array,
430
+ required: false,
431
+ default: [],
432
+ items: {
433
+ type: :string
434
+ }
435
+ },
436
+ confirm_files: {
437
+ type: :array,
438
+ required: false,
439
+ default: [],
440
+ items: {
441
+ type: :string
442
+ }
443
+ },
444
+ max_lines_per_commit: {
445
+ type: :integer,
446
+ required: false,
447
+ min: 1,
448
+ max: 10000
449
+ },
450
+ bypass: {
451
+ type: :boolean,
452
+ required: false,
453
+ default: false
454
+ }
455
+ }
407
456
  }
408
457
  }
409
458
  }
@@ -576,6 +625,45 @@ module Aidp
576
625
  }
577
626
  }
578
627
  }
628
+ },
629
+ logging: {
630
+ type: :hash,
631
+ required: false,
632
+ default: {},
633
+ properties: {
634
+ level: {
635
+ type: :string,
636
+ required: false,
637
+ default: "info",
638
+ enum: ["debug", "info", "warn", "error"]
639
+ },
640
+ json: {
641
+ type: :boolean,
642
+ required: false,
643
+ default: false
644
+ },
645
+ max_size_mb: {
646
+ type: :integer,
647
+ required: false,
648
+ default: 10,
649
+ min: 1,
650
+ max: 100
651
+ },
652
+ max_backups: {
653
+ type: :integer,
654
+ required: false,
655
+ default: 5,
656
+ min: 1,
657
+ max: 20
658
+ },
659
+ max_age_days: {
660
+ type: :integer,
661
+ required: false,
662
+ default: 14,
663
+ min: 1,
664
+ max: 365
665
+ }
666
+ }
579
667
  }
580
668
  }.freeze
581
669
 
@@ -170,6 +170,41 @@ module Aidp
170
170
  work_loop_config[:lint_commands] || []
171
171
  end
172
172
 
173
+ # Get guards configuration
174
+ def guards_config
175
+ work_loop_config[:guards] || default_guards_config
176
+ end
177
+
178
+ # Check if guards are enabled
179
+ def guards_enabled?
180
+ guards_config[:enabled] == true
181
+ end
182
+
183
+ # Get include file patterns for guards
184
+ def guards_include_files
185
+ guards_config[:include_files] || []
186
+ end
187
+
188
+ # Get exclude file patterns for guards
189
+ def guards_exclude_files
190
+ guards_config[:exclude_files] || []
191
+ end
192
+
193
+ # Get files requiring confirmation for guards
194
+ def guards_confirm_files
195
+ guards_config[:confirm_files] || []
196
+ end
197
+
198
+ # Get max lines per commit for guards
199
+ def guards_max_lines_per_commit
200
+ guards_config[:max_lines_per_commit]
201
+ end
202
+
203
+ # Check if guards are bypassed
204
+ def guards_bypass?
205
+ guards_config[:bypass] == true
206
+ end
207
+
173
208
  # Get provider priority
174
209
  def provider_priority(provider_name)
175
210
  provider_config(provider_name)[:priority] || 0
@@ -447,7 +482,19 @@ module Aidp
447
482
  enabled: true,
448
483
  max_iterations: 50,
449
484
  test_commands: [],
450
- lint_commands: []
485
+ lint_commands: [],
486
+ guards: default_guards_config
487
+ }
488
+ end
489
+
490
+ def default_guards_config
491
+ {
492
+ enabled: false,
493
+ include_files: [],
494
+ exclude_files: [],
495
+ confirm_files: [],
496
+ max_lines_per_commit: nil,
497
+ bypass: false
451
498
  }
452
499
  end
453
500
 
@@ -258,7 +258,9 @@ module Aidp
258
258
  @user_input = result[:user_input]
259
259
 
260
260
  # Return in the expected format
261
+ # IMPORTANT: Include the mode from guided agent result (usually :execute)
261
262
  {
263
+ mode: result[:mode],
262
264
  workflow_type: result[:workflow_type],
263
265
  steps: result[:steps],
264
266
  user_input: @user_input,
@@ -0,0 +1,256 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "time"
5
+
6
+ module Aidp
7
+ module Init
8
+ # Creates project documentation artefacts based on the analyzer output. All
9
+ # documents are deterministic and tailored with repository insights.
10
+ class DocGenerator
11
+ OUTPUT_DIR = "docs"
12
+ STYLE_GUIDE_PATH = File.join(OUTPUT_DIR, "LLM_STYLE_GUIDE.md")
13
+ ANALYSIS_PATH = File.join(OUTPUT_DIR, "PROJECT_ANALYSIS.md")
14
+ QUALITY_PLAN_PATH = File.join(OUTPUT_DIR, "CODE_QUALITY_PLAN.md")
15
+
16
+ def initialize(project_dir = Dir.pwd)
17
+ @project_dir = project_dir
18
+ end
19
+
20
+ def generate(analysis:, preferences: {})
21
+ ensure_output_directory
22
+ write_style_guide(analysis, preferences)
23
+ write_project_analysis(analysis)
24
+ write_quality_plan(analysis, preferences)
25
+ end
26
+
27
+ private
28
+
29
+ def ensure_output_directory
30
+ FileUtils.mkdir_p(File.join(@project_dir, OUTPUT_DIR))
31
+ end
32
+
33
+ def write_style_guide(analysis, preferences)
34
+ languages = format_list(analysis[:languages].keys)
35
+ frameworks = format_list(analysis[:frameworks])
36
+ test_frameworks = format_list(analysis[:test_frameworks])
37
+ key_dirs = format_list(analysis[:key_directories])
38
+ tooling = analysis[:tooling].keys.map { |tool| format_tool(tool) }.sort
39
+
40
+ adoption_note = if truthy?(preferences[:adopt_new_conventions])
41
+ "This project has opted to adopt new conventions recommended by aidp init. When in doubt, prefer the rules below over legacy patterns."
42
+ else
43
+ "Retain existing conventions when they do not conflict with the guidance below."
44
+ end
45
+
46
+ content = <<~GUIDE
47
+ # Project LLM Style Guide
48
+
49
+ > Generated automatically by `aidp init` on #{Time.now.utc.iso8601}.
50
+ >
51
+ > Detected languages: #{languages}
52
+ > Framework hints: #{frameworks.empty? ? "None detected" : frameworks}
53
+ > Primary test frameworks: #{test_frameworks.empty? ? "Unknown" : test_frameworks}
54
+ > Key directories: #{key_dirs.empty? ? "Standard structure" : key_dirs}
55
+
56
+ #{adoption_note}
57
+
58
+ ## 1. Core Engineering Rules
59
+ - Prioritise readability and maintainability; extract objects or modules once business logic exceeds a few branches.
60
+ - Co-locate domain objects with their tests under the matching directory (e.g., `lib/` ↔ `spec/`).
61
+ - Remove dead code and feature flags that are no longer exercised; keep git history as the source of truth.
62
+ - Use small, composable services rather than bloated classes.
63
+
64
+ ## 2. Naming & Structure
65
+ - Follow idiomatic naming for the detected languages (#{languages}); align files under #{key_dirs.empty? ? "the project root" : key_dirs}.
66
+ - Ensure top-level namespaces mirror the directory structure (e.g., `Aidp::Init` lives in `lib/aidp/init/`).
67
+ - Keep public APIs explicit with keyword arguments and descriptive method names.
68
+
69
+ ## 3. Parameters & Data
70
+ - Limit positional arguments to three; prefer keyword arguments or value objects beyond that.
71
+ - Reuse shared data structures to capture configuration (YAML/JSON) instead of scattered constants.
72
+ - Validate incoming data at boundaries; rely on plain objects internally.
73
+
74
+ ## 4. Error Handling
75
+ - Raise domain-specific errors; avoid using plain `StandardError` without context.
76
+ - Wrap external calls with rescuable adapters and surface actionable error messages.
77
+ - Log failures with relevant identifiers only—never entire payloads.
78
+
79
+ ## 5. Testing Contracts
80
+ - Mirror production directory structure inside `#{preferred_test_dirs(analysis)}`.
81
+ - Keep tests independent; mock external services only at the boundary layers.
82
+ - Use the project's native assertions (#{test_frameworks.empty? ? "choose an appropriate framework" : test_frameworks}) and ensure every bug fix comes with a regression test.
83
+
84
+ ## 6. Framework-Specific Guidelines
85
+ - Adopt the idioms of detected frameworks#{frameworks.empty? ? " once adopted." : " (#{frameworks})."}
86
+ - Keep controllers/handlers thin; delegate logic to service objects or interactors.
87
+ - Store shared UI or component primitives in a central folder to make reuse easier.
88
+
89
+ ## 7. Dependencies & External Services
90
+ - Document every external integration inside `docs/` and keep credentials outside the repo.
91
+ - Use dependency injection for clients; avoid global state or singletons.
92
+ - When adding new gems or packages, document the rationale in `PROJECT_ANALYSIS.md`.
93
+
94
+ ## 8. Build & Development
95
+ - Run linters before committing: #{tooling.empty? ? "add rubocop/eslint/flake8 as appropriate." : tooling.join(", ")}.
96
+ - Keep build scripts in `bin/` or `scripts/` and ensure they are idempotent.
97
+ - Prefer `mise` or language-specific version managers to keep toolchains aligned.
98
+
99
+ ## 9. Performance
100
+ - Measure before optimising; add benchmarks for hotspots.
101
+ - Cache expensive computations when they are pure and repeatable.
102
+ - Review dependency load time; lazy-load optional components where possible.
103
+
104
+ ## 10. Project-Specific Anti-Patterns
105
+ - Avoid sprawling God objects that mix persistence, business logic, and presentation.
106
+ - Resist ad-hoc shelling out; prefer library APIs with proper error handling.
107
+ - Do not bypass the agreed testing workflow—even for small fixes.
108
+
109
+ ---
110
+ Generated from template `planning/generate_llm_style_guide.md` with repository-aware adjustments.
111
+ GUIDE
112
+
113
+ File.write(File.join(@project_dir, STYLE_GUIDE_PATH), content)
114
+ end
115
+
116
+ def write_project_analysis(analysis)
117
+ languages = format_language_breakdown(analysis[:languages])
118
+ frameworks = bullet_list(analysis[:frameworks], default: "_None detected_")
119
+ config_files = bullet_list(analysis[:config_files], default: "_No dedicated configuration files discovered_")
120
+ tooling = format_tooling_section(analysis[:tooling])
121
+
122
+ stats = analysis[:repo_stats]
123
+ stats_lines = [
124
+ "- Total files scanned: #{stats[:total_files]}",
125
+ "- Unique directories: #{stats[:total_directories]}",
126
+ "- Documentation folder present: #{stats[:docs_present] ? "Yes" : "No"}",
127
+ "- CI configuration present: #{stats[:has_ci_config] ? "Yes" : "No"}",
128
+ "- Containerisation assets: #{stats[:has_containerization] ? "Yes" : "No"}"
129
+ ]
130
+
131
+ content = <<~ANALYSIS
132
+ # Project Analysis
133
+
134
+ Generated automatically by `aidp init` on #{Time.now.utc.iso8601}. This document summarises the repository structure to guide future autonomous work loops.
135
+
136
+ ## Language & Framework Footprint
137
+ #{languages}
138
+
139
+ ### Framework Signals
140
+ #{frameworks}
141
+
142
+ ## Key Directories
143
+ #{bullet_list(analysis[:key_directories], default: "_No conventional application directories detected_")}
144
+
145
+ ## Configuration & Tooling Files
146
+ #{config_files}
147
+
148
+ ## Test & Quality Signals
149
+ #{bullet_list(analysis[:test_frameworks], prefix: "- Detected test suite: ", default: "_Unable to infer test suite_")}
150
+
151
+ ## Local Quality Toolchain
152
+ #{tooling}
153
+
154
+ ## Repository Stats
155
+ #{stats_lines.join("\n")}
156
+
157
+ ---
158
+ Template inspiration: `analysis/analyze_repository.md`, `analysis/analyze_tests.md`.
159
+ ANALYSIS
160
+
161
+ File.write(File.join(@project_dir, ANALYSIS_PATH), content)
162
+ end
163
+
164
+ def write_quality_plan(analysis, preferences)
165
+ tooling = analysis[:tooling]
166
+ proactive = if truthy?(preferences[:stricter_linters])
167
+ "- Enable stricter linting rules and fail CI on offences.\n- Enforce formatting checks (`mise exec --` for consistent environments).\n"
168
+ else
169
+ "- Maintain current linting thresholds while documenting exceptions.\n"
170
+ end
171
+
172
+ migration = if truthy?(preferences[:migrate_styles])
173
+ "- Plan refactors to align legacy files with the new style guide.\n- Schedule incremental clean-up tasks to avoid large-batch rewrites.\n"
174
+ else
175
+ "- Keep legacy style deviations documented until dedicated refactors are scheduled.\n"
176
+ end
177
+
178
+ content = <<~PLAN
179
+ # Code Quality Plan
180
+
181
+ This plan captures the current tooling landscape and proposes next steps for keeping the codebase healthy. Generated by `aidp init` on #{Time.now.utc.iso8601}.
182
+
183
+ ## Local Quality Toolchain
184
+ #{tooling.empty? ? "_No linting/formatting tools detected. Consider adding RuboCop, ESLint, or Prettier based on the primary language._" : format_tooling_table(tooling)}
185
+
186
+ ## Immediate Actions
187
+ #{proactive}#{migration}- Document onboarding steps in `docs/` to ensure future contributors follow the agreed workflow.
188
+
189
+ ## Long-Term Improvements
190
+ - Keep the style guide in sync with real-world code changes; regenerate with `aidp init` after major rewrites.
191
+ - Automate test and lint runs via CI (detected: #{analysis.dig(:repo_stats, :has_ci_config) ? "yes" : "no"}).
192
+ - Track flaky tests or unstable tooling in `PROJECT_ANALYSIS.md` under a “Health Log” section.
193
+
194
+ ---
195
+ Based on templates: `analysis/analyze_static_code.md`, `analysis/analyze_tests.md`.
196
+ PLAN
197
+
198
+ File.write(File.join(@project_dir, QUALITY_PLAN_PATH), content)
199
+ end
200
+
201
+ def preferred_test_dirs(analysis)
202
+ detected = Array(analysis[:key_directories]).select { |dir| dir =~ /\b(spec|test|tests)\b/ }
203
+ detected.empty? ? "the chosen test directory" : detected.join(", ")
204
+ end
205
+
206
+ def format_list(values)
207
+ Array(values).join(", ")
208
+ end
209
+
210
+ def bullet_list(values, prefix: "- ", default: "_None_")
211
+ items = Array(values)
212
+ return default if items.empty?
213
+
214
+ items.map { |value| "#{prefix}#{value}" }.join("\n")
215
+ end
216
+
217
+ def format_language_breakdown(languages)
218
+ return "_No source files detected._" if languages.nil? || languages.empty?
219
+
220
+ total = languages.values.sum
221
+ languages.map do |language, weight|
222
+ percentage = total.zero? ? 0 : ((weight.to_f / total) * 100).round(2)
223
+ "- #{language}: #{percentage}% of codebase"
224
+ end.join("\n")
225
+ end
226
+
227
+ def format_tooling_table(tooling)
228
+ rows = tooling.map do |tool, evidence|
229
+ "| #{format_tool(tool)} | #{evidence.uniq.join(", ")} |"
230
+ end
231
+ header = "| Tool | Evidence |\n|------|----------|"
232
+ ([header] + rows).join("\n")
233
+ end
234
+
235
+ def format_tooling_section(tooling)
236
+ return "_No tooling detected._" if tooling.nil? || tooling.empty?
237
+
238
+ header = "| Tool | Evidence |\n|------|----------|"
239
+ rows = tooling.map do |tool, evidence|
240
+ "| #{format_tool(tool)} | #{Array(evidence).uniq.join(", ")} |"
241
+ end
242
+ ([header] + rows).join("\n")
243
+ end
244
+
245
+ def format_tool(tool)
246
+ tool.to_s.split("_").map(&:capitalize).join(" ")
247
+ end
248
+
249
+ def truthy?(value)
250
+ value == true || value.to_s.strip.casecmp("yes").zero?
251
+ rescue
252
+ false
253
+ end
254
+ end
255
+ end
256
+ end