steerdev 0.4.27__py3-none-any.whl

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 (57) hide show
  1. steerdev-0.4.27.dist-info/METADATA +224 -0
  2. steerdev-0.4.27.dist-info/RECORD +57 -0
  3. steerdev-0.4.27.dist-info/WHEEL +4 -0
  4. steerdev-0.4.27.dist-info/entry_points.txt +2 -0
  5. steerdev_agent/__init__.py +10 -0
  6. steerdev_agent/api/__init__.py +32 -0
  7. steerdev_agent/api/activity.py +278 -0
  8. steerdev_agent/api/agents.py +145 -0
  9. steerdev_agent/api/client.py +158 -0
  10. steerdev_agent/api/commands.py +399 -0
  11. steerdev_agent/api/configs.py +238 -0
  12. steerdev_agent/api/context.py +306 -0
  13. steerdev_agent/api/events.py +294 -0
  14. steerdev_agent/api/hooks.py +178 -0
  15. steerdev_agent/api/implementation_plan.py +408 -0
  16. steerdev_agent/api/messages.py +231 -0
  17. steerdev_agent/api/prd.py +281 -0
  18. steerdev_agent/api/runs.py +526 -0
  19. steerdev_agent/api/sessions.py +403 -0
  20. steerdev_agent/api/specs.py +321 -0
  21. steerdev_agent/api/tasks.py +659 -0
  22. steerdev_agent/api/workflow_runs.py +351 -0
  23. steerdev_agent/api/workflows.py +191 -0
  24. steerdev_agent/cli.py +2254 -0
  25. steerdev_agent/config/__init__.py +19 -0
  26. steerdev_agent/config/models.py +236 -0
  27. steerdev_agent/config/platform.py +272 -0
  28. steerdev_agent/config/settings.py +62 -0
  29. steerdev_agent/daemon.py +675 -0
  30. steerdev_agent/executor/__init__.py +64 -0
  31. steerdev_agent/executor/base.py +121 -0
  32. steerdev_agent/executor/claude.py +328 -0
  33. steerdev_agent/executor/stream.py +163 -0
  34. steerdev_agent/git/__init__.py +1 -0
  35. steerdev_agent/handlers/__init__.py +5 -0
  36. steerdev_agent/handlers/prd.py +533 -0
  37. steerdev_agent/integration.py +334 -0
  38. steerdev_agent/prompt/__init__.py +10 -0
  39. steerdev_agent/prompt/builder.py +263 -0
  40. steerdev_agent/prompt/templates.py +422 -0
  41. steerdev_agent/py.typed +0 -0
  42. steerdev_agent/runner.py +829 -0
  43. steerdev_agent/setup/__init__.py +5 -0
  44. steerdev_agent/setup/claude_setup.py +560 -0
  45. steerdev_agent/setup/templates/claude_md_section.md +140 -0
  46. steerdev_agent/setup/templates/settings.json +69 -0
  47. steerdev_agent/setup/templates/skills/activity/SKILL.md +160 -0
  48. steerdev_agent/setup/templates/skills/context/SKILL.md +122 -0
  49. steerdev_agent/setup/templates/skills/git-workflow/SKILL.md +218 -0
  50. steerdev_agent/setup/templates/skills/progress-logging/SKILL.md +211 -0
  51. steerdev_agent/setup/templates/skills/specs-management/SKILL.md +161 -0
  52. steerdev_agent/setup/templates/skills/task-management/SKILL.md +343 -0
  53. steerdev_agent/setup/templates/steerdev.yaml +51 -0
  54. steerdev_agent/version.py +149 -0
  55. steerdev_agent/workflow/__init__.py +10 -0
  56. steerdev_agent/workflow/executor.py +494 -0
  57. steerdev_agent/workflow/memory.py +185 -0
@@ -0,0 +1,533 @@
1
+ """PRD message handler for agent-driven PRD analysis and task generation.
2
+
3
+ This handler processes messages from the SteerDev API related to PRD documents:
4
+ - prd_analyze: Analyze PRD and generate clarification questions
5
+ - prd_generate_plan: Generate implementation plan from clarified PRD
6
+ - prd_generate_tasks: Generate development tasks from implementation plan
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ from typing import Any
13
+
14
+ from loguru import logger
15
+ from pydantic import BaseModel
16
+ from rich.console import Console
17
+
18
+ from steerdev_agent.api.client import get_agent_id
19
+ from steerdev_agent.api.prd import PRDClient
20
+ from steerdev_agent.api.tasks import TasksClient
21
+ from steerdev_agent.prompt.templates import PromptTemplates
22
+
23
+ console = Console()
24
+
25
+
26
+ class PRDAnalyzePayload(BaseModel):
27
+ """Payload for prd_analyze message type."""
28
+
29
+ type: str = "prd_analyze"
30
+ prd_id: str
31
+ prd_content: str
32
+ prd_title: str
33
+
34
+
35
+ class PRDGeneratePlanPayload(BaseModel):
36
+ """Payload for prd_generate_plan message type."""
37
+
38
+ type: str = "prd_generate_plan"
39
+ prd_id: str
40
+ prd_content: str
41
+ prd_title: str
42
+ clarifications: list[dict[str, str]] = []
43
+
44
+
45
+ class PRDGenerateTasksPayload(BaseModel):
46
+ """Payload for prd_generate_tasks message type."""
47
+
48
+ type: str = "prd_generate_tasks"
49
+ prd_id: str
50
+ project_id: str
51
+ prd_title: str
52
+ implementation_plan: dict[str, Any]
53
+ linear_team_id: str | None = None
54
+ linear_project_id: str | None = None
55
+
56
+
57
+ class PRDHandlerResult(BaseModel):
58
+ """Result of PRD handler operation."""
59
+
60
+ success: bool
61
+ message_type: str
62
+ prd_id: str
63
+ data: dict[str, Any] = {}
64
+ error: str | None = None
65
+
66
+
67
+ class PRDHandler:
68
+ """Handler for PRD-related agent messages.
69
+
70
+ This handler integrates with the SteerDev API to:
71
+ 1. Analyze PRDs and post clarification questions
72
+ 2. Generate implementation plans
73
+ 3. Generate development tasks
74
+
75
+ It uses LLM (via prompts) for analysis and stores results via the API.
76
+ """
77
+
78
+ def __init__(
79
+ self,
80
+ api_key: str | None = None,
81
+ agent_id: str | None = None,
82
+ agent_name: str = "SteerDev Agent",
83
+ ) -> None:
84
+ """Initialize the PRD handler.
85
+
86
+ Args:
87
+ api_key: API key for authentication.
88
+ agent_id: Agent ID for attribution.
89
+ agent_name: Display name for the agent.
90
+ """
91
+ self.api_key = api_key
92
+ self.agent_id = agent_id or get_agent_id() or "unknown"
93
+ self.agent_name = agent_name
94
+ self._prd_client: PRDClient | None = None
95
+ self._tasks_client: TasksClient | None = None
96
+
97
+ @property
98
+ def prd_client(self) -> PRDClient:
99
+ """Get or create PRD client."""
100
+ if self._prd_client is None:
101
+ self._prd_client = PRDClient(api_key=self.api_key)
102
+ return self._prd_client
103
+
104
+ @property
105
+ def tasks_client(self) -> TasksClient:
106
+ """Get or create Tasks client."""
107
+ if self._tasks_client is None:
108
+ self._tasks_client = TasksClient(api_key=self.api_key)
109
+ return self._tasks_client
110
+
111
+ def handle_message(self, message_content: str) -> PRDHandlerResult:
112
+ """Handle a PRD-related message.
113
+
114
+ Args:
115
+ message_content: JSON string containing the message payload.
116
+
117
+ Returns:
118
+ Handler result with success status and data.
119
+ """
120
+ try:
121
+ payload = json.loads(message_content)
122
+ message_type = payload.get("type", "unknown")
123
+
124
+ if message_type == "prd_analyze":
125
+ return self._handle_analyze(PRDAnalyzePayload(**payload))
126
+ elif message_type == "prd_generate_plan":
127
+ return self._handle_generate_plan(PRDGeneratePlanPayload(**payload))
128
+ elif message_type == "prd_generate_tasks":
129
+ return self._handle_generate_tasks(PRDGenerateTasksPayload(**payload))
130
+ else:
131
+ return PRDHandlerResult(
132
+ success=False,
133
+ message_type=message_type,
134
+ prd_id=payload.get("prd_id", "unknown"),
135
+ error=f"Unknown message type: {message_type}",
136
+ )
137
+
138
+ except json.JSONDecodeError as e:
139
+ logger.error(f"Failed to parse message content: {e}")
140
+ return PRDHandlerResult(
141
+ success=False,
142
+ message_type="unknown",
143
+ prd_id="unknown",
144
+ error=f"Invalid JSON: {e}",
145
+ )
146
+ except Exception as e:
147
+ logger.exception(f"Error handling PRD message: {e}")
148
+ return PRDHandlerResult(
149
+ success=False,
150
+ message_type="unknown",
151
+ prd_id="unknown",
152
+ error=str(e),
153
+ )
154
+
155
+ def _handle_analyze(self, payload: PRDAnalyzePayload) -> PRDHandlerResult:
156
+ """Handle PRD analysis - generate clarification questions.
157
+
158
+ This method:
159
+ 1. Builds the analysis prompt
160
+ 2. Returns the prompt for the agent to execute
161
+ 3. The agent response should be parsed and questions posted via API
162
+
163
+ Args:
164
+ payload: Analysis payload with PRD content.
165
+
166
+ Returns:
167
+ Result with prompt to execute.
168
+ """
169
+ console.print(f"\n[bold cyan]Analyzing PRD: {payload.prd_title}[/bold cyan]")
170
+
171
+ # Build the analysis prompt
172
+ prompt = PromptTemplates.format_prd_analyze(
173
+ prd_title=payload.prd_title,
174
+ prd_content=payload.prd_content,
175
+ )
176
+
177
+ # Return the prompt - the runner will execute this and call back with results
178
+ return PRDHandlerResult(
179
+ success=True,
180
+ message_type="prd_analyze",
181
+ prd_id=payload.prd_id,
182
+ data={
183
+ "prompt": prompt,
184
+ "next_action": "execute_and_parse",
185
+ "response_handler": "parse_analysis_response",
186
+ },
187
+ )
188
+
189
+ def parse_analysis_response(
190
+ self,
191
+ prd_id: str,
192
+ response_text: str,
193
+ ) -> PRDHandlerResult:
194
+ """Parse LLM analysis response and post questions to API.
195
+
196
+ Args:
197
+ prd_id: PRD document ID.
198
+ response_text: Raw LLM response text.
199
+
200
+ Returns:
201
+ Result with posted questions data.
202
+ """
203
+ try:
204
+ # Extract JSON from response (may be wrapped in markdown code block)
205
+ json_text = response_text
206
+ if "```json" in response_text:
207
+ start = response_text.find("```json") + 7
208
+ end = response_text.find("```", start)
209
+ json_text = response_text[start:end].strip()
210
+ elif "```" in response_text:
211
+ start = response_text.find("```") + 3
212
+ end = response_text.find("```", start)
213
+ json_text = response_text[start:end].strip()
214
+
215
+ data = json.loads(json_text)
216
+ questions = data.get("questions", [])
217
+ summary = data.get("summary", "")
218
+ observations = data.get("initial_observations", "")
219
+
220
+ # Post questions as comments
221
+ if questions:
222
+ posted_comments = self.prd_client.post_questions(
223
+ prd_id=prd_id,
224
+ questions=questions,
225
+ agent_id=self.agent_id,
226
+ agent_name=self.agent_name,
227
+ )
228
+ console.print(f"[green]Posted {len(posted_comments)} questions[/green]")
229
+ else:
230
+ console.print("[yellow]No questions to post[/yellow]")
231
+
232
+ # Post summary and observations as notes
233
+ if summary:
234
+ self.prd_client.post_note(
235
+ prd_id=prd_id,
236
+ content=f"**Summary:** {summary}",
237
+ agent_id=self.agent_id,
238
+ agent_name=self.agent_name,
239
+ )
240
+
241
+ if observations:
242
+ self.prd_client.post_note(
243
+ prd_id=prd_id,
244
+ content=f"**Initial Observations:** {observations}",
245
+ agent_id=self.agent_id,
246
+ agent_name=self.agent_name,
247
+ )
248
+
249
+ # Update PRD status to clarifying
250
+ self.prd_client.update_prd_status(prd_id, "clarifying")
251
+
252
+ return PRDHandlerResult(
253
+ success=True,
254
+ message_type="prd_analyze",
255
+ prd_id=prd_id,
256
+ data={
257
+ "questions_count": len(questions),
258
+ "summary": summary,
259
+ },
260
+ )
261
+
262
+ except json.JSONDecodeError as e:
263
+ logger.error(f"Failed to parse analysis response: {e}")
264
+ # Try to recover - update status anyway
265
+ self.prd_client.update_prd_status(prd_id, "clarifying")
266
+ return PRDHandlerResult(
267
+ success=False,
268
+ message_type="prd_analyze",
269
+ prd_id=prd_id,
270
+ error=f"Failed to parse response: {e}",
271
+ )
272
+ except Exception as e:
273
+ logger.exception(f"Error parsing analysis response: {e}")
274
+ return PRDHandlerResult(
275
+ success=False,
276
+ message_type="prd_analyze",
277
+ prd_id=prd_id,
278
+ error=str(e),
279
+ )
280
+
281
+ def _handle_generate_plan(self, payload: PRDGeneratePlanPayload) -> PRDHandlerResult:
282
+ """Handle implementation plan generation.
283
+
284
+ Args:
285
+ payload: Plan generation payload with PRD content and clarifications.
286
+
287
+ Returns:
288
+ Result with prompt to execute.
289
+ """
290
+ console.print(f"\n[bold cyan]Generating plan for: {payload.prd_title}[/bold cyan]")
291
+
292
+ # Build the plan generation prompt
293
+ prompt = PromptTemplates.format_prd_generate_plan(
294
+ prd_title=payload.prd_title,
295
+ prd_content=payload.prd_content,
296
+ clarifications=payload.clarifications,
297
+ )
298
+
299
+ return PRDHandlerResult(
300
+ success=True,
301
+ message_type="prd_generate_plan",
302
+ prd_id=payload.prd_id,
303
+ data={
304
+ "prompt": prompt,
305
+ "next_action": "execute_and_parse",
306
+ "response_handler": "parse_plan_response",
307
+ },
308
+ )
309
+
310
+ def parse_plan_response(
311
+ self,
312
+ prd_id: str,
313
+ response_text: str,
314
+ ) -> PRDHandlerResult:
315
+ """Parse LLM plan response and save to API.
316
+
317
+ Args:
318
+ prd_id: PRD document ID.
319
+ response_text: Raw LLM response text.
320
+
321
+ Returns:
322
+ Result with saved plan data.
323
+ """
324
+ try:
325
+ # Extract JSON from response
326
+ json_text = response_text
327
+ if "```json" in response_text:
328
+ start = response_text.find("```json") + 7
329
+ end = response_text.find("```", start)
330
+ json_text = response_text[start:end].strip()
331
+ elif "```" in response_text:
332
+ start = response_text.find("```") + 3
333
+ end = response_text.find("```", start)
334
+ json_text = response_text[start:end].strip()
335
+
336
+ plan = json.loads(json_text)
337
+
338
+ # Save the implementation plan
339
+ updated_prd = self.prd_client.save_implementation_plan(prd_id, plan)
340
+
341
+ if updated_prd:
342
+ console.print("[green]Implementation plan saved[/green]")
343
+ sections_count = len(plan.get("sections", []))
344
+ console.print(f"[dim]Sections: {sections_count}[/dim]")
345
+
346
+ return PRDHandlerResult(
347
+ success=True,
348
+ message_type="prd_generate_plan",
349
+ prd_id=prd_id,
350
+ data={
351
+ "plan": plan,
352
+ "sections_count": sections_count,
353
+ },
354
+ )
355
+ else:
356
+ return PRDHandlerResult(
357
+ success=False,
358
+ message_type="prd_generate_plan",
359
+ prd_id=prd_id,
360
+ error="Failed to save implementation plan",
361
+ )
362
+
363
+ except json.JSONDecodeError as e:
364
+ logger.error(f"Failed to parse plan response: {e}")
365
+ return PRDHandlerResult(
366
+ success=False,
367
+ message_type="prd_generate_plan",
368
+ prd_id=prd_id,
369
+ error=f"Failed to parse response: {e}",
370
+ )
371
+ except Exception as e:
372
+ logger.exception(f"Error parsing plan response: {e}")
373
+ return PRDHandlerResult(
374
+ success=False,
375
+ message_type="prd_generate_plan",
376
+ prd_id=prd_id,
377
+ error=str(e),
378
+ )
379
+
380
+ def _handle_generate_tasks(self, payload: PRDGenerateTasksPayload) -> PRDHandlerResult:
381
+ """Handle task generation from implementation plan.
382
+
383
+ Args:
384
+ payload: Task generation payload with implementation plan.
385
+
386
+ Returns:
387
+ Result with prompt to execute.
388
+ """
389
+ console.print(f"\n[bold cyan]Generating tasks for: {payload.prd_title}[/bold cyan]")
390
+
391
+ # Build the task generation prompt
392
+ prompt = PromptTemplates.format_prd_generate_tasks(
393
+ prd_title=payload.prd_title,
394
+ implementation_plan=payload.implementation_plan,
395
+ )
396
+
397
+ return PRDHandlerResult(
398
+ success=True,
399
+ message_type="prd_generate_tasks",
400
+ prd_id=payload.prd_id,
401
+ data={
402
+ "prompt": prompt,
403
+ "next_action": "execute_and_parse",
404
+ "response_handler": "parse_tasks_response",
405
+ "project_id": payload.project_id,
406
+ "linear_team_id": payload.linear_team_id,
407
+ "linear_project_id": payload.linear_project_id,
408
+ },
409
+ )
410
+
411
+ def parse_tasks_response(
412
+ self,
413
+ prd_id: str,
414
+ response_text: str,
415
+ project_id: str,
416
+ linear_team_id: str | None = None,
417
+ linear_project_id: str | None = None,
418
+ ) -> PRDHandlerResult:
419
+ """Parse LLM tasks response and create tasks via API.
420
+
421
+ Args:
422
+ prd_id: PRD document ID.
423
+ response_text: Raw LLM response text.
424
+ project_id: Project ID to create tasks in.
425
+ linear_team_id: Optional Linear team ID.
426
+ linear_project_id: Optional Linear project ID.
427
+
428
+ Returns:
429
+ Result with created tasks data.
430
+ """
431
+ try:
432
+ # Extract JSON from response
433
+ json_text = response_text
434
+ if "```json" in response_text:
435
+ start = response_text.find("```json") + 7
436
+ end = response_text.find("```", start)
437
+ json_text = response_text[start:end].strip()
438
+ elif "```" in response_text:
439
+ start = response_text.find("```") + 3
440
+ end = response_text.find("```", start)
441
+ json_text = response_text[start:end].strip()
442
+
443
+ data = json.loads(json_text)
444
+ tasks = data.get("tasks", [])
445
+
446
+ if not tasks:
447
+ return PRDHandlerResult(
448
+ success=True,
449
+ message_type="prd_generate_tasks",
450
+ prd_id=prd_id,
451
+ data={"tasks_count": 0, "message": "No tasks to create"},
452
+ )
453
+
454
+ # Create tasks via API
455
+ created_count = 0
456
+ failed_count = 0
457
+ created_task_ids: list[str] = []
458
+
459
+ for task in tasks:
460
+ try:
461
+ created_task = self.tasks_client.create_task(
462
+ project_id=project_id,
463
+ title=task.get("title", "Untitled Task"),
464
+ prompt=task.get("description", ""),
465
+ priority=task.get("priority", 3),
466
+ source="prd",
467
+ metadata={
468
+ "prd_id": prd_id,
469
+ "section": task.get("section"),
470
+ "dependencies": task.get("dependencies", []),
471
+ },
472
+ )
473
+ if created_task:
474
+ created_count += 1
475
+ created_task_ids.append(created_task.get("id", ""))
476
+ else:
477
+ failed_count += 1
478
+
479
+ except Exception as e:
480
+ logger.error(f"Failed to create task: {e}")
481
+ failed_count += 1
482
+
483
+ console.print(f"[green]Created {created_count} tasks[/green]")
484
+ if failed_count > 0:
485
+ console.print(f"[yellow]Failed to create {failed_count} tasks[/yellow]")
486
+
487
+ # Update PRD status to completed
488
+ self.prd_client.update_prd_status(prd_id, "completed")
489
+
490
+ return PRDHandlerResult(
491
+ success=True,
492
+ message_type="prd_generate_tasks",
493
+ prd_id=prd_id,
494
+ data={
495
+ "tasks_count": created_count,
496
+ "failed_count": failed_count,
497
+ "task_ids": created_task_ids,
498
+ },
499
+ )
500
+
501
+ except json.JSONDecodeError as e:
502
+ logger.error(f"Failed to parse tasks response: {e}")
503
+ return PRDHandlerResult(
504
+ success=False,
505
+ message_type="prd_generate_tasks",
506
+ prd_id=prd_id,
507
+ error=f"Failed to parse response: {e}",
508
+ )
509
+ except Exception as e:
510
+ logger.exception(f"Error parsing tasks response: {e}")
511
+ return PRDHandlerResult(
512
+ success=False,
513
+ message_type="prd_generate_tasks",
514
+ prd_id=prd_id,
515
+ error=str(e),
516
+ )
517
+
518
+ def close(self) -> None:
519
+ """Close client connections."""
520
+ if self._prd_client:
521
+ self._prd_client.close()
522
+ self._prd_client = None
523
+ if self._tasks_client:
524
+ self._tasks_client.close()
525
+ self._tasks_client = None
526
+
527
+ def __enter__(self) -> PRDHandler:
528
+ """Enter context manager."""
529
+ return self
530
+
531
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
532
+ """Exit context manager."""
533
+ self.close()