steerdev 1.0.58__tar.gz → 1.0.60__tar.gz
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-1.0.58 → steerdev-1.0.60}/PKG-INFO +1 -1
- {steerdev-1.0.58 → steerdev-1.0.60}/pyproject.toml +1 -1
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/agent_loop.py +66 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/api/commands.py +1 -1
- steerdev-1.0.60/src/steerdev_agent/api/merger.py +71 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/cli.py +27 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/config/models.py +20 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/prompt/builder.py +15 -3
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/prompt/templates.py +10 -3
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/runner.py +66 -24
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/setup/templates/skills/steerdev-merge-into-canal-skill/SKILL.md +6 -5
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/setup/templates/skills/steerdev-single-task-merge-skill/SKILL.md +10 -40
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/setup/templates/skills/steerdev-wave-tasks-merge-skill/SKILL.md +10 -22
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/setup/templates/steerdev.yaml +5 -0
- steerdev-1.0.60/src/steerdev_agent/workspace/preparation.py +243 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_config.py +29 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_conflict_mitigation.py +2 -0
- steerdev-1.0.60/tests/test_preparation.py +358 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/.github/workflows/pre-commit.yml +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/.github/workflows/publish.yml +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/.gitignore +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/.pre-commit-config.yaml +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/AGENTS.md +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/CLAUDE.md +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/README.md +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/scripts/pre-commit-version-bump.sh +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/snapshots/steerdev-agent-v1/Dockerfile +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/snapshots/steerdev-agent-v1/start-agent.sh +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/__init__.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/api/__init__.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/api/activity.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/api/agents.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/api/canals.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/api/client.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/api/configs.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/api/context.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/api/events.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/api/hooks.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/api/implementation_plan.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/api/messages.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/api/prd.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/api/reports.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/api/runs.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/api/sessions.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/api/specs.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/api/tasks.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/api/workflow_runs.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/api/workflows.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/config/__init__.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/config/platform.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/config/settings.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/evidence.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/evidence_assets.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/executor/__init__.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/executor/base.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/executor/claude.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/executor/stream.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/handlers/__init__.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/handlers/prd.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/integration.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/prompt/__init__.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/prompt/workflow_template.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/py.typed +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/retry.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/setup/__init__.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/setup/claude_setup.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/setup/repo_setup.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/setup/templates/ci/canal-integration.yml +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/setup/templates/claude_md_section.md +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/setup/templates/settings.json +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/setup/templates/skills/steerdev-activity-skill/SKILL.md +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/setup/templates/skills/steerdev-canal-workflow-skill/SKILL.md +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/setup/templates/skills/steerdev-context-skill/SKILL.md +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/setup/templates/skills/steerdev-git-workflow-skill/SKILL.md +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/setup/templates/skills/steerdev-progress-logging-skill/SKILL.md +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/setup/templates/skills/steerdev-specs-management-skill/SKILL.md +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/setup/templates/skills/steerdev-task-management-skill/SKILL.md +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/setup/templates/worktrunk.config.toml +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/update_check.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/version.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/workflow/__init__.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/workflow/context.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/workflow/executor.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/workflow/memory.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/workspace/__init__.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/workspace/project_manager.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/workspace/tool_detection.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/src/steerdev_agent/worktree.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/__init__.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_agent_loop.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_agent_loop_extended.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_agents_api.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_api_client.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_claude_executor.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_claude_setup.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_client_methods.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_commands_api.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_config_extended.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_context_search.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_executor.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_platform_config.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_prompt.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_reports_client.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_retry.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_runner_merge_modes.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_runner_worktrees.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_stream_parser.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_tasks.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_version.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_workflow_context.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_workflow_memory.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_workflow_prompt_template.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_workflow_runs_api.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_workspace.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_workspace_extended.py +0 -0
- {steerdev-1.0.58 → steerdev-1.0.60}/tests/test_worktree.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: steerdev
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.60
|
|
4
4
|
Summary: Backend task runner for steerdev.com - orchestrates CLI coding agents with activity reporting
|
|
5
5
|
Project-URL: Homepage, https://github.com/pentoai/steerdev-agent
|
|
6
6
|
Project-URL: Repository, https://github.com/pentoai/steerdev-agent
|
|
@@ -36,6 +36,7 @@ from steerdev_agent.api.sessions import SessionCreateRequest, SessionsClient
|
|
|
36
36
|
from steerdev_agent.api.tasks import TasksClient
|
|
37
37
|
from steerdev_agent.config.models import (
|
|
38
38
|
AgentLoopConfig,
|
|
39
|
+
BranchConfig,
|
|
39
40
|
EvidenceConfig,
|
|
40
41
|
ExecutorConfig,
|
|
41
42
|
RetryConfig,
|
|
@@ -77,6 +78,7 @@ class CommandExecutor:
|
|
|
77
78
|
_workflow_id: str | None
|
|
78
79
|
_enable_waves: bool
|
|
79
80
|
_enable_canals: bool
|
|
81
|
+
_branch_config: BranchConfig
|
|
80
82
|
_worktree_config: WorktreeConfig
|
|
81
83
|
_evidence_config: EvidenceConfig
|
|
82
84
|
_agent_loop_config: AgentLoopConfig
|
|
@@ -126,6 +128,8 @@ class CommandExecutor:
|
|
|
126
128
|
await self._execute_task_command(command, project_id, working_directory)
|
|
127
129
|
elif command.command_type == "prompt":
|
|
128
130
|
await self._execute_prompt_command(command, project_id, working_directory)
|
|
131
|
+
elif command.command_type == "merger":
|
|
132
|
+
await self._execute_merger_command(command, project_id, working_directory)
|
|
129
133
|
else:
|
|
130
134
|
await self._commands_client.mark_failed(
|
|
131
135
|
command.id, error=f"Unknown command type: {command.command_type}"
|
|
@@ -179,6 +183,7 @@ class CommandExecutor:
|
|
|
179
183
|
evidence_config=self._evidence_config,
|
|
180
184
|
executor_config=self._executor_config,
|
|
181
185
|
force_workflow_id=None,
|
|
186
|
+
branch_config=self._branch_config,
|
|
182
187
|
shutdown_event=self._shutdown_event,
|
|
183
188
|
agent_id=self._agent_id,
|
|
184
189
|
)
|
|
@@ -403,6 +408,59 @@ class CommandExecutor:
|
|
|
403
408
|
finally:
|
|
404
409
|
await events_client.close()
|
|
405
410
|
|
|
411
|
+
async def _execute_merger_command(
|
|
412
|
+
self,
|
|
413
|
+
command: CommandResponse,
|
|
414
|
+
project_id: str,
|
|
415
|
+
working_directory: Path,
|
|
416
|
+
) -> None:
|
|
417
|
+
"""Execute a merger command - triggers merger run and polls for completion."""
|
|
418
|
+
from steerdev_agent.api.merger import MergerClient
|
|
419
|
+
|
|
420
|
+
assert self._commands_client is not None
|
|
421
|
+
|
|
422
|
+
merger_run_id = command.metadata.get("mergerRunId")
|
|
423
|
+
if not merger_run_id:
|
|
424
|
+
raise ValueError("merger command missing mergerRunId in metadata")
|
|
425
|
+
|
|
426
|
+
merger_client = MergerClient(api_key=self._api_key)
|
|
427
|
+
|
|
428
|
+
logger.info(f"Starting merger run: {merger_run_id}")
|
|
429
|
+
|
|
430
|
+
# Mark command as executing
|
|
431
|
+
await self._commands_client.mark_executing(command.id)
|
|
432
|
+
|
|
433
|
+
# Trigger execution
|
|
434
|
+
result = merger_client.execute_merger_run(merger_run_id)
|
|
435
|
+
if not result:
|
|
436
|
+
raise RuntimeError(f"Failed to trigger merger run {merger_run_id}")
|
|
437
|
+
|
|
438
|
+
# Poll for completion
|
|
439
|
+
max_polls = 60 # 30 minutes at 30s intervals
|
|
440
|
+
for _ in range(max_polls):
|
|
441
|
+
run = merger_client.get_merger_run(merger_run_id)
|
|
442
|
+
if not run:
|
|
443
|
+
raise RuntimeError("Failed to get merger run status")
|
|
444
|
+
|
|
445
|
+
if run.status in ("completed", "failed", "partial"):
|
|
446
|
+
logger.info(f"Merger run {merger_run_id} finished: {run.status}")
|
|
447
|
+
if run.status == "failed":
|
|
448
|
+
raise RuntimeError(f"Merger run failed: {run.error_message}")
|
|
449
|
+
await self._commands_client.mark_completed(
|
|
450
|
+
command.id,
|
|
451
|
+
result=f"Merger run {merger_run_id} {run.status}",
|
|
452
|
+
)
|
|
453
|
+
self._commands_succeeded += 1
|
|
454
|
+
return
|
|
455
|
+
|
|
456
|
+
if self._shutdown_event.is_set():
|
|
457
|
+
logger.info("Shutdown during merger polling, aborting")
|
|
458
|
+
raise RuntimeError("Shutdown requested during merger run")
|
|
459
|
+
|
|
460
|
+
await asyncio.sleep(30)
|
|
461
|
+
|
|
462
|
+
raise RuntimeError(f"Merger run {merger_run_id} timed out")
|
|
463
|
+
|
|
406
464
|
|
|
407
465
|
# ---------------------------------------------------------------------------
|
|
408
466
|
# Project-scoped agent (original)
|
|
@@ -443,6 +501,7 @@ class AgentLoop(CommandExecutor):
|
|
|
443
501
|
worktree_config: WorktreeConfig | None = None,
|
|
444
502
|
evidence_config: EvidenceConfig | None = None,
|
|
445
503
|
retry_config: RetryConfig | None = None,
|
|
504
|
+
branch_config: BranchConfig | None = None,
|
|
446
505
|
) -> None:
|
|
447
506
|
self.project_id = project_id
|
|
448
507
|
self.agent_name = agent_name
|
|
@@ -454,6 +513,7 @@ class AgentLoop(CommandExecutor):
|
|
|
454
513
|
self._agent_loop_config = agent_loop_config or AgentLoopConfig()
|
|
455
514
|
self._executor_config = executor_config or ExecutorConfig()
|
|
456
515
|
self._retry_config = retry_config or RetryConfig()
|
|
516
|
+
self._branch_config = branch_config or BranchConfig()
|
|
457
517
|
self._workflow_id = force_workflow_id
|
|
458
518
|
self._enable_waves = enable_waves
|
|
459
519
|
self._enable_canals = enable_canals
|
|
@@ -810,6 +870,7 @@ class WorkspaceAgentLoop(CommandExecutor):
|
|
|
810
870
|
worktree_config: WorktreeConfig | None = None,
|
|
811
871
|
evidence_config: EvidenceConfig | None = None,
|
|
812
872
|
retry_config: RetryConfig | None = None,
|
|
873
|
+
branch_config: BranchConfig | None = None,
|
|
813
874
|
) -> None:
|
|
814
875
|
self.workspace_path = Path(workspace_path)
|
|
815
876
|
self.agent_name = agent_name
|
|
@@ -820,6 +881,7 @@ class WorkspaceAgentLoop(CommandExecutor):
|
|
|
820
881
|
self._agent_loop_config = agent_loop_config or AgentLoopConfig()
|
|
821
882
|
self._executor_config = executor_config or ExecutorConfig()
|
|
822
883
|
self._retry_config = retry_config or RetryConfig()
|
|
884
|
+
self._branch_config = branch_config or BranchConfig()
|
|
823
885
|
self._workspace_config = workspace_config or WorkspaceConfig()
|
|
824
886
|
self._workflow_id = force_workflow_id
|
|
825
887
|
self._enable_waves = enable_waves
|
|
@@ -1232,6 +1294,7 @@ async def run_agent_loop(
|
|
|
1232
1294
|
worktree_config: WorktreeConfig | None = None,
|
|
1233
1295
|
evidence_config: EvidenceConfig | None = None,
|
|
1234
1296
|
retry_config: RetryConfig | None = None,
|
|
1297
|
+
branch_config: BranchConfig | None = None,
|
|
1235
1298
|
) -> None:
|
|
1236
1299
|
"""Run the project-scoped agent loop.
|
|
1237
1300
|
|
|
@@ -1253,6 +1316,7 @@ async def run_agent_loop(
|
|
|
1253
1316
|
worktree_config=worktree_config,
|
|
1254
1317
|
evidence_config=evidence_config,
|
|
1255
1318
|
retry_config=retry_config,
|
|
1319
|
+
branch_config=branch_config,
|
|
1256
1320
|
)
|
|
1257
1321
|
await agent.start()
|
|
1258
1322
|
|
|
@@ -1273,6 +1337,7 @@ async def run_workspace_agent_loop(
|
|
|
1273
1337
|
worktree_config: WorktreeConfig | None = None,
|
|
1274
1338
|
evidence_config: EvidenceConfig | None = None,
|
|
1275
1339
|
retry_config: RetryConfig | None = None,
|
|
1340
|
+
branch_config: BranchConfig | None = None,
|
|
1276
1341
|
) -> None:
|
|
1277
1342
|
"""Run the workspace (multi-project) agent loop.
|
|
1278
1343
|
|
|
@@ -1294,5 +1359,6 @@ async def run_workspace_agent_loop(
|
|
|
1294
1359
|
worktree_config=worktree_config,
|
|
1295
1360
|
evidence_config=evidence_config,
|
|
1296
1361
|
retry_config=retry_config,
|
|
1362
|
+
branch_config=branch_config,
|
|
1297
1363
|
)
|
|
1298
1364
|
await agent.start()
|
|
@@ -13,7 +13,7 @@ from steerdev_agent.api.client import get_agent_id, get_api_endpoint, get_api_ke
|
|
|
13
13
|
|
|
14
14
|
console = Console()
|
|
15
15
|
|
|
16
|
-
CommandType = Literal["task", "prompt", "control"]
|
|
16
|
+
CommandType = Literal["task", "prompt", "control", "merger"]
|
|
17
17
|
CommandStatus = Literal[
|
|
18
18
|
"pending", "claimed", "executing", "completed", "failed", "cancelled", "expired"
|
|
19
19
|
]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Merger API client for merger agent operations."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from steerdev_agent.api.client import SteerDevClient
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MergerRunResponse(BaseModel):
|
|
11
|
+
id: str
|
|
12
|
+
status: str
|
|
13
|
+
wave_ids: list[str] = []
|
|
14
|
+
task_ids: list[str] = []
|
|
15
|
+
pr_ids: list[str] = []
|
|
16
|
+
evidence_report_ids: list[str] = []
|
|
17
|
+
summary: str | None = None
|
|
18
|
+
merged_branches: list[str] | None = None
|
|
19
|
+
error_message: str | None = None
|
|
20
|
+
started_at: str | None = None
|
|
21
|
+
completed_at: str | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class GateResponse(BaseModel):
|
|
25
|
+
id: str
|
|
26
|
+
merger_run_id: str
|
|
27
|
+
canal_id: str
|
|
28
|
+
gate_number: int
|
|
29
|
+
status: str
|
|
30
|
+
started_at: str | None = None
|
|
31
|
+
completed_at: str | None = None
|
|
32
|
+
details: dict[str, Any] | None = None
|
|
33
|
+
error_message: str | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class MergerClient(SteerDevClient):
|
|
37
|
+
"""Client for merger agent API operations."""
|
|
38
|
+
|
|
39
|
+
def execute_merger_run(self, merger_run_id: str) -> dict[str, Any] | None:
|
|
40
|
+
"""Trigger execution of a full merger run."""
|
|
41
|
+
response = self.post(f"/merger-runs/{merger_run_id}/execute")
|
|
42
|
+
if response and response.status_code == 200:
|
|
43
|
+
return response.json()
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
def get_merger_run(self, merger_run_id: str) -> MergerRunResponse | None:
|
|
47
|
+
"""Get merger run status."""
|
|
48
|
+
response = self.get(f"/merger-runs/{merger_run_id}")
|
|
49
|
+
if response and response.status_code == 200:
|
|
50
|
+
return MergerRunResponse(**response.json())
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
def get_merger_gates(self, merger_run_id: str) -> list[GateResponse]:
|
|
54
|
+
"""Get gate statuses for a merger run."""
|
|
55
|
+
response = self.get(f"/merger-runs/{merger_run_id}/gates")
|
|
56
|
+
if response and response.status_code == 200:
|
|
57
|
+
data = response.json()
|
|
58
|
+
return [GateResponse(**g) for g in data.get("gates", [])]
|
|
59
|
+
return []
|
|
60
|
+
|
|
61
|
+
def execute_gate(
|
|
62
|
+
self, canal_id: str, gate_number: int, merger_run_id: str, merge_id: str | None = None
|
|
63
|
+
) -> dict[str, Any] | None:
|
|
64
|
+
"""Trigger execution of a specific gate."""
|
|
65
|
+
payload: dict[str, Any] = {"merger_run_id": merger_run_id}
|
|
66
|
+
if merge_id:
|
|
67
|
+
payload["merge_id"] = merge_id
|
|
68
|
+
response = self.post(f"/canals/{canal_id}/gates/{gate_number}", json=payload)
|
|
69
|
+
if response and response.status_code == 200:
|
|
70
|
+
return response.json()
|
|
71
|
+
return None
|
|
@@ -1719,6 +1719,13 @@ def run(
|
|
|
1719
1719
|
help="Delay between retries in seconds (default: from config or 3600)",
|
|
1720
1720
|
),
|
|
1721
1721
|
] = None,
|
|
1722
|
+
default_branch: Annotated[
|
|
1723
|
+
str | None,
|
|
1724
|
+
typer.Option(
|
|
1725
|
+
"--default-branch",
|
|
1726
|
+
help="Default branch for sync/rebase/PR targets (default: from config or 'main')",
|
|
1727
|
+
),
|
|
1728
|
+
] = None,
|
|
1722
1729
|
) -> None:
|
|
1723
1730
|
"""Run the agent for a project!
|
|
1724
1731
|
|
|
@@ -1820,6 +1827,11 @@ def run(
|
|
|
1820
1827
|
if retry_delay is not None:
|
|
1821
1828
|
retry_config.delay_seconds = retry_delay
|
|
1822
1829
|
|
|
1830
|
+
# Resolve branch config: CLI > config > default
|
|
1831
|
+
resolved_branch_config = config.branches.model_copy()
|
|
1832
|
+
if default_branch is not None:
|
|
1833
|
+
resolved_branch_config.default_branch = default_branch
|
|
1834
|
+
|
|
1823
1835
|
try:
|
|
1824
1836
|
result = asyncio.run(
|
|
1825
1837
|
run_agent(
|
|
@@ -1838,6 +1850,7 @@ def run(
|
|
|
1838
1850
|
force_workflow_id=resolved_workflow_id,
|
|
1839
1851
|
dry_run=dry_run,
|
|
1840
1852
|
retry_config=retry_config,
|
|
1853
|
+
branch_config=resolved_branch_config,
|
|
1841
1854
|
)
|
|
1842
1855
|
)
|
|
1843
1856
|
|
|
@@ -1998,6 +2011,13 @@ def agent(
|
|
|
1998
2011
|
help="Submit evidence reports after task completion (default: from config or enabled)",
|
|
1999
2012
|
),
|
|
2000
2013
|
] = None,
|
|
2014
|
+
default_branch: Annotated[
|
|
2015
|
+
str | None,
|
|
2016
|
+
typer.Option(
|
|
2017
|
+
"--default-branch",
|
|
2018
|
+
help="Default branch for sync/rebase/PR targets (default: from config or 'main')",
|
|
2019
|
+
),
|
|
2020
|
+
] = None,
|
|
2001
2021
|
) -> None:
|
|
2002
2022
|
"""Run the agent in persistent mode.
|
|
2003
2023
|
|
|
@@ -2075,6 +2095,11 @@ def agent(
|
|
|
2075
2095
|
if retry_delay is not None:
|
|
2076
2096
|
retry_config.delay_seconds = retry_delay
|
|
2077
2097
|
|
|
2098
|
+
# Resolve branch config: CLI > config > default
|
|
2099
|
+
resolved_branch_config = config.branches.model_copy()
|
|
2100
|
+
if default_branch is not None:
|
|
2101
|
+
resolved_branch_config.default_branch = default_branch
|
|
2102
|
+
|
|
2078
2103
|
# Resolve workspace path: CLI > config
|
|
2079
2104
|
resolved_workspace = workspace or config.workspace.workspace_dir
|
|
2080
2105
|
|
|
@@ -2099,6 +2124,7 @@ def agent(
|
|
|
2099
2124
|
worktree_config=resolved_worktree_config,
|
|
2100
2125
|
evidence_config=resolved_evidence_config,
|
|
2101
2126
|
retry_config=retry_config,
|
|
2127
|
+
branch_config=resolved_branch_config,
|
|
2102
2128
|
)
|
|
2103
2129
|
)
|
|
2104
2130
|
except KeyboardInterrupt:
|
|
@@ -2140,6 +2166,7 @@ def agent(
|
|
|
2140
2166
|
worktree_config=resolved_worktree_config,
|
|
2141
2167
|
evidence_config=resolved_evidence_config,
|
|
2142
2168
|
retry_config=retry_config,
|
|
2169
|
+
branch_config=resolved_branch_config,
|
|
2143
2170
|
)
|
|
2144
2171
|
)
|
|
2145
2172
|
except KeyboardInterrupt:
|
|
@@ -246,6 +246,22 @@ class CanalConfig(BaseModel):
|
|
|
246
246
|
]
|
|
247
247
|
|
|
248
248
|
|
|
249
|
+
class BranchConfig(BaseModel):
|
|
250
|
+
"""Branch configuration for git operations.
|
|
251
|
+
|
|
252
|
+
Controls which branch is used for pre-flight sync, rebase targets, and PR base.
|
|
253
|
+
Users set default_branch to whatever their project uses — main, dev, develop, etc.
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
default_branch: Annotated[
|
|
257
|
+
str,
|
|
258
|
+
Field(
|
|
259
|
+
default="main",
|
|
260
|
+
description="Default branch for sync, rebase, and PR targets (e.g. main, dev, develop)",
|
|
261
|
+
),
|
|
262
|
+
]
|
|
263
|
+
|
|
264
|
+
|
|
249
265
|
class EvidenceConfig(BaseModel):
|
|
250
266
|
"""Evidence report configuration.
|
|
251
267
|
|
|
@@ -341,6 +357,10 @@ class SteerDevConfig(BaseModel):
|
|
|
341
357
|
CanalConfig,
|
|
342
358
|
Field(default_factory=CanalConfig, description="Canal workflow configuration"),
|
|
343
359
|
]
|
|
360
|
+
branches: Annotated[
|
|
361
|
+
BranchConfig,
|
|
362
|
+
Field(default_factory=BranchConfig, description="Branch configuration for git operations"),
|
|
363
|
+
]
|
|
344
364
|
workspace: Annotated[
|
|
345
365
|
WorkspaceConfig,
|
|
346
366
|
Field(default_factory=WorkspaceConfig, description="Workspace agent configuration"),
|
|
@@ -68,6 +68,10 @@ class PromptContext(BaseModel):
|
|
|
68
68
|
resume_message: str | None = Field(
|
|
69
69
|
default=None, description="Message for resume (if resuming session)"
|
|
70
70
|
)
|
|
71
|
+
default_branch: str | None = Field(
|
|
72
|
+
default=None,
|
|
73
|
+
description="Default branch for git operations (e.g. main, dev). Falls back to 'main'.",
|
|
74
|
+
)
|
|
71
75
|
|
|
72
76
|
|
|
73
77
|
class PromptBuilder:
|
|
@@ -105,7 +109,7 @@ class PromptBuilder:
|
|
|
105
109
|
|
|
106
110
|
# Build components
|
|
107
111
|
project_info = self._build_project_info(context.project)
|
|
108
|
-
task_info = self._build_task_info(context.task, context.project)
|
|
112
|
+
task_info = self._build_task_info(context.task, context.project, context.default_branch)
|
|
109
113
|
instructions = self._build_instructions(context)
|
|
110
114
|
|
|
111
115
|
# Add workflow phase if present
|
|
@@ -157,12 +161,14 @@ class PromptBuilder:
|
|
|
157
161
|
self,
|
|
158
162
|
task: TaskContext | None,
|
|
159
163
|
project: ProjectContext | None,
|
|
164
|
+
default_branch: str | None = None,
|
|
160
165
|
) -> str:
|
|
161
166
|
"""Build task information section.
|
|
162
167
|
|
|
163
168
|
Args:
|
|
164
169
|
task: Task context or None.
|
|
165
170
|
project: Project context for working directory fallback.
|
|
171
|
+
default_branch: Default branch for git operations (falls back to "main").
|
|
166
172
|
|
|
167
173
|
Returns:
|
|
168
174
|
Formatted task information.
|
|
@@ -204,8 +210,14 @@ class PromptBuilder:
|
|
|
204
210
|
wave_section += f"\n\n**Tasks in This Wave:**\n{task.wave.wave_tasks_summary}"
|
|
205
211
|
task_info = f"{task_info}{wave_section}"
|
|
206
212
|
|
|
207
|
-
# Add git pre-flight sync instructions
|
|
208
|
-
|
|
213
|
+
# Add git branch configuration and pre-flight sync instructions
|
|
214
|
+
branch = default_branch or "main"
|
|
215
|
+
task_info = (
|
|
216
|
+
f"{task_info}\n\n{self.templates.GIT_BRANCH_CONFIG.format(default_branch=branch)}"
|
|
217
|
+
)
|
|
218
|
+
task_info = (
|
|
219
|
+
f"{task_info}\n{self.templates.GIT_PREFLIGHT_SYNC.format(default_branch=branch)}"
|
|
220
|
+
)
|
|
209
221
|
|
|
210
222
|
return task_info
|
|
211
223
|
|
|
@@ -246,13 +246,20 @@ Create tasks that can be completed by a single developer in a reasonable time (1
|
|
|
246
246
|
Larger work should be broken into multiple tasks.
|
|
247
247
|
"""
|
|
248
248
|
|
|
249
|
-
# Git pre-flight sync instructions injected into task prompts
|
|
249
|
+
# Git pre-flight sync instructions injected into task prompts.
|
|
250
|
+
# Parameterized with {default_branch} — call .format(default_branch=...) before use.
|
|
250
251
|
GIT_PREFLIGHT_SYNC = """## Git Pre-flight Sync
|
|
251
252
|
Before starting this task, ensure you are on an up-to-date default branch:
|
|
252
253
|
1. `git stash push -m "steerdev-preflight" 2>/dev/null || true`
|
|
253
|
-
2. `git checkout
|
|
254
|
-
3. If ff-only fails: `git fetch origin && git reset --hard origin/
|
|
254
|
+
2. `git checkout {default_branch} && git pull origin {default_branch} --ff-only`
|
|
255
|
+
3. If ff-only fails: `git fetch origin && git reset --hard origin/{default_branch}`
|
|
255
256
|
Only skip this if you are continuing mid-wave on an existing wave branch.
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
# Git branch configuration note injected into task prompts
|
|
260
|
+
GIT_BRANCH_CONFIG = """## Git Branch Configuration
|
|
261
|
+
- Default branch: `{default_branch}`
|
|
262
|
+
Use this branch for all fetch, rebase, and merge operations.
|
|
256
263
|
"""
|
|
257
264
|
|
|
258
265
|
# Phase context instructions appended to phase prompts
|
|
@@ -19,7 +19,13 @@ from steerdev_agent.api.events import EventsClient
|
|
|
19
19
|
from steerdev_agent.api.runs import RunCreateRequest, RunsClient
|
|
20
20
|
from steerdev_agent.api.sessions import SessionCreateRequest, SessionsClient
|
|
21
21
|
from steerdev_agent.api.tasks import TasksClient
|
|
22
|
-
from steerdev_agent.config.models import
|
|
22
|
+
from steerdev_agent.config.models import (
|
|
23
|
+
BranchConfig,
|
|
24
|
+
EvidenceConfig,
|
|
25
|
+
ExecutorConfig,
|
|
26
|
+
RetryConfig,
|
|
27
|
+
WorktreeConfig,
|
|
28
|
+
)
|
|
23
29
|
from steerdev_agent.executor import ExecutorFactory
|
|
24
30
|
from steerdev_agent.executor.base import AgentExecutor, EventType, StreamEvent
|
|
25
31
|
from steerdev_agent.executor.claude import ClaudeExecutorError
|
|
@@ -277,6 +283,7 @@ class Runner:
|
|
|
277
283
|
force_workflow_id: str | None = None,
|
|
278
284
|
dry_run: bool = False,
|
|
279
285
|
retry_config: RetryConfig | None = None,
|
|
286
|
+
branch_config: BranchConfig | None = None,
|
|
280
287
|
shutdown_event: asyncio.Event | None = None,
|
|
281
288
|
agent_id: str | None = None,
|
|
282
289
|
) -> None:
|
|
@@ -314,6 +321,7 @@ class Runner:
|
|
|
314
321
|
self.workflow_id = force_workflow_id
|
|
315
322
|
self.dry_run = dry_run
|
|
316
323
|
self._retry_config = retry_config or RetryConfig()
|
|
324
|
+
self._branch_config = branch_config or BranchConfig()
|
|
317
325
|
self._shutdown_event = shutdown_event
|
|
318
326
|
|
|
319
327
|
# Executor configuration
|
|
@@ -723,6 +731,7 @@ class Runner:
|
|
|
723
731
|
linear_identifier=task.get("linear_identifier"),
|
|
724
732
|
wave=wave_context,
|
|
725
733
|
),
|
|
734
|
+
default_branch=self._branch_config.default_branch,
|
|
726
735
|
)
|
|
727
736
|
prompt = self._prompt_builder.build(context)
|
|
728
737
|
|
|
@@ -753,25 +762,45 @@ class Runner:
|
|
|
753
762
|
if self._sessions_client and not self.dry_run:
|
|
754
763
|
await self._sessions_client.mark_running(session_id)
|
|
755
764
|
|
|
756
|
-
#
|
|
765
|
+
# Prepare workspace (git fetch, checkout, branch creation)
|
|
757
766
|
effective_working_dir = self.working_directory
|
|
758
767
|
worktree_branch: str | None = None
|
|
759
768
|
legacy_worktree_name: str | None = None
|
|
760
769
|
|
|
761
|
-
if self._worktree_config.enabled:
|
|
762
|
-
|
|
763
|
-
|
|
770
|
+
if self._worktree_config.enabled and self._worktree_config.provider == "claude":
|
|
771
|
+
# Legacy: pass --worktree to Claude CLI (bypasses preparation)
|
|
772
|
+
legacy_worktree_name = compute_worktree_name(task_id, wave_context)
|
|
773
|
+
console.print(f"[dim]Using legacy worktree: {legacy_worktree_name}[/dim]")
|
|
774
|
+
else:
|
|
775
|
+
from steerdev_agent.workspace.preparation import WorkspacePreparation
|
|
764
776
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
777
|
+
preparation = WorkspacePreparation(
|
|
778
|
+
working_directory=self.working_directory,
|
|
779
|
+
worktree_config=self._worktree_config,
|
|
780
|
+
branch_config=self._branch_config,
|
|
781
|
+
)
|
|
782
|
+
prep_result = await preparation.prepare(task, wave_context)
|
|
783
|
+
if not prep_result.success:
|
|
784
|
+
error_msg = f"Workspace preparation failed: {prep_result.error}"
|
|
785
|
+
logger.error(error_msg)
|
|
786
|
+
if self._sessions_client and not self.dry_run:
|
|
787
|
+
await self._sessions_client.mark_failed(
|
|
788
|
+
session_id, metadata={"error": error_msg}
|
|
789
|
+
)
|
|
790
|
+
return {
|
|
791
|
+
"success": False,
|
|
792
|
+
"error": error_msg,
|
|
793
|
+
"events_sent": self._events_sent,
|
|
794
|
+
}
|
|
795
|
+
effective_working_dir = prep_result.working_directory
|
|
796
|
+
if prep_result.is_worktree:
|
|
797
|
+
worktree_branch = prep_result.branch_name
|
|
798
|
+
console.print(
|
|
799
|
+
f"[dim]Workspace ready: {effective_working_dir} "
|
|
800
|
+
f"(branch: {prep_result.branch_name})[/dim]"
|
|
801
|
+
)
|
|
802
|
+
for warning in prep_result.warnings:
|
|
803
|
+
console.print(f"[yellow]Warning: {warning}[/yellow]")
|
|
775
804
|
|
|
776
805
|
# Create executor using factory
|
|
777
806
|
self._executor = ExecutorFactory.create(
|
|
@@ -948,19 +977,29 @@ class Runner:
|
|
|
948
977
|
resolved_wf_id = workflow_id or self.workflow_id
|
|
949
978
|
console.print(f"[dim]Using workflow: {resolved_wf_id}[/dim]")
|
|
950
979
|
|
|
951
|
-
#
|
|
980
|
+
# Prepare workspace (git fetch, checkout, branch creation)
|
|
952
981
|
effective_working_dir = self.working_directory
|
|
953
982
|
worktree_branch: str | None = None
|
|
954
983
|
|
|
955
|
-
|
|
956
|
-
from steerdev_agent.worktree import WorktrunkClient, compute_branch_name
|
|
984
|
+
from steerdev_agent.workspace.preparation import WorkspacePreparation
|
|
957
985
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
986
|
+
preparation = WorkspacePreparation(
|
|
987
|
+
working_directory=self.working_directory,
|
|
988
|
+
worktree_config=self._worktree_config,
|
|
989
|
+
branch_config=self._branch_config,
|
|
990
|
+
)
|
|
991
|
+
prep_result = await preparation.prepare(task, wave_context)
|
|
992
|
+
if not prep_result.success:
|
|
993
|
+
error_msg = f"Workspace preparation failed: {prep_result.error}"
|
|
994
|
+
logger.error(error_msg)
|
|
995
|
+
return {"success": False, "error": error_msg}
|
|
996
|
+
effective_working_dir = prep_result.working_directory
|
|
997
|
+
if prep_result.is_worktree:
|
|
998
|
+
worktree_branch = prep_result.branch_name
|
|
999
|
+
console.print(
|
|
1000
|
+
f"[dim]Workspace ready: {effective_working_dir} "
|
|
1001
|
+
f"(branch: {prep_result.branch_name})[/dim]"
|
|
1002
|
+
)
|
|
964
1003
|
|
|
965
1004
|
# Build task context for workflow
|
|
966
1005
|
task_context = build_workflow_task_context(
|
|
@@ -1354,6 +1393,7 @@ async def run_agent(
|
|
|
1354
1393
|
force_workflow_id: str | None = None,
|
|
1355
1394
|
dry_run: bool = False,
|
|
1356
1395
|
retry_config: RetryConfig | None = None,
|
|
1396
|
+
branch_config: BranchConfig | None = None,
|
|
1357
1397
|
) -> dict[str, Any]:
|
|
1358
1398
|
"""Run the steerdev agent.
|
|
1359
1399
|
|
|
@@ -1377,6 +1417,7 @@ async def run_agent(
|
|
|
1377
1417
|
force_workflow_id: Workflow ID override for multi-phase execution.
|
|
1378
1418
|
dry_run: If True, print the command without executing it.
|
|
1379
1419
|
retry_config: Retry configuration for failed tasks.
|
|
1420
|
+
branch_config: Branch configuration for git operations.
|
|
1380
1421
|
|
|
1381
1422
|
Returns:
|
|
1382
1423
|
Run result metadata.
|
|
@@ -1399,6 +1440,7 @@ async def run_agent(
|
|
|
1399
1440
|
force_workflow_id=force_workflow_id,
|
|
1400
1441
|
dry_run=dry_run,
|
|
1401
1442
|
retry_config=retry_config,
|
|
1443
|
+
branch_config=branch_config,
|
|
1402
1444
|
)
|
|
1403
1445
|
|
|
1404
1446
|
# Install signal handler that kills the subprocess immediately on Ctrl+C
|
|
@@ -18,20 +18,20 @@ Use this skill when a workflow phase tells you to merge the **current task branc
|
|
|
18
18
|
|
|
19
19
|
## Pre-check
|
|
20
20
|
|
|
21
|
-
Before merging into a canal, verify your current branch has been pushed and has a PR. Do NOT switch branches or sync to
|
|
21
|
+
Before merging into a canal, verify your current branch has been pushed and has a PR. Do NOT switch branches or sync to the default branch — this skill operates on the current task/wave branch.
|
|
22
22
|
|
|
23
23
|
## Rebase Before Canal Merge
|
|
24
24
|
|
|
25
|
-
Before merging into the canal, ensure your branch is up to date
|
|
25
|
+
Before merging into the canal, ensure your branch is up to date. Refer to the **Git Branch Configuration** section in your prompt for the project's default branch name.
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
git fetch origin
|
|
29
|
-
git merge-tree --write-tree origin
|
|
28
|
+
git fetch origin <default-branch>
|
|
29
|
+
git merge-tree --write-tree origin/<default-branch> HEAD
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
- **If clean** (exit 0): Rebase and force-push with lease:
|
|
33
33
|
```bash
|
|
34
|
-
git rebase origin
|
|
34
|
+
git rebase origin/<default-branch>
|
|
35
35
|
git push --force-with-lease origin HEAD
|
|
36
36
|
```
|
|
37
37
|
- **If conflicts**: Attempt rebase. If it fails on files you did not modify, abort (`git rebase --abort`) and re-queue the task:
|
|
@@ -75,6 +75,7 @@ steerdev canal pr CANAL_ID
|
|
|
75
75
|
- Do not force-push to resolve canal conflicts
|
|
76
76
|
- Do not manually resolve canal merge conflicts outside the supported tooling
|
|
77
77
|
- If canal merge fails, report the blocker with task comments or status updates
|
|
78
|
+
- Replace `<default-branch>` in commands above with the actual branch name from the Git Branch Configuration section
|
|
78
79
|
|
|
79
80
|
## Example
|
|
80
81
|
|