steerdev 1.0.52__tar.gz → 1.0.56__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.52 → steerdev-1.0.56}/PKG-INFO +1 -1
- {steerdev-1.0.52 → steerdev-1.0.56}/pyproject.toml +1 -1
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/agent_loop.py +2 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/api/tasks.py +100 -2
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/cli.py +48 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/runner.py +61 -7
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/setup/templates/skills/steerdev-merge-into-canal-skill/SKILL.md +20 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/setup/templates/skills/steerdev-single-task-merge-skill/SKILL.md +26 -2
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/setup/templates/skills/steerdev-wave-tasks-merge-skill/SKILL.md +26 -2
- steerdev-1.0.56/tests/test_conflict_mitigation.py +622 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/.github/workflows/pre-commit.yml +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/.github/workflows/publish.yml +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/.gitignore +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/.pre-commit-config.yaml +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/AGENTS.md +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/CLAUDE.md +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/README.md +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/scripts/pre-commit-version-bump.sh +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/__init__.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/api/__init__.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/api/activity.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/api/agents.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/api/canals.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/api/client.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/api/commands.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/api/configs.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/api/context.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/api/events.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/api/hooks.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/api/implementation_plan.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/api/messages.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/api/prd.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/api/reports.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/api/runs.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/api/sessions.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/api/specs.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/api/workflow_runs.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/api/workflows.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/config/__init__.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/config/models.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/config/platform.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/config/settings.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/evidence.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/executor/__init__.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/executor/base.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/executor/claude.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/executor/stream.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/handlers/__init__.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/handlers/prd.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/integration.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/prompt/__init__.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/prompt/builder.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/prompt/templates.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/prompt/workflow_template.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/py.typed +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/retry.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/setup/__init__.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/setup/claude_setup.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/setup/repo_setup.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/setup/templates/ci/canal-integration.yml +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/setup/templates/claude_md_section.md +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/setup/templates/settings.json +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/setup/templates/skills/steerdev-activity-skill/SKILL.md +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/setup/templates/skills/steerdev-canal-workflow-skill/SKILL.md +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/setup/templates/skills/steerdev-context-skill/SKILL.md +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/setup/templates/skills/steerdev-git-workflow-skill/SKILL.md +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/setup/templates/skills/steerdev-progress-logging-skill/SKILL.md +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/setup/templates/skills/steerdev-specs-management-skill/SKILL.md +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/setup/templates/skills/steerdev-task-management-skill/SKILL.md +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/setup/templates/steerdev.yaml +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/setup/templates/worktrunk.config.toml +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/update_check.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/version.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/workflow/__init__.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/workflow/context.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/workflow/executor.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/workflow/memory.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/workspace/__init__.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/workspace/project_manager.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/workspace/tool_detection.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/src/steerdev_agent/worktree.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/__init__.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_agent_loop.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_agent_loop_extended.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_agents_api.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_api_client.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_claude_executor.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_claude_setup.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_client_methods.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_commands_api.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_config.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_config_extended.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_context_search.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_executor.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_platform_config.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_prompt.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_reports_client.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_retry.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_runner_merge_modes.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_runner_worktrees.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_stream_parser.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_tasks.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_version.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_workflow_context.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_workflow_memory.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_workflow_prompt_template.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_workspace.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/tests/test_workspace_extended.py +0 -0
- {steerdev-1.0.52 → steerdev-1.0.56}/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.56
|
|
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
|
|
@@ -85,6 +85,7 @@ class CommandExecutor:
|
|
|
85
85
|
_shutdown_event: asyncio.Event
|
|
86
86
|
_current_task: asyncio.Task[bool] | None
|
|
87
87
|
_signal_count: int
|
|
88
|
+
_agent_id: str | None
|
|
88
89
|
_commands_executed: int
|
|
89
90
|
_commands_succeeded: int
|
|
90
91
|
_commands_failed: int
|
|
@@ -179,6 +180,7 @@ class CommandExecutor:
|
|
|
179
180
|
executor_config=self._executor_config,
|
|
180
181
|
force_workflow_id=None,
|
|
181
182
|
shutdown_event=self._shutdown_event,
|
|
183
|
+
agent_id=self._agent_id,
|
|
182
184
|
)
|
|
183
185
|
|
|
184
186
|
# Fetch task details
|
|
@@ -65,7 +65,11 @@ def validate_status_transition(current: str, new: str) -> tuple[bool, str | None
|
|
|
65
65
|
class TasksClient(SteerDevClient):
|
|
66
66
|
"""Client for task management operations."""
|
|
67
67
|
|
|
68
|
-
def get_next_task(
|
|
68
|
+
def get_next_task(
|
|
69
|
+
self,
|
|
70
|
+
project_id: str | None = None,
|
|
71
|
+
agent_id: str | None = None,
|
|
72
|
+
) -> dict[str, Any] | None:
|
|
69
73
|
"""Fetch the next task to work on (single-task mode, no waves).
|
|
70
74
|
|
|
71
75
|
Returns the highest priority task in order:
|
|
@@ -75,6 +79,7 @@ class TasksClient(SteerDevClient):
|
|
|
75
79
|
|
|
76
80
|
Args:
|
|
77
81
|
project_id: Filter by project ID. Falls back to STEERDEV_PROJECT_ID env var.
|
|
82
|
+
agent_id: Agent ID for conflict-aware scheduling.
|
|
78
83
|
|
|
79
84
|
Returns:
|
|
80
85
|
Task data dict or None if no tasks available.
|
|
@@ -84,6 +89,8 @@ class TasksClient(SteerDevClient):
|
|
|
84
89
|
params: dict[str, str] = {"waves": "false"}
|
|
85
90
|
if effective_project_id:
|
|
86
91
|
params["project_id"] = effective_project_id
|
|
92
|
+
if agent_id:
|
|
93
|
+
params["agent_id"] = agent_id
|
|
87
94
|
|
|
88
95
|
console.print(f"Fetching next task from {self.api_base}/tasks/next")
|
|
89
96
|
response = self.get("/tasks/next", params=params if params else None)
|
|
@@ -97,12 +104,19 @@ class TasksClient(SteerDevClient):
|
|
|
97
104
|
|
|
98
105
|
return response.json()
|
|
99
106
|
|
|
100
|
-
def get_next_wave(
|
|
107
|
+
def get_next_wave(
|
|
108
|
+
self,
|
|
109
|
+
project_id: str | None = None,
|
|
110
|
+
agent_id: str | None = None,
|
|
111
|
+
) -> dict[str, Any] | None:
|
|
101
112
|
"""Fetch the next task with wave-aware scheduling.
|
|
102
113
|
|
|
103
114
|
Calls the API with waves=true. The API tries wave scheduling first,
|
|
104
115
|
then falls back to returning a plain task if no waves exist.
|
|
105
116
|
|
|
117
|
+
When agent_id is provided, the server uses conflict-aware scheduling
|
|
118
|
+
to avoid assigning tasks that overlap files with in-flight tasks.
|
|
119
|
+
|
|
106
120
|
Returns either:
|
|
107
121
|
- A wave response dict (has "wave" and "context" keys) with full wave context
|
|
108
122
|
- A plain task dict (no wave keys) when no waves exist for the project
|
|
@@ -110,6 +124,7 @@ class TasksClient(SteerDevClient):
|
|
|
110
124
|
|
|
111
125
|
Args:
|
|
112
126
|
project_id: Filter by project ID. Falls back to STEERDEV_PROJECT_ID env var.
|
|
127
|
+
agent_id: Agent ID for conflict-aware scheduling.
|
|
113
128
|
|
|
114
129
|
Returns:
|
|
115
130
|
Wave response, plain task, or None.
|
|
@@ -119,6 +134,8 @@ class TasksClient(SteerDevClient):
|
|
|
119
134
|
params: dict[str, str] = {"waves": "true"}
|
|
120
135
|
if effective_project_id:
|
|
121
136
|
params["project_id"] = effective_project_id
|
|
137
|
+
if agent_id:
|
|
138
|
+
params["agent_id"] = agent_id
|
|
122
139
|
|
|
123
140
|
console.print(f"Fetching next task from {self.api_base}/tasks/next")
|
|
124
141
|
response = self.get("/tasks/next", params=params)
|
|
@@ -413,6 +430,87 @@ class TasksClient(SteerDevClient):
|
|
|
413
430
|
"""
|
|
414
431
|
return self.update_task(task_id, status="canceled", error_message=error_message)
|
|
415
432
|
|
|
433
|
+
def report_files(
|
|
434
|
+
self,
|
|
435
|
+
task_id: str,
|
|
436
|
+
actual_files: list[str] | None = None,
|
|
437
|
+
predicted_files: list[str] | None = None,
|
|
438
|
+
) -> bool:
|
|
439
|
+
"""Report files modified by this task for conflict detection.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
task_id: Task ID to update.
|
|
443
|
+
actual_files: Files actually modified (from git diff).
|
|
444
|
+
predicted_files: Predicted files (from pre-analysis).
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
True if update succeeded.
|
|
448
|
+
"""
|
|
449
|
+
payload: dict[str, Any] = {}
|
|
450
|
+
if actual_files is not None:
|
|
451
|
+
payload["actual_files"] = actual_files
|
|
452
|
+
if predicted_files is not None:
|
|
453
|
+
payload["predicted_files"] = predicted_files
|
|
454
|
+
payload["source"] = "agent_reported"
|
|
455
|
+
|
|
456
|
+
if not payload:
|
|
457
|
+
return False
|
|
458
|
+
|
|
459
|
+
response = self.patch(f"/tasks/{task_id}/files", json=payload)
|
|
460
|
+
if response.status_code not in (200, 204):
|
|
461
|
+
logger.warning(f"Failed to report files for task {task_id}: {response.status_code}")
|
|
462
|
+
return False
|
|
463
|
+
|
|
464
|
+
logger.info(f"Reported {len(actual_files or [])} actual files for task {task_id}")
|
|
465
|
+
return True
|
|
466
|
+
|
|
467
|
+
def requeue_task(
|
|
468
|
+
self,
|
|
469
|
+
task_id: str,
|
|
470
|
+
conflict_files: list[str],
|
|
471
|
+
conflicting_task_id: str | None = None,
|
|
472
|
+
previous_branch: str | None = None,
|
|
473
|
+
) -> bool:
|
|
474
|
+
"""Re-queue a task due to merge conflict.
|
|
475
|
+
|
|
476
|
+
The server will increment the retry count and reset the task to unstarted.
|
|
477
|
+
Returns False if max retries exceeded (409 status).
|
|
478
|
+
|
|
479
|
+
Args:
|
|
480
|
+
task_id: Task ID to re-queue.
|
|
481
|
+
conflict_files: Files that caused the conflict.
|
|
482
|
+
conflicting_task_id: ID of the conflicting task, if known.
|
|
483
|
+
previous_branch: Branch name from the failed attempt.
|
|
484
|
+
|
|
485
|
+
Returns:
|
|
486
|
+
True if re-queue succeeded, False if max retries exceeded or error.
|
|
487
|
+
"""
|
|
488
|
+
payload: dict[str, Any] = {
|
|
489
|
+
"conflict_files": conflict_files,
|
|
490
|
+
}
|
|
491
|
+
if conflicting_task_id:
|
|
492
|
+
payload["conflicting_task_id"] = conflicting_task_id
|
|
493
|
+
if previous_branch:
|
|
494
|
+
payload["previous_branch"] = previous_branch
|
|
495
|
+
|
|
496
|
+
response = self.post(f"/tasks/{task_id}/requeue", json=payload)
|
|
497
|
+
|
|
498
|
+
if response.status_code == 409:
|
|
499
|
+
logger.warning(f"Task {task_id} exceeded max conflict retries")
|
|
500
|
+
console.print(
|
|
501
|
+
f"[red]Task {task_id} has exceeded max conflict retries. "
|
|
502
|
+
f"Manual intervention required.[/red]"
|
|
503
|
+
)
|
|
504
|
+
return False
|
|
505
|
+
|
|
506
|
+
if response.status_code not in (200, 201):
|
|
507
|
+
logger.warning(f"Failed to requeue task {task_id}: {response.status_code}")
|
|
508
|
+
return False
|
|
509
|
+
|
|
510
|
+
logger.info(f"Task {task_id} re-queued due to merge conflict")
|
|
511
|
+
console.print(f"[yellow]Task {task_id} re-queued due to merge conflict[/yellow]")
|
|
512
|
+
return True
|
|
513
|
+
|
|
416
514
|
|
|
417
515
|
def get_priority_display(priority: int | None) -> str:
|
|
418
516
|
"""Get human-readable priority display.
|
|
@@ -349,6 +349,54 @@ def tasks_create(
|
|
|
349
349
|
raise typer.Exit(1) from None
|
|
350
350
|
|
|
351
351
|
|
|
352
|
+
@tasks_app.command("requeue")
|
|
353
|
+
def tasks_requeue(
|
|
354
|
+
task_id: Annotated[
|
|
355
|
+
str,
|
|
356
|
+
typer.Argument(help="Task ID (UUID) to re-queue"),
|
|
357
|
+
],
|
|
358
|
+
conflict_files: Annotated[
|
|
359
|
+
str,
|
|
360
|
+
typer.Option("--files", "-f", help="Comma-separated list of conflicting file paths"),
|
|
361
|
+
],
|
|
362
|
+
conflicting_task_id: Annotated[
|
|
363
|
+
str | None,
|
|
364
|
+
typer.Option("--conflicting-task-id", help="ID of the conflicting task"),
|
|
365
|
+
] = None,
|
|
366
|
+
previous_branch: Annotated[
|
|
367
|
+
str | None,
|
|
368
|
+
typer.Option("--branch", "-b", help="Branch name from the failed attempt"),
|
|
369
|
+
] = None,
|
|
370
|
+
) -> None:
|
|
371
|
+
"""Re-queue a task due to merge conflict.
|
|
372
|
+
|
|
373
|
+
Resets the task to 'unstarted' so it can be retried on a fresh base.
|
|
374
|
+
Fails with exit code 1 if max retries exceeded (requires manual intervention).
|
|
375
|
+
"""
|
|
376
|
+
from steerdev_agent.api.tasks import TasksClient
|
|
377
|
+
|
|
378
|
+
files = [f.strip() for f in conflict_files.split(",") if f.strip()]
|
|
379
|
+
if not files:
|
|
380
|
+
console.print("[red]Error: At least one conflict file is required[/red]")
|
|
381
|
+
raise typer.Exit(1)
|
|
382
|
+
|
|
383
|
+
with TasksClient() as client:
|
|
384
|
+
if not client.check_api_key():
|
|
385
|
+
raise typer.Exit(1)
|
|
386
|
+
|
|
387
|
+
success = client.requeue_task(
|
|
388
|
+
task_id=task_id,
|
|
389
|
+
conflict_files=files,
|
|
390
|
+
conflicting_task_id=conflicting_task_id,
|
|
391
|
+
previous_branch=previous_branch,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
if not success:
|
|
395
|
+
raise typer.Exit(1)
|
|
396
|
+
|
|
397
|
+
console.print(f"[green]Task {task_id} re-queued for retry[/green]")
|
|
398
|
+
|
|
399
|
+
|
|
352
400
|
# ============================================================================
|
|
353
401
|
# Hooks Command Group
|
|
354
402
|
# ============================================================================
|
|
@@ -151,6 +151,32 @@ def resolve_merge_flow(
|
|
|
151
151
|
return "single_task", "single-task-merge"
|
|
152
152
|
|
|
153
153
|
|
|
154
|
+
async def _get_modified_files(working_dir: str | Path) -> list[str]:
|
|
155
|
+
"""Get files modified in the current branch compared to the default branch.
|
|
156
|
+
|
|
157
|
+
Uses git diff to find files changed in the working tree and staged area.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
List of file paths relative to the repo root.
|
|
161
|
+
"""
|
|
162
|
+
try:
|
|
163
|
+
proc = await asyncio.create_subprocess_exec(
|
|
164
|
+
"git",
|
|
165
|
+
"diff",
|
|
166
|
+
"--name-only",
|
|
167
|
+
"HEAD",
|
|
168
|
+
cwd=str(working_dir),
|
|
169
|
+
stdout=asyncio.subprocess.PIPE,
|
|
170
|
+
stderr=asyncio.subprocess.PIPE,
|
|
171
|
+
)
|
|
172
|
+
stdout, _ = await proc.communicate()
|
|
173
|
+
files = [f for f in stdout.decode().strip().split("\n") if f]
|
|
174
|
+
return files
|
|
175
|
+
except Exception:
|
|
176
|
+
logger.debug("Failed to get modified files via git diff", exc_info=True)
|
|
177
|
+
return []
|
|
178
|
+
|
|
179
|
+
|
|
154
180
|
def compute_worktree_name(task_id: str, wave_context: WaveContext | None = None) -> str:
|
|
155
181
|
"""Compute the git worktree name for a task execution.
|
|
156
182
|
|
|
@@ -252,6 +278,7 @@ class Runner:
|
|
|
252
278
|
dry_run: bool = False,
|
|
253
279
|
retry_config: RetryConfig | None = None,
|
|
254
280
|
shutdown_event: asyncio.Event | None = None,
|
|
281
|
+
agent_id: str | None = None,
|
|
255
282
|
) -> None:
|
|
256
283
|
"""Initialize the runner.
|
|
257
284
|
|
|
@@ -273,6 +300,7 @@ class Runner:
|
|
|
273
300
|
force_workflow_id: Workflow ID override for multi-phase execution.
|
|
274
301
|
dry_run: If True, print the command without executing it.
|
|
275
302
|
retry_config: Retry configuration for failed tasks.
|
|
303
|
+
agent_id: Database agent ID for conflict-aware scheduling.
|
|
276
304
|
"""
|
|
277
305
|
self.project_id = project_id
|
|
278
306
|
self.working_directory = Path(working_directory or Path.cwd())
|
|
@@ -296,6 +324,7 @@ class Runner:
|
|
|
296
324
|
self._evidence_config = evidence_config or EvidenceConfig()
|
|
297
325
|
self._enable_waves = enable_waves
|
|
298
326
|
self._enable_canals = enable_canals
|
|
327
|
+
self._agent_id = agent_id
|
|
299
328
|
|
|
300
329
|
# State
|
|
301
330
|
self._state = RunState.STOPPED
|
|
@@ -590,11 +619,22 @@ class Runner:
|
|
|
590
619
|
current_status = task.get("status", "")
|
|
591
620
|
if current_status in ("unstarted", "backlog"):
|
|
592
621
|
with TasksClient(api_key=self._api_key) as tasks_client:
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
622
|
+
# backlog -> unstarted -> started (two-step transition)
|
|
623
|
+
if current_status == "backlog":
|
|
624
|
+
if tasks_client.update_task(task_id, status="unstarted"):
|
|
625
|
+
logger.info(f"Task {task_id} moved from backlog to unstarted")
|
|
626
|
+
task["status"] = "unstarted"
|
|
627
|
+
else:
|
|
628
|
+
logger.warning(
|
|
629
|
+
f"Failed to move task {task_id} from backlog to unstarted"
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
if task.get("status") == "unstarted":
|
|
633
|
+
if tasks_client.update_task(task_id, status="started"):
|
|
634
|
+
logger.info(f"Task {task_id} marked as started")
|
|
635
|
+
task["status"] = "started"
|
|
636
|
+
else:
|
|
637
|
+
logger.warning(f"Failed to mark task {task_id} as started")
|
|
598
638
|
|
|
599
639
|
# Notify observer
|
|
600
640
|
if self._observer:
|
|
@@ -762,6 +802,14 @@ class Runner:
|
|
|
762
802
|
else:
|
|
763
803
|
logger.warning(f"Failed to mark task {task_id} as completed")
|
|
764
804
|
|
|
805
|
+
# Report actual files modified (for conflict-aware scheduling)
|
|
806
|
+
try:
|
|
807
|
+
actual_files = await _get_modified_files(effective_working_dir)
|
|
808
|
+
if actual_files:
|
|
809
|
+
tasks_client.report_files(task_id, actual_files=actual_files)
|
|
810
|
+
except Exception:
|
|
811
|
+
logger.debug("Failed to report modified files", exc_info=True)
|
|
812
|
+
|
|
765
813
|
if self._evidence_config.enabled and not self.dry_run:
|
|
766
814
|
await self._submit_evidence_report(
|
|
767
815
|
task=task,
|
|
@@ -979,7 +1027,10 @@ class Runner:
|
|
|
979
1027
|
if self._enable_waves:
|
|
980
1028
|
try:
|
|
981
1029
|
task, wave_ctx, suggested_workflow_id = extract_task_and_wave_context(
|
|
982
|
-
client.get_next_wave(
|
|
1030
|
+
client.get_next_wave(
|
|
1031
|
+
project_id=self.project_id,
|
|
1032
|
+
agent_id=self._agent_id,
|
|
1033
|
+
)
|
|
983
1034
|
)
|
|
984
1035
|
except Exception:
|
|
985
1036
|
logger.debug(
|
|
@@ -988,7 +1039,10 @@ class Runner:
|
|
|
988
1039
|
)
|
|
989
1040
|
|
|
990
1041
|
if not task:
|
|
991
|
-
task = client.get_next_task(
|
|
1042
|
+
task = client.get_next_task(
|
|
1043
|
+
project_id=self.project_id,
|
|
1044
|
+
agent_id=self._agent_id,
|
|
1045
|
+
)
|
|
992
1046
|
|
|
993
1047
|
if not task:
|
|
994
1048
|
if self._tasks_executed == 0:
|
|
@@ -20,6 +20,26 @@ Use this skill when a workflow phase tells you to merge the **current task branc
|
|
|
20
20
|
|
|
21
21
|
Before merging into a canal, verify your current branch has been pushed and has a PR. Do NOT switch branches or sync to main — this skill operates on the current task/wave branch.
|
|
22
22
|
|
|
23
|
+
## Rebase Before Canal Merge
|
|
24
|
+
|
|
25
|
+
Before merging into the canal, ensure your branch is up to date:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
git fetch origin main
|
|
29
|
+
git merge-tree --write-tree origin/main HEAD
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
- **If clean** (exit 0): Rebase and force-push with lease:
|
|
33
|
+
```bash
|
|
34
|
+
git rebase origin/main
|
|
35
|
+
git push --force-with-lease origin HEAD
|
|
36
|
+
```
|
|
37
|
+
- **If conflicts**: Attempt rebase. If it fails on files you did not modify, abort (`git rebase --abort`) and re-queue the task:
|
|
38
|
+
```bash
|
|
39
|
+
steerdev tasks requeue TASK_ID --files "conflicting/file1.ts,conflicting/file2.ts" --branch "$(git branch --show-current)"
|
|
40
|
+
```
|
|
41
|
+
Do NOT force-push to resolve canal conflicts. If re-queue fails, report as a blocker.
|
|
42
|
+
|
|
23
43
|
## Workflow
|
|
24
44
|
|
|
25
45
|
1. Confirm the workflow phase says the branch is ready for canal integration
|
|
@@ -56,12 +56,36 @@ This fetches the task from the API and builds the branch name automatically:
|
|
|
56
56
|
|
|
57
57
|
3. Implement the requested changes
|
|
58
58
|
4. Commit with a conventional commit message that explains why the change exists
|
|
59
|
-
5.
|
|
59
|
+
5. Rebase onto the latest target branch before pushing:
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
|
-
git
|
|
62
|
+
git fetch origin main
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
Dry-run merge check (requires git 2.38+):
|
|
66
|
+
```bash
|
|
67
|
+
git merge-tree --write-tree origin/main HEAD
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- **If exit code 0** (clean merge): Rebase and push:
|
|
71
|
+
```bash
|
|
72
|
+
git rebase origin/main
|
|
73
|
+
git push -u origin HEAD
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- **If exit code non-zero** (conflicts detected): Attempt rebase:
|
|
77
|
+
```bash
|
|
78
|
+
git rebase origin/main
|
|
79
|
+
```
|
|
80
|
+
- If rebase succeeds after resolving conflicts in files you modified: `git push --force-with-lease origin HEAD`
|
|
81
|
+
- If conflicts are in files you did NOT modify: abort the rebase (`git rebase --abort`) and re-queue the task for a fresh attempt:
|
|
82
|
+
```bash
|
|
83
|
+
steerdev tasks requeue TASK_ID --files "path/to/file1.ts,path/to/file2.ts" --branch "$(git branch --show-current)"
|
|
84
|
+
```
|
|
85
|
+
If re-queue fails (max retries exceeded), report as a blocker using `steerdev activity report`.
|
|
86
|
+
|
|
87
|
+
**Fallback** (if `git merge-tree` is unavailable): Skip the dry-run and rebase directly. If rebase fails, abort and re-queue as above.
|
|
88
|
+
|
|
65
89
|
6. Create the pull request:
|
|
66
90
|
|
|
67
91
|
```bash
|
|
@@ -54,12 +54,36 @@ git commit -m "[PROJ-123] feat: add user endpoint"
|
|
|
54
54
|
git commit -m "[task:abc12345] feat: add user endpoint"
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
3. After the wave work is ready for review,
|
|
57
|
+
3. After the wave work is ready for review, rebase onto the latest target branch:
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
|
-
git
|
|
60
|
+
git fetch origin main
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
+
Dry-run merge check (requires git 2.38+):
|
|
64
|
+
```bash
|
|
65
|
+
git merge-tree --write-tree origin/main HEAD
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- **If exit code 0** (clean merge): Rebase and push:
|
|
69
|
+
```bash
|
|
70
|
+
git rebase origin/main
|
|
71
|
+
git push -u origin HEAD
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- **If exit code non-zero** (conflicts detected): Attempt rebase:
|
|
75
|
+
```bash
|
|
76
|
+
git rebase origin/main
|
|
77
|
+
```
|
|
78
|
+
- If rebase succeeds after resolving conflicts in files you modified: `git push --force-with-lease origin HEAD`
|
|
79
|
+
- If conflicts are in files you did NOT modify: abort the rebase (`git rebase --abort`) and re-queue the current task:
|
|
80
|
+
```bash
|
|
81
|
+
steerdev tasks requeue TASK_ID --files "path/to/file1.ts,path/to/file2.ts" --branch "$(git branch --show-current)"
|
|
82
|
+
```
|
|
83
|
+
If re-queue fails (max retries exceeded), report as a blocker using `steerdev activity report`.
|
|
84
|
+
|
|
85
|
+
**Fallback** (if `git merge-tree` is unavailable): Skip the dry-run and rebase directly. If rebase fails, abort and re-queue as above.
|
|
86
|
+
|
|
63
87
|
4. Create one pull request for the wave:
|
|
64
88
|
|
|
65
89
|
```bash
|