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.
- steerdev-0.4.27.dist-info/METADATA +224 -0
- steerdev-0.4.27.dist-info/RECORD +57 -0
- steerdev-0.4.27.dist-info/WHEEL +4 -0
- steerdev-0.4.27.dist-info/entry_points.txt +2 -0
- steerdev_agent/__init__.py +10 -0
- steerdev_agent/api/__init__.py +32 -0
- steerdev_agent/api/activity.py +278 -0
- steerdev_agent/api/agents.py +145 -0
- steerdev_agent/api/client.py +158 -0
- steerdev_agent/api/commands.py +399 -0
- steerdev_agent/api/configs.py +238 -0
- steerdev_agent/api/context.py +306 -0
- steerdev_agent/api/events.py +294 -0
- steerdev_agent/api/hooks.py +178 -0
- steerdev_agent/api/implementation_plan.py +408 -0
- steerdev_agent/api/messages.py +231 -0
- steerdev_agent/api/prd.py +281 -0
- steerdev_agent/api/runs.py +526 -0
- steerdev_agent/api/sessions.py +403 -0
- steerdev_agent/api/specs.py +321 -0
- steerdev_agent/api/tasks.py +659 -0
- steerdev_agent/api/workflow_runs.py +351 -0
- steerdev_agent/api/workflows.py +191 -0
- steerdev_agent/cli.py +2254 -0
- steerdev_agent/config/__init__.py +19 -0
- steerdev_agent/config/models.py +236 -0
- steerdev_agent/config/platform.py +272 -0
- steerdev_agent/config/settings.py +62 -0
- steerdev_agent/daemon.py +675 -0
- steerdev_agent/executor/__init__.py +64 -0
- steerdev_agent/executor/base.py +121 -0
- steerdev_agent/executor/claude.py +328 -0
- steerdev_agent/executor/stream.py +163 -0
- steerdev_agent/git/__init__.py +1 -0
- steerdev_agent/handlers/__init__.py +5 -0
- steerdev_agent/handlers/prd.py +533 -0
- steerdev_agent/integration.py +334 -0
- steerdev_agent/prompt/__init__.py +10 -0
- steerdev_agent/prompt/builder.py +263 -0
- steerdev_agent/prompt/templates.py +422 -0
- steerdev_agent/py.typed +0 -0
- steerdev_agent/runner.py +829 -0
- steerdev_agent/setup/__init__.py +5 -0
- steerdev_agent/setup/claude_setup.py +560 -0
- steerdev_agent/setup/templates/claude_md_section.md +140 -0
- steerdev_agent/setup/templates/settings.json +69 -0
- steerdev_agent/setup/templates/skills/activity/SKILL.md +160 -0
- steerdev_agent/setup/templates/skills/context/SKILL.md +122 -0
- steerdev_agent/setup/templates/skills/git-workflow/SKILL.md +218 -0
- steerdev_agent/setup/templates/skills/progress-logging/SKILL.md +211 -0
- steerdev_agent/setup/templates/skills/specs-management/SKILL.md +161 -0
- steerdev_agent/setup/templates/skills/task-management/SKILL.md +343 -0
- steerdev_agent/setup/templates/steerdev.yaml +51 -0
- steerdev_agent/version.py +149 -0
- steerdev_agent/workflow/__init__.py +10 -0
- steerdev_agent/workflow/executor.py +494 -0
- steerdev_agent/workflow/memory.py +185 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"""Specs API client for SteerDev Agent.
|
|
2
|
+
|
|
3
|
+
Provides methods for interacting with specification documents (PRDs)
|
|
4
|
+
through the CLI skill interface.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
|
|
16
|
+
from steerdev_agent.api.client import SteerDevClient, get_project_id
|
|
17
|
+
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Valid spec statuses
|
|
22
|
+
VALID_SPEC_STATUSES = [
|
|
23
|
+
"draft",
|
|
24
|
+
"analyzing",
|
|
25
|
+
"clarifying",
|
|
26
|
+
"planning",
|
|
27
|
+
"ready",
|
|
28
|
+
"completed",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SpecDocument(BaseModel):
|
|
33
|
+
"""Spec document model."""
|
|
34
|
+
|
|
35
|
+
id: str
|
|
36
|
+
project_id: str
|
|
37
|
+
title: str
|
|
38
|
+
content: str
|
|
39
|
+
source: str
|
|
40
|
+
source_file_name: str | None = None
|
|
41
|
+
status: str # draft, analyzing, clarifying, planning, ready, completed
|
|
42
|
+
implementation_plan: dict[str, Any] | None = None
|
|
43
|
+
run_id: str | None = None
|
|
44
|
+
created_by: str
|
|
45
|
+
created_at: str
|
|
46
|
+
updated_at: str
|
|
47
|
+
linear_identifier: str | None = Field(default=None, description="Linear issue ID")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class SpecsClient(SteerDevClient):
|
|
51
|
+
"""Client for spec management operations.
|
|
52
|
+
|
|
53
|
+
Provides CRUD operations for specification documents (PRDs).
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def get_spec(self, spec_id: str) -> dict[str, Any] | None:
|
|
57
|
+
"""Get a spec document by ID.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
spec_id: Spec document ID (UUID).
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Spec data dict or None if not found.
|
|
64
|
+
"""
|
|
65
|
+
console.print(f"Fetching spec {spec_id} from {self.api_base}/prd/{spec_id}")
|
|
66
|
+
response = self.get(f"/prd/{spec_id}")
|
|
67
|
+
|
|
68
|
+
if response.status_code == 404:
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
if response.status_code != 200:
|
|
72
|
+
console.print(f"[red]API Error: {response.status_code} - {response.text}[/red]")
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
return response.json()
|
|
76
|
+
|
|
77
|
+
def list_specs(
|
|
78
|
+
self,
|
|
79
|
+
project_id: str | None = None,
|
|
80
|
+
status: str | None = None,
|
|
81
|
+
limit: int = 20,
|
|
82
|
+
) -> list[dict[str, Any]]:
|
|
83
|
+
"""List specs with optional filters.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
project_id: Filter by project ID. Falls back to STEERDEV_PROJECT_ID env var.
|
|
87
|
+
status: Filter by status.
|
|
88
|
+
limit: Maximum number of specs to return.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
List of spec dicts.
|
|
92
|
+
"""
|
|
93
|
+
effective_project_id = project_id or get_project_id()
|
|
94
|
+
|
|
95
|
+
params: dict[str, str | int] = {"limit": limit}
|
|
96
|
+
if status:
|
|
97
|
+
params["status"] = status
|
|
98
|
+
if effective_project_id:
|
|
99
|
+
params["project_id"] = effective_project_id
|
|
100
|
+
|
|
101
|
+
console.print(f"Fetching specs from {self.api_base}/prd")
|
|
102
|
+
response = self.get("/prd", params=params)
|
|
103
|
+
|
|
104
|
+
if response.status_code != 200:
|
|
105
|
+
console.print(f"[red]API Error: {response.status_code} - {response.text}[/red]")
|
|
106
|
+
return []
|
|
107
|
+
|
|
108
|
+
data = response.json()
|
|
109
|
+
return data.get("specs", data.get("documents", [])) if isinstance(data, dict) else data
|
|
110
|
+
|
|
111
|
+
def update_spec(
|
|
112
|
+
self,
|
|
113
|
+
spec_id: str,
|
|
114
|
+
content: str | None = None,
|
|
115
|
+
status: str | None = None,
|
|
116
|
+
title: str | None = None,
|
|
117
|
+
) -> bool:
|
|
118
|
+
"""Update a spec's fields.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
spec_id: Spec ID to update.
|
|
122
|
+
content: New content (markdown).
|
|
123
|
+
status: New status.
|
|
124
|
+
title: New title.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
True if update succeeded.
|
|
128
|
+
"""
|
|
129
|
+
payload: dict[str, str] = {}
|
|
130
|
+
if content:
|
|
131
|
+
payload["content"] = content
|
|
132
|
+
if status:
|
|
133
|
+
payload["status"] = status
|
|
134
|
+
if title:
|
|
135
|
+
payload["title"] = title
|
|
136
|
+
|
|
137
|
+
if not payload:
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
console.print(f"Updating spec {spec_id} at {self.api_base}/prd/{spec_id}")
|
|
141
|
+
response = self.patch(f"/prd/{spec_id}", json=payload)
|
|
142
|
+
|
|
143
|
+
if response.status_code == 404:
|
|
144
|
+
console.print(f"[red]Error: Spec '{spec_id}' not found[/red]")
|
|
145
|
+
return False
|
|
146
|
+
|
|
147
|
+
if response.status_code == 400:
|
|
148
|
+
console.print(f"[red]Validation Error: {response.text}[/red]")
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
if response.status_code not in (200, 204):
|
|
152
|
+
console.print(f"[red]API Error: {response.status_code} - {response.text}[/red]")
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
return True
|
|
156
|
+
|
|
157
|
+
def create_spec(
|
|
158
|
+
self,
|
|
159
|
+
title: str,
|
|
160
|
+
content: str,
|
|
161
|
+
project_id: str | None = None,
|
|
162
|
+
source: str = "agent",
|
|
163
|
+
) -> dict[str, Any] | None:
|
|
164
|
+
"""Create a new spec document.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
title: Spec title.
|
|
168
|
+
content: Spec content (markdown).
|
|
169
|
+
project_id: Project ID. Falls back to STEERDEV_PROJECT_ID env var.
|
|
170
|
+
source: Source of the spec (default: "agent").
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Created spec data dict or None on failure.
|
|
174
|
+
"""
|
|
175
|
+
effective_project_id = project_id or get_project_id()
|
|
176
|
+
if not effective_project_id:
|
|
177
|
+
console.print(
|
|
178
|
+
"[red]Error: project_id is required. "
|
|
179
|
+
"Use --project-id or set STEERDEV_PROJECT_ID environment variable[/red]"
|
|
180
|
+
)
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
payload = {
|
|
184
|
+
"project_id": effective_project_id,
|
|
185
|
+
"title": title,
|
|
186
|
+
"content": content,
|
|
187
|
+
"source": source,
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
console.print(f"Creating spec at {self.api_base}/prd")
|
|
191
|
+
response = self.post("/prd", json=payload)
|
|
192
|
+
|
|
193
|
+
if response.status_code not in (200, 201):
|
|
194
|
+
console.print(f"[red]API Error: {response.status_code} - {response.text}[/red]")
|
|
195
|
+
return None
|
|
196
|
+
|
|
197
|
+
return response.json()
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def get_status_style(status: str) -> str:
|
|
201
|
+
"""Get Rich style for a spec status.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
status: Spec status value.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Rich style string.
|
|
208
|
+
"""
|
|
209
|
+
return {
|
|
210
|
+
"draft": "dim",
|
|
211
|
+
"analyzing": "yellow",
|
|
212
|
+
"clarifying": "cyan",
|
|
213
|
+
"planning": "blue",
|
|
214
|
+
"ready": "green",
|
|
215
|
+
"completed": "dim green",
|
|
216
|
+
}.get(status, "white")
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def display_spec(spec: dict[str, Any], title: str = "Spec") -> None:
|
|
220
|
+
"""Display a spec in a formatted panel.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
spec: Spec data dict.
|
|
224
|
+
title: Panel title.
|
|
225
|
+
"""
|
|
226
|
+
linear_id = spec.get("linear_identifier", "N/A")
|
|
227
|
+
status_style = get_status_style(spec.get("status", ""))
|
|
228
|
+
|
|
229
|
+
spec_info = (
|
|
230
|
+
f"[bold cyan]Linear ID:[/bold cyan] {linear_id}\n"
|
|
231
|
+
f"[bold cyan]ID:[/bold cyan] {spec.get('id', 'N/A')}\n"
|
|
232
|
+
f"[bold cyan]Title:[/bold cyan] {spec.get('title', 'N/A')}\n"
|
|
233
|
+
f"[bold cyan]Status:[/bold cyan] [{status_style}]{spec.get('status', 'N/A')}[/{status_style}]\n"
|
|
234
|
+
f"[bold cyan]Project ID:[/bold cyan] {spec.get('project_id', 'N/A')}\n"
|
|
235
|
+
f"[bold cyan]Source:[/bold cyan] {spec.get('source', 'N/A')}"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
if spec.get("source_file_name"):
|
|
239
|
+
spec_info += f"\n[bold cyan]Source File:[/bold cyan] {spec['source_file_name']}"
|
|
240
|
+
|
|
241
|
+
if spec.get("content"):
|
|
242
|
+
# Truncate content for display, show first 500 chars
|
|
243
|
+
content = spec["content"]
|
|
244
|
+
if len(content) > 500:
|
|
245
|
+
content = content[:500] + "..."
|
|
246
|
+
spec_info += f"\n\n[bold cyan]Content:[/bold cyan]\n{content}"
|
|
247
|
+
|
|
248
|
+
if spec.get("run_id"):
|
|
249
|
+
spec_info += f"\n\n[bold cyan]Run ID:[/bold cyan] {spec['run_id']}"
|
|
250
|
+
|
|
251
|
+
console.print(Panel(spec_info, title=title, border_style="green"))
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def display_spec_list(specs: list[dict[str, Any]], full_ids: bool = True) -> None:
|
|
255
|
+
"""Display a list of specs in a formatted table.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
specs: List of spec data dicts.
|
|
259
|
+
full_ids: If True, show full UUIDs. If False, truncate to 8 chars.
|
|
260
|
+
"""
|
|
261
|
+
if not specs:
|
|
262
|
+
console.print("[yellow]No specs found[/yellow]")
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
table = Table(title="Specs")
|
|
266
|
+
table.add_column("Linear ID", style="cyan", no_wrap=True)
|
|
267
|
+
table.add_column("Title", style="white")
|
|
268
|
+
table.add_column("Status", style="magenta")
|
|
269
|
+
table.add_column("Source", style="dim")
|
|
270
|
+
table.add_column("ID", style="dim")
|
|
271
|
+
|
|
272
|
+
for spec in specs:
|
|
273
|
+
status_style = get_status_style(spec.get("status", ""))
|
|
274
|
+
spec_id = str(spec.get("id", "N/A"))
|
|
275
|
+
linear_id = spec.get("linear_identifier", "N/A")
|
|
276
|
+
|
|
277
|
+
if not full_ids:
|
|
278
|
+
spec_id = spec_id[:8] + "..." if len(spec_id) > 8 else spec_id
|
|
279
|
+
|
|
280
|
+
table.add_row(
|
|
281
|
+
linear_id,
|
|
282
|
+
spec.get("title", "N/A")[:50],
|
|
283
|
+
f"[{status_style}]{spec.get('status', 'N/A')}[/{status_style}]",
|
|
284
|
+
spec.get("source", "N/A"),
|
|
285
|
+
spec_id,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
console.print(table)
|
|
289
|
+
console.print(f"\n[dim]Total: {len(specs)} specs[/dim]")
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def display_spec_update_success(
|
|
293
|
+
spec_id: str,
|
|
294
|
+
content: str | None = None,
|
|
295
|
+
status: str | None = None,
|
|
296
|
+
title: str | None = None,
|
|
297
|
+
) -> None:
|
|
298
|
+
"""Display update success message.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
spec_id: Updated spec ID.
|
|
302
|
+
content: Updated content if any.
|
|
303
|
+
status: Updated status if any.
|
|
304
|
+
title: Updated title if any.
|
|
305
|
+
"""
|
|
306
|
+
updates = []
|
|
307
|
+
if content:
|
|
308
|
+
updates.append("Content updated")
|
|
309
|
+
if status:
|
|
310
|
+
updates.append(f"Status -> {status}")
|
|
311
|
+
if title:
|
|
312
|
+
updates.append("Title updated")
|
|
313
|
+
|
|
314
|
+
console.print(
|
|
315
|
+
Panel(
|
|
316
|
+
f"[bold green]Spec {spec_id} updated[/bold green]\n\n"
|
|
317
|
+
+ "\n".join(f"* {u}" for u in updates),
|
|
318
|
+
title="Success",
|
|
319
|
+
border_style="green",
|
|
320
|
+
)
|
|
321
|
+
)
|