steerdev 1.1.26__tar.gz → 1.1.28__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 (125) hide show
  1. {steerdev-1.1.26 → steerdev-1.1.28}/PKG-INFO +1 -1
  2. {steerdev-1.1.26 → steerdev-1.1.28}/pyproject.toml +1 -1
  3. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/agent_loop.py +24 -2
  4. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/runner.py +24 -0
  5. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/workspace/preparation.py +7 -0
  6. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_agent_loop.py +31 -0
  7. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_preparation.py +9 -1
  8. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_runner_worktrees.py +24 -0
  9. {steerdev-1.1.26 → steerdev-1.1.28}/.github/workflows/pre-commit.yml +0 -0
  10. {steerdev-1.1.26 → steerdev-1.1.28}/.github/workflows/publish.yml +0 -0
  11. {steerdev-1.1.26 → steerdev-1.1.28}/.gitignore +0 -0
  12. {steerdev-1.1.26 → steerdev-1.1.28}/.pre-commit-config.yaml +0 -0
  13. {steerdev-1.1.26 → steerdev-1.1.28}/AGENTS.md +0 -0
  14. {steerdev-1.1.26 → steerdev-1.1.28}/CLAUDE.md +0 -0
  15. {steerdev-1.1.26 → steerdev-1.1.28}/README.md +0 -0
  16. {steerdev-1.1.26 → steerdev-1.1.28}/docs/superpowers/plans/2026-06-05-wave-id-implies-unlimited-max-tasks.md +0 -0
  17. {steerdev-1.1.26 → steerdev-1.1.28}/docs/superpowers/specs/2026-06-05-wave-id-implies-unlimited-max-tasks-design.md +0 -0
  18. {steerdev-1.1.26 → steerdev-1.1.28}/scripts/pre-commit-version-bump.sh +0 -0
  19. {steerdev-1.1.26 → steerdev-1.1.28}/snapshots/steerdev-agent-v1/Dockerfile +0 -0
  20. {steerdev-1.1.26 → steerdev-1.1.28}/snapshots/steerdev-agent-v1/start-agent.sh +0 -0
  21. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/__init__.py +0 -0
  22. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/api/__init__.py +0 -0
  23. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/api/activity.py +0 -0
  24. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/api/agents.py +0 -0
  25. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/api/canals.py +0 -0
  26. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/api/client.py +0 -0
  27. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/api/commands.py +0 -0
  28. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/api/configs.py +0 -0
  29. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/api/context.py +0 -0
  30. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/api/events.py +0 -0
  31. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/api/hooks.py +0 -0
  32. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/api/implementation_plan.py +0 -0
  33. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/api/merger.py +0 -0
  34. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/api/messages.py +0 -0
  35. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/api/prd.py +0 -0
  36. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/api/reports.py +0 -0
  37. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/api/runs.py +0 -0
  38. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/api/sessions.py +0 -0
  39. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/api/specs.py +0 -0
  40. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/api/tasks.py +0 -0
  41. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/api/workflow_runs.py +0 -0
  42. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/api/workflows.py +0 -0
  43. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/cli.py +0 -0
  44. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/config/__init__.py +0 -0
  45. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/config/models.py +0 -0
  46. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/config/platform.py +0 -0
  47. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/config/settings.py +0 -0
  48. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/evidence.py +0 -0
  49. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/evidence_assets.py +0 -0
  50. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/executor/__init__.py +0 -0
  51. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/executor/base.py +0 -0
  52. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/executor/claude.py +0 -0
  53. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/executor/stream.py +0 -0
  54. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/git_ops.py +0 -0
  55. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/handlers/__init__.py +0 -0
  56. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/handlers/prd.py +0 -0
  57. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/integration.py +0 -0
  58. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/prompt/__init__.py +0 -0
  59. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/prompt/builder.py +0 -0
  60. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/prompt/templates.py +0 -0
  61. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/prompt/workflow_template.py +0 -0
  62. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/py.typed +0 -0
  63. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/retry.py +0 -0
  64. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/setup/__init__.py +0 -0
  65. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/setup/claude_setup.py +0 -0
  66. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/setup/repo_setup.py +0 -0
  67. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/setup/templates/ci/canal-integration.yml +0 -0
  68. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/setup/templates/claude_md_section.md +0 -0
  69. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/setup/templates/settings.json +0 -0
  70. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/setup/templates/skills/steerdev-activity-skill/SKILL.md +0 -0
  71. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/setup/templates/skills/steerdev-canal-workflow-skill/SKILL.md +0 -0
  72. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/setup/templates/skills/steerdev-context-skill/SKILL.md +0 -0
  73. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/setup/templates/skills/steerdev-git-workflow-skill/SKILL.md +0 -0
  74. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/setup/templates/skills/steerdev-merge-into-canal-skill/SKILL.md +0 -0
  75. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/setup/templates/skills/steerdev-progress-logging-skill/SKILL.md +0 -0
  76. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/setup/templates/skills/steerdev-single-task-merge-skill/SKILL.md +0 -0
  77. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/setup/templates/skills/steerdev-specs-management-skill/SKILL.md +0 -0
  78. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/setup/templates/skills/steerdev-task-management-skill/SKILL.md +0 -0
  79. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/setup/templates/skills/steerdev-wave-tasks-merge-skill/SKILL.md +0 -0
  80. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/setup/templates/steerdev.yaml +0 -0
  81. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/setup/templates/worktrunk.config.toml +0 -0
  82. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/update_check.py +0 -0
  83. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/version.py +0 -0
  84. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/workflow/__init__.py +0 -0
  85. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/workflow/context.py +0 -0
  86. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/workflow/executor.py +0 -0
  87. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/workflow/memory.py +0 -0
  88. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/workspace/__init__.py +0 -0
  89. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/workspace/project_manager.py +0 -0
  90. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/workspace/tool_detection.py +0 -0
  91. {steerdev-1.1.26 → steerdev-1.1.28}/src/steerdev_agent/worktree.py +0 -0
  92. {steerdev-1.1.26 → steerdev-1.1.28}/tests/__init__.py +0 -0
  93. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_agent_loop_extended.py +0 -0
  94. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_agents_api.py +0 -0
  95. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_api_client.py +0 -0
  96. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_claude_executor.py +0 -0
  97. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_claude_setup.py +0 -0
  98. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_client_methods.py +0 -0
  99. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_commands_api.py +0 -0
  100. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_config.py +0 -0
  101. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_config_extended.py +0 -0
  102. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_conflict_mitigation.py +0 -0
  103. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_context_search.py +0 -0
  104. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_executor.py +0 -0
  105. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_git_ops.py +0 -0
  106. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_hooks_post_tool_use.py +0 -0
  107. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_platform_config.py +0 -0
  108. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_preparation_provenance.py +0 -0
  109. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_prompt.py +0 -0
  110. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_reports_client.py +0 -0
  111. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_resolve_max_tasks.py +0 -0
  112. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_retry.py +0 -0
  113. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_runner_merge_modes.py +0 -0
  114. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_stream_parser.py +0 -0
  115. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_tasks.py +0 -0
  116. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_tasks_update_branch.py +0 -0
  117. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_version.py +0 -0
  118. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_wave_scoped_fetch.py +0 -0
  119. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_workflow_context.py +0 -0
  120. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_workflow_memory.py +0 -0
  121. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_workflow_prompt_template.py +0 -0
  122. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_workflow_runs_api.py +0 -0
  123. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_workspace.py +0 -0
  124. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_workspace_extended.py +0 -0
  125. {steerdev-1.1.26 → steerdev-1.1.28}/tests/test_worktree.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: steerdev
3
- Version: 1.1.26
3
+ Version: 1.1.28
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.1.26"
3
+ version = "1.1.28"
4
4
  description = "Backend task runner for steerdev.com - orchestrates CLI coding agents with activity reporting"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -582,10 +582,15 @@ class AgentLoop(CommandExecutor):
582
582
  # Step 2: Recover stale commands
583
583
  await self._recover_stale_commands()
584
584
 
585
- # Step 3: Start heartbeat
585
+ # Step 3: Reconcile .claude config (hooks) for this checkout, so a
586
+ # repo that predates newer steerdev hooks (e.g. the PostToolUse
587
+ # checkpoint-push hook) self-heals before any task launches Claude.
588
+ self._reconcile_settings()
589
+
590
+ # Step 4: Start heartbeat
586
591
  self._heartbeat_task = asyncio.create_task(self._heartbeat_loop())
587
592
 
588
- # Step 4: Main poll loop
593
+ # Step 5: Main poll loop
589
594
  await self._poll_loop()
590
595
 
591
596
  except AgentLoopError as e:
@@ -647,6 +652,23 @@ class AgentLoop(CommandExecutor):
647
652
  )
648
653
  await self._commands_client.mark_pending(cmd.id)
649
654
 
655
+ def _reconcile_settings(self) -> None:
656
+ """Re-apply steerdev-owned .claude/settings.json hooks for this checkout.
657
+
658
+ Single-project mode runs against an already-existing repo that never
659
+ went through project bootstrap, so its .claude config can be missing or
660
+ predate newer steerdev hooks. Reconciling here (idempotent, best-effort)
661
+ ensures the PostToolUse checkpoint-push hook is present before Claude
662
+ launches. A setup failure must never block the agent.
663
+ """
664
+ from steerdev_agent.setup.claude_setup import ClaudeSetup
665
+
666
+ try:
667
+ setup = ClaudeSetup(self.working_directory, install_target="project")
668
+ setup.setup_settings(force=False, include_hooks=True)
669
+ except Exception:
670
+ logger.exception(f"Failed to reconcile .claude settings for {self.working_directory}")
671
+
650
672
  async def _poll_loop(self) -> None:
651
673
  """Main loop: poll for commands and execute them."""
652
674
  logger.info("Entering poll loop")
@@ -1109,6 +1109,23 @@ class Runner:
1109
1109
  except Exception:
1110
1110
  logger.debug(f"Failed to cleanup worktree {worktree_branch}", exc_info=True)
1111
1111
 
1112
+ def _reconcile_settings(self) -> None:
1113
+ """Re-apply steerdev-owned .claude/settings.json hooks for this checkout.
1114
+
1115
+ `steerdev run` operates on an existing or freshly-cloned repo that never
1116
+ went through project bootstrap (notably sandbox dispatch, which clones
1117
+ and immediately runs). Reconciling here (idempotent, best-effort)
1118
+ installs/heals the PostToolUse checkpoint-push hook before Claude
1119
+ launches, so commits push. A setup failure must never block the run.
1120
+ """
1121
+ from steerdev_agent.setup.claude_setup import ClaudeSetup
1122
+
1123
+ try:
1124
+ setup = ClaudeSetup(self.working_directory, install_target="project")
1125
+ setup.setup_settings(force=False, include_hooks=True)
1126
+ except Exception:
1127
+ logger.exception(f"Failed to reconcile .claude settings for {self.working_directory}")
1128
+
1112
1129
  async def run(self) -> dict[str, Any]:
1113
1130
  """Run the agent.
1114
1131
 
@@ -1137,6 +1154,13 @@ class Runner:
1137
1154
  try:
1138
1155
  await self._notify_state_change(RunState.RUNNING)
1139
1156
 
1157
+ # Reconcile .claude config (hooks) for this checkout before any task
1158
+ # launches Claude. `steerdev run` (used by sandbox dispatch) operates
1159
+ # on a fresh clone with no .claude config, so without this the
1160
+ # PostToolUse checkpoint-push hook is absent and sandbox commits
1161
+ # never push. Idempotent and best-effort.
1162
+ self._reconcile_settings()
1163
+
1140
1164
  from steerdev_agent.retry import execute_with_retry
1141
1165
 
1142
1166
  # Multi-task loop
@@ -322,6 +322,13 @@ class WorkspacePreparation:
322
322
  if wave_context and current_branch == target_branch:
323
323
  logger.info(f"Continuing wave on branch {target_branch}, fetching only")
324
324
  await _run_git("fetch", "origin", cwd=cwd, check=False)
325
+ # Persist provenance here too: a wave-continuation task arrives
326
+ # already on the branch, so it skips the create/checkout path
327
+ # below where provenance is normally written. Without this, the
328
+ # second+ task in a wave never records its branch on the task.
329
+ await persist_branch_provenance(
330
+ task_id=task_id, branch=target_branch, base_branch=default_branch
331
+ )
325
332
  return PreparationResult(
326
333
  success=True,
327
334
  branch_name=target_branch,
@@ -72,6 +72,37 @@ class TestAgentLoopError:
72
72
  assert str(err) == "test error"
73
73
 
74
74
 
75
+ class TestAgentLoopReconcileSettings:
76
+ """Tests for single-project AgentLoop hook reconciliation."""
77
+
78
+ def _make_agent_loop(self, working_directory):
79
+ from steerdev_agent.agent_loop import AgentLoop
80
+
81
+ return AgentLoop(
82
+ project_id="proj-1",
83
+ agent_name="test-agent",
84
+ working_directory=working_directory,
85
+ api_key="test-key",
86
+ )
87
+
88
+ def test_reconciles_settings_at_working_directory(self, tmp_path):
89
+ agent = self._make_agent_loop(tmp_path)
90
+
91
+ with patch("steerdev_agent.setup.claude_setup.ClaudeSetup") as cls:
92
+ agent._reconcile_settings()
93
+
94
+ cls.assert_called_once_with(tmp_path, install_target="project")
95
+ cls.return_value.setup_settings.assert_called_once_with(force=False, include_hooks=True)
96
+
97
+ def test_reconcile_swallows_errors(self, tmp_path):
98
+ agent = self._make_agent_loop(tmp_path)
99
+
100
+ with patch("steerdev_agent.setup.claude_setup.ClaudeSetup") as cls:
101
+ cls.return_value.setup_settings.side_effect = RuntimeError("disk full")
102
+ # Must not raise — durability must never block the agent.
103
+ agent._reconcile_settings()
104
+
105
+
75
106
  class TestCommandExecutor:
76
107
  """Tests for CommandExecutor._execute_command dispatch logic."""
77
108
 
@@ -232,7 +232,10 @@ class TestWorkspacePreparationInPlace:
232
232
  return (0, "", "")
233
233
  return (0, "", "")
234
234
 
235
- with patch("steerdev_agent.workspace.preparation._run_git", side_effect=mock_run_git):
235
+ with (
236
+ patch("steerdev_agent.workspace.preparation._run_git", side_effect=mock_run_git),
237
+ patch("steerdev_agent.workspace.preparation.persist_branch_provenance") as mock_persist,
238
+ ):
236
239
  result = await prep.prepare(sample_task, wave_ctx)
237
240
 
238
241
  assert result.success is True
@@ -240,6 +243,11 @@ class TestWorkspacePreparationInPlace:
240
243
  # Should NOT have checkout commands (stayed on branch)
241
244
  checkout_cmds = [c for c in calls if c[0] == "checkout"]
242
245
  assert len(checkout_cmds) == 0
246
+ # Wave-continuation must still record branch provenance on the task,
247
+ # even though it skips the create/checkout path.
248
+ mock_persist.assert_awaited_once_with(
249
+ task_id=sample_task["id"], branch="wave-a1b2c3", base_branch="main"
250
+ )
243
251
 
244
252
  @pytest.mark.asyncio
245
253
  async def test_dirty_repo_stashes(self, worktree_disabled, default_config, sample_task):
@@ -1,5 +1,8 @@
1
1
  """Tests for worktree configuration and Runner worktree integration."""
2
2
 
3
+ from pathlib import Path
4
+ from unittest.mock import patch
5
+
3
6
  from steerdev_agent.config.models import EvidenceConfig, WorktreeConfig
4
7
  from steerdev_agent.prompt.builder import WaveContext
5
8
  from steerdev_agent.runner import Runner
@@ -50,6 +53,27 @@ class TestComputeBranchName:
50
53
  assert compute_branch_name(task) == "task/JIRA-456-fix"
51
54
 
52
55
 
56
+ class TestRunnerReconcileSettings:
57
+ """Hook reconciliation for `steerdev run` (sandbox + local)."""
58
+
59
+ def test_reconciles_settings_at_working_directory(self):
60
+ r = Runner(project_id="p", api_key="k", working_directory=Path("/code/repo"))
61
+
62
+ with patch("steerdev_agent.setup.claude_setup.ClaudeSetup") as cls:
63
+ r._reconcile_settings()
64
+
65
+ cls.assert_called_once_with(Path("/code/repo"), install_target="project")
66
+ cls.return_value.setup_settings.assert_called_once_with(force=False, include_hooks=True)
67
+
68
+ def test_reconcile_swallows_errors(self):
69
+ r = Runner(project_id="p", api_key="k", working_directory=Path("/code/repo"))
70
+
71
+ with patch("steerdev_agent.setup.claude_setup.ClaudeSetup") as cls:
72
+ cls.return_value.setup_settings.side_effect = RuntimeError("disk full")
73
+ # Must not raise — durability must never block the run.
74
+ r._reconcile_settings()
75
+
76
+
53
77
  class TestRunnerWorktreeConfig:
54
78
  def test_disabled_by_default(self):
55
79
  r = Runner(project_id="p", api_key="k")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes