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.
- checksums.yaml +4 -4
- data/README.md +89 -0
- data/lib/aidp/cli/models_command.rb +5 -6
- data/lib/aidp/cli.rb +10 -8
- data/lib/aidp/config.rb +54 -0
- data/lib/aidp/debug_mixin.rb +23 -1
- data/lib/aidp/execute/agent_signal_parser.rb +22 -0
- data/lib/aidp/execute/repl_macros.rb +2 -2
- data/lib/aidp/execute/steps.rb +94 -1
- data/lib/aidp/execute/work_loop_runner.rb +209 -17
- data/lib/aidp/execute/workflow_selector.rb +2 -25
- data/lib/aidp/firewall/provider_requirements_collector.rb +262 -0
- data/lib/aidp/harness/ai_decision_engine.rb +35 -2
- data/lib/aidp/harness/config_manager.rb +0 -5
- data/lib/aidp/harness/config_schema.rb +8 -0
- data/lib/aidp/harness/configuration.rb +27 -19
- data/lib/aidp/harness/enhanced_runner.rb +1 -4
- data/lib/aidp/harness/error_handler.rb +1 -72
- data/lib/aidp/harness/provider_factory.rb +11 -2
- data/lib/aidp/harness/state_manager.rb +0 -7
- data/lib/aidp/harness/thinking_depth_manager.rb +47 -68
- data/lib/aidp/harness/ui/enhanced_tui.rb +8 -18
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +0 -18
- data/lib/aidp/harness/ui/progress_display.rb +6 -2
- data/lib/aidp/harness/user_interface.rb +0 -58
- data/lib/aidp/init/runner.rb +7 -2
- data/lib/aidp/planning/analyzers/feedback_analyzer.rb +365 -0
- data/lib/aidp/planning/builders/agile_plan_builder.rb +387 -0
- data/lib/aidp/planning/builders/project_plan_builder.rb +193 -0
- data/lib/aidp/planning/generators/gantt_generator.rb +190 -0
- data/lib/aidp/planning/generators/iteration_plan_generator.rb +392 -0
- data/lib/aidp/planning/generators/legacy_research_planner.rb +473 -0
- data/lib/aidp/planning/generators/marketing_report_generator.rb +348 -0
- data/lib/aidp/planning/generators/mvp_scope_generator.rb +310 -0
- data/lib/aidp/planning/generators/user_test_plan_generator.rb +373 -0
- data/lib/aidp/planning/generators/wbs_generator.rb +259 -0
- data/lib/aidp/planning/mappers/persona_mapper.rb +163 -0
- data/lib/aidp/planning/parsers/document_parser.rb +141 -0
- data/lib/aidp/planning/parsers/feedback_data_parser.rb +252 -0
- data/lib/aidp/provider_manager.rb +8 -32
- data/lib/aidp/providers/aider.rb +264 -0
- data/lib/aidp/providers/anthropic.rb +74 -2
- data/lib/aidp/providers/base.rb +25 -1
- data/lib/aidp/providers/codex.rb +26 -3
- data/lib/aidp/providers/cursor.rb +16 -0
- data/lib/aidp/providers/gemini.rb +13 -0
- data/lib/aidp/providers/github_copilot.rb +17 -0
- data/lib/aidp/providers/kilocode.rb +11 -0
- data/lib/aidp/providers/opencode.rb +11 -0
- data/lib/aidp/setup/wizard.rb +249 -39
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +211 -30
- data/lib/aidp/watch/change_request_processor.rb +128 -14
- data/lib/aidp/watch/ci_fix_processor.rb +103 -37
- data/lib/aidp/watch/ci_log_extractor.rb +258 -0
- data/lib/aidp/watch/github_state_extractor.rb +177 -0
- data/lib/aidp/watch/implementation_verifier.rb +284 -0
- data/lib/aidp/watch/plan_generator.rb +7 -43
- data/lib/aidp/watch/plan_processor.rb +7 -6
- data/lib/aidp/watch/repository_client.rb +245 -17
- data/lib/aidp/watch/review_processor.rb +98 -17
- data/lib/aidp/watch/reviewers/base_reviewer.rb +1 -1
- data/lib/aidp/watch/runner.rb +181 -29
- data/lib/aidp/watch/state_store.rb +22 -1
- data/lib/aidp/workflows/definitions.rb +147 -0
- data/lib/aidp/workstream_cleanup.rb +245 -0
- data/lib/aidp/worktree.rb +19 -0
- data/templates/aidp.yml.example +57 -0
- data/templates/implementation/generate_tdd_specs.md +213 -0
- data/templates/implementation/iterative_implementation.md +122 -0
- data/templates/planning/agile/analyze_feedback.md +183 -0
- data/templates/planning/agile/generate_iteration_plan.md +179 -0
- data/templates/planning/agile/generate_legacy_research_plan.md +171 -0
- data/templates/planning/agile/generate_marketing_report.md +162 -0
- data/templates/planning/agile/generate_mvp_scope.md +127 -0
- data/templates/planning/agile/generate_user_test_plan.md +143 -0
- data/templates/planning/agile/ingest_feedback.md +174 -0
- data/templates/planning/assemble_project_plan.md +113 -0
- data/templates/planning/assign_personas.md +108 -0
- data/templates/planning/create_tasks.md +52 -6
- data/templates/planning/generate_gantt.md +86 -0
- data/templates/planning/generate_wbs.md +85 -0
- data/templates/planning/initialize_planning_mode.md +70 -0
- data/templates/skills/README.md +2 -2
- data/templates/skills/marketing_strategist/SKILL.md +279 -0
- data/templates/skills/product_manager/SKILL.md +177 -0
- data/templates/skills/ruby_aidp_planning/SKILL.md +497 -0
- data/templates/skills/ruby_rspec_tdd/SKILL.md +514 -0
- data/templates/skills/ux_researcher/SKILL.md +222 -0
- metadata +39 -1
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../logger"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Planning
|
|
7
|
+
module Generators
|
|
8
|
+
# Generates user testing plan with recruitment criteria and survey templates
|
|
9
|
+
# Uses AI to create contextual testing questions based on feature set
|
|
10
|
+
# Follows Zero Framework Cognition (ZFC) pattern
|
|
11
|
+
class UserTestPlanGenerator
|
|
12
|
+
def initialize(ai_decision_engine:, config: nil)
|
|
13
|
+
@ai_decision_engine = ai_decision_engine
|
|
14
|
+
@config = config || Aidp::Config.agile_config
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Generate user test plan from MVP scope
|
|
18
|
+
# @param mvp_scope [Hash] MVP scope definition
|
|
19
|
+
# @param target_users [String] Description of target users (optional)
|
|
20
|
+
# @return [Hash] User test plan structure
|
|
21
|
+
def generate(mvp_scope:, target_users: nil)
|
|
22
|
+
Aidp.log_debug("user_test_plan_generator", "generate",
|
|
23
|
+
feature_count: mvp_scope[:mvp_features]&.size || 0)
|
|
24
|
+
|
|
25
|
+
# Use AI to generate contextual testing plan
|
|
26
|
+
test_plan = generate_test_plan_with_ai(mvp_scope, target_users)
|
|
27
|
+
|
|
28
|
+
{
|
|
29
|
+
overview: test_plan[:overview],
|
|
30
|
+
target_users: test_plan[:target_users],
|
|
31
|
+
recruitment: test_plan[:recruitment],
|
|
32
|
+
testing_stages: test_plan[:testing_stages],
|
|
33
|
+
survey_questions: test_plan[:survey_questions],
|
|
34
|
+
interview_script: test_plan[:interview_script],
|
|
35
|
+
success_metrics: test_plan[:success_metrics],
|
|
36
|
+
timeline: test_plan[:timeline],
|
|
37
|
+
metadata: {
|
|
38
|
+
generated_at: Time.now.iso8601,
|
|
39
|
+
mvp_feature_count: mvp_scope[:mvp_features]&.size || 0,
|
|
40
|
+
testing_stage_count: test_plan[:testing_stages]&.size || 0,
|
|
41
|
+
survey_question_count: test_plan[:survey_questions]&.size || 0
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Format user test plan as markdown
|
|
47
|
+
# @param test_plan [Hash] User test plan structure
|
|
48
|
+
# @return [String] Markdown formatted test plan
|
|
49
|
+
def format_as_markdown(test_plan)
|
|
50
|
+
Aidp.log_debug("user_test_plan_generator", "format_as_markdown")
|
|
51
|
+
|
|
52
|
+
output = ["# User Testing Plan", ""]
|
|
53
|
+
output << "**Generated:** #{test_plan[:metadata][:generated_at]}"
|
|
54
|
+
output << "**Features to Test:** #{test_plan[:metadata][:mvp_feature_count]}"
|
|
55
|
+
output << "**Testing Stages:** #{test_plan[:metadata][:testing_stage_count]}"
|
|
56
|
+
output << ""
|
|
57
|
+
|
|
58
|
+
output << "## Overview"
|
|
59
|
+
output << ""
|
|
60
|
+
output << test_plan[:overview]
|
|
61
|
+
output << ""
|
|
62
|
+
|
|
63
|
+
output << "## Target Users"
|
|
64
|
+
output << ""
|
|
65
|
+
test_plan[:target_users].each do |segment|
|
|
66
|
+
output << "### #{segment[:name]}"
|
|
67
|
+
output << ""
|
|
68
|
+
output << "**Description:** #{segment[:description]}"
|
|
69
|
+
output << ""
|
|
70
|
+
output << "**Characteristics:**"
|
|
71
|
+
segment[:characteristics].each do |char|
|
|
72
|
+
output << "- #{char}"
|
|
73
|
+
end
|
|
74
|
+
output << ""
|
|
75
|
+
output << "**Sample Size:** #{segment[:sample_size]}"
|
|
76
|
+
output << ""
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
output << "## Recruitment Criteria"
|
|
80
|
+
output << ""
|
|
81
|
+
output << "### Screener Questions"
|
|
82
|
+
output << ""
|
|
83
|
+
test_plan[:recruitment][:screener_questions].each_with_index do |q, idx|
|
|
84
|
+
output << "#{idx + 1}. **#{q[:question]}**"
|
|
85
|
+
output << " - Type: #{q[:type]}"
|
|
86
|
+
output << " - Qualifying Answer: #{q[:qualifying_answer]}" if q[:qualifying_answer]
|
|
87
|
+
output << ""
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
output << "### Recruitment Channels"
|
|
91
|
+
output << ""
|
|
92
|
+
test_plan[:recruitment][:channels].each do |channel|
|
|
93
|
+
output << "- #{channel}"
|
|
94
|
+
end
|
|
95
|
+
output << ""
|
|
96
|
+
|
|
97
|
+
output << "### Incentives"
|
|
98
|
+
output << ""
|
|
99
|
+
output << test_plan[:recruitment][:incentives]
|
|
100
|
+
output << ""
|
|
101
|
+
|
|
102
|
+
output << "## Testing Stages"
|
|
103
|
+
output << ""
|
|
104
|
+
test_plan[:testing_stages].each_with_index do |stage, idx|
|
|
105
|
+
output << "### Stage #{idx + 1}: #{stage[:name]}"
|
|
106
|
+
output << ""
|
|
107
|
+
output << "**Objective:** #{stage[:objective]}"
|
|
108
|
+
output << ""
|
|
109
|
+
output << "**Duration:** #{stage[:duration]}"
|
|
110
|
+
output << ""
|
|
111
|
+
output << "**Participants:** #{stage[:participants]}"
|
|
112
|
+
output << ""
|
|
113
|
+
output << "**Activities:**"
|
|
114
|
+
stage[:activities].each do |activity|
|
|
115
|
+
output << "- #{activity}"
|
|
116
|
+
end
|
|
117
|
+
output << ""
|
|
118
|
+
output << "**Success Criteria:**"
|
|
119
|
+
stage[:success_criteria].each do |criterion|
|
|
120
|
+
output << "- #{criterion}"
|
|
121
|
+
end
|
|
122
|
+
output << ""
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
output << "## Survey Questions"
|
|
126
|
+
output << ""
|
|
127
|
+
output << "### Likert Scale Questions (1-5: Strongly Disagree to Strongly Agree)"
|
|
128
|
+
output << ""
|
|
129
|
+
test_plan[:survey_questions][:likert].each_with_index do |q, idx|
|
|
130
|
+
output << "#{idx + 1}. #{q}"
|
|
131
|
+
end
|
|
132
|
+
output << ""
|
|
133
|
+
|
|
134
|
+
output << "### Multiple Choice Questions"
|
|
135
|
+
output << ""
|
|
136
|
+
test_plan[:survey_questions][:multiple_choice].each_with_index do |q, idx|
|
|
137
|
+
output << "#{idx + 1}. **#{q[:question]}**"
|
|
138
|
+
q[:options].each_with_index do |opt, oidx|
|
|
139
|
+
output << " #{("a".."z").to_a[oidx]}. #{opt}"
|
|
140
|
+
end
|
|
141
|
+
output << ""
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
output << "### Open-Ended Questions"
|
|
145
|
+
output << ""
|
|
146
|
+
test_plan[:survey_questions][:open_ended].each_with_index do |q, idx|
|
|
147
|
+
output << "#{idx + 1}. #{q}"
|
|
148
|
+
end
|
|
149
|
+
output << ""
|
|
150
|
+
|
|
151
|
+
output << "## Interview Script"
|
|
152
|
+
output << ""
|
|
153
|
+
output << "### Introduction"
|
|
154
|
+
output << ""
|
|
155
|
+
output << test_plan[:interview_script][:introduction]
|
|
156
|
+
output << ""
|
|
157
|
+
|
|
158
|
+
output << "### Main Questions"
|
|
159
|
+
output << ""
|
|
160
|
+
test_plan[:interview_script][:main_questions].each_with_index do |q, idx|
|
|
161
|
+
output << "#{idx + 1}. #{q[:question]}"
|
|
162
|
+
output << " - **Follow-ups:** #{q[:follow_ups].join(", ")}" if q[:follow_ups]&.any?
|
|
163
|
+
output << ""
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
output << "### Closing"
|
|
167
|
+
output << ""
|
|
168
|
+
output << test_plan[:interview_script][:closing]
|
|
169
|
+
output << ""
|
|
170
|
+
|
|
171
|
+
output << "## Success Metrics"
|
|
172
|
+
output << ""
|
|
173
|
+
test_plan[:success_metrics].each do |metric|
|
|
174
|
+
output << "- **#{metric[:name]}:** #{metric[:description]}"
|
|
175
|
+
output << " - Target: #{metric[:target]}"
|
|
176
|
+
output << ""
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
output << "## Timeline"
|
|
180
|
+
output << ""
|
|
181
|
+
test_plan[:timeline].each do |phase|
|
|
182
|
+
output << "- **#{phase[:phase]}:** #{phase[:duration]}"
|
|
183
|
+
end
|
|
184
|
+
output << ""
|
|
185
|
+
|
|
186
|
+
output.join("\n")
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
private
|
|
190
|
+
|
|
191
|
+
def generate_test_plan_with_ai(mvp_scope, target_users)
|
|
192
|
+
Aidp.log_debug("user_test_plan_generator", "generate_test_plan_with_ai")
|
|
193
|
+
|
|
194
|
+
prompt = build_test_plan_prompt(mvp_scope, target_users)
|
|
195
|
+
|
|
196
|
+
schema = {
|
|
197
|
+
type: "object",
|
|
198
|
+
properties: {
|
|
199
|
+
overview: {type: "string"},
|
|
200
|
+
target_users: {
|
|
201
|
+
type: "array",
|
|
202
|
+
items: {
|
|
203
|
+
type: "object",
|
|
204
|
+
properties: {
|
|
205
|
+
name: {type: "string"},
|
|
206
|
+
description: {type: "string"},
|
|
207
|
+
characteristics: {type: "array", items: {type: "string"}},
|
|
208
|
+
sample_size: {type: "string"}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
recruitment: {
|
|
213
|
+
type: "object",
|
|
214
|
+
properties: {
|
|
215
|
+
screener_questions: {
|
|
216
|
+
type: "array",
|
|
217
|
+
items: {
|
|
218
|
+
type: "object",
|
|
219
|
+
properties: {
|
|
220
|
+
question: {type: "string"},
|
|
221
|
+
type: {type: "string"},
|
|
222
|
+
qualifying_answer: {type: "string"}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
channels: {type: "array", items: {type: "string"}},
|
|
227
|
+
incentives: {type: "string"}
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
testing_stages: {
|
|
231
|
+
type: "array",
|
|
232
|
+
items: {
|
|
233
|
+
type: "object",
|
|
234
|
+
properties: {
|
|
235
|
+
name: {type: "string"},
|
|
236
|
+
objective: {type: "string"},
|
|
237
|
+
duration: {type: "string"},
|
|
238
|
+
participants: {type: "string"},
|
|
239
|
+
activities: {type: "array", items: {type: "string"}},
|
|
240
|
+
success_criteria: {type: "array", items: {type: "string"}}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
survey_questions: {
|
|
245
|
+
type: "object",
|
|
246
|
+
properties: {
|
|
247
|
+
likert: {type: "array", items: {type: "string"}},
|
|
248
|
+
multiple_choice: {
|
|
249
|
+
type: "array",
|
|
250
|
+
items: {
|
|
251
|
+
type: "object",
|
|
252
|
+
properties: {
|
|
253
|
+
question: {type: "string"},
|
|
254
|
+
options: {type: "array", items: {type: "string"}}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
open_ended: {type: "array", items: {type: "string"}}
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
interview_script: {
|
|
262
|
+
type: "object",
|
|
263
|
+
properties: {
|
|
264
|
+
introduction: {type: "string"},
|
|
265
|
+
main_questions: {
|
|
266
|
+
type: "array",
|
|
267
|
+
items: {
|
|
268
|
+
type: "object",
|
|
269
|
+
properties: {
|
|
270
|
+
question: {type: "string"},
|
|
271
|
+
follow_ups: {type: "array", items: {type: "string"}}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
closing: {type: "string"}
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
success_metrics: {
|
|
279
|
+
type: "array",
|
|
280
|
+
items: {
|
|
281
|
+
type: "object",
|
|
282
|
+
properties: {
|
|
283
|
+
name: {type: "string"},
|
|
284
|
+
description: {type: "string"},
|
|
285
|
+
target: {type: "string"}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
timeline: {
|
|
290
|
+
type: "array",
|
|
291
|
+
items: {
|
|
292
|
+
type: "object",
|
|
293
|
+
properties: {
|
|
294
|
+
phase: {type: "string"},
|
|
295
|
+
duration: {type: "string"}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
required: ["overview", "target_users", "recruitment", "testing_stages", "survey_questions"]
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
decision = @ai_decision_engine.decide(
|
|
304
|
+
context: "user_test_plan_generation",
|
|
305
|
+
prompt: prompt,
|
|
306
|
+
data: {
|
|
307
|
+
mvp_scope: mvp_scope,
|
|
308
|
+
target_users: target_users
|
|
309
|
+
},
|
|
310
|
+
schema: schema
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
Aidp.log_debug("user_test_plan_generator", "ai_plan_generated",
|
|
314
|
+
stages: decision[:testing_stages]&.size || 0,
|
|
315
|
+
questions: decision[:survey_questions][:likert]&.size || 0)
|
|
316
|
+
|
|
317
|
+
decision
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def build_test_plan_prompt(mvp_scope, target_users)
|
|
321
|
+
<<~PROMPT
|
|
322
|
+
Generate a comprehensive user testing plan for the following MVP scope.
|
|
323
|
+
|
|
324
|
+
MVP FEATURES:
|
|
325
|
+
#{mvp_scope[:mvp_features]&.map { |f| "- #{f[:name]}: #{f[:description]}" }&.join("\n") || "No features provided"}
|
|
326
|
+
|
|
327
|
+
TARGET USERS:
|
|
328
|
+
#{target_users || mvp_scope.dig(:metadata, :user_priorities)&.find { |p| p.start_with?("Target users:") } || "General users"}
|
|
329
|
+
|
|
330
|
+
TASK:
|
|
331
|
+
Create a detailed user testing plan that includes:
|
|
332
|
+
|
|
333
|
+
1. OVERVIEW
|
|
334
|
+
- Brief description of testing goals
|
|
335
|
+
- Why user testing is important for this MVP
|
|
336
|
+
|
|
337
|
+
2. TARGET USERS (segments)
|
|
338
|
+
- Define 2-3 user segments to test
|
|
339
|
+
- For each: name, description, characteristics, recommended sample size
|
|
340
|
+
|
|
341
|
+
3. RECRUITMENT
|
|
342
|
+
- Screener questions to qualify participants
|
|
343
|
+
- Recruitment channels (where to find users)
|
|
344
|
+
- Suggested incentives
|
|
345
|
+
|
|
346
|
+
4. TESTING STAGES
|
|
347
|
+
- Define 3-4 testing stages (e.g., Alpha, Beta, Launch)
|
|
348
|
+
- For each stage: objective, duration, number of participants, activities, success criteria
|
|
349
|
+
|
|
350
|
+
5. SURVEY QUESTIONS
|
|
351
|
+
- 5-7 Likert scale questions (1-5 scale)
|
|
352
|
+
- 3-5 multiple choice questions with options
|
|
353
|
+
- 3-5 open-ended questions
|
|
354
|
+
|
|
355
|
+
6. INTERVIEW SCRIPT
|
|
356
|
+
- Introduction text
|
|
357
|
+
- 5-7 main interview questions with follow-ups
|
|
358
|
+
- Closing text
|
|
359
|
+
|
|
360
|
+
7. SUCCESS METRICS
|
|
361
|
+
- Quantitative and qualitative metrics
|
|
362
|
+
- Target values for each metric
|
|
363
|
+
|
|
364
|
+
8. TIMELINE
|
|
365
|
+
- Estimated duration for each phase (recruitment, testing, analysis)
|
|
366
|
+
|
|
367
|
+
Make questions specific to the MVP features. Focus on usability, value proposition, and user satisfaction.
|
|
368
|
+
PROMPT
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
end
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../logger"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Planning
|
|
7
|
+
module Generators
|
|
8
|
+
# Generates Work Breakdown Structure (WBS) with phase-based decomposition
|
|
9
|
+
# Breaks down projects into phases, tasks, and subtasks with dependencies
|
|
10
|
+
class WBSGenerator
|
|
11
|
+
DEFAULT_PHASES = [
|
|
12
|
+
"Requirements",
|
|
13
|
+
"Design",
|
|
14
|
+
"Implementation",
|
|
15
|
+
"Testing",
|
|
16
|
+
"Deployment"
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
def initialize(phases: DEFAULT_PHASES, config: nil)
|
|
20
|
+
@phases = phases
|
|
21
|
+
@config = config || Aidp::Config.waterfall_config
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Generate WBS from PRD and technical design
|
|
25
|
+
# @param prd [Hash] Parsed PRD document
|
|
26
|
+
# @param tech_design [Hash] Parsed technical design document
|
|
27
|
+
# @return [Hash] WBS structure with phases and tasks
|
|
28
|
+
def generate(prd:, tech_design: nil)
|
|
29
|
+
Aidp.log_debug("wbs_generator", "generate", has_prd: !prd.nil?, has_design: !tech_design.nil?)
|
|
30
|
+
|
|
31
|
+
wbs = {
|
|
32
|
+
phases: build_phases(prd, tech_design),
|
|
33
|
+
metadata: {
|
|
34
|
+
generated_at: Time.now.iso8601,
|
|
35
|
+
phase_count: @phases.size,
|
|
36
|
+
total_tasks: 0
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
wbs[:metadata][:total_tasks] = count_total_tasks(wbs[:phases])
|
|
41
|
+
|
|
42
|
+
Aidp.log_debug("wbs_generator", "generated", total_tasks: wbs[:metadata][:total_tasks])
|
|
43
|
+
wbs
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Format WBS as markdown
|
|
47
|
+
# @param wbs [Hash] WBS structure
|
|
48
|
+
# @return [String] Markdown formatted WBS
|
|
49
|
+
def format_as_markdown(wbs)
|
|
50
|
+
Aidp.log_debug("wbs_generator", "format_as_markdown")
|
|
51
|
+
|
|
52
|
+
output = ["# Work Breakdown Structure", ""]
|
|
53
|
+
output << "Generated: #{wbs[:metadata][:generated_at]}"
|
|
54
|
+
output << "Total Phases: #{wbs[:metadata][:phase_count]}"
|
|
55
|
+
output << "Total Tasks: #{wbs[:metadata][:total_tasks]}"
|
|
56
|
+
output << ""
|
|
57
|
+
|
|
58
|
+
wbs[:phases].each do |phase|
|
|
59
|
+
output << "## Phase: #{phase[:name]}"
|
|
60
|
+
output << ""
|
|
61
|
+
output << phase[:description] if phase[:description]
|
|
62
|
+
output << ""
|
|
63
|
+
|
|
64
|
+
phase[:tasks].each_with_index do |task, idx|
|
|
65
|
+
output << "### #{idx + 1}. #{task[:name]}"
|
|
66
|
+
output << ""
|
|
67
|
+
output << task[:description] if task[:description]
|
|
68
|
+
output << ""
|
|
69
|
+
|
|
70
|
+
if task[:subtasks]&.any?
|
|
71
|
+
task[:subtasks].each do |subtask|
|
|
72
|
+
output << " - #{subtask[:name]}"
|
|
73
|
+
end
|
|
74
|
+
output << ""
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
if task[:dependencies]&.any?
|
|
78
|
+
output << "**Dependencies:** #{task[:dependencies].join(", ")}"
|
|
79
|
+
output << ""
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
if task[:effort]
|
|
83
|
+
output << "**Effort:** #{task[:effort]}"
|
|
84
|
+
output << ""
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
output.join("\n")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
# Build phase structure from documents
|
|
95
|
+
def build_phases(prd, tech_design)
|
|
96
|
+
Aidp.log_debug("wbs_generator", "build_phases", phase_count: @phases.size)
|
|
97
|
+
|
|
98
|
+
@phases.map do |phase_name|
|
|
99
|
+
{
|
|
100
|
+
name: phase_name,
|
|
101
|
+
description: phase_description(phase_name),
|
|
102
|
+
tasks: build_phase_tasks(phase_name, prd, tech_design)
|
|
103
|
+
}
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Get description for a phase
|
|
108
|
+
def phase_description(phase_name)
|
|
109
|
+
descriptions = {
|
|
110
|
+
"Requirements" => "Gather and document all requirements",
|
|
111
|
+
"Design" => "Design system architecture and components",
|
|
112
|
+
"Implementation" => "Implement features and functionality",
|
|
113
|
+
"Testing" => "Test all features and fix bugs",
|
|
114
|
+
"Deployment" => "Deploy to production and monitor"
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
descriptions[phase_name] || "Complete #{phase_name} activities"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Build tasks for a specific phase
|
|
121
|
+
# This is a simplified version - in production, would use AI to extract tasks
|
|
122
|
+
def build_phase_tasks(phase_name, prd, tech_design)
|
|
123
|
+
case phase_name
|
|
124
|
+
when "Requirements"
|
|
125
|
+
requirements_tasks(prd)
|
|
126
|
+
when "Design"
|
|
127
|
+
design_tasks(tech_design)
|
|
128
|
+
when "Implementation"
|
|
129
|
+
implementation_tasks(prd, tech_design)
|
|
130
|
+
when "Testing"
|
|
131
|
+
testing_tasks(prd)
|
|
132
|
+
when "Deployment"
|
|
133
|
+
deployment_tasks
|
|
134
|
+
else
|
|
135
|
+
[]
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def requirements_tasks(prd)
|
|
140
|
+
[
|
|
141
|
+
{
|
|
142
|
+
name: "Document functional requirements",
|
|
143
|
+
description: "Extract and document all functional requirements from PRD",
|
|
144
|
+
effort: "3 story points",
|
|
145
|
+
dependencies: []
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: "Document non-functional requirements",
|
|
149
|
+
description: "Extract and document NFRs including performance, security, scalability",
|
|
150
|
+
effort: "2 story points",
|
|
151
|
+
dependencies: []
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def design_tasks(tech_design)
|
|
157
|
+
[
|
|
158
|
+
{
|
|
159
|
+
name: "Design system architecture",
|
|
160
|
+
description: "Create high-level architecture diagram and component breakdown",
|
|
161
|
+
effort: "5 story points",
|
|
162
|
+
dependencies: []
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: "Design data models",
|
|
166
|
+
description: "Define data models and database schema",
|
|
167
|
+
effort: "3 story points",
|
|
168
|
+
dependencies: ["Design system architecture"]
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: "Design API interfaces",
|
|
172
|
+
description: "Define API endpoints and contracts",
|
|
173
|
+
effort: "3 story points",
|
|
174
|
+
dependencies: ["Design system architecture"]
|
|
175
|
+
}
|
|
176
|
+
]
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def implementation_tasks(prd, tech_design)
|
|
180
|
+
[
|
|
181
|
+
{
|
|
182
|
+
name: "Set up project infrastructure",
|
|
183
|
+
description: "Initialize project, configure build tools, set up CI/CD",
|
|
184
|
+
effort: "3 story points",
|
|
185
|
+
dependencies: []
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: "Implement core features",
|
|
189
|
+
description: "Implement main functionality per PRD",
|
|
190
|
+
effort: "13 story points",
|
|
191
|
+
dependencies: ["Set up project infrastructure"],
|
|
192
|
+
subtasks: [
|
|
193
|
+
{name: "Feature module 1"},
|
|
194
|
+
{name: "Feature module 2"},
|
|
195
|
+
{name: "Feature module 3"}
|
|
196
|
+
]
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "Implement API layer",
|
|
200
|
+
description: "Build API endpoints per design",
|
|
201
|
+
effort: "8 story points",
|
|
202
|
+
dependencies: ["Implement core features"]
|
|
203
|
+
}
|
|
204
|
+
]
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def testing_tasks(prd)
|
|
208
|
+
[
|
|
209
|
+
{
|
|
210
|
+
name: "Write unit tests",
|
|
211
|
+
description: "Achieve 85%+ code coverage with unit tests",
|
|
212
|
+
effort: "8 story points",
|
|
213
|
+
dependencies: []
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: "Write integration tests",
|
|
217
|
+
description: "Test component integration and workflows",
|
|
218
|
+
effort: "5 story points",
|
|
219
|
+
dependencies: ["Write unit tests"]
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: "Perform UAT",
|
|
223
|
+
description: "User acceptance testing against PRD success criteria",
|
|
224
|
+
effort: "3 story points",
|
|
225
|
+
dependencies: ["Write integration tests"]
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def deployment_tasks
|
|
231
|
+
[
|
|
232
|
+
{
|
|
233
|
+
name: "Set up production environment",
|
|
234
|
+
description: "Configure production infrastructure and monitoring",
|
|
235
|
+
effort: "3 story points",
|
|
236
|
+
dependencies: []
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: "Deploy to production",
|
|
240
|
+
description: "Execute deployment and verify health",
|
|
241
|
+
effort: "2 story points",
|
|
242
|
+
dependencies: ["Set up production environment"]
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: "Monitor and stabilize",
|
|
246
|
+
description: "Monitor system performance and fix production issues",
|
|
247
|
+
effort: "3 story points",
|
|
248
|
+
dependencies: ["Deploy to production"]
|
|
249
|
+
}
|
|
250
|
+
]
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def count_total_tasks(phases)
|
|
254
|
+
phases.sum { |phase| phase[:tasks].size }
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|