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,334 @@
1
+ """Integration test harness for steerdev.
2
+
3
+ Creates a fresh Python project and adds tasks for building a todo-list API with SQLite.
4
+ """
5
+
6
+ import json
7
+ import os
8
+ import shutil
9
+ import subprocess
10
+ from pathlib import Path
11
+
12
+ import httpx
13
+ import typer
14
+ from rich import print as rprint
15
+ from rich.console import Console
16
+ from rich.panel import Panel
17
+ from rich.table import Table
18
+
19
+ console = Console()
20
+
21
+ # Default test tasks for todo-list API
22
+ DEFAULT_TASKS = [
23
+ {
24
+ "title": "Set up project with FastAPI and SQLite dependencies",
25
+ "prompt": """Set up the Python project for a todo-list API:
26
+ 1. Add dependencies to pyproject.toml: fastapi, uvicorn, sqlalchemy, pydantic
27
+ 2. Run `uv sync` to install them
28
+ 3. Create a basic src/todo_api/__init__.py file""",
29
+ "priority": 3,
30
+ },
31
+ {
32
+ "title": "Create SQLite database models",
33
+ "prompt": """Create the database layer in src/todo_api/database.py:
34
+ 1. Set up SQLAlchemy with SQLite (use 'todos.db' as the database file)
35
+ 2. Create a Todo model with fields: id (int, primary key), title (str), description (str, optional), completed (bool, default False), created_at (datetime)
36
+ 3. Add a function to initialize the database and create tables""",
37
+ "priority": 2,
38
+ },
39
+ {
40
+ "title": "Create Pydantic schemas",
41
+ "prompt": """Create Pydantic schemas in src/todo_api/schemas.py:
42
+ 1. TodoCreate schema for creating todos (title required, description optional)
43
+ 2. TodoUpdate schema for updating todos (all fields optional)
44
+ 3. TodoResponse schema for API responses (includes id, created_at, completed)""",
45
+ "priority": 2,
46
+ },
47
+ {
48
+ "title": "Implement CRUD API endpoints",
49
+ "prompt": """Create the FastAPI application in src/todo_api/main.py:
50
+ 1. Initialize FastAPI app with title "Todo API"
51
+ 2. Add startup event to initialize database
52
+ 3. Implement endpoints:
53
+ - GET /todos - list all todos
54
+ - POST /todos - create a todo
55
+ - GET /todos/{id} - get a single todo
56
+ - PATCH /todos/{id} - update a todo
57
+ - DELETE /todos/{id} - delete a todo
58
+ 4. Add proper error handling (404 for not found)""",
59
+ "priority": 2,
60
+ },
61
+ {
62
+ "title": "Add tests for the API",
63
+ "prompt": """Create tests in tests/test_api.py:
64
+ 1. Add pytest and httpx as dev dependencies
65
+ 2. Use FastAPI TestClient to test all endpoints
66
+ 3. Test cases: create todo, list todos, get single todo, update todo, delete todo, 404 handling
67
+ 4. Make sure tests are isolated (clean database between tests)""",
68
+ "priority": 1,
69
+ },
70
+ ]
71
+
72
+
73
+ def log_info(msg: str) -> None:
74
+ rprint(f"[blue][INFO][/blue] {msg}")
75
+
76
+
77
+ def log_success(msg: str) -> None:
78
+ rprint(f"[green][OK][/green] {msg}")
79
+
80
+
81
+ def log_warn(msg: str) -> None:
82
+ rprint(f"[yellow][WARN][/yellow] {msg}")
83
+
84
+
85
+ def log_error(msg: str) -> None:
86
+ rprint(f"[red][ERROR][/red] {msg}")
87
+
88
+
89
+ def run_cmd(
90
+ cmd: list[str],
91
+ cwd: Path | None = None,
92
+ check: bool = True,
93
+ capture: bool = True,
94
+ ) -> subprocess.CompletedProcess:
95
+ """Run a command and return the result."""
96
+ return subprocess.run(
97
+ cmd,
98
+ cwd=cwd,
99
+ check=check,
100
+ capture_output=capture,
101
+ text=True,
102
+ )
103
+
104
+
105
+ def create_project(project_dir: Path) -> None:
106
+ """Create a new Python project using uv init."""
107
+ log_info(f"Creating project at {project_dir}...")
108
+
109
+ if project_dir.exists():
110
+ log_warn(f"Directory exists, removing: {project_dir}")
111
+ shutil.rmtree(project_dir)
112
+
113
+ # Create with uv init
114
+ project_dir.parent.mkdir(parents=True, exist_ok=True)
115
+ run_cmd(["uv", "init", "--lib", str(project_dir)])
116
+
117
+ # Rename the default package to todo_api
118
+ src_dir = project_dir / "src"
119
+ default_pkg = src_dir / project_dir.name.replace("-", "_")
120
+ todo_pkg = src_dir / "todo_api"
121
+
122
+ if default_pkg.exists() and default_pkg != todo_pkg:
123
+ default_pkg.rename(todo_pkg)
124
+
125
+ # Update pyproject.toml to use todo_api
126
+ pyproject = project_dir / "pyproject.toml"
127
+ content = pyproject.read_text()
128
+ content = content.replace(
129
+ f'"{project_dir.name.replace("-", "_")}"',
130
+ '"todo_api"',
131
+ )
132
+ pyproject.write_text(content)
133
+
134
+ # Create tests directory
135
+ (project_dir / "tests").mkdir(exist_ok=True)
136
+ (project_dir / "tests" / "__init__.py").touch()
137
+
138
+ # Create .env.example
139
+ env_example = """\
140
+ # SteerDev Agent Configuration
141
+ # Copy this file to .env and fill in your values
142
+
143
+ # Required: Your SteerDev API key (get from steerdev.com dashboard)
144
+ STEERDEV_API_KEY=
145
+
146
+ # Required: Project ID to fetch tasks from
147
+ STEERDEV_PROJECT_ID=
148
+
149
+ # Optional: API endpoint (defaults to production)
150
+ # STEERDEV_API_ENDPOINT=https://steerdev.com/api/v1
151
+
152
+ # Optional: Agent identification
153
+ # STEERDEV_AGENT_ID=
154
+ # STEERDEV_AGENT_NAME=
155
+
156
+ # Optional: LLM API keys (for Claude Code / other agents)
157
+ # ANTHROPIC_API_KEY=
158
+ # OPENAI_API_KEY=
159
+
160
+ # Optional: Notifications
161
+ # DISCORD_BOT_TOKEN=
162
+ # DISCORD_CHANNEL_ID=
163
+ # SLACK_WEBHOOK_URL=
164
+ """
165
+ (project_dir / ".env.steerdev.example").write_text(env_example)
166
+
167
+ # Create .gitignore with common patterns
168
+ gitignore = """\
169
+ # Python
170
+ __pycache__/
171
+ *.py[cod]
172
+ *$py.class
173
+ .venv/
174
+ venv/
175
+ *.egg-info/
176
+ dist/
177
+ build/
178
+
179
+ # Environment
180
+ .env
181
+ .env.local
182
+
183
+ # Database
184
+ *.db
185
+ *.sqlite
186
+ *.sqlite3
187
+
188
+ # IDE
189
+ .idea/
190
+ .vscode/
191
+ *.swp
192
+ *.swo
193
+
194
+ # OS
195
+ .DS_Store
196
+ Thumbs.db
197
+
198
+ # Testing
199
+ .pytest_cache/
200
+ .coverage
201
+ htmlcov/
202
+ """
203
+ (project_dir / ".gitignore").write_text(gitignore)
204
+
205
+ # Initialize git
206
+ run_cmd(["git", "init"], cwd=project_dir)
207
+ run_cmd(["git", "add", "-A"], cwd=project_dir)
208
+ run_cmd(["git", "commit", "-m", "Initial commit"], cwd=project_dir)
209
+
210
+ log_success("Project created")
211
+
212
+
213
+ def add_tasks(
214
+ project_id: str,
215
+ tasks: list[dict],
216
+ api_key: str,
217
+ api_endpoint: str,
218
+ ) -> list[str]:
219
+ """Add tasks via the SteerDev API. Returns list of created task IDs."""
220
+ log_info(f"Adding {len(tasks)} tasks to project {project_id}...")
221
+
222
+ created_ids = []
223
+
224
+ for task in tasks:
225
+ title = task["title"]
226
+ prompt = task["prompt"]
227
+ priority = task.get("priority", 1)
228
+
229
+ log_info(f"Creating: {title}")
230
+
231
+ try:
232
+ response = httpx.post(
233
+ f"{api_endpoint}/tasks",
234
+ headers={
235
+ "Authorization": f"Bearer {api_key}",
236
+ "Content-Type": "application/json",
237
+ },
238
+ json={
239
+ "project_id": project_id,
240
+ "title": title,
241
+ "prompt": prompt,
242
+ "priority": priority,
243
+ },
244
+ timeout=30,
245
+ )
246
+
247
+ if response.status_code == 201:
248
+ data = response.json()
249
+ task_id = data.get("id", "unknown")
250
+ created_ids.append(task_id)
251
+ log_success(f"Created: {task_id[:8]}...")
252
+ else:
253
+ log_error(f"Failed: {response.text}")
254
+
255
+ except httpx.RequestError as e:
256
+ log_error(f"Request failed: {e}")
257
+
258
+ return created_ids
259
+
260
+
261
+ def start_agent(project_id: str, project_dir: Path) -> None:
262
+ """Start the steerdev."""
263
+ log_info("Starting steerdev...")
264
+
265
+ os.chdir(project_dir)
266
+ os.environ["STEERDEV_PROJECT_ID"] = project_id
267
+
268
+ subprocess.run(
269
+ ["steerdev", "run", "--project-id", project_id, "--workdir", str(project_dir)],
270
+ check=False,
271
+ )
272
+
273
+
274
+ def run_integration_test(
275
+ project_id: str,
276
+ project_dir: Path,
277
+ tasks_file: Path | None = None,
278
+ skip_tasks: bool = False,
279
+ auto_start: bool = False,
280
+ ) -> None:
281
+ """Run the full integration test."""
282
+ # Get environment
283
+ api_key = os.environ.get("STEERDEV_API_KEY")
284
+ if not api_key:
285
+ log_error("STEERDEV_API_KEY environment variable is required")
286
+ raise typer.Exit(1)
287
+
288
+ api_endpoint = os.environ.get("STEERDEV_API_ENDPOINT", "https://steerdev.com/api/v1")
289
+
290
+ # Show config
291
+ rprint("")
292
+ rprint(Panel.fit("SteerDev Integration Test", style="bold blue"))
293
+ rprint("")
294
+
295
+ table = Table(show_header=False, box=None)
296
+ table.add_column("Key", style="dim")
297
+ table.add_column("Value")
298
+ table.add_row("Project ID", project_id)
299
+ table.add_row("Project Dir", str(project_dir))
300
+ table.add_row("API Endpoint", api_endpoint)
301
+ console.print(table)
302
+ rprint("")
303
+
304
+ # Check uv is installed
305
+ if shutil.which("uv") is None:
306
+ log_error("uv is required but not installed")
307
+ raise typer.Exit(1)
308
+
309
+ # Create project
310
+ create_project(project_dir)
311
+
312
+ # Add tasks
313
+ if not skip_tasks:
314
+ if tasks_file and tasks_file.exists():
315
+ tasks = json.loads(tasks_file.read_text())
316
+ else:
317
+ tasks = DEFAULT_TASKS
318
+
319
+ add_tasks(project_id, tasks, api_key, api_endpoint)
320
+ else:
321
+ log_warn("Skipping task creation (--skip-tasks)")
322
+
323
+ log_success("Integration test environment ready!")
324
+ rprint("")
325
+ rprint(f"Project created at: [bold]{project_dir}[/bold]")
326
+ rprint("")
327
+
328
+ # Start agent
329
+ if auto_start or typer.confirm("Start the agent now?", default=True):
330
+ start_agent(project_id, project_dir)
331
+ else:
332
+ log_info("Run manually with:")
333
+ rprint(f" cd {project_dir}")
334
+ rprint(f" steerdev run --project-id {project_id}")
@@ -0,0 +1,10 @@
1
+ """Prompt building module."""
2
+
3
+ from steerdev_agent.prompt.builder import PromptBuilder, PromptContext
4
+ from steerdev_agent.prompt.templates import PromptTemplates
5
+
6
+ __all__ = [
7
+ "PromptBuilder",
8
+ "PromptContext",
9
+ "PromptTemplates",
10
+ ]
@@ -0,0 +1,263 @@
1
+ """Dynamic prompt builder for agent execution."""
2
+
3
+ from typing import Any
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+ from steerdev_agent.prompt.templates import PromptTemplates
8
+
9
+
10
+ class ProjectContext(BaseModel):
11
+ """Project context for prompt building."""
12
+
13
+ id: str = Field(description="Project ID")
14
+ name: str = Field(description="Project name")
15
+ description: str = Field(default="", description="Project description")
16
+ working_directory: str = Field(default=".", description="Default working directory")
17
+ custom_instructions: str | None = Field(
18
+ default=None, description="Custom instructions for this project"
19
+ )
20
+
21
+
22
+ class WaveContext(BaseModel):
23
+ """Wave context for prompt building."""
24
+
25
+ wave_number: int = Field(description="Current wave number")
26
+ total_waves: int = Field(description="Total number of waves")
27
+ wave_description: str = Field(default="", description="Description of this wave")
28
+ wave_tasks_summary: str = Field(default="", description="Summary of tasks in this wave")
29
+ completed_waves_summary: str = Field(default="", description="Summary of completed waves")
30
+
31
+
32
+ class TaskContext(BaseModel):
33
+ """Task context for prompt building."""
34
+
35
+ id: str = Field(description="Task ID")
36
+ title: str = Field(description="Task title")
37
+ prompt: str = Field(description="Task prompt/description")
38
+ status: str = Field(default="unstarted", description="Task status")
39
+ priority: int = Field(default=3, description="Task priority (1=urgent, 4=low)")
40
+ working_directory: str | None = Field(default=None, description="Task working directory")
41
+ spec: str | None = Field(default=None, description="Technical specification")
42
+ metadata: dict[str, Any] | None = Field(default=None, description="Additional metadata")
43
+ wave: WaveContext | None = Field(
44
+ default=None, description="Wave context if task is part of a wave"
45
+ )
46
+
47
+
48
+ class WorkflowContext(BaseModel):
49
+ """Workflow phase context for prompt building."""
50
+
51
+ phase_name: str = Field(description="Current workflow phase name")
52
+ phase_description: str = Field(default="", description="Phase description")
53
+ phase_instructions: str = Field(default="", description="Phase-specific instructions")
54
+
55
+
56
+ class PromptContext(BaseModel):
57
+ """Complete context for building a prompt."""
58
+
59
+ project: ProjectContext | None = Field(default=None, description="Project context")
60
+ task: TaskContext | None = Field(default=None, description="Task context")
61
+ workflow: WorkflowContext | None = Field(default=None, description="Workflow context")
62
+ custom_instructions: str | None = Field(
63
+ default=None, description="Additional custom instructions"
64
+ )
65
+ resume_message: str | None = Field(
66
+ default=None, description="Message for resume (if resuming session)"
67
+ )
68
+
69
+
70
+ class PromptBuilder:
71
+ """Builds prompts by combining context from various sources.
72
+
73
+ The builder combines:
74
+ - Project metadata and instructions
75
+ - Task details and requirements
76
+ - Workflow phase information
77
+ - Custom instructions
78
+
79
+ Into a single coherent prompt for the agent.
80
+ """
81
+
82
+ def __init__(self, templates: PromptTemplates | None = None) -> None:
83
+ """Initialize the prompt builder.
84
+
85
+ Args:
86
+ templates: Custom templates to use. Defaults to PromptTemplates.
87
+ """
88
+ self.templates = templates or PromptTemplates()
89
+
90
+ def build(self, context: PromptContext) -> str:
91
+ """Build a complete prompt from the context.
92
+
93
+ Args:
94
+ context: The prompt context containing all information.
95
+
96
+ Returns:
97
+ A formatted prompt string.
98
+ """
99
+ # Handle resume case
100
+ if context.resume_message:
101
+ return self._build_resume_prompt(context)
102
+
103
+ # Build components
104
+ project_info = self._build_project_info(context.project)
105
+ task_info = self._build_task_info(context.task, context.project)
106
+ instructions = self._build_instructions(context)
107
+
108
+ # Add workflow phase if present
109
+ if context.workflow:
110
+ workflow_info = self.templates.format_workflow_phase(
111
+ phase_name=context.workflow.phase_name,
112
+ phase_description=context.workflow.phase_description,
113
+ phase_instructions=context.workflow.phase_instructions,
114
+ )
115
+ task_info = f"{task_info}\n\n{workflow_info}"
116
+
117
+ return self.templates.format_system_prompt(
118
+ project_info=project_info,
119
+ task_info=task_info,
120
+ instructions=instructions,
121
+ )
122
+
123
+ def _build_resume_prompt(self, context: PromptContext) -> str:
124
+ """Build a prompt for resuming a session.
125
+
126
+ Args:
127
+ context: The prompt context.
128
+
129
+ Returns:
130
+ A resume prompt string.
131
+ """
132
+ message = context.resume_message or "Continue working on the task."
133
+ return self.templates.RESUME_PROMPT.format(message=message)
134
+
135
+ def _build_project_info(self, project: ProjectContext | None) -> str:
136
+ """Build project information section.
137
+
138
+ Args:
139
+ project: Project context or None.
140
+
141
+ Returns:
142
+ Formatted project information.
143
+ """
144
+ if project is None:
145
+ return ""
146
+
147
+ return self.templates.format_project(
148
+ name=project.name,
149
+ project_id=project.id,
150
+ description=project.description,
151
+ )
152
+
153
+ def _build_task_info(
154
+ self,
155
+ task: TaskContext | None,
156
+ project: ProjectContext | None,
157
+ ) -> str:
158
+ """Build task information section.
159
+
160
+ Args:
161
+ task: Task context or None.
162
+ project: Project context for working directory fallback.
163
+
164
+ Returns:
165
+ Formatted task information.
166
+ """
167
+ if task is None:
168
+ return ""
169
+
170
+ # Determine working directory
171
+ working_dir = task.working_directory
172
+ if working_dir is None and project is not None:
173
+ working_dir = project.working_directory
174
+ if working_dir is None:
175
+ working_dir = "."
176
+
177
+ task_info = self.templates.format_task(
178
+ title=task.title,
179
+ task_id=task.id,
180
+ prompt=task.prompt,
181
+ priority=task.priority,
182
+ status=task.status,
183
+ working_directory=working_dir,
184
+ )
185
+
186
+ # Add spec if present
187
+ if task.spec:
188
+ task_info = f"{task_info}\n\n### Specification\n{task.spec}"
189
+
190
+ # Add wave context if present
191
+ if task.wave:
192
+ wave_section = (
193
+ f"\n\n### Wave Context\n"
194
+ f"**Wave {task.wave.wave_number} of {task.wave.total_waves}**"
195
+ )
196
+ if task.wave.wave_description:
197
+ wave_section += f" — {task.wave.wave_description}"
198
+ if task.wave.completed_waves_summary:
199
+ wave_section += f"\n\n**Completed Waves:**\n{task.wave.completed_waves_summary}"
200
+ if task.wave.wave_tasks_summary:
201
+ wave_section += f"\n\n**Tasks in This Wave:**\n{task.wave.wave_tasks_summary}"
202
+ task_info = f"{task_info}{wave_section}"
203
+
204
+ return task_info
205
+
206
+ def _build_instructions(self, context: PromptContext) -> str:
207
+ """Build the instructions section.
208
+
209
+ Combines default instructions with custom instructions from
210
+ project and context.
211
+
212
+ Args:
213
+ context: The prompt context.
214
+
215
+ Returns:
216
+ Combined instructions string.
217
+ """
218
+ instructions_parts = []
219
+
220
+ # Add project custom instructions
221
+ if context.project and context.project.custom_instructions:
222
+ instructions_parts.append(context.project.custom_instructions)
223
+
224
+ # Add context custom instructions
225
+ if context.custom_instructions:
226
+ instructions_parts.append(context.custom_instructions)
227
+
228
+ # If no custom instructions, use defaults
229
+ if not instructions_parts:
230
+ return self.templates.DEFAULT_INSTRUCTIONS
231
+
232
+ return "\n\n".join(instructions_parts)
233
+
234
+ def build_from_dict(
235
+ self,
236
+ project: dict[str, Any] | None = None,
237
+ task: dict[str, Any] | None = None,
238
+ workflow: dict[str, Any] | None = None,
239
+ custom_instructions: str | None = None,
240
+ resume_message: str | None = None,
241
+ ) -> str:
242
+ """Build a prompt from dictionary inputs.
243
+
244
+ Convenience method for building prompts from API responses.
245
+
246
+ Args:
247
+ project: Project data dict.
248
+ task: Task data dict.
249
+ workflow: Workflow data dict.
250
+ custom_instructions: Additional instructions.
251
+ resume_message: Message for resume.
252
+
253
+ Returns:
254
+ A formatted prompt string.
255
+ """
256
+ context = PromptContext(
257
+ project=ProjectContext(**project) if project else None,
258
+ task=TaskContext(**task) if task else None,
259
+ workflow=WorkflowContext(**workflow) if workflow else None,
260
+ custom_instructions=custom_instructions,
261
+ resume_message=resume_message,
262
+ )
263
+ return self.build(context)