steerdev 1.1.7__tar.gz → 1.1.16__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.7 → steerdev-1.1.16}/.github/workflows/publish.yml +4 -3
- {steerdev-1.1.7 → steerdev-1.1.16}/PKG-INFO +1 -1
- steerdev-1.1.16/docs/superpowers/plans/2026-06-05-wave-id-implies-unlimited-max-tasks.md +253 -0
- steerdev-1.1.16/docs/superpowers/specs/2026-06-05-wave-id-implies-unlimited-max-tasks-design.md +130 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/pyproject.toml +1 -1
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/cli.py +10 -6
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/runner.py +16 -0
- steerdev-1.1.16/tests/test_resolve_max_tasks.py +27 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/.github/workflows/pre-commit.yml +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/.gitignore +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/.pre-commit-config.yaml +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/AGENTS.md +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/CLAUDE.md +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/README.md +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/scripts/pre-commit-version-bump.sh +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/snapshots/steerdev-agent-v1/Dockerfile +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/snapshots/steerdev-agent-v1/start-agent.sh +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/__init__.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/agent_loop.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/api/__init__.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/api/activity.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/api/agents.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/api/canals.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/api/client.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/api/commands.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/api/configs.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/api/context.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/api/events.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/api/hooks.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/api/implementation_plan.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/api/merger.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/api/messages.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/api/prd.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/api/reports.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/api/runs.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/api/sessions.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/api/specs.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/api/tasks.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/api/workflow_runs.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/api/workflows.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/config/__init__.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/config/models.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/config/platform.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/config/settings.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/evidence.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/evidence_assets.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/executor/__init__.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/executor/base.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/executor/claude.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/executor/stream.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/handlers/__init__.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/handlers/prd.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/integration.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/prompt/__init__.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/prompt/builder.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/prompt/templates.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/prompt/workflow_template.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/py.typed +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/retry.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/setup/__init__.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/setup/claude_setup.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/setup/repo_setup.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/setup/templates/ci/canal-integration.yml +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/setup/templates/claude_md_section.md +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/setup/templates/settings.json +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/setup/templates/skills/steerdev-activity-skill/SKILL.md +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/setup/templates/skills/steerdev-canal-workflow-skill/SKILL.md +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/setup/templates/skills/steerdev-context-skill/SKILL.md +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/setup/templates/skills/steerdev-git-workflow-skill/SKILL.md +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/setup/templates/skills/steerdev-merge-into-canal-skill/SKILL.md +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/setup/templates/skills/steerdev-progress-logging-skill/SKILL.md +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/setup/templates/skills/steerdev-single-task-merge-skill/SKILL.md +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/setup/templates/skills/steerdev-specs-management-skill/SKILL.md +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/setup/templates/skills/steerdev-task-management-skill/SKILL.md +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/setup/templates/skills/steerdev-wave-tasks-merge-skill/SKILL.md +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/setup/templates/steerdev.yaml +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/setup/templates/worktrunk.config.toml +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/update_check.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/version.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/workflow/__init__.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/workflow/context.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/workflow/executor.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/workflow/memory.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/workspace/__init__.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/workspace/preparation.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/workspace/project_manager.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/workspace/tool_detection.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/src/steerdev_agent/worktree.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/__init__.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_agent_loop.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_agent_loop_extended.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_agents_api.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_api_client.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_claude_executor.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_claude_setup.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_client_methods.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_commands_api.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_config.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_config_extended.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_conflict_mitigation.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_context_search.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_executor.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_platform_config.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_preparation.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_prompt.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_reports_client.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_retry.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_runner_merge_modes.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_runner_worktrees.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_stream_parser.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_tasks.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_version.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_wave_scoped_fetch.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_workflow_context.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_workflow_memory.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_workflow_prompt_template.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_workflow_runs_api.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_workspace.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_workspace_extended.py +0 -0
- {steerdev-1.1.7 → steerdev-1.1.16}/tests/test_worktree.py +0 -0
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
name: Publish to PyPI
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
4
7
|
workflow_dispatch:
|
|
5
8
|
|
|
6
9
|
permissions:
|
|
@@ -53,9 +56,7 @@ jobs:
|
|
|
53
56
|
run: uv build
|
|
54
57
|
|
|
55
58
|
- name: Publish to PyPI
|
|
56
|
-
|
|
57
|
-
UV_PUBLISH_TOKEN: ${{ secrets.UV_PUBLISH_TOKEN }}
|
|
58
|
-
run: uv publish
|
|
59
|
+
run: uv publish --trusted-publishing always
|
|
59
60
|
|
|
60
61
|
- name: Create git tag and GitHub release
|
|
61
62
|
env:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: steerdev
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.16
|
|
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
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# `--wave-id` implies unlimited `--max-tasks` — Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Make `steerdev run --wave-id X` (with no `--max-tasks`) process the whole wave, while keeping every other `--max-tasks` case unchanged.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Make the `--max-tasks` CLI option nullable (`None` = unspecified), add a pure `resolve_max_tasks(max_tasks, wave_id)` helper in `runner.py` that returns the effective int (unspecified + wave → 0/unlimited; unspecified + no wave → 1; explicit value always honored), and use it in `cli.py:run` for both the `run_agent(...)` call and the "Max Tasks" banner. `AgentRunner`/`run_agent` are unchanged (still take a concrete int, `0` = unlimited).
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Python 3.12, Typer CLI, pytest, uv. Lint/format: ruff. Type check: basedpyright. (Repo pre-commit runs ruff + basedpyright + an auto version bump.)
|
|
10
|
+
|
|
11
|
+
**Spec:** `docs/superpowers/specs/2026-06-05-wave-id-implies-unlimited-max-tasks-design.md`
|
|
12
|
+
**Branch:** `feat/wave-id-implies-unlimited` (already created; the spec is committed there).
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## File Structure
|
|
17
|
+
|
|
18
|
+
- `src/steerdev_agent/runner.py` — add pure `resolve_max_tasks(max_tasks: int | None, wave_id: str | None) -> int` next to the other pure resolvers (`resolve_merge_flow` is at ~line 139). One clear responsibility: resolve the effective max-tasks. No I/O.
|
|
19
|
+
- `src/steerdev_agent/cli.py` — in the `run` command: change the `--max-tasks` option to `int | None = None`, call `resolve_max_tasks` to get `resolved_max_tasks`, and use it for both the `max_tasks_display` banner (~line 1824) and the `max_tasks=` argument to `run_agent` (~line 1875).
|
|
20
|
+
- `tests/test_resolve_max_tasks.py` — new pure unit tests for `resolve_max_tasks`.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Task 1: Pure `resolve_max_tasks` helper (TDD)
|
|
25
|
+
|
|
26
|
+
**Files:**
|
|
27
|
+
- Modify: `src/steerdev_agent/runner.py` (add the function near `resolve_merge_flow`, ~line 139)
|
|
28
|
+
- Test: `tests/test_resolve_max_tasks.py` (new)
|
|
29
|
+
|
|
30
|
+
- [ ] **Step 1: Write the failing test**
|
|
31
|
+
|
|
32
|
+
Create `tests/test_resolve_max_tasks.py` with exactly:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
"""Tests for resolve_max_tasks: --wave-id implies unlimited when --max-tasks is unset."""
|
|
36
|
+
|
|
37
|
+
from steerdev_agent.runner import resolve_max_tasks
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_unspecified_with_wave_is_unlimited():
|
|
41
|
+
# Bare `steerdev run --wave-id X` → pull the whole wave (0 = unlimited).
|
|
42
|
+
assert resolve_max_tasks(None, "wave-1") == 0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_unspecified_without_wave_is_one():
|
|
46
|
+
# Historical single-task default is preserved.
|
|
47
|
+
assert resolve_max_tasks(None, None) == 1
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_explicit_zero_is_honored_with_wave():
|
|
51
|
+
# The sandbox bootstrap passes --max-tasks 0 explicitly; still 0.
|
|
52
|
+
assert resolve_max_tasks(0, "wave-1") == 0
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_explicit_cap_is_honored_with_wave():
|
|
56
|
+
# An explicit cap wins even when a wave is scoped.
|
|
57
|
+
assert resolve_max_tasks(2, "wave-1") == 2
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_explicit_is_honored_without_wave():
|
|
61
|
+
assert resolve_max_tasks(5, None) == 5
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- [ ] **Step 2: Run test to verify it fails**
|
|
65
|
+
|
|
66
|
+
Run: `uv run pytest tests/test_resolve_max_tasks.py -q`
|
|
67
|
+
Expected: FAIL — `ImportError: cannot import name 'resolve_max_tasks' from 'steerdev_agent.runner'`.
|
|
68
|
+
|
|
69
|
+
- [ ] **Step 3: Implement the helper**
|
|
70
|
+
|
|
71
|
+
In `src/steerdev_agent/runner.py`, add this function immediately ABOVE `def resolve_merge_flow(` (around line 139), as a module-level function:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
def resolve_max_tasks(max_tasks: int | None, wave_id: str | None) -> int:
|
|
75
|
+
"""Resolve the effective --max-tasks for a run.
|
|
76
|
+
|
|
77
|
+
- An explicit value (including 0 = unlimited) always wins.
|
|
78
|
+
- Unspecified (None) with a wave scope → 0 (unlimited): a wave run pulls
|
|
79
|
+
every task of the wave, then stops. This makes a bare
|
|
80
|
+
`steerdev run --wave-id X` behave like the sandbox's
|
|
81
|
+
`steerdev run --wave-id X --max-tasks 0`.
|
|
82
|
+
- Unspecified (None) with no wave scope → 1 (the historical single-task
|
|
83
|
+
default).
|
|
84
|
+
"""
|
|
85
|
+
if max_tasks is not None:
|
|
86
|
+
return max_tasks
|
|
87
|
+
return 0 if wave_id else 1
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
- [ ] **Step 4: Run test to verify it passes**
|
|
91
|
+
|
|
92
|
+
Run: `uv run pytest tests/test_resolve_max_tasks.py -q`
|
|
93
|
+
Expected: PASS (5 passed).
|
|
94
|
+
|
|
95
|
+
- [ ] **Step 5: Commit**
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
git add src/steerdev_agent/runner.py tests/test_resolve_max_tasks.py
|
|
99
|
+
git commit -m "feat: add resolve_max_tasks (--wave-id implies unlimited)"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Note: the repo pre-commit runs ruff, basedpyright, and an auto version bump. If ruff reformats, re-add and re-commit. If basedpyright complains about the new function, fix the typing (signature is fully annotated, so it should pass). The version-bump hook modifying a version file is expected — let it include that change.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Task 2: Wire `resolve_max_tasks` into the `run` CLI command
|
|
107
|
+
|
|
108
|
+
**Files:**
|
|
109
|
+
- Modify: `src/steerdev_agent/cli.py` (the `run` command — the `max_tasks` option definition ~line 1646-1652; the `max_tasks_display` line ~1824; the `run_agent(... max_tasks=max_tasks ...)` call ~1875)
|
|
110
|
+
|
|
111
|
+
This task has no new unit test of its own (the logic is the Task 1 helper; the CLI wiring is thin glue). It is verified by a manual `--help`/dry-run check and the full suite.
|
|
112
|
+
|
|
113
|
+
- [ ] **Step 1: Make the `--max-tasks` option nullable**
|
|
114
|
+
|
|
115
|
+
In `src/steerdev_agent/cli.py`, find the `max_tasks` parameter in the `run` command (currently):
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
max_tasks: Annotated[
|
|
119
|
+
int,
|
|
120
|
+
typer.Option(
|
|
121
|
+
"--max-tasks",
|
|
122
|
+
help="Maximum number of tasks to process (default: 1, use 0 for unlimited)",
|
|
123
|
+
),
|
|
124
|
+
] = 1,
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Replace it with:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
max_tasks: Annotated[
|
|
131
|
+
int | None,
|
|
132
|
+
typer.Option(
|
|
133
|
+
"--max-tasks",
|
|
134
|
+
help=(
|
|
135
|
+
"Maximum number of tasks to process (default: 1, or unlimited "
|
|
136
|
+
"when --wave-id is set; use 0 for unlimited)"
|
|
137
|
+
),
|
|
138
|
+
),
|
|
139
|
+
] = None,
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
- [ ] **Step 2: Import and call `resolve_max_tasks`**
|
|
143
|
+
|
|
144
|
+
In `src/steerdev_agent/cli.py`, the `run` command already imports from the runner at the top of its body:
|
|
145
|
+
`from steerdev_agent.runner import run_agent`.
|
|
146
|
+
Change that import to also bring in the helper:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from steerdev_agent.runner import resolve_max_tasks, run_agent
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Then, BEFORE the `console.print(Panel(...))` banner block (i.e. before the `max_tasks_display` line ~1824), add:
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
resolved_max_tasks = resolve_max_tasks(max_tasks, wave_id)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
- [ ] **Step 3: Use `resolved_max_tasks` in the banner**
|
|
159
|
+
|
|
160
|
+
Change the display line (~1824) from:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
max_tasks_display = "unlimited" if max_tasks == 0 else str(max_tasks)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
to:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
max_tasks_display = "unlimited" if resolved_max_tasks == 0 else str(resolved_max_tasks)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
- [ ] **Step 4: Use `resolved_max_tasks` in the run_agent call**
|
|
173
|
+
|
|
174
|
+
In the `run_agent(...)` call (~line 1875), change:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
max_tasks=max_tasks,
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
to:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
max_tasks=resolved_max_tasks,
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
- [ ] **Step 5: Verify the CLI resolves correctly (dry run + help)**
|
|
187
|
+
|
|
188
|
+
Run these and read the "Max Tasks" line in the Starting panel (use `--dry` so nothing executes; `--project-id x` satisfies the required arg):
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
uv run steerdev run --project-id x --wave-id w1 --dry 2>&1 | grep -i "max tasks"
|
|
192
|
+
```
|
|
193
|
+
Expected: `Max Tasks: unlimited`
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
uv run steerdev run --project-id x --dry 2>&1 | grep -i "max tasks"
|
|
197
|
+
```
|
|
198
|
+
Expected: `Max Tasks: 1`
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
uv run steerdev run --project-id x --wave-id w1 --max-tasks 2 --dry 2>&1 | grep -i "max tasks"
|
|
202
|
+
```
|
|
203
|
+
Expected: `Max Tasks: 2`
|
|
204
|
+
|
|
205
|
+
If `--dry` exits before printing the panel or requires more args, instead run `uv run steerdev run --help` to confirm the `--max-tasks` help text now mentions "unlimited when --wave-id is set", and rely on Task 1's unit tests + the full suite for the resolution logic. Report which verification path you used.
|
|
206
|
+
|
|
207
|
+
- [ ] **Step 6: Run the full suite + type check**
|
|
208
|
+
|
|
209
|
+
Run: `uv run pytest -q`
|
|
210
|
+
Expected: all pass (the new 5 tests included; no regressions).
|
|
211
|
+
|
|
212
|
+
Run: `uv run basedpyright src/steerdev_agent/cli.py src/steerdev_agent/runner.py`
|
|
213
|
+
Expected: no new errors. (`max_tasks` is now `int | None`; `resolved_max_tasks` is `int`, matching `run_agent`'s `max_tasks: int` param — so no type error at the call site.)
|
|
214
|
+
|
|
215
|
+
- [ ] **Step 7: Commit**
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
git add src/steerdev_agent/cli.py
|
|
219
|
+
git commit -m "feat: steerdev run --wave-id pulls the whole wave by default"
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
(Pre-commit ruff/basedpyright/version-bump as in Task 1.)
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Task 3: Full verification
|
|
227
|
+
|
|
228
|
+
**Files:** none (verification only)
|
|
229
|
+
|
|
230
|
+
- [ ] **Step 1: Full test suite**
|
|
231
|
+
|
|
232
|
+
Run: `uv run pytest -q`
|
|
233
|
+
Expected: all pass.
|
|
234
|
+
|
|
235
|
+
- [ ] **Step 2: Lint + format check**
|
|
236
|
+
|
|
237
|
+
Run: `uv run ruff check . && uv run ruff format --check .`
|
|
238
|
+
Expected: clean. If format check fails, run `uv run ruff format .`, then `git add -A && git commit -m "chore: ruff format"`.
|
|
239
|
+
|
|
240
|
+
- [ ] **Step 3: Type check (whole package)**
|
|
241
|
+
|
|
242
|
+
Run: `uv run basedpyright`
|
|
243
|
+
Expected: no NEW errors introduced by this change. (Pre-existing errors elsewhere, if any, are out of scope — report them but don't fix.)
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Notes for the implementer
|
|
248
|
+
|
|
249
|
+
- **Why nullable:** Typer can't distinguish "user typed `--max-tasks 1`" from "default 1" if the default is `1`. Making it `None` lets `resolve_max_tasks` apply the wave-aware default only when the user didn't specify.
|
|
250
|
+
- **`run_agent` is unchanged:** it still takes `max_tasks: int` where `0` means unlimited (`runner.py`: `self.max_tasks = max_tasks if max_tasks != 0 else None`). We only change what value the CLI computes and passes.
|
|
251
|
+
- **Sandbox bootstrap is unaffected:** it passes `--max-tasks 0` explicitly, which `resolve_max_tasks(0, waveId)` returns as `0` — identical to today.
|
|
252
|
+
- **Do NOT** change `runner.py`'s loop, `run_agent`'s signature, or the server. Scope is the helper + the CLI wiring only.
|
|
253
|
+
- **Version-bump hook:** committing in this repo auto-bumps a version file via pre-commit. That extra change landing in your commit is expected; don't fight it.
|
steerdev-1.1.16/docs/superpowers/specs/2026-06-05-wave-id-implies-unlimited-max-tasks-design.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# `steerdev run --wave-id` should pull the whole wave (imply unlimited max-tasks)
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-06-05
|
|
4
|
+
**Status:** Design — awaiting review
|
|
5
|
+
**Scope:** `steerdev-agent` CLI only (`cli.py` option resolution). No server change.
|
|
6
|
+
|
|
7
|
+
## Problem
|
|
8
|
+
|
|
9
|
+
`steerdev run --wave-id <id>` is meant to "process every task of a specific wave,
|
|
10
|
+
then stop" (its own `--wave-id` help text). But `--max-tasks` defaults to `1`, and
|
|
11
|
+
the run loop is governed solely by `max_tasks` (`runner.py`: `tasks_remaining =
|
|
12
|
+
self.max_tasks`; `0 → None → unlimited`). So a bare `steerdev run --wave-id X`
|
|
13
|
+
runs exactly **one** task and exits — contradicting the help text and surprising
|
|
14
|
+
the user.
|
|
15
|
+
|
|
16
|
+
Today the only way to get whole-wave behavior is `steerdev run --wave-id X
|
|
17
|
+
--max-tasks 0`, which the sandbox bootstrap passes explicitly. A human running it
|
|
18
|
+
locally must remember `--max-tasks 0`.
|
|
19
|
+
|
|
20
|
+
## Goal
|
|
21
|
+
|
|
22
|
+
`steerdev run --wave-id X` (no `--max-tasks`) pulls **the whole wave** — the run
|
|
23
|
+
process requests the wave's next task from `/api/v1/tasks/next?wave_id=…` after
|
|
24
|
+
each one, until the wave is exhausted (404 → stop). This is the SAME behavior as
|
|
25
|
+
the sandbox; local and sandbox `run --wave-id` are identical. An explicit
|
|
26
|
+
`--max-tasks N` still caps the run if the user wants.
|
|
27
|
+
|
|
28
|
+
## Key context: one shared behavior, local or sandbox
|
|
29
|
+
|
|
30
|
+
`steerdev run --wave-id` is a single code path. The run process is the puller:
|
|
31
|
+
it loops `get_next_wave(wave_id=…)` and executes each task. The only difference
|
|
32
|
+
between environments is who starts it and whether anything is pre-seeded:
|
|
33
|
+
|
|
34
|
+
- **Sandbox:** the dispatch seeds the first task as a Run row (for tracking), then
|
|
35
|
+
the VM runs `steerdev run --wave-id --max-tasks 0` which pulls the rest.
|
|
36
|
+
- **Local CLI:** nothing is dispatched; the human runs `steerdev run --wave-id X`
|
|
37
|
+
and it starts pulling from the first task immediately.
|
|
38
|
+
|
|
39
|
+
Standalone (no `--agent-id`) works: the server's no-agent path
|
|
40
|
+
(`getNextWaveTask(projectId, waveId)`) returns the wave's next task without
|
|
41
|
+
conflict-claiming, and the runtime marks each task `started` itself, so the
|
|
42
|
+
state-ordered selection advances correctly on each pull.
|
|
43
|
+
|
|
44
|
+
## Design
|
|
45
|
+
|
|
46
|
+
Distinguish "user gave `--max-tasks`" from "default" by making the option
|
|
47
|
+
nullable, then resolve the effective value with a pure helper.
|
|
48
|
+
|
|
49
|
+
### 1. Make `--max-tasks` nullable in `cli.py:run`
|
|
50
|
+
|
|
51
|
+
Change:
|
|
52
|
+
```python
|
|
53
|
+
max_tasks: Annotated[int, typer.Option("--max-tasks", help="...default: 1...")] = 1,
|
|
54
|
+
```
|
|
55
|
+
to:
|
|
56
|
+
```python
|
|
57
|
+
max_tasks: Annotated[
|
|
58
|
+
int | None,
|
|
59
|
+
typer.Option(
|
|
60
|
+
"--max-tasks",
|
|
61
|
+
help="Maximum number of tasks to process (default: 1, or unlimited when --wave-id is set; use 0 for unlimited)",
|
|
62
|
+
),
|
|
63
|
+
] = None,
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 2. Pure resolution helper
|
|
67
|
+
|
|
68
|
+
In `runner.py` (next to the other pure resolvers like `resolve_merge_flow`),
|
|
69
|
+
add:
|
|
70
|
+
```python
|
|
71
|
+
def resolve_max_tasks(max_tasks: int | None, wave_id: str | None) -> int:
|
|
72
|
+
"""Resolve the effective --max-tasks.
|
|
73
|
+
|
|
74
|
+
- Explicit value (incl. 0 = unlimited) wins.
|
|
75
|
+
- Unspecified + a wave scope → 0 (unlimited): a wave run pulls the whole wave.
|
|
76
|
+
- Unspecified + no wave scope → 1 (the historical single-task default).
|
|
77
|
+
"""
|
|
78
|
+
if max_tasks is not None:
|
|
79
|
+
return max_tasks
|
|
80
|
+
return 0 if wave_id else 1
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 3. Use it in `cli.py:run`
|
|
84
|
+
|
|
85
|
+
Before the `run_agent(...)` call, resolve:
|
|
86
|
+
```python
|
|
87
|
+
resolved_max_tasks = resolve_max_tasks(max_tasks, wave_id)
|
|
88
|
+
```
|
|
89
|
+
Pass `max_tasks=resolved_max_tasks` to `run_agent`, and use `resolved_max_tasks`
|
|
90
|
+
for the `max_tasks_display` panel line (so the "Max Tasks: unlimited" banner is
|
|
91
|
+
correct).
|
|
92
|
+
|
|
93
|
+
`run_agent`/`AgentRunner` are unchanged — they still take a concrete `int`
|
|
94
|
+
(`0 → unlimited` as today).
|
|
95
|
+
|
|
96
|
+
## Behavior matrix (after change)
|
|
97
|
+
|
|
98
|
+
| Command | Effective max_tasks | Result |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| `run --wave-id X` | 0 (unlimited) | whole wave |
|
|
101
|
+
| `run --wave-id X --max-tasks 0` | 0 | whole wave (sandbox bootstrap; unchanged) |
|
|
102
|
+
| `run --wave-id X --max-tasks 2` | 2 | first 2 wave tasks then stop |
|
|
103
|
+
| `run` (no wave) | 1 | one task (unchanged) |
|
|
104
|
+
| `run --max-tasks 0` (no wave) | 0 | unlimited single-task queue drain (unchanged) |
|
|
105
|
+
| `run --task-id T` | (loop breaks after 1 by `_target_task_id` guard) | one task (unchanged) |
|
|
106
|
+
|
|
107
|
+
## Tests
|
|
108
|
+
|
|
109
|
+
Pure unit tests for `resolve_max_tasks` (in `tests/`, e.g. `test_runner_merge_modes.py`
|
|
110
|
+
style or a new `test_resolve_max_tasks.py`):
|
|
111
|
+
- `resolve_max_tasks(None, "wave-1") == 0` (wave + unspecified → unlimited)
|
|
112
|
+
- `resolve_max_tasks(None, None) == 1` (no wave + unspecified → 1)
|
|
113
|
+
- `resolve_max_tasks(0, "wave-1") == 0` (explicit 0 honored)
|
|
114
|
+
- `resolve_max_tasks(2, "wave-1") == 2` (explicit cap honored, even with a wave)
|
|
115
|
+
- `resolve_max_tasks(5, None) == 5` (explicit honored, no wave)
|
|
116
|
+
|
|
117
|
+
(The CLI wiring + display are thin and covered by the helper; no full CliRunner
|
|
118
|
+
test required, matching the repo's pure-resolver test pattern.)
|
|
119
|
+
|
|
120
|
+
## Out of scope
|
|
121
|
+
|
|
122
|
+
- No server change. No sandbox dispatch change (it already passes `--max-tasks 0`
|
|
123
|
+
explicitly, which `resolve_max_tasks` honors unchanged).
|
|
124
|
+
- The sandbox bootstrap can KEEP passing `--max-tasks 0` (explicit, still correct)
|
|
125
|
+
or later drop it (now redundant) — not required by this change.
|
|
126
|
+
|
|
127
|
+
## Decisions to confirm in review
|
|
128
|
+
|
|
129
|
+
1. Unspecified `--max-tasks` + `--wave-id` → unlimited (0). (The core ask.)
|
|
130
|
+
2. The historical no-wave default stays `1` (unchanged single-task behavior).
|
|
@@ -1644,12 +1644,15 @@ def run(
|
|
|
1644
1644
|
),
|
|
1645
1645
|
] = None,
|
|
1646
1646
|
max_tasks: Annotated[
|
|
1647
|
-
int,
|
|
1647
|
+
int | None,
|
|
1648
1648
|
typer.Option(
|
|
1649
1649
|
"--max-tasks",
|
|
1650
|
-
help=
|
|
1650
|
+
help=(
|
|
1651
|
+
"Maximum number of tasks to process (default: 1, or unlimited "
|
|
1652
|
+
"when --wave-id is set; use 0 for unlimited)"
|
|
1653
|
+
),
|
|
1651
1654
|
),
|
|
1652
|
-
] =
|
|
1655
|
+
] = None,
|
|
1653
1656
|
timeout: Annotated[
|
|
1654
1657
|
int | None,
|
|
1655
1658
|
typer.Option(
|
|
@@ -1776,7 +1779,7 @@ def run(
|
|
|
1776
1779
|
steerdev run --project-id abc123 --workflow-id wf-abc123
|
|
1777
1780
|
steerdev run --config custom.yaml --project-id abc123
|
|
1778
1781
|
"""
|
|
1779
|
-
from steerdev_agent.runner import run_agent
|
|
1782
|
+
from steerdev_agent.runner import resolve_max_tasks, run_agent
|
|
1780
1783
|
|
|
1781
1784
|
# Get config from context (loaded in app callback)
|
|
1782
1785
|
config: SteerDevConfig = ctx.obj or SteerDevConfig()
|
|
@@ -1821,7 +1824,8 @@ def run(
|
|
|
1821
1824
|
dry_run_status = "enabled" if dry_run else "disabled"
|
|
1822
1825
|
waves_status = "enabled" if waves else "disabled"
|
|
1823
1826
|
canals_status = "enabled" if canals else "disabled"
|
|
1824
|
-
|
|
1827
|
+
resolved_max_tasks = resolve_max_tasks(max_tasks, wave_id)
|
|
1828
|
+
max_tasks_display = "unlimited" if resolved_max_tasks == 0 else str(resolved_max_tasks)
|
|
1825
1829
|
workflow_status = resolved_workflow_id or "auto (per-task)"
|
|
1826
1830
|
agent_name_display = agent_name or "(auto from env)"
|
|
1827
1831
|
wave_scope_display = wave_id or "(all waves)"
|
|
@@ -1872,7 +1876,7 @@ def run(
|
|
|
1872
1876
|
agent_name=agent_name,
|
|
1873
1877
|
model=resolved_model,
|
|
1874
1878
|
max_turns=resolved_max_turns,
|
|
1875
|
-
max_tasks=
|
|
1879
|
+
max_tasks=resolved_max_tasks,
|
|
1876
1880
|
timeout_seconds=resolved_timeout,
|
|
1877
1881
|
enable_waves=waves,
|
|
1878
1882
|
enable_canals=canals,
|
|
@@ -136,6 +136,22 @@ def extract_task_and_wave_context(
|
|
|
136
136
|
return wave_response, None, suggested_workflow_id
|
|
137
137
|
|
|
138
138
|
|
|
139
|
+
def resolve_max_tasks(max_tasks: int | None, wave_id: str | None) -> int:
|
|
140
|
+
"""Resolve the effective --max-tasks for a run.
|
|
141
|
+
|
|
142
|
+
- An explicit value (including 0 = unlimited) always wins.
|
|
143
|
+
- Unspecified (None) with a wave scope → 0 (unlimited): a wave run pulls
|
|
144
|
+
every task of the wave, then stops. This makes a bare
|
|
145
|
+
`steerdev run --wave-id X` behave like the sandbox's
|
|
146
|
+
`steerdev run --wave-id X --max-tasks 0`.
|
|
147
|
+
- Unspecified (None) with no wave scope → 1 (the historical single-task
|
|
148
|
+
default).
|
|
149
|
+
"""
|
|
150
|
+
if max_tasks is not None:
|
|
151
|
+
return max_tasks
|
|
152
|
+
return 0 if wave_id else 1
|
|
153
|
+
|
|
154
|
+
|
|
139
155
|
def resolve_merge_flow(
|
|
140
156
|
waves_enabled: bool,
|
|
141
157
|
canals_enabled: bool,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Tests for resolve_max_tasks: --wave-id implies unlimited when --max-tasks is unset."""
|
|
2
|
+
|
|
3
|
+
from steerdev_agent.runner import resolve_max_tasks
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_unspecified_with_wave_is_unlimited():
|
|
7
|
+
# Bare `steerdev run --wave-id X` → pull the whole wave (0 = unlimited).
|
|
8
|
+
assert resolve_max_tasks(None, "wave-1") == 0
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_unspecified_without_wave_is_one():
|
|
12
|
+
# Historical single-task default is preserved.
|
|
13
|
+
assert resolve_max_tasks(None, None) == 1
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_explicit_zero_is_honored_with_wave():
|
|
17
|
+
# The sandbox bootstrap passes --max-tasks 0 explicitly; still 0.
|
|
18
|
+
assert resolve_max_tasks(0, "wave-1") == 0
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_explicit_cap_is_honored_with_wave():
|
|
22
|
+
# An explicit cap wins even when a wave is scoped.
|
|
23
|
+
assert resolve_max_tasks(2, "wave-1") == 2
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_explicit_is_honored_without_wave():
|
|
27
|
+
assert resolve_max_tasks(5, None) == 5
|
|
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.7 → steerdev-1.1.16}/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
|
|
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
|