steerdev 1.1.25__tar.gz → 1.1.27__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.1.25 → steerdev-1.1.27}/PKG-INFO +1 -1
- {steerdev-1.1.25 → steerdev-1.1.27}/pyproject.toml +1 -1
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/agent_loop.py +24 -2
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/setup/claude_setup.py +46 -4
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/workspace/preparation.py +7 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/workspace/project_manager.py +17 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_agent_loop.py +31 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_claude_setup.py +70 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_preparation.py +9 -1
- {steerdev-1.1.25 → steerdev-1.1.27}/.github/workflows/pre-commit.yml +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/.github/workflows/publish.yml +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/.gitignore +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/.pre-commit-config.yaml +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/AGENTS.md +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/CLAUDE.md +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/README.md +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/docs/superpowers/plans/2026-06-05-wave-id-implies-unlimited-max-tasks.md +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/docs/superpowers/specs/2026-06-05-wave-id-implies-unlimited-max-tasks-design.md +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/scripts/pre-commit-version-bump.sh +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/snapshots/steerdev-agent-v1/Dockerfile +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/snapshots/steerdev-agent-v1/start-agent.sh +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/__init__.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/api/__init__.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/api/activity.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/api/agents.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/api/canals.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/api/client.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/api/commands.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/api/configs.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/api/context.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/api/events.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/api/hooks.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/api/implementation_plan.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/api/merger.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/api/messages.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/api/prd.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/api/reports.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/api/runs.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/api/sessions.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/api/specs.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/api/tasks.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/api/workflow_runs.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/api/workflows.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/cli.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/config/__init__.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/config/models.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/config/platform.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/config/settings.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/evidence.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/evidence_assets.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/executor/__init__.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/executor/base.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/executor/claude.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/executor/stream.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/git_ops.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/handlers/__init__.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/handlers/prd.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/integration.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/prompt/__init__.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/prompt/builder.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/prompt/templates.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/prompt/workflow_template.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/py.typed +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/retry.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/runner.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/setup/__init__.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/setup/repo_setup.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/setup/templates/ci/canal-integration.yml +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/setup/templates/claude_md_section.md +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/setup/templates/settings.json +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/setup/templates/skills/steerdev-activity-skill/SKILL.md +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/setup/templates/skills/steerdev-canal-workflow-skill/SKILL.md +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/setup/templates/skills/steerdev-context-skill/SKILL.md +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/setup/templates/skills/steerdev-git-workflow-skill/SKILL.md +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/setup/templates/skills/steerdev-merge-into-canal-skill/SKILL.md +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/setup/templates/skills/steerdev-progress-logging-skill/SKILL.md +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/setup/templates/skills/steerdev-single-task-merge-skill/SKILL.md +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/setup/templates/skills/steerdev-specs-management-skill/SKILL.md +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/setup/templates/skills/steerdev-task-management-skill/SKILL.md +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/setup/templates/skills/steerdev-wave-tasks-merge-skill/SKILL.md +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/setup/templates/steerdev.yaml +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/setup/templates/worktrunk.config.toml +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/update_check.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/version.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/workflow/__init__.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/workflow/context.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/workflow/executor.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/workflow/memory.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/workspace/__init__.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/workspace/tool_detection.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/worktree.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/__init__.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_agent_loop_extended.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_agents_api.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_api_client.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_claude_executor.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_client_methods.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_commands_api.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_config.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_config_extended.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_conflict_mitigation.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_context_search.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_executor.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_git_ops.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_hooks_post_tool_use.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_platform_config.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_preparation_provenance.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_prompt.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_reports_client.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_resolve_max_tasks.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_retry.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_runner_merge_modes.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_runner_worktrees.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_stream_parser.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_tasks.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_tasks_update_branch.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_version.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_wave_scoped_fetch.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_workflow_context.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_workflow_memory.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_workflow_prompt_template.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_workflow_runs_api.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_workspace.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_workspace_extended.py +0 -0
- {steerdev-1.1.25 → steerdev-1.1.27}/tests/test_worktree.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: steerdev
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.27
|
|
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
|
|
@@ -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:
|
|
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
|
|
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")
|
|
@@ -14,6 +14,43 @@ from loguru import logger
|
|
|
14
14
|
|
|
15
15
|
from steerdev_agent.config.platform import PlatformConfig
|
|
16
16
|
|
|
17
|
+
# Commands owned by steerdev are reconciled to the template; anything else in
|
|
18
|
+
# an existing settings file is treated as user-added and left untouched.
|
|
19
|
+
_STEERDEV_HOOK_PREFIX = "steerdev hooks"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _is_steerdev_hook_entry(entry: dict) -> bool:
|
|
23
|
+
"""Return True if a hook matcher-group entry is steerdev-owned.
|
|
24
|
+
|
|
25
|
+
An entry is steerdev-owned when any of its commands invoke
|
|
26
|
+
`steerdev hooks ...`. Used to reconcile only our own hooks while
|
|
27
|
+
preserving user-added hooks on the same event.
|
|
28
|
+
"""
|
|
29
|
+
for hook in entry.get("hooks", []):
|
|
30
|
+
if isinstance(hook, dict) and str(hook.get("command", "")).strip().startswith(
|
|
31
|
+
_STEERDEV_HOOK_PREFIX
|
|
32
|
+
):
|
|
33
|
+
return True
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _merge_steerdev_hooks(existing_hooks: dict, template_hooks: dict) -> None:
|
|
38
|
+
"""Reconcile steerdev-owned hooks into *existing_hooks* in place.
|
|
39
|
+
|
|
40
|
+
For each event in the template, drop any existing steerdev-owned entries
|
|
41
|
+
(so stale/missing definitions are replaced) while keeping user-added
|
|
42
|
+
entries, then append the template's entries. Events not in the template
|
|
43
|
+
are left untouched.
|
|
44
|
+
"""
|
|
45
|
+
for event, template_entries in template_hooks.items():
|
|
46
|
+
user_entries = [
|
|
47
|
+
entry
|
|
48
|
+
for entry in existing_hooks.get(event, [])
|
|
49
|
+
if isinstance(entry, dict) and not _is_steerdev_hook_entry(entry)
|
|
50
|
+
]
|
|
51
|
+
existing_hooks[event] = user_entries + template_entries
|
|
52
|
+
|
|
53
|
+
|
|
17
54
|
# ---------------------------------------------------------------------------
|
|
18
55
|
# CLI dependency checking
|
|
19
56
|
# ---------------------------------------------------------------------------
|
|
@@ -319,13 +356,18 @@ class ClaudeSetup:
|
|
|
319
356
|
if rule not in existing["permissions"]["allow"]:
|
|
320
357
|
existing["permissions"]["allow"].append(rule)
|
|
321
358
|
|
|
322
|
-
#
|
|
359
|
+
# Reconcile hooks configuration if include_hooks=True.
|
|
360
|
+
#
|
|
361
|
+
# The template hooks are steerdev-owned (their commands all start
|
|
362
|
+
# with `steerdev hooks`). We reconcile them by command identity so
|
|
363
|
+
# that pre-existing settings files self-heal when new steerdev
|
|
364
|
+
# hooks are added (e.g. the PostToolUse checkpoint-push hook) or
|
|
365
|
+
# when an existing steerdev hook's definition changes. Any
|
|
366
|
+
# user-added hooks on the same event are preserved untouched.
|
|
323
367
|
if include_hooks and "hooks" in new_settings:
|
|
324
368
|
if "hooks" not in existing:
|
|
325
369
|
existing["hooks"] = {}
|
|
326
|
-
|
|
327
|
-
if hook_event not in existing["hooks"]:
|
|
328
|
-
existing["hooks"][hook_event] = hook_configs
|
|
370
|
+
_merge_steerdev_hooks(existing["hooks"], new_settings["hooks"])
|
|
329
371
|
|
|
330
372
|
settings_path.write_text(json.dumps(existing, indent=2) + "\n")
|
|
331
373
|
logger.info(f"Updated existing settings: {settings_path}")
|
|
@@ -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,
|
|
@@ -45,6 +45,10 @@ class ProjectManager:
|
|
|
45
45
|
local_path = self._resolve_path(assignment)
|
|
46
46
|
if local_path and local_path.is_dir():
|
|
47
47
|
self._path_cache[assignment.project_id] = local_path
|
|
48
|
+
# Existing checkout: reconcile .claude config so settings files
|
|
49
|
+
# that predate newer steerdev hooks (e.g. the PostToolUse
|
|
50
|
+
# checkpoint-push hook) self-heal on every dispatch.
|
|
51
|
+
self._reconcile_settings(local_path)
|
|
48
52
|
continue
|
|
49
53
|
|
|
50
54
|
# Project not found locally — bootstrap it
|
|
@@ -55,6 +59,19 @@ class ProjectManager:
|
|
|
55
59
|
except Exception:
|
|
56
60
|
logger.exception(f"Failed to setup project {assignment.project_id}")
|
|
57
61
|
|
|
62
|
+
def _reconcile_settings(self, project_dir: Path) -> None:
|
|
63
|
+
"""Re-apply steerdev-owned .claude/settings.json hooks for an existing
|
|
64
|
+
checkout. Idempotent and best-effort: a setup failure must never block
|
|
65
|
+
an otherwise-usable project.
|
|
66
|
+
"""
|
|
67
|
+
from steerdev_agent.setup.claude_setup import ClaudeSetup
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
setup = ClaudeSetup(project_dir, install_target="project")
|
|
71
|
+
setup.setup_settings(force=False, include_hooks=True)
|
|
72
|
+
except Exception:
|
|
73
|
+
logger.exception(f"Failed to reconcile .claude settings for {project_dir}")
|
|
74
|
+
|
|
58
75
|
async def setup_project(self, assignment: ProjectAssignment) -> Path | None:
|
|
59
76
|
"""Bootstrap a project using existing setup primitives.
|
|
60
77
|
|
|
@@ -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
|
|
|
@@ -51,6 +51,76 @@ class TestClaudeSetup:
|
|
|
51
51
|
) # skill now references merge skills by name
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
class TestSettingsHookReconciliation:
|
|
55
|
+
"""Tests for steerdev-owned hook reconciliation in setup_settings."""
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def _commands(settings: dict, event: str) -> list[str]:
|
|
59
|
+
commands = []
|
|
60
|
+
for entry in settings.get("hooks", {}).get(event, []):
|
|
61
|
+
for hook in entry.get("hooks", []):
|
|
62
|
+
commands.append(hook.get("command"))
|
|
63
|
+
return commands
|
|
64
|
+
|
|
65
|
+
def test_fresh_create_includes_post_tool_use(self, tmp_path):
|
|
66
|
+
import json
|
|
67
|
+
|
|
68
|
+
setup = ClaudeSetup(project_dir=tmp_path)
|
|
69
|
+
|
|
70
|
+
path = setup.setup_settings(force=False, include_hooks=True)
|
|
71
|
+
settings = json.loads(path.read_text())
|
|
72
|
+
|
|
73
|
+
assert "steerdev hooks post-tool-use" in self._commands(settings, "PostToolUse")
|
|
74
|
+
|
|
75
|
+
def test_stale_file_self_heals_post_tool_use(self, tmp_path):
|
|
76
|
+
"""A settings file predating the PostToolUse hook gains it on re-run."""
|
|
77
|
+
import json
|
|
78
|
+
|
|
79
|
+
stale = {
|
|
80
|
+
"permissions": {"allow": ["Bash(git commit:*)"]},
|
|
81
|
+
"hooks": {
|
|
82
|
+
"SessionStart": [
|
|
83
|
+
{"hooks": [{"type": "command", "command": "steerdev hooks session-start"}]}
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
claude_dir = tmp_path / ".claude"
|
|
88
|
+
claude_dir.mkdir(parents=True)
|
|
89
|
+
(claude_dir / "settings.json").write_text(json.dumps(stale))
|
|
90
|
+
|
|
91
|
+
setup = ClaudeSetup(project_dir=tmp_path)
|
|
92
|
+
path = setup.setup_settings(force=False, include_hooks=True)
|
|
93
|
+
settings = json.loads(path.read_text())
|
|
94
|
+
|
|
95
|
+
assert "steerdev hooks post-tool-use" in self._commands(settings, "PostToolUse")
|
|
96
|
+
# Pre-existing steerdev hook is reconciled, not duplicated.
|
|
97
|
+
assert self._commands(settings, "SessionStart").count("steerdev hooks session-start") == 1
|
|
98
|
+
|
|
99
|
+
def test_user_added_hooks_preserved(self, tmp_path):
|
|
100
|
+
import json
|
|
101
|
+
|
|
102
|
+
existing = {
|
|
103
|
+
"hooks": {
|
|
104
|
+
"PostToolUse": [
|
|
105
|
+
{
|
|
106
|
+
"matcher": "Write",
|
|
107
|
+
"hooks": [{"type": "command", "command": "my-custom-linter"}],
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
claude_dir = tmp_path / ".claude"
|
|
113
|
+
claude_dir.mkdir(parents=True)
|
|
114
|
+
(claude_dir / "settings.json").write_text(json.dumps(existing))
|
|
115
|
+
|
|
116
|
+
setup = ClaudeSetup(project_dir=tmp_path)
|
|
117
|
+
path = setup.setup_settings(force=False, include_hooks=True)
|
|
118
|
+
commands = self._commands(json.loads(path.read_text()), "PostToolUse")
|
|
119
|
+
|
|
120
|
+
assert "my-custom-linter" in commands
|
|
121
|
+
assert "steerdev hooks post-tool-use" in commands
|
|
122
|
+
|
|
123
|
+
|
|
54
124
|
class TestCheckCliDependencies:
|
|
55
125
|
"""Tests for CLI dependency checking."""
|
|
56
126
|
|
|
@@ -232,7 +232,10 @@ class TestWorkspacePreparationInPlace:
|
|
|
232
232
|
return (0, "", "")
|
|
233
233
|
return (0, "", "")
|
|
234
234
|
|
|
235
|
-
with
|
|
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):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/setup/templates/ci/canal-integration.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{steerdev-1.1.25 → steerdev-1.1.27}/src/steerdev_agent/setup/templates/worktrunk.config.toml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|