From 6cd67835735472e6eaa189c0e6f73a41e0d3fffc Mon Sep 17 00:00:00 2001 From: Chris Krough <461869+ckrough@users.noreply.github.com> Date: Fri, 9 Jan 2026 15:37:18 -0500 Subject: [PATCH] feat(workspace): simplify navigation after workspace creation Combine cd and venv activation into single command to reduce copy/paste operations. Users now see one command instead of 2-3 separate steps. Changes: - Combine 'cd' and 'source .venv/bin/activate' with && operator - Remove 'workspace remove' from next steps (confusing after creation) - Add shlex.quote() for shell-safe path handling - Update tests to verify new behavior Closes agentspaces-yjh --- .beads/issues.jsonl | 1 + src/agentspaces/cli/formatters.py | 14 +++++++++----- src/agentspaces/cli/workspace.py | 1 - tests/unit/cli/test_formatters.py | 31 ++++++++++++++++++++++++------- 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 80abc95..c46b3bf 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -17,3 +17,4 @@ {"id":"agentspaces-u7i","title":"Add E2E tests for project create command","description":"## Context\nThe agentspaces CLI has a `project create` command (src/agentspaces/cli/project.py) that initializes new projects with templates. It:\n- Works in current directory (requires empty or git-only directory)\n- Runs `git init` if not already a git repo\n- Creates skeleton files (CLAUDE.md, README.md, docs/, .claude/)\n- Optionally creates Python pack files with `--python` flag\n\n## Goal\nAdd comprehensive E2E/integration tests that exercise the full command flow with real git operations in isolated temporary directories.\n\n## Deliverables\n1. pytest marker configuration in pyproject.toml\n2. Integration test fixtures in tests/integration/cli/conftest.py\n3. Test implementation in tests/integration/cli/test_project_create.py (~22 tests)\n\n## Test Categories\n- Basic creation (empty dir, existing git, --python flag)\n- Python package naming (edge cases for name derivation)\n- Error handling (non-empty dir, confirmation prompt)\n- Content validation (project info in generated files)\n- Git integration (valid repo, untracked files)\n- Edge cases (special chars, unicode, nested dirs)\n\n## Safety Requirements\n- All tests use temporary directories (auto-cleanup)\n- Tests marked with @pytest.mark.integration for selective runs\n- No modifications to host environment or ~/.agentspaces","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-01-09T07:56:26.870453-05:00","created_by":"ckrough","updated_at":"2026-01-09T09:58:34.334635-05:00","closed_at":"2026-01-09T09:58:34.334635-05:00","close_reason":"All subtasks completed: pytest markers, fixtures, 23 tests, CLAUDE.md updated"} {"id":"agentspaces-um3","title":"Phase 2: Extend Project CLAUDE.md with Beads section","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-08T14:52:51.261354-05:00","created_by":"ckrough","updated_at":"2026-01-08T14:54:02.518158-05:00","closed_at":"2026-01-08T14:54:02.518158-05:00","close_reason":"Added Beads workflow and Learned Patterns sections to project CLAUDE.md"} {"id":"agentspaces-w0j","title":"Phase 8: Add PostToolUse auto-format hook","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-01-08T14:52:52.768009-05:00","created_by":"ckrough","updated_at":"2026-01-08T15:30:39.116749-05:00","closed_at":"2026-01-08T15:30:39.116749-05:00","close_reason":"Created PostToolUse auto-format hook for Python files"} +{"id":"agentspaces-yjh","title":"Simplify workspace navigation and venv activation after creation","description":"## Context\nAfter running 'agentspaces workspace create', users see a 'Next Steps' section with commands to cd and activate venv. This requires copying/pasting long paths.\n\n## Goal\nProvide a simpler way for users to navigate to workspace and activate venv without manual copying/pasting.\n\n## Current Behavior\nUsers must copy/paste:\n1. cd /Users/ckrough/.agentspaces/agentspaces/sleepy-fermat\n2. source .venv/bin/activate\n\n## Desired Behavior\nSingle command or automated navigation that minimizes user effort.\n\n## Files Involved\n- src/agentspaces/cli/workspace.py - workspace create command\n- src/agentspaces/modules/workspace/service.py - workspace creation logic\n\n## Verification\n1. Create a new workspace\n2. Verify simplified navigation/activation works\n3. uv run pytest tests/ -k workspace","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-01-09T14:52:35.442563-05:00","created_by":"ckrough","updated_at":"2026-01-09T15:21:26.303403-05:00","closed_at":"2026-01-09T15:21:26.303403-05:00","close_reason":"Implemented: Combined cd and venv activation into single command, removed workspace remove step, added path quoting for safety"} diff --git a/src/agentspaces/cli/formatters.py b/src/agentspaces/cli/formatters.py index 78cdf0c..92ce793 100644 --- a/src/agentspaces/cli/formatters.py +++ b/src/agentspaces/cli/formatters.py @@ -2,6 +2,7 @@ from __future__ import annotations +import shlex from datetime import UTC, datetime from typing import TYPE_CHECKING @@ -97,18 +98,21 @@ def print_workspace_created( console.print(panel) -def print_next_steps(workspace_name: str, workspace_path: str, has_venv: bool) -> None: +def print_next_steps(workspace_path: str, has_venv: bool) -> None: """Print actionable next steps after workspace creation. Args: - workspace_name: Name of the created workspace. workspace_path: Path to the workspace directory. has_venv: Whether a virtual environment was created. """ - steps = [f"cd {workspace_path}"] + # Quote path for shell safety + quoted_path = shlex.quote(workspace_path) + + # Combine cd and venv activation into single command if has_venv: - steps.append("source .venv/bin/activate") - steps.append(f"agentspaces workspace remove {workspace_name}") + steps = [f"cd {quoted_path} && source .venv/bin/activate"] + else: + steps = [f"cd {quoted_path}"] lines = [f" {i + 1}. [cyan]{step}[/cyan]" for i, step in enumerate(steps)] panel = Panel( diff --git a/src/agentspaces/cli/workspace.py b/src/agentspaces/cli/workspace.py index 46cd942..99ed306 100644 --- a/src/agentspaces/cli/workspace.py +++ b/src/agentspaces/cli/workspace.py @@ -110,7 +110,6 @@ def create( ) print_next_steps( - workspace_name=workspace.name, workspace_path=str(workspace.path), has_venv=workspace.has_venv, ) diff --git a/tests/unit/cli/test_formatters.py b/tests/unit/cli/test_formatters.py index 9274048..f91af3a 100644 --- a/tests/unit/cli/test_formatters.py +++ b/tests/unit/cli/test_formatters.py @@ -57,7 +57,7 @@ class TestPrintNextSteps: def test_prints_cd_step(self) -> None: """Should include cd to workspace path.""" with patch("agentspaces.cli.formatters.console") as mock_console: - print_next_steps("test-ws", "/path/to/workspace", has_venv=False) + print_next_steps("/path/to/workspace", has_venv=False) mock_console.print.assert_called() # Get all printed content - find the Panel with "Next Steps" panel = _find_next_steps_panel(mock_console) @@ -67,23 +67,40 @@ def test_prints_cd_step(self) -> None: def test_includes_venv_activation_when_has_venv(self) -> None: """Should include venv activation when has_venv is True.""" with patch("agentspaces.cli.formatters.console") as mock_console: - print_next_steps("test-ws", "/path/to/workspace", has_venv=True) + print_next_steps("/path/to/workspace", has_venv=True) panel = _find_next_steps_panel(mock_console) assert "source .venv/bin/activate" in panel.renderable def test_excludes_venv_activation_when_no_venv(self) -> None: """Should not include venv activation when has_venv is False.""" with patch("agentspaces.cli.formatters.console") as mock_console: - print_next_steps("test-ws", "/path/to/workspace", has_venv=False) + print_next_steps("/path/to/workspace", has_venv=False) panel = _find_next_steps_panel(mock_console) assert "source .venv/bin/activate" not in panel.renderable - def test_includes_remove_step(self) -> None: - """Should include workspace remove step with workspace name.""" + def test_combines_cd_and_activation(self) -> None: + """Should combine cd and activation into single command with &&.""" with patch("agentspaces.cli.formatters.console") as mock_console: - print_next_steps("test-ws", "/path/to/workspace", has_venv=False) + print_next_steps("/path/to/workspace", has_venv=True) panel = _find_next_steps_panel(mock_console) - assert "agentspaces workspace remove test-ws" in panel.renderable + assert "cd" in panel.renderable + assert "&&" in panel.renderable + assert "source .venv/bin/activate" in panel.renderable + + def test_does_not_include_remove_command(self) -> None: + """Should not include workspace remove command.""" + with patch("agentspaces.cli.formatters.console") as mock_console: + print_next_steps("/path/to/workspace", has_venv=False) + panel = _find_next_steps_panel(mock_console) + assert "remove" not in panel.renderable + + def test_quotes_path_with_spaces(self) -> None: + """Should properly quote paths containing spaces.""" + with patch("agentspaces.cli.formatters.console") as mock_console: + print_next_steps("/path/with spaces/workspace", has_venv=False) + panel = _find_next_steps_panel(mock_console) + # shlex.quote adds single quotes around paths with spaces + assert "'/path/with spaces/workspace'" in panel.renderable class TestPrintDidYouMean: