Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions mcp_server/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ async def cmd_retry_generation(args: argparse.Namespace) -> int:
print(f"Using project root: {root}")
set_project_root(root)

generation_id = resolve_generation_id(None, root)
generation_id = resolve_generation_id(args.generation_id, root)
if not generation_id:
print("No previous generation found. Run `specflow run-generation` to start one.")
return 0
Expand Down Expand Up @@ -564,7 +564,13 @@ def _build_parser() -> argparse.ArgumentParser:
subparsers.add_parser("check-status", help="Check progress of a running generation")

# retry-generation
subparsers.add_parser("retry-generation", help="Retry a failed generation")
p_retry = subparsers.add_parser("retry-generation", help="Retry a failed generation")
p_retry.add_argument(
"--generation-id",
default=None,
dest="generation_id",
help="Generation ID (default: from specflow_session.json)",
)

# download-outputs
p_dl = subparsers.add_parser(
Expand Down
4 changes: 2 additions & 2 deletions mcp_server/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ async def test_calls_retry_endpoint(self, tmp_project):
from services.session import write_session
write_session("gen-xyz", tmp_project)

args = SimpleNamespace(root_path=str(tmp_project), command="retry-generation")
args = SimpleNamespace(root_path=str(tmp_project), command="retry-generation", generation_id=None)

# check_status_safe returns failed → proceed to retry POST
with patch(
Expand All @@ -308,7 +308,7 @@ async def test_blocks_when_already_running(self, tmp_project, capsys):
from services.session import write_session
write_session("gen-running", tmp_project)

args = SimpleNamespace(root_path=str(tmp_project), command="retry-generation")
args = SimpleNamespace(root_path=str(tmp_project), command="retry-generation", generation_id=None)

with patch(
"services.tool_helpers.check_status_safe",
Expand Down
4 changes: 2 additions & 2 deletions mcp_server/tests/test_tui_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -1136,13 +1136,13 @@ async def test_retry_runs_action_when_confirmed(self):
patch.object(app, "push_screen_wait", new=AsyncMock(return_value=True)),
patch.object(screen, "_run_suspended", new=AsyncMock()) as run_susp,
# do_retry is async; force a sync mock so the flow's
# ``do_retry(root)`` yields a sentinel, not a live coroutine.
# ``do_retry(root, generation_id)`` yields a sentinel, not a live coroutine.
patch(
"tui.app.actions.do_retry", new=MagicMock(return_value="retry-coro")
) as do_retry,
):
await screen._retry_flow()
do_retry.assert_called_once_with(app.root)
do_retry.assert_called_once_with(app.root, "gen_x")
run_susp.assert_awaited_once_with("retry-coro")

@pytest.mark.asyncio
Expand Down
6 changes: 4 additions & 2 deletions mcp_server/tui/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,11 @@ def _ns(**overrides: object) -> SimpleNamespace:
return SimpleNamespace(**base)


async def do_retry(root: Path) -> int:
async def do_retry(root: Path, generation_id: str | None = None) -> int:
"""Retry the current generation (reuses ``cmd_retry_generation`` guards)."""
return await cli.cmd_retry_generation(_ns(root_path=str(root)))
return await cli.cmd_retry_generation(
_ns(root_path=str(root), generation_id=generation_id)
)


async def do_clear_set(set_number: int) -> int:
Expand Down
2 changes: 1 addition & 1 deletion mcp_server/tui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,7 @@ async def _retry_flow(self) -> None:
)
)
if ok:
await self._run_suspended(actions.do_retry(self.app.root))
await self._run_suspended(actions.do_retry(self.app.root, self._generation_id))

def action_clear(self) -> None:
self.run_worker(self._clear_flow(), exclusive=True)
Expand Down