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,560 @@
|
|
|
1
|
+
"""Claude Code setup utilities for steerdev integration."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import random
|
|
5
|
+
import re
|
|
6
|
+
import uuid
|
|
7
|
+
from importlib import resources
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from loguru import logger
|
|
11
|
+
|
|
12
|
+
from steerdev_agent.config.platform import PlatformConfig
|
|
13
|
+
|
|
14
|
+
# Word lists for generating agent names
|
|
15
|
+
ADJECTIVES = [
|
|
16
|
+
"bright",
|
|
17
|
+
"swift",
|
|
18
|
+
"calm",
|
|
19
|
+
"bold",
|
|
20
|
+
"keen",
|
|
21
|
+
"wise",
|
|
22
|
+
"warm",
|
|
23
|
+
"cool",
|
|
24
|
+
"kind",
|
|
25
|
+
"fair",
|
|
26
|
+
"brave",
|
|
27
|
+
"quick",
|
|
28
|
+
"sharp",
|
|
29
|
+
"clear",
|
|
30
|
+
"pure",
|
|
31
|
+
"firm",
|
|
32
|
+
"soft",
|
|
33
|
+
"deep",
|
|
34
|
+
"high",
|
|
35
|
+
"vast",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
ANIMALS = [
|
|
39
|
+
"falcon",
|
|
40
|
+
"eagle",
|
|
41
|
+
"wolf",
|
|
42
|
+
"bear",
|
|
43
|
+
"lion",
|
|
44
|
+
"tiger",
|
|
45
|
+
"hawk",
|
|
46
|
+
"owl",
|
|
47
|
+
"fox",
|
|
48
|
+
"deer",
|
|
49
|
+
"swan",
|
|
50
|
+
"crane",
|
|
51
|
+
"raven",
|
|
52
|
+
"otter",
|
|
53
|
+
"lynx",
|
|
54
|
+
"heron",
|
|
55
|
+
"finch",
|
|
56
|
+
"badger",
|
|
57
|
+
"panda",
|
|
58
|
+
"koala",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def generate_agent_name() -> str:
|
|
63
|
+
"""Generate a random agent name in adjective-animal-id format.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
A name like "bright-falcon-abc123"
|
|
67
|
+
"""
|
|
68
|
+
adjective = random.choice(ADJECTIVES)
|
|
69
|
+
animal = random.choice(ANIMALS)
|
|
70
|
+
short_id = uuid.uuid4().hex[:6]
|
|
71
|
+
return f"{adjective}-{animal}-{short_id}"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ClaudeSetupError(Exception):
|
|
75
|
+
"""Error during Claude Code setup."""
|
|
76
|
+
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ClaudeSetup:
|
|
81
|
+
"""Handles Claude Code configuration setup for a project.
|
|
82
|
+
|
|
83
|
+
This class manages the creation and configuration of Claude Code
|
|
84
|
+
settings, skills, and CLAUDE.md sections for steerdev.com integration.
|
|
85
|
+
|
|
86
|
+
Note: Scripts and hooks are now part of the steerdev CLI,
|
|
87
|
+
so setup_scripts() and setup_hooks() methods have been removed.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def __init__(
|
|
91
|
+
self,
|
|
92
|
+
project_dir: Path | None = None,
|
|
93
|
+
install_target: str = "project",
|
|
94
|
+
) -> None:
|
|
95
|
+
"""Initialize the setup utility.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
project_dir: Target project directory. Uses current directory if not specified.
|
|
99
|
+
install_target: Where to install Claude configs - "project" for .claude/ in
|
|
100
|
+
project_dir, or "user" for ~/.claude/ (user-level).
|
|
101
|
+
"""
|
|
102
|
+
self.project_dir = project_dir or Path.cwd()
|
|
103
|
+
self.install_target = install_target
|
|
104
|
+
|
|
105
|
+
if install_target == "user":
|
|
106
|
+
self.claude_dir = Path.home() / ".claude"
|
|
107
|
+
else:
|
|
108
|
+
self.claude_dir = self.project_dir / ".claude"
|
|
109
|
+
|
|
110
|
+
self.skills_dir = self.claude_dir / "skills"
|
|
111
|
+
|
|
112
|
+
def _get_template_path(self) -> Path:
|
|
113
|
+
"""Get the path to the templates directory."""
|
|
114
|
+
# Use importlib.resources to locate templates relative to this module
|
|
115
|
+
with resources.as_file(
|
|
116
|
+
resources.files("steerdev_agent.setup") / "templates"
|
|
117
|
+
) as templates_path:
|
|
118
|
+
return templates_path
|
|
119
|
+
|
|
120
|
+
def _read_template(self, template_name: str) -> str:
|
|
121
|
+
"""Read a template file from the package.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
template_name: Name of the template file relative to templates directory.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Template content as string.
|
|
128
|
+
"""
|
|
129
|
+
template_ref = resources.files("steerdev_agent.setup").joinpath("templates", template_name)
|
|
130
|
+
return template_ref.read_text()
|
|
131
|
+
|
|
132
|
+
def setup_settings(self, force: bool = False, include_hooks: bool = True) -> Path:
|
|
133
|
+
"""Create or update .claude/settings.json.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
force: Overwrite existing settings if True.
|
|
137
|
+
include_hooks: Include hooks configuration in settings.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Path to the created settings file.
|
|
141
|
+
"""
|
|
142
|
+
settings_path = self.claude_dir / "settings.json"
|
|
143
|
+
new_settings = json.loads(self._read_template("settings.json"))
|
|
144
|
+
|
|
145
|
+
# Remove hooks from new_settings if not including them
|
|
146
|
+
if not include_hooks:
|
|
147
|
+
new_settings.pop("hooks", None)
|
|
148
|
+
|
|
149
|
+
if settings_path.exists() and not force:
|
|
150
|
+
# Merge with existing settings
|
|
151
|
+
existing = json.loads(settings_path.read_text())
|
|
152
|
+
|
|
153
|
+
# Merge permissions.allow lists
|
|
154
|
+
if "permissions" not in existing:
|
|
155
|
+
existing["permissions"] = {}
|
|
156
|
+
if "allow" not in existing["permissions"]:
|
|
157
|
+
existing["permissions"]["allow"] = []
|
|
158
|
+
|
|
159
|
+
for rule in new_settings.get("permissions", {}).get("allow", []):
|
|
160
|
+
if rule not in existing["permissions"]["allow"]:
|
|
161
|
+
existing["permissions"]["allow"].append(rule)
|
|
162
|
+
|
|
163
|
+
# Merge hooks configuration if include_hooks=True
|
|
164
|
+
if include_hooks and "hooks" in new_settings:
|
|
165
|
+
if "hooks" not in existing:
|
|
166
|
+
existing["hooks"] = {}
|
|
167
|
+
for hook_event, hook_configs in new_settings["hooks"].items():
|
|
168
|
+
if hook_event not in existing["hooks"]:
|
|
169
|
+
existing["hooks"][hook_event] = hook_configs
|
|
170
|
+
|
|
171
|
+
settings_path.write_text(json.dumps(existing, indent=2) + "\n")
|
|
172
|
+
logger.info(f"Updated existing settings: {settings_path}")
|
|
173
|
+
else:
|
|
174
|
+
# Create new settings
|
|
175
|
+
self.claude_dir.mkdir(parents=True, exist_ok=True)
|
|
176
|
+
settings_path.write_text(json.dumps(new_settings, indent=2) + "\n")
|
|
177
|
+
logger.info(f"Created settings: {settings_path}")
|
|
178
|
+
|
|
179
|
+
return settings_path
|
|
180
|
+
|
|
181
|
+
def setup_skills(self, force: bool = False) -> Path:
|
|
182
|
+
"""Create all skills (task-management, specs, context, activity, git-workflow).
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
force: Overwrite existing skills if True.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Path to the skills directory.
|
|
189
|
+
"""
|
|
190
|
+
# List of all skills to install
|
|
191
|
+
skills = [
|
|
192
|
+
"task-management",
|
|
193
|
+
"specs-management",
|
|
194
|
+
"context",
|
|
195
|
+
"activity",
|
|
196
|
+
"git-workflow",
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
for skill_name in skills:
|
|
200
|
+
skill_dir = self.skills_dir / skill_name
|
|
201
|
+
skill_file = skill_dir / "SKILL.md"
|
|
202
|
+
|
|
203
|
+
if skill_file.exists() and not force:
|
|
204
|
+
logger.info(f"Skill already exists: {skill_file}")
|
|
205
|
+
continue
|
|
206
|
+
|
|
207
|
+
skill_dir.mkdir(parents=True, exist_ok=True)
|
|
208
|
+
skill_content = self._read_template(f"skills/{skill_name}/SKILL.md")
|
|
209
|
+
skill_file.write_text(skill_content)
|
|
210
|
+
logger.info(f"Created skill: {skill_file}")
|
|
211
|
+
|
|
212
|
+
return self.skills_dir
|
|
213
|
+
|
|
214
|
+
def setup_steerdev_config(self, force: bool = False) -> tuple[Path, bool]:
|
|
215
|
+
"""Create steerdev.yaml configuration file.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
force: Overwrite existing config if True.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
Tuple of (path to config file, True if file was created).
|
|
222
|
+
"""
|
|
223
|
+
config_path = self.project_dir / "steerdev.yaml"
|
|
224
|
+
|
|
225
|
+
if config_path.exists() and not force:
|
|
226
|
+
logger.info(f"Config already exists: {config_path}")
|
|
227
|
+
return config_path, False
|
|
228
|
+
|
|
229
|
+
config_content = self._read_template("steerdev.yaml")
|
|
230
|
+
config_path.write_text(config_content)
|
|
231
|
+
logger.info(f"Created config: {config_path}")
|
|
232
|
+
return config_path, True
|
|
233
|
+
|
|
234
|
+
def setup_env(
|
|
235
|
+
self,
|
|
236
|
+
project_id: str | None = None,
|
|
237
|
+
api_key: str | None = None,
|
|
238
|
+
agent_name: str | None = None,
|
|
239
|
+
) -> tuple[Path, bool]:
|
|
240
|
+
"""Create or update .env file with steerdev environment variables.
|
|
241
|
+
|
|
242
|
+
If .env doesn't exist, creates a new one with steerdev variables.
|
|
243
|
+
If .env exists, appends steerdev variables if not already present,
|
|
244
|
+
or updates existing values if provided.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
project_id: SteerDev project ID to configure.
|
|
248
|
+
api_key: SteerDev API key to configure.
|
|
249
|
+
agent_name: Agent name for identification. If not provided, generates one.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
Tuple of (path to .env file, True if file was created/updated).
|
|
253
|
+
"""
|
|
254
|
+
env_path = self.project_dir / ".env"
|
|
255
|
+
|
|
256
|
+
# Generate agent name if not provided
|
|
257
|
+
if agent_name is None:
|
|
258
|
+
agent_name = generate_agent_name()
|
|
259
|
+
|
|
260
|
+
# Build the env vars content based on provided values
|
|
261
|
+
api_key_line = (
|
|
262
|
+
f"STEERDEV_API_KEY={api_key}" if api_key else "# STEERDEV_API_KEY=your-api-key-here"
|
|
263
|
+
)
|
|
264
|
+
project_id_line = (
|
|
265
|
+
f"STEERDEV_PROJECT_ID={project_id}"
|
|
266
|
+
if project_id
|
|
267
|
+
else "# STEERDEV_PROJECT_ID=your-project-id-here"
|
|
268
|
+
)
|
|
269
|
+
agent_name_line = f"STEERDEV_AGENT_NAME={agent_name}"
|
|
270
|
+
|
|
271
|
+
steerdev_env_vars = f"""
|
|
272
|
+
# SteerDev Configuration
|
|
273
|
+
{api_key_line}
|
|
274
|
+
{project_id_line}
|
|
275
|
+
{agent_name_line}
|
|
276
|
+
"""
|
|
277
|
+
|
|
278
|
+
if env_path.exists():
|
|
279
|
+
existing_content = env_path.read_text()
|
|
280
|
+
updated = False
|
|
281
|
+
|
|
282
|
+
# Check if we need to update existing values or add new ones
|
|
283
|
+
has_api_key = "STEERDEV_API_KEY" in existing_content
|
|
284
|
+
has_project_id = "STEERDEV_PROJECT_ID" in existing_content
|
|
285
|
+
has_agent_name = "STEERDEV_AGENT_NAME" in existing_content
|
|
286
|
+
|
|
287
|
+
if has_api_key and has_project_id and has_agent_name:
|
|
288
|
+
# All exist - update if new values provided
|
|
289
|
+
lines = existing_content.split("\n")
|
|
290
|
+
new_lines = []
|
|
291
|
+
|
|
292
|
+
for line in lines:
|
|
293
|
+
if line.startswith("STEERDEV_API_KEY=") or line.startswith(
|
|
294
|
+
"# STEERDEV_API_KEY="
|
|
295
|
+
):
|
|
296
|
+
if api_key:
|
|
297
|
+
new_lines.append(f"STEERDEV_API_KEY={api_key}")
|
|
298
|
+
updated = True
|
|
299
|
+
else:
|
|
300
|
+
new_lines.append(line)
|
|
301
|
+
elif line.startswith("STEERDEV_PROJECT_ID=") or line.startswith(
|
|
302
|
+
"# STEERDEV_PROJECT_ID="
|
|
303
|
+
):
|
|
304
|
+
if project_id:
|
|
305
|
+
new_lines.append(f"STEERDEV_PROJECT_ID={project_id}")
|
|
306
|
+
updated = True
|
|
307
|
+
else:
|
|
308
|
+
new_lines.append(line)
|
|
309
|
+
else:
|
|
310
|
+
new_lines.append(line)
|
|
311
|
+
|
|
312
|
+
if updated:
|
|
313
|
+
env_path.write_text("\n".join(new_lines))
|
|
314
|
+
logger.info(f"Updated steerdev variables in: {env_path}")
|
|
315
|
+
else:
|
|
316
|
+
logger.info(".env already contains steerdev variables")
|
|
317
|
+
return env_path, updated
|
|
318
|
+
else:
|
|
319
|
+
# Append missing steerdev variables
|
|
320
|
+
missing_vars = []
|
|
321
|
+
if not has_api_key:
|
|
322
|
+
missing_vars.append(api_key_line)
|
|
323
|
+
if not has_project_id:
|
|
324
|
+
missing_vars.append(project_id_line)
|
|
325
|
+
if not has_agent_name:
|
|
326
|
+
missing_vars.append(agent_name_line)
|
|
327
|
+
|
|
328
|
+
if missing_vars:
|
|
329
|
+
with env_path.open("a") as f:
|
|
330
|
+
if not existing_content.endswith("\n"):
|
|
331
|
+
f.write("\n")
|
|
332
|
+
if not has_api_key and not has_project_id and not has_agent_name:
|
|
333
|
+
f.write("\n# SteerDev Configuration\n")
|
|
334
|
+
f.write("\n".join(missing_vars) + "\n")
|
|
335
|
+
logger.info(f"Added steerdev variables to: {env_path}")
|
|
336
|
+
return env_path, bool(missing_vars)
|
|
337
|
+
else:
|
|
338
|
+
# Create new .env file
|
|
339
|
+
env_path.write_text(steerdev_env_vars.lstrip())
|
|
340
|
+
logger.info(f"Created .env file: {env_path}")
|
|
341
|
+
return env_path, True
|
|
342
|
+
|
|
343
|
+
def update_claude_md(self, force: bool = False) -> bool:
|
|
344
|
+
"""Append task management section to CLAUDE.md.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
force: Add section even if it appears to exist.
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
True if CLAUDE.md was updated, False otherwise.
|
|
351
|
+
"""
|
|
352
|
+
claude_md = self.project_dir / "CLAUDE.md"
|
|
353
|
+
|
|
354
|
+
# Read the section template
|
|
355
|
+
section_content = self._read_template("claude_md_section.md")
|
|
356
|
+
section_marker = "## Task Management"
|
|
357
|
+
|
|
358
|
+
if claude_md.exists():
|
|
359
|
+
existing_content = claude_md.read_text()
|
|
360
|
+
|
|
361
|
+
# Check if section already exists
|
|
362
|
+
if section_marker in existing_content and not force:
|
|
363
|
+
logger.info("Task management section already exists in CLAUDE.md")
|
|
364
|
+
return False
|
|
365
|
+
|
|
366
|
+
# Append section
|
|
367
|
+
with claude_md.open("a") as f:
|
|
368
|
+
f.write("\n\n" + section_content)
|
|
369
|
+
logger.info(f"Appended task management section to: {claude_md}")
|
|
370
|
+
else:
|
|
371
|
+
# Create new CLAUDE.md with just the section
|
|
372
|
+
claude_md.write_text(section_content)
|
|
373
|
+
logger.info(f"Created CLAUDE.md with task management section: {claude_md}")
|
|
374
|
+
|
|
375
|
+
return True
|
|
376
|
+
|
|
377
|
+
def setup_all(self, force: bool = False) -> dict[str, Path | bool]:
|
|
378
|
+
"""Run complete setup for Claude Code integration.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
force: Overwrite existing files if True.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
Dictionary with paths/status for each component.
|
|
385
|
+
"""
|
|
386
|
+
results: dict[str, Path | bool] = {}
|
|
387
|
+
|
|
388
|
+
# Create .claude directory
|
|
389
|
+
self.claude_dir.mkdir(parents=True, exist_ok=True)
|
|
390
|
+
|
|
391
|
+
# Setup each component
|
|
392
|
+
results["settings"] = self.setup_settings(force=force)
|
|
393
|
+
results["skills"] = self.setup_skills(force=force)
|
|
394
|
+
results["claude_md"] = self.update_claude_md(force=force)
|
|
395
|
+
|
|
396
|
+
return results
|
|
397
|
+
|
|
398
|
+
# ========================================================================
|
|
399
|
+
# Platform Config Sync
|
|
400
|
+
# ========================================================================
|
|
401
|
+
|
|
402
|
+
_SHARED_START = "<!-- STEERDEV:SHARED:START -->"
|
|
403
|
+
_SHARED_END = "<!-- STEERDEV:SHARED:END -->"
|
|
404
|
+
_ENV_START = "# Shared Variables (Synced)"
|
|
405
|
+
_ENV_END = "# End Shared Variables"
|
|
406
|
+
|
|
407
|
+
def apply_platform_configs(self, config: PlatformConfig) -> dict[str, int]:
|
|
408
|
+
"""Apply synced platform configs to the local project.
|
|
409
|
+
|
|
410
|
+
Writes system prompts to CLAUDE.md, skills to .claude/skills/,
|
|
411
|
+
MCP servers to .claude/settings.json, and env vars to .env.
|
|
412
|
+
|
|
413
|
+
Args:
|
|
414
|
+
config: Platform configuration from sync API.
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
Dictionary with counts of applied configs per type.
|
|
418
|
+
"""
|
|
419
|
+
counts: dict[str, int] = {
|
|
420
|
+
"system_prompts": 0,
|
|
421
|
+
"skills": 0,
|
|
422
|
+
"mcps": 0,
|
|
423
|
+
"env_vars": 0,
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if config.system_prompts:
|
|
427
|
+
counts["system_prompts"] = self._apply_system_prompts(config)
|
|
428
|
+
|
|
429
|
+
if config.skills:
|
|
430
|
+
counts["skills"] = self._apply_skills(config)
|
|
431
|
+
|
|
432
|
+
if config.mcps:
|
|
433
|
+
counts["mcps"] = self._merge_mcp_configs(config)
|
|
434
|
+
|
|
435
|
+
if config.env_vars:
|
|
436
|
+
counts["env_vars"] = self._merge_env_vars(config)
|
|
437
|
+
|
|
438
|
+
return counts
|
|
439
|
+
|
|
440
|
+
def _apply_system_prompts(self, config: PlatformConfig) -> int:
|
|
441
|
+
"""Append system prompts to CLAUDE.md between markers.
|
|
442
|
+
|
|
443
|
+
Replaces any existing shared section, or appends if not present.
|
|
444
|
+
"""
|
|
445
|
+
claude_md = self.project_dir / "CLAUDE.md"
|
|
446
|
+
combined = config.get_combined_system_prompt()
|
|
447
|
+
if not combined:
|
|
448
|
+
return 0
|
|
449
|
+
|
|
450
|
+
shared_block = (
|
|
451
|
+
f"{self._SHARED_START}\n## Shared Agent Instructions\n\n{combined}\n{self._SHARED_END}"
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
if claude_md.exists():
|
|
455
|
+
content = claude_md.read_text()
|
|
456
|
+
# Replace existing shared block
|
|
457
|
+
pattern = re.compile(
|
|
458
|
+
re.escape(self._SHARED_START) + r".*?" + re.escape(self._SHARED_END),
|
|
459
|
+
re.DOTALL,
|
|
460
|
+
)
|
|
461
|
+
if pattern.search(content):
|
|
462
|
+
content = pattern.sub(shared_block, content)
|
|
463
|
+
else:
|
|
464
|
+
content = content.rstrip() + "\n\n" + shared_block + "\n"
|
|
465
|
+
claude_md.write_text(content)
|
|
466
|
+
else:
|
|
467
|
+
claude_md.write_text(shared_block + "\n")
|
|
468
|
+
|
|
469
|
+
logger.info(f"Applied {len(config.system_prompts)} system prompt(s) to CLAUDE.md")
|
|
470
|
+
return len(config.system_prompts)
|
|
471
|
+
|
|
472
|
+
def _apply_skills(self, config: PlatformConfig) -> int:
|
|
473
|
+
"""Write skills to .claude/skills/{slug}/SKILL.md."""
|
|
474
|
+
count = 0
|
|
475
|
+
for skill in config.skills:
|
|
476
|
+
# Slugify the skill name
|
|
477
|
+
slug = re.sub(r"[^a-z0-9]+", "-", skill.name.lower()).strip("-")
|
|
478
|
+
skill_dir = self.skills_dir / slug
|
|
479
|
+
skill_dir.mkdir(parents=True, exist_ok=True)
|
|
480
|
+
|
|
481
|
+
skill_file = skill_dir / "SKILL.md"
|
|
482
|
+
instructions = skill.content.get("instructions", "")
|
|
483
|
+
if not instructions:
|
|
484
|
+
continue
|
|
485
|
+
|
|
486
|
+
# Build skill content
|
|
487
|
+
lines = [f"# {skill.name}"]
|
|
488
|
+
if skill.description:
|
|
489
|
+
lines.append(f"\n{skill.description}")
|
|
490
|
+
lines.append(f"\n{instructions}")
|
|
491
|
+
|
|
492
|
+
skill_file.write_text("\n".join(lines) + "\n")
|
|
493
|
+
count += 1
|
|
494
|
+
logger.info(f"Wrote skill: {skill_file}")
|
|
495
|
+
|
|
496
|
+
return count
|
|
497
|
+
|
|
498
|
+
def _merge_mcp_configs(self, config: PlatformConfig) -> int:
|
|
499
|
+
"""Merge MCP server configs into .claude/settings.json."""
|
|
500
|
+
settings_path = self.claude_dir / "settings.json"
|
|
501
|
+
self.claude_dir.mkdir(parents=True, exist_ok=True)
|
|
502
|
+
|
|
503
|
+
settings = json.loads(settings_path.read_text()) if settings_path.exists() else {}
|
|
504
|
+
|
|
505
|
+
if "mcpServers" not in settings:
|
|
506
|
+
settings["mcpServers"] = {}
|
|
507
|
+
|
|
508
|
+
count = 0
|
|
509
|
+
for mcp in config.mcps:
|
|
510
|
+
server_name = re.sub(r"[^a-z0-9]+", "-", mcp.name.lower()).strip("-")
|
|
511
|
+
server_config: dict = {}
|
|
512
|
+
|
|
513
|
+
if "command" in mcp.content:
|
|
514
|
+
server_config["command"] = mcp.content["command"]
|
|
515
|
+
if "args" in mcp.content:
|
|
516
|
+
server_config["args"] = mcp.content["args"]
|
|
517
|
+
if mcp.content.get("env"):
|
|
518
|
+
server_config["env"] = mcp.content["env"]
|
|
519
|
+
|
|
520
|
+
if server_config:
|
|
521
|
+
settings["mcpServers"][server_name] = server_config
|
|
522
|
+
count += 1
|
|
523
|
+
|
|
524
|
+
settings_path.write_text(json.dumps(settings, indent=2) + "\n")
|
|
525
|
+
logger.info(f"Merged {count} MCP server(s) into settings.json")
|
|
526
|
+
return count
|
|
527
|
+
|
|
528
|
+
def _merge_env_vars(self, config: PlatformConfig) -> int:
|
|
529
|
+
"""Merge shared env vars into .env between markers."""
|
|
530
|
+
env_path = self.project_dir / ".env"
|
|
531
|
+
combined = config.get_combined_env_vars()
|
|
532
|
+
if not combined:
|
|
533
|
+
return 0
|
|
534
|
+
|
|
535
|
+
# Build the env block
|
|
536
|
+
env_lines = [self._ENV_START]
|
|
537
|
+
for key, value in sorted(combined.items()):
|
|
538
|
+
env_lines.append(f"{key}={value}")
|
|
539
|
+
env_lines.append(self._ENV_END)
|
|
540
|
+
env_block = "\n".join(env_lines)
|
|
541
|
+
|
|
542
|
+
if env_path.exists():
|
|
543
|
+
content = env_path.read_text()
|
|
544
|
+
# Replace existing shared block
|
|
545
|
+
pattern = re.compile(
|
|
546
|
+
re.escape(self._ENV_START) + r".*?" + re.escape(self._ENV_END),
|
|
547
|
+
re.DOTALL,
|
|
548
|
+
)
|
|
549
|
+
if pattern.search(content):
|
|
550
|
+
content = pattern.sub(env_block, content)
|
|
551
|
+
else:
|
|
552
|
+
if not content.endswith("\n"):
|
|
553
|
+
content += "\n"
|
|
554
|
+
content += "\n" + env_block + "\n"
|
|
555
|
+
env_path.write_text(content)
|
|
556
|
+
else:
|
|
557
|
+
env_path.write_text(env_block + "\n")
|
|
558
|
+
|
|
559
|
+
logger.info(f"Merged {len(combined)} shared env var(s) into .env")
|
|
560
|
+
return len(combined)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
## Task Management
|
|
2
|
+
|
|
3
|
+
This project uses steerdev.com for task management. **You must respect the task lifecycle.**
|
|
4
|
+
|
|
5
|
+
### Task Lifecycle
|
|
6
|
+
|
|
7
|
+
Tasks follow this Linear-compatible lifecycle:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
BACKLOG → TODO → IN-PROGRESS → DONE
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
| Status | Description | Your Action |
|
|
14
|
+
|--------|-------------|-------------|
|
|
15
|
+
| `backlog` | Task is queued, not ready for work | Do not work on these tasks |
|
|
16
|
+
| `todo` | Ready to be worked on | Pick up task and move to `in-progress` |
|
|
17
|
+
| `in-progress` | Currently being worked on | Implement the task |
|
|
18
|
+
| `done` | Task completed | No action needed |
|
|
19
|
+
| `cancelled` | Task was cancelled | No action needed |
|
|
20
|
+
|
|
21
|
+
### Available Commands
|
|
22
|
+
|
|
23
|
+
| Command | Description |
|
|
24
|
+
|---------|-------------|
|
|
25
|
+
| `steerdev tasks next` | Get the next task to work on |
|
|
26
|
+
| `steerdev tasks list` | List all tasks with optional filters |
|
|
27
|
+
| `steerdev tasks get TASK_ID` | Get details of a specific task |
|
|
28
|
+
| `steerdev tasks update TASK_ID --status STATUS` | Update task status |
|
|
29
|
+
| `steerdev tasks create --title "..." --prompt "..."` | Create a new task |
|
|
30
|
+
|
|
31
|
+
### Workflow
|
|
32
|
+
|
|
33
|
+
1. Run `steerdev tasks next` to get the next task
|
|
34
|
+
2. Update task to `in-progress` before starting work
|
|
35
|
+
3. **Create a feature branch** using the task ID: `git checkout -b task/<task-id-short>`
|
|
36
|
+
4. Implement the task
|
|
37
|
+
5. **Commit your changes** with clear, descriptive commit messages
|
|
38
|
+
6. **Create a pull request** using GitHub CLI: `gh pr create`
|
|
39
|
+
7. Update task to `done` with a result summary and PR URL: `--result "Summary of work done. PR: <url>"`
|
|
40
|
+
|
|
41
|
+
### Git Workflow (Required)
|
|
42
|
+
|
|
43
|
+
**You MUST use Git and GitHub CLI to manage your work.** Every task implementation must result in a pull request.
|
|
44
|
+
|
|
45
|
+
#### Branch Naming
|
|
46
|
+
|
|
47
|
+
Create a branch before starting any implementation work:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Use task ID for branch name (use first 8 chars of UUID)
|
|
51
|
+
git checkout -b task/<short-task-id>
|
|
52
|
+
|
|
53
|
+
# Example
|
|
54
|
+
git checkout -b task/abc12345
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### Committing Changes
|
|
58
|
+
|
|
59
|
+
Make atomic commits with clear messages:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Stage and commit your changes
|
|
63
|
+
git add .
|
|
64
|
+
git commit -m "feat: implement user authentication
|
|
65
|
+
|
|
66
|
+
- Add JWT token generation
|
|
67
|
+
- Create login/logout endpoints
|
|
68
|
+
- Add auth middleware"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Follow conventional commit format:
|
|
72
|
+
- `feat:` - New features
|
|
73
|
+
- `fix:` - Bug fixes
|
|
74
|
+
- `refactor:` - Code refactoring
|
|
75
|
+
- `docs:` - Documentation changes
|
|
76
|
+
- `test:` - Adding or updating tests
|
|
77
|
+
- `chore:` - Maintenance tasks
|
|
78
|
+
|
|
79
|
+
#### Creating Pull Requests
|
|
80
|
+
|
|
81
|
+
**Always create a PR when completing a task:**
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Push branch and create PR
|
|
85
|
+
git push -u origin HEAD
|
|
86
|
+
gh pr create --title "Task: <title>" --body "## Summary
|
|
87
|
+
|
|
88
|
+
<description of changes>
|
|
89
|
+
|
|
90
|
+
## Task Reference
|
|
91
|
+
Task ID: <task-id>
|
|
92
|
+
|
|
93
|
+
## Changes Made
|
|
94
|
+
- Change 1
|
|
95
|
+
- Change 2
|
|
96
|
+
|
|
97
|
+
## Testing
|
|
98
|
+
- How to test the changes"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### Complete Workflow Example
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# 1. Get next task
|
|
105
|
+
steerdev tasks next
|
|
106
|
+
|
|
107
|
+
# 2. Mark as in-progress
|
|
108
|
+
steerdev tasks update abc123-... --status in-progress
|
|
109
|
+
|
|
110
|
+
# 3. Create feature branch
|
|
111
|
+
git checkout -b task/abc12345
|
|
112
|
+
|
|
113
|
+
# 4. Implement the feature (make commits as you go)
|
|
114
|
+
git add .
|
|
115
|
+
git commit -m "feat: add new endpoint for user data"
|
|
116
|
+
|
|
117
|
+
# 5. Push and create PR
|
|
118
|
+
git push -u origin HEAD
|
|
119
|
+
gh pr create --title "Add user data endpoint" --body "Implementation for task abc123..."
|
|
120
|
+
|
|
121
|
+
# 6. Update task to done with PR link
|
|
122
|
+
steerdev tasks update abc123-... --status done --result "Implemented user data endpoint. PR: https://github.com/org/repo/pull/42"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**IMPORTANT:** Never leave work uncommitted. Always create a PR before marking a task as `done`.
|
|
126
|
+
|
|
127
|
+
### Status Values
|
|
128
|
+
|
|
129
|
+
- `backlog` - Task queued, not ready for work
|
|
130
|
+
- `todo` - Ready to be worked on
|
|
131
|
+
- `in-progress` - Currently working on task
|
|
132
|
+
- `done` - Task completed
|
|
133
|
+
- `cancelled` - Task was cancelled
|
|
134
|
+
|
|
135
|
+
### Environment Variables
|
|
136
|
+
|
|
137
|
+
- `STEERDEV_API_KEY` - Required for API authentication
|
|
138
|
+
- `STEERDEV_PROJECT_ID` - Optional, default project ID
|
|
139
|
+
- `STEERDEV_AGENT_ID` - Optional, agent identifier for tracking
|
|
140
|
+
- `STEERDEV_API_ENDPOINT` - Optional, defaults to https://steerdev.com/api/v1
|