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,5 @@
1
+ """Setup module for configuring Claude Code integration."""
2
+
3
+ from steerdev_agent.setup.claude_setup import ClaudeSetup
4
+
5
+ __all__ = ["ClaudeSetup"]
@@ -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