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,238 @@
|
|
|
1
|
+
"""Agent configuration sync client for platform configs and workflows."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Self
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from loguru import logger
|
|
7
|
+
|
|
8
|
+
from steerdev_agent.api.client import get_api_endpoint, get_api_key, get_project_id
|
|
9
|
+
from steerdev_agent.config.platform import PlatformConfig, WorkflowConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ConfigsClient:
|
|
13
|
+
"""Async HTTP client for syncing agent configurations from platform.
|
|
14
|
+
|
|
15
|
+
Manages fetching platform configs (system prompts, skills, MCPs, env vars)
|
|
16
|
+
and workflow definitions for agent startup and execution.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
api_key: str | None = None,
|
|
22
|
+
project_id: str | None = None,
|
|
23
|
+
timeout: float = 30.0,
|
|
24
|
+
) -> None:
|
|
25
|
+
"""Initialize the client.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
api_key: API key for authentication. If not provided, reads from STEERDEV_API_KEY.
|
|
29
|
+
project_id: Project ID to sync configs for. If not provided, reads from STEERDEV_PROJECT_ID.
|
|
30
|
+
timeout: Request timeout in seconds.
|
|
31
|
+
"""
|
|
32
|
+
self.api_key = api_key or get_api_key()
|
|
33
|
+
self.project_id = project_id or get_project_id()
|
|
34
|
+
self.api_base = get_api_endpoint()
|
|
35
|
+
self.timeout = timeout
|
|
36
|
+
self._client: httpx.AsyncClient | None = None
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def headers(self) -> dict[str, str]:
|
|
40
|
+
"""Get request headers with authentication."""
|
|
41
|
+
return {
|
|
42
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
43
|
+
"Content-Type": "application/json",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async def _get_client(self) -> httpx.AsyncClient:
|
|
47
|
+
"""Get or create async HTTP client."""
|
|
48
|
+
if self._client is None:
|
|
49
|
+
self._client = httpx.AsyncClient(timeout=self.timeout)
|
|
50
|
+
return self._client
|
|
51
|
+
|
|
52
|
+
async def close(self) -> None:
|
|
53
|
+
"""Close the HTTP client."""
|
|
54
|
+
if self._client is not None:
|
|
55
|
+
await self._client.aclose()
|
|
56
|
+
self._client = None
|
|
57
|
+
|
|
58
|
+
async def __aenter__(self) -> Self:
|
|
59
|
+
"""Enter async context manager."""
|
|
60
|
+
return self
|
|
61
|
+
|
|
62
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
63
|
+
"""Exit async context manager."""
|
|
64
|
+
await self.close()
|
|
65
|
+
|
|
66
|
+
async def sync_configs(self, project_id: str | None = None) -> PlatformConfig | None:
|
|
67
|
+
"""Sync all active configurations from platform.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
project_id: Override project ID for this request.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
PlatformConfig with all synced configs, or None on failure.
|
|
74
|
+
"""
|
|
75
|
+
project_id = project_id or self.project_id
|
|
76
|
+
if not project_id:
|
|
77
|
+
logger.error("No project_id provided for config sync")
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
client = await self._get_client()
|
|
81
|
+
logger.debug(f"Syncing configs for project {project_id}")
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
response = await client.get(
|
|
85
|
+
f"{self.api_base}/configs/sync",
|
|
86
|
+
headers=self.headers,
|
|
87
|
+
params={"project_id": project_id},
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if response.status_code == 200:
|
|
91
|
+
data = response.json()
|
|
92
|
+
config = PlatformConfig.from_api_response(data)
|
|
93
|
+
logger.info(
|
|
94
|
+
f"Synced configs: {len(config.system_prompts)} prompts, "
|
|
95
|
+
f"{len(config.skills)} skills, {len(config.mcps)} MCPs, "
|
|
96
|
+
f"{len(config.env_vars)} env var sets"
|
|
97
|
+
)
|
|
98
|
+
return config
|
|
99
|
+
|
|
100
|
+
logger.error(f"Failed to sync configs: {response.status_code} - {response.text}")
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
except httpx.RequestError as e:
|
|
104
|
+
logger.error(f"Request error syncing configs: {e}")
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
async def get_workflow(
|
|
108
|
+
self,
|
|
109
|
+
workflow_id: str,
|
|
110
|
+
) -> WorkflowConfig | None:
|
|
111
|
+
"""Get a specific workflow by ID.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
workflow_id: Workflow ID to fetch.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
WorkflowConfig or None if not found.
|
|
118
|
+
"""
|
|
119
|
+
client = await self._get_client()
|
|
120
|
+
logger.debug(f"Fetching workflow {workflow_id}")
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
response = await client.get(
|
|
124
|
+
f"{self.api_base}/workflows/{workflow_id}",
|
|
125
|
+
headers=self.headers,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if response.status_code == 200:
|
|
129
|
+
data = response.json()
|
|
130
|
+
return WorkflowConfig.from_api_response(data)
|
|
131
|
+
|
|
132
|
+
if response.status_code == 404:
|
|
133
|
+
logger.warning(f"Workflow {workflow_id} not found")
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
logger.error(f"Failed to get workflow: {response.status_code} - {response.text}")
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
except httpx.RequestError as e:
|
|
140
|
+
logger.error(f"Request error getting workflow: {e}")
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
async def get_default_workflow(
|
|
144
|
+
self,
|
|
145
|
+
project_id: str | None = None,
|
|
146
|
+
) -> WorkflowConfig | None:
|
|
147
|
+
"""Get the default workflow for a project.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
project_id: Override project ID for this request.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
WorkflowConfig or None if no default workflow exists.
|
|
154
|
+
"""
|
|
155
|
+
project_id = project_id or self.project_id
|
|
156
|
+
if not project_id:
|
|
157
|
+
logger.error("No project_id provided for getting default workflow")
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
client = await self._get_client()
|
|
161
|
+
logger.debug(f"Fetching default workflow for project {project_id}")
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
response = await client.get(
|
|
165
|
+
f"{self.api_base}/workflows",
|
|
166
|
+
headers=self.headers,
|
|
167
|
+
params={"project_id": project_id},
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if response.status_code == 200:
|
|
171
|
+
data = response.json()
|
|
172
|
+
workflows = data.get("workflows", [])
|
|
173
|
+
|
|
174
|
+
# Find the default workflow
|
|
175
|
+
for wf in workflows:
|
|
176
|
+
if wf.get("is_default"):
|
|
177
|
+
return WorkflowConfig.from_api_response(wf)
|
|
178
|
+
|
|
179
|
+
# If no default, return the first workflow if any
|
|
180
|
+
if workflows:
|
|
181
|
+
logger.debug("No default workflow found, using first available")
|
|
182
|
+
return WorkflowConfig.from_api_response(workflows[0])
|
|
183
|
+
|
|
184
|
+
logger.debug("No workflows found for project")
|
|
185
|
+
return None
|
|
186
|
+
|
|
187
|
+
logger.error(f"Failed to list workflows: {response.status_code} - {response.text}")
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
except httpx.RequestError as e:
|
|
191
|
+
logger.error(f"Request error listing workflows: {e}")
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
async def list_workflows(
|
|
195
|
+
self,
|
|
196
|
+
project_id: str | None = None,
|
|
197
|
+
limit: int = 50,
|
|
198
|
+
offset: int = 0,
|
|
199
|
+
) -> list[WorkflowConfig]:
|
|
200
|
+
"""List all workflows for a project.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
project_id: Override project ID for this request.
|
|
204
|
+
limit: Maximum number of workflows to return.
|
|
205
|
+
offset: Offset for pagination.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
List of WorkflowConfig objects.
|
|
209
|
+
"""
|
|
210
|
+
project_id = project_id or self.project_id
|
|
211
|
+
if not project_id:
|
|
212
|
+
logger.error("No project_id provided for listing workflows")
|
|
213
|
+
return []
|
|
214
|
+
|
|
215
|
+
client = await self._get_client()
|
|
216
|
+
logger.debug(f"Listing workflows for project {project_id}")
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
response = await client.get(
|
|
220
|
+
f"{self.api_base}/workflows",
|
|
221
|
+
headers=self.headers,
|
|
222
|
+
params={"project_id": project_id, "limit": limit, "offset": offset},
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
if response.status_code == 200:
|
|
226
|
+
data = response.json()
|
|
227
|
+
workflows = [
|
|
228
|
+
WorkflowConfig.from_api_response(wf) for wf in data.get("workflows", [])
|
|
229
|
+
]
|
|
230
|
+
logger.debug(f"Found {len(workflows)} workflows")
|
|
231
|
+
return workflows
|
|
232
|
+
|
|
233
|
+
logger.error(f"Failed to list workflows: {response.status_code} - {response.text}")
|
|
234
|
+
return []
|
|
235
|
+
|
|
236
|
+
except httpx.RequestError as e:
|
|
237
|
+
logger.error(f"Request error listing workflows: {e}")
|
|
238
|
+
return []
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"""Context API client for SteerDev Agent.
|
|
2
|
+
|
|
3
|
+
Provides methods for retrieving and managing project context
|
|
4
|
+
(tech stack, patterns, structure, etc.) for AI agents.
|
|
5
|
+
|
|
6
|
+
The Codebase Awareness feature provides:
|
|
7
|
+
- Tech stack detection (frameworks, libraries, tools)
|
|
8
|
+
- Directory structure analysis
|
|
9
|
+
- Code patterns and conventions
|
|
10
|
+
- Dependencies mapping
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from pydantic import BaseModel, Field
|
|
18
|
+
from rich.console import Console
|
|
19
|
+
from rich.panel import Panel
|
|
20
|
+
|
|
21
|
+
from steerdev_agent.api.client import SteerDevClient, get_project_id
|
|
22
|
+
|
|
23
|
+
console = Console()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TechStackInfo(BaseModel):
|
|
27
|
+
"""Tech stack detection result."""
|
|
28
|
+
|
|
29
|
+
framework: dict[str, Any] | None = None
|
|
30
|
+
language: dict[str, Any] | None = None
|
|
31
|
+
database: dict[str, Any] | None = None
|
|
32
|
+
auth: dict[str, Any] | None = None
|
|
33
|
+
styling: dict[str, Any] | None = None
|
|
34
|
+
testing: dict[str, Any] | None = None
|
|
35
|
+
package_manager: str | None = Field(default=None, alias="packageManager")
|
|
36
|
+
build_tool: str | None = Field(default=None, alias="buildTool")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class StructureInfo(BaseModel):
|
|
40
|
+
"""Directory structure analysis."""
|
|
41
|
+
|
|
42
|
+
type: str = "single"
|
|
43
|
+
directories: list[dict[str, Any]] = Field(default_factory=list)
|
|
44
|
+
entry_points: list[str] = Field(default_factory=list, alias="entryPoints")
|
|
45
|
+
config_files: list[str] = Field(default_factory=list, alias="configFiles")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class PatternsInfo(BaseModel):
|
|
49
|
+
"""Code patterns and conventions."""
|
|
50
|
+
|
|
51
|
+
naming: dict[str, str] = Field(default_factory=dict)
|
|
52
|
+
architecture: list[str] = Field(default_factory=list)
|
|
53
|
+
conventions: list[dict[str, Any]] = Field(default_factory=list)
|
|
54
|
+
abstractions: dict[str, list[str]] = Field(default_factory=dict)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CodebaseContext(BaseModel):
|
|
58
|
+
"""Codebase context model - full analysis result."""
|
|
59
|
+
|
|
60
|
+
project_id: str
|
|
61
|
+
repositories: list[dict[str, Any]] = Field(default_factory=list)
|
|
62
|
+
context: dict[str, Any] | None = None
|
|
63
|
+
status: str
|
|
64
|
+
last_analyzed_at: str | None = None
|
|
65
|
+
error: str | None = None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class ContextClient(SteerDevClient):
|
|
69
|
+
"""Client for project context operations.
|
|
70
|
+
|
|
71
|
+
Provides methods to retrieve and refresh project context
|
|
72
|
+
that helps AI agents understand the codebase.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def get_context(
|
|
76
|
+
self, project_id: str | None = None, format: str = "json"
|
|
77
|
+
) -> dict[str, Any] | str | None:
|
|
78
|
+
"""Get project context.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
project_id: Project ID. Falls back to STEERDEV_PROJECT_ID env var.
|
|
82
|
+
format: Response format - "json" or "markdown".
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Project context (dict for JSON, str for markdown) or None if not found.
|
|
86
|
+
"""
|
|
87
|
+
effective_project_id = project_id or get_project_id()
|
|
88
|
+
if not effective_project_id:
|
|
89
|
+
console.print(
|
|
90
|
+
"[red]Error: project_id is required. "
|
|
91
|
+
"Use --project-id or set STEERDEV_PROJECT_ID environment variable[/red]"
|
|
92
|
+
)
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
console.print(
|
|
96
|
+
f"Fetching context for project {effective_project_id} "
|
|
97
|
+
f"from {self.api_base}/context?project_id={effective_project_id}"
|
|
98
|
+
)
|
|
99
|
+
response = self.get(f"/context?project_id={effective_project_id}&format={format}")
|
|
100
|
+
|
|
101
|
+
if response.status_code == 404:
|
|
102
|
+
console.print(f"[yellow]No context found for project {effective_project_id}[/yellow]")
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
if response.status_code != 200:
|
|
106
|
+
console.print(f"[red]API Error: {response.status_code} - {response.text}[/red]")
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
# Return raw text for markdown format
|
|
110
|
+
if format == "markdown":
|
|
111
|
+
return response.text
|
|
112
|
+
|
|
113
|
+
return response.json()
|
|
114
|
+
|
|
115
|
+
def refresh_context(
|
|
116
|
+
self, project_id: str | None = None, force: bool = False
|
|
117
|
+
) -> dict[str, Any] | None:
|
|
118
|
+
"""Force refresh of project context.
|
|
119
|
+
|
|
120
|
+
Triggers a re-analysis of the project repositories to update cached context.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
project_id: Project ID. Falls back to STEERDEV_PROJECT_ID env var.
|
|
124
|
+
force: Force re-analysis even if context is up to date.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Analysis result dict or None on failure.
|
|
128
|
+
"""
|
|
129
|
+
effective_project_id = project_id or get_project_id()
|
|
130
|
+
if not effective_project_id:
|
|
131
|
+
console.print(
|
|
132
|
+
"[red]Error: project_id is required. "
|
|
133
|
+
"Use --project-id or set STEERDEV_PROJECT_ID environment variable[/red]"
|
|
134
|
+
)
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
console.print(
|
|
138
|
+
f"Refreshing context for project {effective_project_id} "
|
|
139
|
+
f"at {self.api_base}/context/analyze"
|
|
140
|
+
)
|
|
141
|
+
response = self.post(
|
|
142
|
+
"/context/analyze",
|
|
143
|
+
json={"project_id": effective_project_id, "force": force},
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if response.status_code == 404:
|
|
147
|
+
console.print(f"[red]Error: Project '{effective_project_id}' not found[/red]")
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
if response.status_code == 400:
|
|
151
|
+
error_data = response.json()
|
|
152
|
+
console.print(f"[red]Error: {error_data.get('error', 'Unknown error')}[/red]")
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
if response.status_code not in (200, 201, 202):
|
|
156
|
+
console.print(f"[red]API Error: {response.status_code} - {response.text}[/red]")
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
return response.json()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def display_context(context: dict[str, Any]) -> None:
|
|
163
|
+
"""Display project context in a formatted panel.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
context: Project context dict from the API.
|
|
167
|
+
"""
|
|
168
|
+
project_id = context.get("project_id", "Unknown")
|
|
169
|
+
status = context.get("status", "unknown")
|
|
170
|
+
ctx = context.get("context") or {}
|
|
171
|
+
repos = context.get("repositories", [])
|
|
172
|
+
|
|
173
|
+
# Build context info
|
|
174
|
+
info_lines = []
|
|
175
|
+
|
|
176
|
+
# Repositories
|
|
177
|
+
if repos:
|
|
178
|
+
info_lines.append("[bold cyan]Repositories:[/bold cyan]")
|
|
179
|
+
for repo in repos:
|
|
180
|
+
info_lines.append(f" • {repo.get('full_name', 'unknown')}")
|
|
181
|
+
info_lines.append("")
|
|
182
|
+
|
|
183
|
+
# Summary (markdown)
|
|
184
|
+
if ctx.get("summary"):
|
|
185
|
+
info_lines.append("[bold cyan]Summary:[/bold cyan]")
|
|
186
|
+
# Truncate long summaries
|
|
187
|
+
summary = ctx["summary"]
|
|
188
|
+
lines = summary.split("\n")[:15]
|
|
189
|
+
for line in lines:
|
|
190
|
+
info_lines.append(f" {line}")
|
|
191
|
+
if len(summary.split("\n")) > 15:
|
|
192
|
+
info_lines.append(" ...")
|
|
193
|
+
info_lines.append("")
|
|
194
|
+
|
|
195
|
+
# Tech stack
|
|
196
|
+
tech_stack = ctx.get("tech_stack", {})
|
|
197
|
+
if tech_stack:
|
|
198
|
+
info_lines.append("[bold cyan]Tech Stack:[/bold cyan]")
|
|
199
|
+
if tech_stack.get("framework"):
|
|
200
|
+
fw = tech_stack["framework"]
|
|
201
|
+
fw_str = fw.get("name", "")
|
|
202
|
+
if fw.get("version"):
|
|
203
|
+
fw_str += f" {fw['version']}"
|
|
204
|
+
if fw.get("router"):
|
|
205
|
+
fw_str += f" ({fw['router']})"
|
|
206
|
+
info_lines.append(f" • Framework: {fw_str}")
|
|
207
|
+
if tech_stack.get("language"):
|
|
208
|
+
info_lines.append(f" • Language: {tech_stack['language'].get('primary', 'unknown')}")
|
|
209
|
+
if tech_stack.get("database"):
|
|
210
|
+
db = tech_stack["database"]
|
|
211
|
+
db_str = db.get("name", "")
|
|
212
|
+
if db.get("client"):
|
|
213
|
+
db_str += f" ({db['client']})"
|
|
214
|
+
info_lines.append(f" • Database: {db_str}")
|
|
215
|
+
if tech_stack.get("auth"):
|
|
216
|
+
info_lines.append(f" • Auth: {tech_stack['auth'].get('provider', 'unknown')}")
|
|
217
|
+
if tech_stack.get("styling"):
|
|
218
|
+
style = tech_stack["styling"]
|
|
219
|
+
style_str = style.get("framework", "")
|
|
220
|
+
if style.get("ui"):
|
|
221
|
+
style_str += f" + {style['ui']}"
|
|
222
|
+
info_lines.append(f" • Styling: {style_str}")
|
|
223
|
+
if tech_stack.get("testing"):
|
|
224
|
+
test = tech_stack["testing"]
|
|
225
|
+
test_parts = []
|
|
226
|
+
if test.get("framework"):
|
|
227
|
+
test_parts.append(test["framework"])
|
|
228
|
+
if test.get("e2e"):
|
|
229
|
+
test_parts.append(test["e2e"])
|
|
230
|
+
if test_parts:
|
|
231
|
+
info_lines.append(f" • Testing: {' + '.join(test_parts)}")
|
|
232
|
+
if tech_stack.get("packageManager"):
|
|
233
|
+
info_lines.append(f" • Package Manager: {tech_stack['packageManager']}")
|
|
234
|
+
info_lines.append("")
|
|
235
|
+
|
|
236
|
+
# Patterns
|
|
237
|
+
patterns = ctx.get("patterns", {})
|
|
238
|
+
if patterns:
|
|
239
|
+
arch = patterns.get("architecture", [])
|
|
240
|
+
if arch:
|
|
241
|
+
info_lines.append("[bold cyan]Architecture:[/bold cyan]")
|
|
242
|
+
for a in arch[:5]:
|
|
243
|
+
info_lines.append(f" • {a}")
|
|
244
|
+
info_lines.append("")
|
|
245
|
+
|
|
246
|
+
conventions = patterns.get("conventions", [])
|
|
247
|
+
if conventions:
|
|
248
|
+
info_lines.append("[bold cyan]Conventions:[/bold cyan]")
|
|
249
|
+
for conv in conventions[:5]:
|
|
250
|
+
info_lines.append(f" • {conv.get('pattern', '')}")
|
|
251
|
+
info_lines.append("")
|
|
252
|
+
|
|
253
|
+
# Structure (directories)
|
|
254
|
+
structure = ctx.get("structure", {})
|
|
255
|
+
if structure:
|
|
256
|
+
dirs = structure.get("directories", [])
|
|
257
|
+
if dirs:
|
|
258
|
+
info_lines.append("[bold cyan]Key Directories:[/bold cyan]")
|
|
259
|
+
for d in dirs[:6]:
|
|
260
|
+
path = d.get("path", "")
|
|
261
|
+
purpose = d.get("purpose", "")
|
|
262
|
+
info_lines.append(f" • {path}: {purpose}")
|
|
263
|
+
if len(dirs) > 6:
|
|
264
|
+
info_lines.append(f" ... and {len(dirs) - 6} more")
|
|
265
|
+
info_lines.append("")
|
|
266
|
+
|
|
267
|
+
# Status and metadata
|
|
268
|
+
status_color = {
|
|
269
|
+
"completed": "green",
|
|
270
|
+
"analyzing": "blue",
|
|
271
|
+
"pending": "yellow",
|
|
272
|
+
"failed": "red",
|
|
273
|
+
}.get(status, "white")
|
|
274
|
+
info_lines.append(f"[dim]Status: [{status_color}]{status}[/{status_color}][/dim]")
|
|
275
|
+
|
|
276
|
+
if context.get("last_analyzed_at"):
|
|
277
|
+
info_lines.append(f"[dim]Last analyzed: {context['last_analyzed_at']}[/dim]")
|
|
278
|
+
|
|
279
|
+
if context.get("error"):
|
|
280
|
+
info_lines.append(f"[red]Error: {context['error']}[/red]")
|
|
281
|
+
|
|
282
|
+
console.print(
|
|
283
|
+
Panel(
|
|
284
|
+
"\n".join(info_lines),
|
|
285
|
+
title=f"Codebase Context: {project_id[:8]}...",
|
|
286
|
+
border_style="blue",
|
|
287
|
+
)
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def display_context_refresh_success(project_id: str) -> None:
|
|
292
|
+
"""Display context refresh success message.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
project_id: Project ID that was refreshed.
|
|
296
|
+
"""
|
|
297
|
+
console.print(
|
|
298
|
+
Panel(
|
|
299
|
+
f"[bold green]Context refresh initiated[/bold green]\n\n"
|
|
300
|
+
f"Project: {project_id}\n\n"
|
|
301
|
+
"The context will be updated in the background. "
|
|
302
|
+
"Run `steerdev context get` to retrieve the updated context.",
|
|
303
|
+
title="Success",
|
|
304
|
+
border_style="green",
|
|
305
|
+
)
|
|
306
|
+
)
|