aidp 0.27.0 → 0.28.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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +89 -0
  3. data/lib/aidp/cli/models_command.rb +5 -6
  4. data/lib/aidp/cli.rb +10 -8
  5. data/lib/aidp/config.rb +54 -0
  6. data/lib/aidp/debug_mixin.rb +23 -1
  7. data/lib/aidp/execute/agent_signal_parser.rb +22 -0
  8. data/lib/aidp/execute/repl_macros.rb +2 -2
  9. data/lib/aidp/execute/steps.rb +94 -1
  10. data/lib/aidp/execute/work_loop_runner.rb +209 -17
  11. data/lib/aidp/execute/workflow_selector.rb +2 -25
  12. data/lib/aidp/firewall/provider_requirements_collector.rb +262 -0
  13. data/lib/aidp/harness/ai_decision_engine.rb +35 -2
  14. data/lib/aidp/harness/config_manager.rb +0 -5
  15. data/lib/aidp/harness/config_schema.rb +8 -0
  16. data/lib/aidp/harness/configuration.rb +27 -19
  17. data/lib/aidp/harness/enhanced_runner.rb +1 -4
  18. data/lib/aidp/harness/error_handler.rb +1 -72
  19. data/lib/aidp/harness/provider_factory.rb +11 -2
  20. data/lib/aidp/harness/state_manager.rb +0 -7
  21. data/lib/aidp/harness/thinking_depth_manager.rb +47 -68
  22. data/lib/aidp/harness/ui/enhanced_tui.rb +8 -18
  23. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +0 -18
  24. data/lib/aidp/harness/ui/progress_display.rb +6 -2
  25. data/lib/aidp/harness/user_interface.rb +0 -58
  26. data/lib/aidp/init/runner.rb +7 -2
  27. data/lib/aidp/planning/analyzers/feedback_analyzer.rb +365 -0
  28. data/lib/aidp/planning/builders/agile_plan_builder.rb +387 -0
  29. data/lib/aidp/planning/builders/project_plan_builder.rb +193 -0
  30. data/lib/aidp/planning/generators/gantt_generator.rb +190 -0
  31. data/lib/aidp/planning/generators/iteration_plan_generator.rb +392 -0
  32. data/lib/aidp/planning/generators/legacy_research_planner.rb +473 -0
  33. data/lib/aidp/planning/generators/marketing_report_generator.rb +348 -0
  34. data/lib/aidp/planning/generators/mvp_scope_generator.rb +310 -0
  35. data/lib/aidp/planning/generators/user_test_plan_generator.rb +373 -0
  36. data/lib/aidp/planning/generators/wbs_generator.rb +259 -0
  37. data/lib/aidp/planning/mappers/persona_mapper.rb +163 -0
  38. data/lib/aidp/planning/parsers/document_parser.rb +141 -0
  39. data/lib/aidp/planning/parsers/feedback_data_parser.rb +252 -0
  40. data/lib/aidp/provider_manager.rb +8 -32
  41. data/lib/aidp/providers/aider.rb +264 -0
  42. data/lib/aidp/providers/anthropic.rb +74 -2
  43. data/lib/aidp/providers/base.rb +25 -1
  44. data/lib/aidp/providers/codex.rb +26 -3
  45. data/lib/aidp/providers/cursor.rb +16 -0
  46. data/lib/aidp/providers/gemini.rb +13 -0
  47. data/lib/aidp/providers/github_copilot.rb +17 -0
  48. data/lib/aidp/providers/kilocode.rb +11 -0
  49. data/lib/aidp/providers/opencode.rb +11 -0
  50. data/lib/aidp/setup/wizard.rb +249 -39
  51. data/lib/aidp/version.rb +1 -1
  52. data/lib/aidp/watch/build_processor.rb +211 -30
  53. data/lib/aidp/watch/change_request_processor.rb +128 -14
  54. data/lib/aidp/watch/ci_fix_processor.rb +103 -37
  55. data/lib/aidp/watch/ci_log_extractor.rb +258 -0
  56. data/lib/aidp/watch/github_state_extractor.rb +177 -0
  57. data/lib/aidp/watch/implementation_verifier.rb +284 -0
  58. data/lib/aidp/watch/plan_generator.rb +7 -43
  59. data/lib/aidp/watch/plan_processor.rb +7 -6
  60. data/lib/aidp/watch/repository_client.rb +245 -17
  61. data/lib/aidp/watch/review_processor.rb +98 -17
  62. data/lib/aidp/watch/reviewers/base_reviewer.rb +1 -1
  63. data/lib/aidp/watch/runner.rb +181 -29
  64. data/lib/aidp/watch/state_store.rb +22 -1
  65. data/lib/aidp/workflows/definitions.rb +147 -0
  66. data/lib/aidp/workstream_cleanup.rb +245 -0
  67. data/lib/aidp/worktree.rb +19 -0
  68. data/templates/aidp.yml.example +57 -0
  69. data/templates/implementation/generate_tdd_specs.md +213 -0
  70. data/templates/implementation/iterative_implementation.md +122 -0
  71. data/templates/planning/agile/analyze_feedback.md +183 -0
  72. data/templates/planning/agile/generate_iteration_plan.md +179 -0
  73. data/templates/planning/agile/generate_legacy_research_plan.md +171 -0
  74. data/templates/planning/agile/generate_marketing_report.md +162 -0
  75. data/templates/planning/agile/generate_mvp_scope.md +127 -0
  76. data/templates/planning/agile/generate_user_test_plan.md +143 -0
  77. data/templates/planning/agile/ingest_feedback.md +174 -0
  78. data/templates/planning/assemble_project_plan.md +113 -0
  79. data/templates/planning/assign_personas.md +108 -0
  80. data/templates/planning/create_tasks.md +52 -6
  81. data/templates/planning/generate_gantt.md +86 -0
  82. data/templates/planning/generate_wbs.md +85 -0
  83. data/templates/planning/initialize_planning_mode.md +70 -0
  84. data/templates/skills/README.md +2 -2
  85. data/templates/skills/marketing_strategist/SKILL.md +279 -0
  86. data/templates/skills/product_manager/SKILL.md +177 -0
  87. data/templates/skills/ruby_aidp_planning/SKILL.md +497 -0
  88. data/templates/skills/ruby_rspec_tdd/SKILL.md +514 -0
  89. data/templates/skills/ux_researcher/SKILL.md +222 -0
  90. metadata +39 -1
@@ -0,0 +1,387 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../logger"
4
+ require_relative "../parsers/document_parser"
5
+ require_relative "../parsers/feedback_data_parser"
6
+ require_relative "../generators/mvp_scope_generator"
7
+ require_relative "../generators/user_test_plan_generator"
8
+ require_relative "../generators/marketing_report_generator"
9
+ require_relative "../generators/iteration_plan_generator"
10
+ require_relative "../generators/legacy_research_planner"
11
+ require_relative "../analyzers/feedback_analyzer"
12
+ require_relative "../mappers/persona_mapper"
13
+
14
+ module Aidp
15
+ module Planning
16
+ module Builders
17
+ # Orchestrates agile planning workflows
18
+ # Coordinates MVP scoping, user testing, feedback analysis, and iteration planning
19
+ class AgilePlanBuilder
20
+ def initialize(
21
+ ai_decision_engine:,
22
+ config: nil,
23
+ prompt: nil,
24
+ document_parser: nil,
25
+ mvp_scope_generator: nil,
26
+ user_test_plan_generator: nil,
27
+ marketing_report_generator: nil,
28
+ feedback_analyzer: nil,
29
+ iteration_plan_generator: nil,
30
+ legacy_research_planner: nil,
31
+ persona_mapper: nil
32
+ )
33
+ @ai_decision_engine = ai_decision_engine
34
+ @config = config || Aidp::Config.agile_config
35
+ @prompt = prompt || TTY::Prompt.new
36
+ @document_parser = document_parser || Parsers::DocumentParser.new(ai_decision_engine: ai_decision_engine)
37
+ @mvp_scope_generator = mvp_scope_generator || Generators::MVPScopeGenerator.new(
38
+ ai_decision_engine: ai_decision_engine,
39
+ prompt: @prompt,
40
+ config: @config
41
+ )
42
+ @user_test_plan_generator = user_test_plan_generator || Generators::UserTestPlanGenerator.new(
43
+ ai_decision_engine: ai_decision_engine,
44
+ config: @config
45
+ )
46
+ @marketing_report_generator = marketing_report_generator || Generators::MarketingReportGenerator.new(
47
+ ai_decision_engine: ai_decision_engine,
48
+ config: @config
49
+ )
50
+ @feedback_analyzer = feedback_analyzer || Analyzers::FeedbackAnalyzer.new(
51
+ ai_decision_engine: ai_decision_engine,
52
+ config: @config
53
+ )
54
+ @iteration_plan_generator = iteration_plan_generator || Generators::IterationPlanGenerator.new(
55
+ ai_decision_engine: ai_decision_engine,
56
+ config: @config
57
+ )
58
+ @legacy_research_planner = legacy_research_planner || Generators::LegacyResearchPlanner.new(
59
+ ai_decision_engine: ai_decision_engine,
60
+ prompt: @prompt,
61
+ config: @config
62
+ )
63
+ @persona_mapper = persona_mapper || Mappers::PersonaMapper.new(
64
+ ai_decision_engine: ai_decision_engine,
65
+ config: @config,
66
+ mode: :agile
67
+ )
68
+ end
69
+
70
+ # Build complete MVP plan from PRD
71
+ # @param prd_path [String] Path to PRD document
72
+ # @param user_priorities [Array<String>] Optional user priorities
73
+ # @return [Hash] Complete MVP plan with all artifacts
74
+ def build_mvp_plan(prd_path:, user_priorities: nil)
75
+ Aidp.log_debug("agile_plan_builder", "build_mvp_plan", prd_path: prd_path)
76
+
77
+ # Parse PRD
78
+ prd = parse_prd(prd_path)
79
+
80
+ # Generate MVP scope
81
+ @prompt.say("Generating MVP scope...")
82
+ mvp_scope = @mvp_scope_generator.generate(prd: prd, user_priorities: user_priorities)
83
+ Aidp.log_debug("agile_plan_builder", "mvp_scope_generated",
84
+ must_have: mvp_scope[:mvp_features].size,
85
+ deferred: mvp_scope[:deferred_features].size)
86
+
87
+ # Generate user test plan
88
+ @prompt.say("Creating user testing plan...")
89
+ test_plan = @user_test_plan_generator.generate(mvp_scope: mvp_scope)
90
+ Aidp.log_debug("agile_plan_builder", "test_plan_generated",
91
+ stages: test_plan[:testing_stages].size)
92
+
93
+ # Generate marketing report
94
+ @prompt.say("Generating marketing materials...")
95
+ marketing_report = @marketing_report_generator.generate(mvp_scope: mvp_scope)
96
+ Aidp.log_debug("agile_plan_builder", "marketing_report_generated",
97
+ messages: marketing_report[:key_messages].size)
98
+
99
+ @prompt.ok("MVP plan complete!")
100
+
101
+ {
102
+ mvp_scope: mvp_scope,
103
+ test_plan: test_plan,
104
+ marketing_report: marketing_report,
105
+ metadata: {
106
+ generated_at: Time.now.iso8601,
107
+ workflow: "agile_mvp"
108
+ }
109
+ }
110
+ end
111
+
112
+ # Analyze feedback data
113
+ # @param feedback_path [String] Path to feedback data file (CSV/JSON/markdown)
114
+ # @return [Hash] Feedback analysis results
115
+ def analyze_feedback(feedback_path:)
116
+ Aidp.log_debug("agile_plan_builder", "analyze_feedback", feedback_path: feedback_path)
117
+
118
+ # Parse feedback data
119
+ @prompt.say("Parsing feedback data...")
120
+ parser = Parsers::FeedbackDataParser.new(file_path: feedback_path)
121
+ feedback_data = parser.parse
122
+
123
+ @prompt.say("Analyzing #{feedback_data[:response_count]} responses...")
124
+
125
+ # Analyze with AI
126
+ analysis = @feedback_analyzer.analyze(feedback_data)
127
+ Aidp.log_debug("agile_plan_builder", "feedback_analyzed",
128
+ findings: analysis[:findings].size,
129
+ recommendations: analysis[:recommendations].size)
130
+
131
+ @prompt.ok("Feedback analysis complete!")
132
+
133
+ {
134
+ analysis: analysis,
135
+ metadata: {
136
+ generated_at: Time.now.iso8601,
137
+ workflow: "agile_feedback_analysis"
138
+ }
139
+ }
140
+ end
141
+
142
+ # Plan next iteration based on feedback
143
+ # @param feedback_analysis_path [String] Path to feedback analysis document
144
+ # @param current_mvp_path [String] Optional path to current MVP scope
145
+ # @return [Hash] Iteration plan
146
+ def plan_next_iteration(feedback_analysis_path:, current_mvp_path: nil)
147
+ Aidp.log_debug("agile_plan_builder", "plan_next_iteration",
148
+ feedback_path: feedback_analysis_path,
149
+ mvp_path: current_mvp_path)
150
+
151
+ # Parse feedback analysis
152
+ @prompt.say("Loading feedback analysis...")
153
+ feedback_analysis = parse_feedback_analysis(feedback_analysis_path)
154
+
155
+ # Parse current MVP if provided
156
+ current_mvp = nil
157
+ if current_mvp_path
158
+ @prompt.say("Loading current MVP scope...")
159
+ current_mvp = parse_mvp_scope(current_mvp_path)
160
+ end
161
+
162
+ # Generate iteration plan
163
+ @prompt.say("Planning next iteration...")
164
+ iteration_plan = @iteration_plan_generator.generate(
165
+ feedback_analysis: feedback_analysis,
166
+ current_mvp: current_mvp
167
+ )
168
+ Aidp.log_debug("agile_plan_builder", "iteration_plan_generated",
169
+ improvements: iteration_plan[:improvements].size,
170
+ tasks: iteration_plan[:tasks].size)
171
+
172
+ @prompt.ok("Iteration plan complete!")
173
+
174
+ {
175
+ iteration_plan: iteration_plan,
176
+ metadata: {
177
+ generated_at: Time.now.iso8601,
178
+ workflow: "agile_iteration"
179
+ }
180
+ }
181
+ end
182
+
183
+ # Plan user research for existing/legacy codebase
184
+ # @param codebase_path [String] Path to codebase directory
185
+ # @param language [String] Optional primary language
186
+ # @param known_users [String] Optional known user segments
187
+ # @return [Hash] Research plan
188
+ def plan_legacy_research(codebase_path:, language: nil, known_users: nil)
189
+ Aidp.log_debug("agile_plan_builder", "plan_legacy_research",
190
+ codebase_path: codebase_path)
191
+
192
+ @prompt.say("Analyzing codebase...")
193
+ research_plan = @legacy_research_planner.generate(
194
+ codebase_path: codebase_path,
195
+ language: language,
196
+ known_users: known_users
197
+ )
198
+ Aidp.log_debug("agile_plan_builder", "research_plan_generated",
199
+ features: research_plan[:current_features].size,
200
+ questions: research_plan[:research_questions].size)
201
+
202
+ # Generate user test plan based on research priorities
203
+ @prompt.say("Creating user testing plan...")
204
+ # Create a minimal MVP scope structure for test plan generation
205
+ mvp_scope_for_testing = {
206
+ mvp_features: research_plan[:current_features],
207
+ metadata: {
208
+ user_priorities: ["Understand existing user experience", "Identify pain points", "Discover improvement opportunities"]
209
+ }
210
+ }
211
+ test_plan = @user_test_plan_generator.generate(mvp_scope: mvp_scope_for_testing)
212
+
213
+ @prompt.ok("Legacy research plan complete!")
214
+
215
+ {
216
+ research_plan: research_plan,
217
+ test_plan: test_plan,
218
+ metadata: {
219
+ generated_at: Time.now.iso8601,
220
+ workflow: "agile_legacy_research"
221
+ }
222
+ }
223
+ end
224
+
225
+ # Write all artifacts to files
226
+ # @param plan_data [Hash] Plan data from any workflow
227
+ # @param output_dir [String] Directory to write files
228
+ def write_artifacts(plan_data, output_dir: ".aidp/docs")
229
+ Aidp.log_debug("agile_plan_builder", "write_artifacts", output_dir: output_dir)
230
+
231
+ FileUtils.mkdir_p(output_dir)
232
+
233
+ artifacts_written = []
234
+
235
+ # Write MVP scope if present
236
+ if plan_data[:mvp_scope]
237
+ path = File.join(output_dir, "MVP_SCOPE.md")
238
+ content = @mvp_scope_generator.format_as_markdown(plan_data[:mvp_scope])
239
+ File.write(path, content)
240
+ artifacts_written << path
241
+ Aidp.log_debug("agile_plan_builder", "wrote_artifact", file: "MVP_SCOPE.md")
242
+ end
243
+
244
+ # Write test plan if present
245
+ if plan_data[:test_plan]
246
+ path = File.join(output_dir, "USER_TEST_PLAN.md")
247
+ content = @user_test_plan_generator.format_as_markdown(plan_data[:test_plan])
248
+ File.write(path, content)
249
+ artifacts_written << path
250
+ Aidp.log_debug("agile_plan_builder", "wrote_artifact", file: "USER_TEST_PLAN.md")
251
+ end
252
+
253
+ # Write marketing report if present
254
+ if plan_data[:marketing_report]
255
+ path = File.join(output_dir, "MARKETING_REPORT.md")
256
+ content = @marketing_report_generator.format_as_markdown(plan_data[:marketing_report])
257
+ File.write(path, content)
258
+ artifacts_written << path
259
+ Aidp.log_debug("agile_plan_builder", "wrote_artifact", file: "MARKETING_REPORT.md")
260
+ end
261
+
262
+ # Write feedback analysis if present
263
+ if plan_data[:analysis]
264
+ path = File.join(output_dir, "USER_FEEDBACK_ANALYSIS.md")
265
+ content = @feedback_analyzer.format_as_markdown(plan_data[:analysis])
266
+ File.write(path, content)
267
+ artifacts_written << path
268
+ Aidp.log_debug("agile_plan_builder", "wrote_artifact", file: "USER_FEEDBACK_ANALYSIS.md")
269
+ end
270
+
271
+ # Write iteration plan if present
272
+ if plan_data[:iteration_plan]
273
+ path = File.join(output_dir, "NEXT_ITERATION_PLAN.md")
274
+ content = @iteration_plan_generator.format_as_markdown(plan_data[:iteration_plan])
275
+ File.write(path, content)
276
+ artifacts_written << path
277
+ Aidp.log_debug("agile_plan_builder", "wrote_artifact", file: "NEXT_ITERATION_PLAN.md")
278
+ end
279
+
280
+ # Write research plan if present
281
+ if plan_data[:research_plan]
282
+ path = File.join(output_dir, "LEGACY_USER_RESEARCH_PLAN.md")
283
+ content = @legacy_research_planner.format_as_markdown(plan_data[:research_plan])
284
+ File.write(path, content)
285
+ artifacts_written << path
286
+ Aidp.log_debug("agile_plan_builder", "wrote_artifact", file: "LEGACY_USER_RESEARCH_PLAN.md")
287
+ end
288
+
289
+ @prompt.ok("Wrote #{artifacts_written.size} artifacts to #{output_dir}")
290
+ artifacts_written
291
+ end
292
+
293
+ private
294
+
295
+ def parse_prd(prd_path)
296
+ Aidp.log_debug("agile_plan_builder", "parse_prd", path: prd_path)
297
+
298
+ unless File.exist?(prd_path)
299
+ raise ArgumentError, "PRD file not found: #{prd_path}"
300
+ end
301
+
302
+ content = File.read(prd_path)
303
+
304
+ # Simple PRD structure extraction
305
+ {
306
+ content: content,
307
+ path: prd_path,
308
+ type: :prd,
309
+ metadata: {
310
+ parsed_at: Time.now.iso8601
311
+ }
312
+ }
313
+ end
314
+
315
+ def parse_feedback_analysis(analysis_path)
316
+ Aidp.log_debug("agile_plan_builder", "parse_feedback_analysis", path: analysis_path)
317
+
318
+ unless File.exist?(analysis_path)
319
+ raise ArgumentError, "Feedback analysis file not found: #{analysis_path}"
320
+ end
321
+
322
+ content = File.read(analysis_path)
323
+
324
+ # Extract key sections from markdown
325
+ {
326
+ content: content,
327
+ summary: extract_section(content, "Executive Summary"),
328
+ findings: extract_list_items(content, "Key Findings"),
329
+ recommendations: extract_list_items(content, "Recommendations"),
330
+ priority_issues: extract_list_items(content, "Priority Issues"),
331
+ metadata: {
332
+ parsed_at: Time.now.iso8601
333
+ }
334
+ }
335
+ end
336
+
337
+ def parse_mvp_scope(mvp_path)
338
+ Aidp.log_debug("agile_plan_builder", "parse_mvp_scope", path: mvp_path)
339
+
340
+ unless File.exist?(mvp_path)
341
+ raise ArgumentError, "MVP scope file not found: #{mvp_path}"
342
+ end
343
+
344
+ content = File.read(mvp_path)
345
+
346
+ {
347
+ content: content,
348
+ mvp_features: extract_features(content, "MVP Features"),
349
+ deferred_features: extract_features(content, "Deferred Features"),
350
+ metadata: {
351
+ parsed_at: Time.now.iso8601
352
+ }
353
+ }
354
+ end
355
+
356
+ def extract_section(content, heading)
357
+ # Simple section extraction
358
+ if content =~ /## #{heading}\s*\n\n(.+?)(\n## |$)/m
359
+ $1.strip
360
+ else
361
+ ""
362
+ end
363
+ end
364
+
365
+ def extract_list_items(content, heading)
366
+ section = extract_section(content, heading)
367
+ # Extract bullet points or numbered items
368
+ section.scan(/^[-*]\s+(.+)$/).flatten
369
+ end
370
+
371
+ def extract_features(content, heading)
372
+ # Simple feature extraction from markdown headings
373
+ section_start = content.index(/## #{heading}/)
374
+ return [] unless section_start
375
+
376
+ section = content[section_start..]
377
+ next_section = section.index(/\n## /, 1)
378
+ section = section[0...next_section] if next_section
379
+
380
+ # Extract feature names from h3 headings
381
+ features = section.scan(/### \d+\.\s+(.+)/).flatten
382
+ features.map { |name| {name: name, description: "Feature from #{heading}"} }
383
+ end
384
+ end
385
+ end
386
+ end
387
+ end
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../logger"
4
+ require_relative "../parsers/document_parser"
5
+ require_relative "../generators/wbs_generator"
6
+ require_relative "../generators/gantt_generator"
7
+ require_relative "../mappers/persona_mapper"
8
+
9
+ module Aidp
10
+ module Planning
11
+ module Builders
12
+ # Orchestrates generation of complete project plan
13
+ # Coordinates all planning components (parsing, generating, mapping)
14
+ class ProjectPlanBuilder
15
+ def initialize(
16
+ ai_decision_engine:,
17
+ config: nil,
18
+ document_parser: nil,
19
+ wbs_generator: nil,
20
+ gantt_generator: nil,
21
+ persona_mapper: nil
22
+ )
23
+ @ai_decision_engine = ai_decision_engine
24
+ @config = config || Aidp::Config.waterfall_config
25
+ @document_parser = document_parser || Parsers::DocumentParser.new(ai_decision_engine: ai_decision_engine)
26
+ @wbs_generator = wbs_generator || Generators::WBSGenerator.new(config: @config)
27
+ @gantt_generator = gantt_generator || Generators::GanttGenerator.new(config: @config)
28
+ @persona_mapper = persona_mapper || Mappers::PersonaMapper.new(ai_decision_engine: ai_decision_engine, config: @config)
29
+ end
30
+
31
+ # Build project plan from existing documentation (ingestion path)
32
+ # @param docs_path [String] Path to existing documentation
33
+ # @return [Hash] Complete project plan
34
+ def build_from_ingestion(docs_path)
35
+ Aidp.log_debug("project_plan_builder", "build_from_ingestion", path: docs_path)
36
+
37
+ # Parse existing docs
38
+ parsed_docs = @document_parser.parse_directory(docs_path)
39
+ Aidp.log_debug("project_plan_builder", "parsed_docs", count: parsed_docs.size)
40
+
41
+ # Extract PRD and design docs
42
+ prd = parsed_docs.find { |d| d[:type] == :prd }
43
+ tech_design = parsed_docs.find { |d| d[:type] == :design }
44
+
45
+ # Generate plan components
46
+ build_plan_components(prd, tech_design)
47
+ end
48
+
49
+ # Build project plan from scratch (generation path)
50
+ # @param requirements [Hash] User-provided requirements
51
+ # @return [Hash] Complete project plan
52
+ def build_from_scratch(requirements)
53
+ Aidp.log_debug("project_plan_builder", "build_from_scratch")
54
+
55
+ # In production, this would use AI to generate PRD and design docs
56
+ # For now, we'll create minimal structure from requirements
57
+ prd = structure_requirements_as_prd(requirements)
58
+ tech_design = nil # Will be generated by AI templates
59
+
60
+ build_plan_components(prd, tech_design)
61
+ end
62
+
63
+ # Assemble complete project plan document
64
+ # @param components [Hash] All plan components
65
+ # @return [String] Complete PROJECT_PLAN.md content
66
+ def assemble_project_plan(components)
67
+ Aidp.log_debug("project_plan_builder", "assemble_project_plan")
68
+
69
+ output = ["# Project Plan", ""]
70
+ output << "Generated: #{Time.now.iso8601}"
71
+ output << ""
72
+
73
+ # Executive Summary
74
+ output << "## Executive Summary"
75
+ output << ""
76
+ output << "This document provides a comprehensive project plan including:"
77
+ output << "- Work Breakdown Structure (WBS)"
78
+ output << "- Gantt Chart with Critical Path"
79
+ output << "- Task Dependencies and Milestones"
80
+ output << "- Persona/Agent Assignments"
81
+ output << ""
82
+
83
+ # WBS Section
84
+ output << "## Work Breakdown Structure"
85
+ output << ""
86
+ output << components[:wbs_markdown]
87
+ output << ""
88
+
89
+ # Gantt Chart Section
90
+ output << "## Timeline and Gantt Chart"
91
+ output << ""
92
+ output << "```mermaid"
93
+ output << components[:gantt_mermaid]
94
+ output << "```"
95
+ output << ""
96
+
97
+ # Critical Path
98
+ output << "## Critical Path"
99
+ output << ""
100
+ output << "The following tasks form the critical path (longest sequence):"
101
+ output << ""
102
+ components[:critical_path].each_with_index do |task_id, idx|
103
+ output << "#{idx + 1}. #{task_id}"
104
+ end
105
+ output << ""
106
+
107
+ # Persona Assignments
108
+ output << "## Persona Assignments"
109
+ output << ""
110
+ output << "Tasks are assigned to the following personas:"
111
+ output << ""
112
+ persona_summary = summarize_persona_assignments(components[:persona_assignments])
113
+ output << persona_summary
114
+ output << ""
115
+
116
+ # Metadata
117
+ output << "## Metadata"
118
+ output << ""
119
+ output << "- **Total Phases:** #{components[:wbs][:metadata][:phase_count]}"
120
+ output << "- **Total Tasks:** #{components[:wbs][:metadata][:total_tasks]}"
121
+ output << "- **Critical Path Length:** #{components[:gantt][:metadata][:critical_path_length]} tasks"
122
+ output << "- **Personas Used:** #{components[:persona_assignments][:metadata][:personas_used].size}"
123
+ output << ""
124
+
125
+ output.join("\n")
126
+ end
127
+
128
+ private
129
+
130
+ def build_plan_components(prd, tech_design)
131
+ # Generate WBS
132
+ wbs = @wbs_generator.generate(prd: prd, tech_design: tech_design)
133
+ wbs_markdown = @wbs_generator.format_as_markdown(wbs)
134
+
135
+ # Generate Gantt chart
136
+ gantt = @gantt_generator.generate(wbs: wbs)
137
+
138
+ # Extract tasks for persona assignment
139
+ tasks = gantt[:tasks]
140
+
141
+ # Assign personas
142
+ persona_assignments = @persona_mapper.assign_personas(tasks)
143
+
144
+ {
145
+ prd: prd,
146
+ tech_design: tech_design,
147
+ wbs: wbs,
148
+ wbs_markdown: wbs_markdown,
149
+ gantt: gantt,
150
+ gantt_mermaid: gantt[:mermaid],
151
+ critical_path: gantt[:critical_path],
152
+ persona_assignments: persona_assignments
153
+ }
154
+ end
155
+
156
+ def structure_requirements_as_prd(requirements)
157
+ {
158
+ type: :prd,
159
+ sections: {
160
+ "problem_statement" => requirements[:problem] || "Problem to solve",
161
+ "goals" => requirements[:goals] || "Project goals",
162
+ "success_criteria" => requirements[:success_criteria] || "Success metrics"
163
+ },
164
+ raw_content: requirements.to_s
165
+ }
166
+ end
167
+
168
+ def summarize_persona_assignments(assignments)
169
+ # Group by persona
170
+ by_persona = {}
171
+ assignments[:assignments].each do |task_id, assignment|
172
+ persona = assignment[:persona]
173
+ by_persona[persona] ||= []
174
+ by_persona[persona] << assignment[:task]
175
+ end
176
+
177
+ # Format summary
178
+ output = []
179
+ by_persona.each do |persona, tasks|
180
+ output << "### #{persona} (#{tasks.size} tasks)"
181
+ output << ""
182
+ tasks.each do |task|
183
+ output << "- #{task}"
184
+ end
185
+ output << ""
186
+ end
187
+
188
+ output.join("\n")
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end