steerdev 1.0.37__tar.gz → 1.0.52__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.
Files changed (108) hide show
  1. {steerdev-1.0.37 → steerdev-1.0.52}/PKG-INFO +1 -1
  2. {steerdev-1.0.37 → steerdev-1.0.52}/pyproject.toml +1 -1
  3. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/agent_loop.py +94 -25
  4. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/__init__.py +2 -0
  5. steerdev-1.0.52/src/steerdev_agent/api/reports.py +82 -0
  6. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/cli.py +82 -8
  7. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/config/models.py +54 -4
  8. steerdev-1.0.52/src/steerdev_agent/evidence.py +156 -0
  9. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/executor/__init__.py +4 -4
  10. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/executor/claude.py +9 -7
  11. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/runner.py +268 -23
  12. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/claude_setup.py +114 -0
  13. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/repo_setup.py +20 -10
  14. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/settings.json +6 -1
  15. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/skills/steerdev-single-task-merge-skill/SKILL.md +7 -3
  16. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/skills/steerdev-wave-tasks-merge-skill/SKILL.md +7 -3
  17. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/steerdev.yaml +16 -0
  18. steerdev-1.0.52/src/steerdev_agent/setup/templates/worktrunk.config.toml +40 -0
  19. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/workflow/executor.py +29 -3
  20. steerdev-1.0.52/src/steerdev_agent/worktree.py +264 -0
  21. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_agent_loop.py +8 -2
  22. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_agent_loop_extended.py +13 -6
  23. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_claude_executor.py +13 -6
  24. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_claude_setup.py +44 -2
  25. steerdev-1.0.52/tests/test_reports_client.py +115 -0
  26. steerdev-1.0.52/tests/test_runner_worktrees.py +92 -0
  27. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_tasks.py +6 -6
  28. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_workspace_extended.py +2 -5
  29. steerdev-1.0.52/tests/test_worktree.py +153 -0
  30. {steerdev-1.0.37 → steerdev-1.0.52}/.github/workflows/pre-commit.yml +0 -0
  31. {steerdev-1.0.37 → steerdev-1.0.52}/.github/workflows/publish.yml +0 -0
  32. {steerdev-1.0.37 → steerdev-1.0.52}/.gitignore +0 -0
  33. {steerdev-1.0.37 → steerdev-1.0.52}/.pre-commit-config.yaml +0 -0
  34. {steerdev-1.0.37 → steerdev-1.0.52}/AGENTS.md +0 -0
  35. {steerdev-1.0.37 → steerdev-1.0.52}/CLAUDE.md +0 -0
  36. {steerdev-1.0.37 → steerdev-1.0.52}/README.md +0 -0
  37. {steerdev-1.0.37 → steerdev-1.0.52}/scripts/pre-commit-version-bump.sh +0 -0
  38. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/__init__.py +0 -0
  39. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/activity.py +0 -0
  40. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/agents.py +0 -0
  41. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/canals.py +0 -0
  42. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/client.py +0 -0
  43. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/commands.py +0 -0
  44. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/configs.py +0 -0
  45. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/context.py +0 -0
  46. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/events.py +0 -0
  47. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/hooks.py +0 -0
  48. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/implementation_plan.py +0 -0
  49. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/messages.py +0 -0
  50. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/prd.py +0 -0
  51. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/runs.py +0 -0
  52. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/sessions.py +0 -0
  53. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/specs.py +0 -0
  54. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/tasks.py +0 -0
  55. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/workflow_runs.py +0 -0
  56. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/workflows.py +0 -0
  57. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/config/__init__.py +0 -0
  58. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/config/platform.py +0 -0
  59. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/config/settings.py +0 -0
  60. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/executor/base.py +0 -0
  61. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/executor/stream.py +0 -0
  62. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/handlers/__init__.py +0 -0
  63. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/handlers/prd.py +0 -0
  64. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/integration.py +0 -0
  65. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/prompt/__init__.py +0 -0
  66. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/prompt/builder.py +0 -0
  67. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/prompt/templates.py +0 -0
  68. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/prompt/workflow_template.py +0 -0
  69. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/py.typed +0 -0
  70. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/retry.py +0 -0
  71. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/__init__.py +0 -0
  72. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/ci/canal-integration.yml +0 -0
  73. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/claude_md_section.md +0 -0
  74. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/skills/steerdev-activity-skill/SKILL.md +0 -0
  75. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/skills/steerdev-canal-workflow-skill/SKILL.md +0 -0
  76. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/skills/steerdev-context-skill/SKILL.md +0 -0
  77. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/skills/steerdev-git-workflow-skill/SKILL.md +0 -0
  78. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/skills/steerdev-merge-into-canal-skill/SKILL.md +0 -0
  79. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/skills/steerdev-progress-logging-skill/SKILL.md +0 -0
  80. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/skills/steerdev-specs-management-skill/SKILL.md +0 -0
  81. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/skills/steerdev-task-management-skill/SKILL.md +0 -0
  82. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/update_check.py +0 -0
  83. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/version.py +0 -0
  84. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/workflow/__init__.py +0 -0
  85. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/workflow/context.py +0 -0
  86. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/workflow/memory.py +0 -0
  87. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/workspace/__init__.py +0 -0
  88. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/workspace/project_manager.py +0 -0
  89. {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/workspace/tool_detection.py +0 -0
  90. {steerdev-1.0.37 → steerdev-1.0.52}/tests/__init__.py +0 -0
  91. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_agents_api.py +0 -0
  92. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_api_client.py +0 -0
  93. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_client_methods.py +0 -0
  94. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_commands_api.py +0 -0
  95. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_config.py +0 -0
  96. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_config_extended.py +0 -0
  97. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_context_search.py +0 -0
  98. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_executor.py +0 -0
  99. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_platform_config.py +0 -0
  100. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_prompt.py +0 -0
  101. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_retry.py +0 -0
  102. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_runner_merge_modes.py +0 -0
  103. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_stream_parser.py +0 -0
  104. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_version.py +0 -0
  105. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_workflow_context.py +0 -0
  106. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_workflow_memory.py +0 -0
  107. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_workflow_prompt_template.py +0 -0
  108. {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_workspace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: steerdev
3
- Version: 1.0.37
3
+ Version: 1.0.52
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "steerdev"
3
- version = "1.0.37"
3
+ version = "1.0.52"
4
4
  description = "Backend task runner for steerdev.com - orchestrates CLI coding agents with activity reporting"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -36,9 +36,11 @@ 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
+ EvidenceConfig,
39
40
  ExecutorConfig,
40
41
  RetryConfig,
41
42
  WorkspaceConfig,
43
+ WorktreeConfig,
42
44
  )
43
45
  from steerdev_agent.executor import ExecutorFactory
44
46
  from steerdev_agent.executor.base import EventType
@@ -60,7 +62,7 @@ class CommandExecutor:
60
62
  """Shared command execution dispatch for both project and workspace agents.
61
63
 
62
64
  Subclasses provide: _api_key, agent_type, agent_name, model, max_turns,
63
- _executor_config, _workflow_id, _enable_worktrees, _agent_loop_config,
65
+ _executor_config, _workflow_id, _worktree_config, _agent_loop_config,
64
66
  _commands_client, _sessions_client, _shutdown_event, and the stats counters.
65
67
  """
66
68
 
@@ -75,11 +77,14 @@ class CommandExecutor:
75
77
  _workflow_id: str | None
76
78
  _enable_waves: bool
77
79
  _enable_canals: bool
78
- _enable_worktrees: bool
80
+ _worktree_config: WorktreeConfig
81
+ _evidence_config: EvidenceConfig
79
82
  _agent_loop_config: AgentLoopConfig
80
83
  _commands_client: CommandsClient | None
81
84
  _sessions_client: SessionsClient | None
82
85
  _shutdown_event: asyncio.Event
86
+ _current_task: asyncio.Task[bool] | None
87
+ _signal_count: int
83
88
  _commands_executed: int
84
89
  _commands_succeeded: int
85
90
  _commands_failed: int
@@ -169,9 +174,11 @@ class CommandExecutor:
169
174
  max_turns=self.max_turns,
170
175
  enable_waves=self._enable_waves,
171
176
  enable_canals=self._enable_canals,
172
- enable_worktrees=self._enable_worktrees,
177
+ worktree_config=self._worktree_config,
178
+ evidence_config=self._evidence_config,
173
179
  executor_config=self._executor_config,
174
180
  force_workflow_id=None,
181
+ shutdown_event=self._shutdown_event,
175
182
  )
176
183
 
177
184
  # Fetch task details
@@ -431,7 +438,8 @@ class AgentLoop(CommandExecutor):
431
438
  force_workflow_id: str | None = None,
432
439
  enable_waves: bool = True,
433
440
  enable_canals: bool = False,
434
- enable_worktrees: bool = False,
441
+ worktree_config: WorktreeConfig | None = None,
442
+ evidence_config: EvidenceConfig | None = None,
435
443
  retry_config: RetryConfig | None = None,
436
444
  ) -> None:
437
445
  self.project_id = project_id
@@ -447,11 +455,14 @@ class AgentLoop(CommandExecutor):
447
455
  self._workflow_id = force_workflow_id
448
456
  self._enable_waves = enable_waves
449
457
  self._enable_canals = enable_canals
450
- self._enable_worktrees = enable_worktrees
458
+ self._worktree_config = worktree_config or WorktreeConfig()
459
+ self._evidence_config = evidence_config or EvidenceConfig()
451
460
 
452
461
  # State
453
462
  self._agent_id: str | None = None
454
463
  self._shutdown_event = asyncio.Event()
464
+ self._current_task: asyncio.Task[bool] | None = None
465
+ self._signal_count = 0
455
466
  self._is_busy = False
456
467
  self._consecutive_errors = 0
457
468
  self._commands_executed = 0
@@ -470,7 +481,11 @@ class AgentLoop(CommandExecutor):
470
481
  workflow_status = self._workflow_id or "single-phase"
471
482
  waves_status = "enabled" if self._enable_waves else "disabled"
472
483
  canals_status = "enabled" if self._enable_canals else "disabled"
473
- worktree_status = "enabled" if self._enable_worktrees else "disabled"
484
+ worktree_status = (
485
+ f"enabled ({self._worktree_config.provider})"
486
+ if self._worktree_config.enabled
487
+ else "disabled"
488
+ )
474
489
 
475
490
  console.print(
476
491
  Panel(
@@ -577,7 +592,14 @@ class AgentLoop(CommandExecutor):
577
592
 
578
593
  while not self._shutdown_event.is_set():
579
594
  try:
580
- executed = await self._poll_once()
595
+ task = asyncio.create_task(self._poll_once())
596
+ self._current_task = task
597
+ try:
598
+ executed = await task
599
+ except asyncio.CancelledError:
600
+ break
601
+ finally:
602
+ self._current_task = None
581
603
 
582
604
  if executed:
583
605
  self._consecutive_errors = 0
@@ -696,13 +718,27 @@ class AgentLoop(CommandExecutor):
696
718
  """Set up SIGINT/SIGTERM handlers for graceful shutdown."""
697
719
  loop = asyncio.get_running_loop()
698
720
 
699
- def _signal_handler() -> None:
700
- logger.info("Received shutdown signal")
701
- console.print("\n[yellow]Received shutdown signal, finishing current work...[/yellow]")
721
+ def _sigint_handler() -> None:
722
+ self._signal_count += 1
723
+ if self._signal_count == 1:
724
+ logger.info("Received shutdown signal")
725
+ console.print(
726
+ "\n[yellow]Received shutdown signal, finishing current work...[/yellow]"
727
+ )
728
+ console.print("[dim]Press Ctrl-C again to force stop[/dim]")
729
+ self._shutdown_event.set()
730
+ else:
731
+ logger.info("Force stop requested")
732
+ console.print("\n[red]Force stopping...[/red]")
733
+ if self._current_task and not self._current_task.done():
734
+ self._current_task.cancel()
735
+
736
+ def _sigterm_handler() -> None:
737
+ logger.info("Received SIGTERM")
702
738
  self._shutdown_event.set()
703
739
 
704
- for sig in (signal.SIGINT, signal.SIGTERM):
705
- loop.add_signal_handler(sig, _signal_handler)
740
+ loop.add_signal_handler(signal.SIGINT, _sigint_handler)
741
+ loop.add_signal_handler(signal.SIGTERM, _sigterm_handler)
706
742
 
707
743
  async def _shutdown(self) -> None:
708
744
  """Clean up resources and send offline heartbeat."""
@@ -769,7 +805,8 @@ class WorkspaceAgentLoop(CommandExecutor):
769
805
  force_workflow_id: str | None = None,
770
806
  enable_waves: bool = True,
771
807
  enable_canals: bool = False,
772
- enable_worktrees: bool = False,
808
+ worktree_config: WorktreeConfig | None = None,
809
+ evidence_config: EvidenceConfig | None = None,
773
810
  retry_config: RetryConfig | None = None,
774
811
  ) -> None:
775
812
  self.workspace_path = Path(workspace_path)
@@ -785,11 +822,14 @@ class WorkspaceAgentLoop(CommandExecutor):
785
822
  self._workflow_id = force_workflow_id
786
823
  self._enable_waves = enable_waves
787
824
  self._enable_canals = enable_canals
788
- self._enable_worktrees = enable_worktrees
825
+ self._worktree_config = worktree_config or WorktreeConfig()
826
+ self._evidence_config = evidence_config or EvidenceConfig()
789
827
 
790
828
  # State
791
829
  self._agent_id: str | None = None
792
830
  self._shutdown_event = asyncio.Event()
831
+ self._current_task: asyncio.Task[bool] | None = None
832
+ self._signal_count = 0
793
833
  self._is_busy = False
794
834
  self._consecutive_errors = 0
795
835
  self._commands_executed = 0
@@ -813,7 +853,11 @@ class WorkspaceAgentLoop(CommandExecutor):
813
853
 
814
854
  waves_status = "enabled" if self._enable_waves else "disabled"
815
855
  canals_status = "enabled" if self._enable_canals else "disabled"
816
- worktree_status = "enabled" if self._enable_worktrees else "disabled"
856
+ worktree_status = (
857
+ f"enabled ({self._worktree_config.provider})"
858
+ if self._worktree_config.enabled
859
+ else "disabled"
860
+ )
817
861
 
818
862
  console.print(
819
863
  Panel(
@@ -956,7 +1000,14 @@ class WorkspaceAgentLoop(CommandExecutor):
956
1000
 
957
1001
  while not self._shutdown_event.is_set():
958
1002
  try:
959
- executed = await self._poll_once()
1003
+ task = asyncio.create_task(self._poll_once())
1004
+ self._current_task = task
1005
+ try:
1006
+ executed = await task
1007
+ except asyncio.CancelledError:
1008
+ break
1009
+ finally:
1010
+ self._current_task = None
960
1011
 
961
1012
  if executed:
962
1013
  self._consecutive_errors = 0
@@ -1099,13 +1150,27 @@ class WorkspaceAgentLoop(CommandExecutor):
1099
1150
  """Set up SIGINT/SIGTERM handlers for graceful shutdown."""
1100
1151
  loop = asyncio.get_running_loop()
1101
1152
 
1102
- def _signal_handler() -> None:
1103
- logger.info("Received shutdown signal")
1104
- console.print("\n[yellow]Received shutdown signal, finishing current work...[/yellow]")
1153
+ def _sigint_handler() -> None:
1154
+ self._signal_count += 1
1155
+ if self._signal_count == 1:
1156
+ logger.info("Received shutdown signal")
1157
+ console.print(
1158
+ "\n[yellow]Received shutdown signal, finishing current work...[/yellow]"
1159
+ )
1160
+ console.print("[dim]Press Ctrl-C again to force stop[/dim]")
1161
+ self._shutdown_event.set()
1162
+ else:
1163
+ logger.info("Force stop requested")
1164
+ console.print("\n[red]Force stopping...[/red]")
1165
+ if self._current_task and not self._current_task.done():
1166
+ self._current_task.cancel()
1167
+
1168
+ def _sigterm_handler() -> None:
1169
+ logger.info("Received SIGTERM")
1105
1170
  self._shutdown_event.set()
1106
1171
 
1107
- for sig in (signal.SIGINT, signal.SIGTERM):
1108
- loop.add_signal_handler(sig, _signal_handler)
1172
+ loop.add_signal_handler(signal.SIGINT, _sigint_handler)
1173
+ loop.add_signal_handler(signal.SIGTERM, _sigterm_handler)
1109
1174
 
1110
1175
  async def _shutdown(self) -> None:
1111
1176
  """Clean up resources and send offline heartbeat."""
@@ -1162,7 +1227,8 @@ async def run_agent_loop(
1162
1227
  force_workflow_id: str | None = None,
1163
1228
  enable_waves: bool = True,
1164
1229
  enable_canals: bool = False,
1165
- enable_worktrees: bool = False,
1230
+ worktree_config: WorktreeConfig | None = None,
1231
+ evidence_config: EvidenceConfig | None = None,
1166
1232
  retry_config: RetryConfig | None = None,
1167
1233
  ) -> None:
1168
1234
  """Run the project-scoped agent loop.
@@ -1182,7 +1248,8 @@ async def run_agent_loop(
1182
1248
  force_workflow_id=force_workflow_id,
1183
1249
  enable_waves=enable_waves,
1184
1250
  enable_canals=enable_canals,
1185
- enable_worktrees=enable_worktrees,
1251
+ worktree_config=worktree_config,
1252
+ evidence_config=evidence_config,
1186
1253
  retry_config=retry_config,
1187
1254
  )
1188
1255
  await agent.start()
@@ -1201,7 +1268,8 @@ async def run_workspace_agent_loop(
1201
1268
  force_workflow_id: str | None = None,
1202
1269
  enable_waves: bool = True,
1203
1270
  enable_canals: bool = False,
1204
- enable_worktrees: bool = False,
1271
+ worktree_config: WorktreeConfig | None = None,
1272
+ evidence_config: EvidenceConfig | None = None,
1205
1273
  retry_config: RetryConfig | None = None,
1206
1274
  ) -> None:
1207
1275
  """Run the workspace (multi-project) agent loop.
@@ -1221,7 +1289,8 @@ async def run_workspace_agent_loop(
1221
1289
  force_workflow_id=force_workflow_id,
1222
1290
  enable_waves=enable_waves,
1223
1291
  enable_canals=enable_canals,
1224
- enable_worktrees=enable_worktrees,
1292
+ worktree_config=worktree_config,
1293
+ evidence_config=evidence_config,
1225
1294
  retry_config=retry_config,
1226
1295
  )
1227
1296
  await agent.start()
@@ -4,6 +4,7 @@ from steerdev_agent.api.client import SteerDevClient, get_api_key, get_project_i
4
4
  from steerdev_agent.api.configs import ConfigsClient
5
5
  from steerdev_agent.api.events import EventData, EventsClient
6
6
  from steerdev_agent.api.hooks import HooksClient
7
+ from steerdev_agent.api.reports import ReportsClient
7
8
  from steerdev_agent.api.runs import RunCreateRequest, RunResponse, RunsClient
8
9
  from steerdev_agent.api.sessions import (
9
10
  SessionCreateRequest,
@@ -18,6 +19,7 @@ __all__ = [
18
19
  "EventData",
19
20
  "EventsClient",
20
21
  "HooksClient",
22
+ "ReportsClient",
21
23
  "RunCreateRequest",
22
24
  "RunResponse",
23
25
  "RunsClient",
@@ -0,0 +1,82 @@
1
+ """Evidence reports API client for SteerDev Agent.
2
+
3
+ Submits evidence reports to the platform after task/workflow completion,
4
+ making work visible on the project review dashboard.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+ from loguru import logger
12
+ from rich.console import Console
13
+
14
+ from steerdev_agent.api.client import SteerDevClient
15
+
16
+ console = Console()
17
+
18
+
19
+ class ReportsClient(SteerDevClient):
20
+ """Client for submitting evidence reports to the platform.
21
+
22
+ Evidence reports capture task completion summaries, making agent work
23
+ visible on the project review page (/projects/[id]/review).
24
+ """
25
+
26
+ def submit(
27
+ self,
28
+ *,
29
+ project_id: str,
30
+ summary: str,
31
+ blocks: list[dict[str, Any]],
32
+ task_id: str | None = None,
33
+ wave_id: str | None = None,
34
+ agent_id: str | None = None,
35
+ evaluation_steps: list[dict[str, Any]] | None = None,
36
+ ) -> dict[str, Any] | None:
37
+ """Submit an evidence report for a completed task or workflow.
38
+
39
+ Args:
40
+ project_id: SteerDev project ID.
41
+ summary: Report title/summary (required for reviewability).
42
+ blocks: List of report blocks (type, content, order).
43
+ task_id: Associated task ID.
44
+ wave_id: Associated wave ID.
45
+ agent_id: Associated agent ID.
46
+ evaluation_steps: Optional evaluation steps (title, result, order).
47
+
48
+ Returns:
49
+ Response dict with report_id and is_reviewable, or None on failure.
50
+ """
51
+ payload: dict[str, Any] = {
52
+ "project_id": project_id,
53
+ "summary": summary,
54
+ "blocks": blocks,
55
+ }
56
+ if task_id:
57
+ payload["task_id"] = task_id
58
+ if wave_id:
59
+ payload["wave_id"] = wave_id
60
+ if agent_id:
61
+ payload["agent_id"] = agent_id
62
+ if evaluation_steps:
63
+ payload["evaluation_steps"] = evaluation_steps
64
+
65
+ try:
66
+ response = self.post("/reports", json=payload)
67
+ if response.status_code in (200, 201):
68
+ data = response.json()
69
+ report_id = data.get("report_id", "unknown")
70
+ is_reviewable = data.get("is_reviewable", False)
71
+ console.print(
72
+ f"[dim]Evidence report submitted: {report_id} "
73
+ f"(reviewable: {is_reviewable})[/dim]"
74
+ )
75
+ return data
76
+ logger.warning(
77
+ f"Evidence report submission failed: {response.status_code} - {response.text}"
78
+ )
79
+ return None
80
+ except Exception:
81
+ logger.debug("Evidence report submission error", exc_info=True)
82
+ return None
@@ -1509,7 +1509,10 @@ def _resolve_run_config(
1509
1509
  resolved_model = model if model is not None else config.agent.model
1510
1510
  resolved_max_turns = max_turns if max_turns is not None else config.agent.max_turns
1511
1511
  resolved_timeout = timeout if timeout is not None else config.agent.timeout_seconds
1512
- resolved_worktrees = worktrees if worktrees is not None else config.worktrees.enabled
1512
+ # Resolve worktree config: CLI flag overrides config file enabled state
1513
+ resolved_worktree_config = config.worktrees.model_copy()
1514
+ if worktrees is not None:
1515
+ resolved_worktree_config.enabled = worktrees
1513
1516
 
1514
1517
  resolved_api_key = api_key
1515
1518
  if not resolved_api_key:
@@ -1528,7 +1531,7 @@ def _resolve_run_config(
1528
1531
  "timeout_seconds": resolved_timeout,
1529
1532
  "enable_waves": waves,
1530
1533
  "enable_canals": canals,
1531
- "enable_worktrees": resolved_worktrees,
1534
+ "worktree_config": resolved_worktree_config,
1532
1535
  "force_workflow_id": resolved_workflow_id,
1533
1536
  "dry_run": dry_run,
1534
1537
  }
@@ -1612,6 +1615,13 @@ def run(
1612
1615
  help="Enable git worktree isolation (default: from config or disabled)",
1613
1616
  ),
1614
1617
  ] = None,
1618
+ evidence: Annotated[
1619
+ bool | None,
1620
+ typer.Option(
1621
+ "--evidence/--no-evidence",
1622
+ help="Submit evidence reports after task completion (default: from config or disabled)",
1623
+ ),
1624
+ ] = None,
1615
1625
  dry_run: Annotated[
1616
1626
  bool,
1617
1627
  typer.Option(
@@ -1700,7 +1710,14 @@ def run(
1700
1710
  resolved_model = model if model is not None else config.agent.model
1701
1711
  resolved_max_turns = max_turns if max_turns is not None else config.agent.max_turns
1702
1712
  resolved_timeout = timeout if timeout is not None else config.agent.timeout_seconds
1703
- resolved_worktrees = worktrees if worktrees is not None else config.worktrees.enabled
1713
+ # Resolve worktree config: CLI flag overrides config file enabled state
1714
+ resolved_worktree_config = config.worktrees.model_copy()
1715
+ if worktrees is not None:
1716
+ resolved_worktree_config.enabled = worktrees
1717
+ # Resolve evidence config: CLI flag overrides config file enabled state
1718
+ resolved_evidence_config = config.evidence.model_copy()
1719
+ if evidence is not None:
1720
+ resolved_evidence_config.enabled = evidence
1704
1721
 
1705
1722
  # API key: CLI > env (via envvar) > config env var
1706
1723
  resolved_api_key = api_key
@@ -1711,7 +1728,12 @@ def run(
1711
1728
  if not resolved_workflow_id:
1712
1729
  resolved_workflow_id = config.agent.workflow_id
1713
1730
 
1714
- worktree_status = "enabled" if resolved_worktrees else "disabled"
1731
+ worktree_status = (
1732
+ f"enabled ({resolved_worktree_config.provider})"
1733
+ if resolved_worktree_config.enabled
1734
+ else "disabled"
1735
+ )
1736
+ evidence_status = "enabled" if resolved_evidence_config.enabled else "disabled"
1715
1737
  dry_run_status = "enabled" if dry_run else "disabled"
1716
1738
  waves_status = "enabled" if waves else "disabled"
1717
1739
  canals_status = "enabled" if canals else "disabled"
@@ -1732,6 +1754,7 @@ def run(
1732
1754
  f"Waves: {waves_status}\n"
1733
1755
  f"Canals: {canals_status}\n"
1734
1756
  f"Worktrees: {worktree_status}\n"
1757
+ f"Evidence: {evidence_status}\n"
1735
1758
  f"Dry Run: {dry_run_status}",
1736
1759
  title="Starting",
1737
1760
  )
@@ -1762,7 +1785,8 @@ def run(
1762
1785
  timeout_seconds=resolved_timeout,
1763
1786
  enable_waves=waves,
1764
1787
  enable_canals=canals,
1765
- enable_worktrees=resolved_worktrees,
1788
+ worktree_config=resolved_worktree_config,
1789
+ evidence_config=resolved_evidence_config,
1766
1790
  force_workflow_id=resolved_workflow_id,
1767
1791
  dry_run=dry_run,
1768
1792
  retry_config=retry_config,
@@ -1911,6 +1935,13 @@ def agent(
1911
1935
  help="Enable canal merge flow selection (default: disabled)",
1912
1936
  ),
1913
1937
  ] = False,
1938
+ evidence: Annotated[
1939
+ bool | None,
1940
+ typer.Option(
1941
+ "--evidence/--no-evidence",
1942
+ help="Submit evidence reports after task completion (default: from config or disabled)",
1943
+ ),
1944
+ ] = None,
1914
1945
  ) -> None:
1915
1946
  """Run the agent in persistent mode.
1916
1947
 
@@ -1970,7 +2001,10 @@ def agent(
1970
2001
  if gap_seconds is not None:
1971
2002
  agent_loop_config.gap_seconds = gap_seconds
1972
2003
 
1973
- resolved_worktrees = config.worktrees.enabled
2004
+ resolved_worktree_config = config.worktrees.model_copy()
2005
+ resolved_evidence_config = config.evidence.model_copy()
2006
+ if evidence is not None:
2007
+ resolved_evidence_config.enabled = evidence
1974
2008
 
1975
2009
  # Resolve retry config: CLI > config > default
1976
2010
  retry_config = config.retry.model_copy()
@@ -2002,7 +2036,8 @@ def agent(
2002
2036
  force_workflow_id=workflow_id,
2003
2037
  enable_waves=waves,
2004
2038
  enable_canals=canals,
2005
- enable_worktrees=resolved_worktrees,
2039
+ worktree_config=resolved_worktree_config,
2040
+ evidence_config=resolved_evidence_config,
2006
2041
  retry_config=retry_config,
2007
2042
  )
2008
2043
  )
@@ -2042,7 +2077,8 @@ def agent(
2042
2077
  force_workflow_id=resolved_workflow_id,
2043
2078
  enable_waves=waves,
2044
2079
  enable_canals=canals,
2045
- enable_worktrees=resolved_worktrees,
2080
+ worktree_config=resolved_worktree_config,
2081
+ evidence_config=resolved_evidence_config,
2046
2082
  retry_config=retry_config,
2047
2083
  )
2048
2084
  )
@@ -2207,6 +2243,32 @@ def _setup_repos(
2207
2243
  )
2208
2244
 
2209
2245
 
2246
+ def _display_dependency_check(deps: list) -> None:
2247
+ """Display CLI dependency check results as a table."""
2248
+ from rich.table import Table
2249
+
2250
+ table = Table(title="CLI Dependencies", show_header=True, header_style="bold")
2251
+ table.add_column("Tool", style="cyan")
2252
+ table.add_column("Status")
2253
+ table.add_column("Details", style="dim")
2254
+
2255
+ for dep in deps:
2256
+ if dep.found:
2257
+ status = "[green]Found[/green]"
2258
+ details = dep.version or dep.path or ""
2259
+ elif dep.required:
2260
+ status = "[red]Missing (required)[/red]"
2261
+ details = f"Install: {dep.install_hint}"
2262
+ else:
2263
+ status = "[yellow]Missing (optional)[/yellow]"
2264
+ details = f"Install: {dep.install_hint}"
2265
+ table.add_row(dep.name, status, details)
2266
+
2267
+ console.print()
2268
+ console.print(table)
2269
+ console.print()
2270
+
2271
+
2210
2272
  def _prompt_install_target() -> str:
2211
2273
  """Prompt the user to choose where to install Claude configs."""
2212
2274
  choices = {
@@ -2324,9 +2386,21 @@ def setup(
2324
2386
  - user: ~/.claude/ in your home directory (shared across all projects)
2325
2387
  """
2326
2388
  from steerdev_agent.setup import ClaudeSetup
2389
+ from steerdev_agent.setup.claude_setup import check_cli_dependencies
2327
2390
 
2328
2391
  target_dir = project_dir or Path.cwd()
2329
2392
 
2393
+ # ── Check CLI dependencies ──
2394
+ deps = check_cli_dependencies()
2395
+ _display_dependency_check(deps)
2396
+
2397
+ missing_required = [d for d in deps if d.required and not d.found]
2398
+ if missing_required:
2399
+ console.print(
2400
+ "\n[red]Missing required dependencies. Install them before running setup.[/red]"
2401
+ )
2402
+ raise typer.Exit(1)
2403
+
2330
2404
  # Prompt for install target if not provided
2331
2405
  if install_target is None:
2332
2406
  install_target = _prompt_install_target()
@@ -10,15 +10,45 @@ from steerdev_agent.api.client import DEFAULT_API_ENDPOINT
10
10
 
11
11
 
12
12
  class WorktreeConfig(BaseModel):
13
- """Git worktree isolation configuration.
13
+ """Worktree isolation configuration using worktrunk.dev (wt CLI).
14
14
 
15
- When enabled, the Claude CLI --worktree flag is used to run each task
16
- in an isolated git worktree. Worktree lifecycle is managed by Claude CLI.
15
+ When enabled with provider "worktrunk", `wt switch --create <branch>`
16
+ provisions isolated worktrees with lifecycle hooks (env setup, pre-merge checks).
17
+ Legacy provider "claude" uses Claude CLI's --worktree flag directly.
17
18
  """
18
19
 
19
20
  enabled: Annotated[
20
21
  bool,
21
- Field(default=False, description="Enable Claude CLI --worktree isolation per task"),
22
+ Field(default=False, description="Enable worktree isolation per task/wave"),
23
+ ]
24
+ provider: Annotated[
25
+ str,
26
+ Field(
27
+ default="worktrunk",
28
+ description='Worktree provider: "worktrunk" (recommended) or "claude" (legacy --worktree flag)',
29
+ ),
30
+ ]
31
+ cleanup_on_complete: Annotated[
32
+ bool,
33
+ Field(default=True, description="Remove worktree after successful task completion"),
34
+ ]
35
+ cleanup_on_failure: Annotated[
36
+ bool,
37
+ Field(default=False, description="Remove worktree after task failure"),
38
+ ]
39
+ copy_gitignored: Annotated[
40
+ list[str],
41
+ Field(
42
+ default_factory=lambda: [".env", ".env.local"],
43
+ description="Gitignored files to copy into new worktrees (via wt step)",
44
+ ),
45
+ ]
46
+ pre_merge_checks: Annotated[
47
+ list[str],
48
+ Field(
49
+ default_factory=list,
50
+ description='Commands to run in pre-merge hook (e.g., ["pnpm check", "pnpm typecheck"])',
51
+ ),
22
52
  ]
23
53
 
24
54
 
@@ -216,6 +246,19 @@ class CanalConfig(BaseModel):
216
246
  ]
217
247
 
218
248
 
249
+ class EvidenceConfig(BaseModel):
250
+ """Evidence report configuration.
251
+
252
+ When enabled, the agent submits evidence reports to the platform after
253
+ completing tasks or workflow phases. Reports appear on the project review page.
254
+ """
255
+
256
+ enabled: Annotated[
257
+ bool,
258
+ Field(default=False, description="Submit evidence reports after task completion"),
259
+ ]
260
+
261
+
219
262
  class RetryConfig(BaseModel):
220
263
  """Retry configuration for failed task/session execution."""
221
264
 
@@ -295,6 +338,13 @@ class SteerDevConfig(BaseModel):
295
338
  WorkspaceConfig,
296
339
  Field(default_factory=WorkspaceConfig, description="Workspace agent configuration"),
297
340
  ]
341
+ evidence: Annotated[
342
+ EvidenceConfig,
343
+ Field(
344
+ default_factory=EvidenceConfig,
345
+ description="Evidence report configuration for project review",
346
+ ),
347
+ ]
298
348
  retry: Annotated[
299
349
  RetryConfig,
300
350
  Field(default_factory=RetryConfig, description="Retry configuration for failed tasks"),