feat(04-03): add long command detection to CommandExecutor
- Add LONG_COMMANDS set with execute-phase, plan-phase, discuss-phase, new-project - Add _is_long_command method to detect long-running commands - Add TestLongCommands test class with 5 test cases - Long commands will use no timeout in execute() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -152,6 +152,9 @@ class CommandExecutor:
|
|||||||
|
|
||||||
DEFAULT_TIMEOUT: float = 60.0
|
DEFAULT_TIMEOUT: float = 60.0
|
||||||
|
|
||||||
|
# Commands that require no timeout (long-running operations)
|
||||||
|
LONG_COMMANDS: set = {'execute-phase', 'plan-phase', 'discuss-phase', 'new-project'}
|
||||||
|
|
||||||
def __init__(self, output_capture: Optional[OutputCapture] = None):
|
def __init__(self, output_capture: Optional[OutputCapture] = None):
|
||||||
"""Initialize CommandExecutor.
|
"""Initialize CommandExecutor.
|
||||||
|
|
||||||
@@ -196,6 +199,25 @@ class CommandExecutor:
|
|||||||
|
|
||||||
return False, None
|
return False, None
|
||||||
|
|
||||||
|
def _is_long_command(self, command: str) -> bool:
|
||||||
|
"""Check if command requires no timeout.
|
||||||
|
|
||||||
|
Long commands like execute-phase, plan-phase, discuss-phase, and new-project
|
||||||
|
can take an extended time to complete and should not have a timeout limit.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command: The full command string (e.g., '/gsd:execute-phase').
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if command is in LONG_COMMANDS, False otherwise.
|
||||||
|
"""
|
||||||
|
# Extract command name from /gsd:command format
|
||||||
|
match = re.match(r'^/gsd:(\S+)', command)
|
||||||
|
if match:
|
||||||
|
command_name = match.group(1)
|
||||||
|
return command_name in self.LONG_COMMANDS
|
||||||
|
return False
|
||||||
|
|
||||||
def execute(
|
def execute(
|
||||||
self,
|
self,
|
||||||
session, # TmuxSession type - avoiding circular import
|
session, # TmuxSession type - avoiding circular import
|
||||||
|
|||||||
@@ -339,3 +339,56 @@ class TestCommandExecutorSendCommand:
|
|||||||
# Verify it was called - the exact implementation should include enter
|
# Verify it was called - the exact implementation should include enter
|
||||||
call_args = mock_pane.send_keys.call_args
|
call_args = mock_pane.send_keys.call_args
|
||||||
assert call_args is not None
|
assert call_args is not None
|
||||||
|
|
||||||
|
|
||||||
|
class TestLongCommands:
|
||||||
|
"""Tests for long command detection in CommandExecutor."""
|
||||||
|
|
||||||
|
def test_long_commands_set_contains_expected_commands(self):
|
||||||
|
"""LONG_COMMANDS should contain execute-phase, plan-phase, discuss-phase, new-project."""
|
||||||
|
executor = CommandExecutor()
|
||||||
|
|
||||||
|
assert 'execute-phase' in executor.LONG_COMMANDS
|
||||||
|
assert 'plan-phase' in executor.LONG_COMMANDS
|
||||||
|
assert 'discuss-phase' in executor.LONG_COMMANDS
|
||||||
|
assert 'new-project' in executor.LONG_COMMANDS
|
||||||
|
|
||||||
|
def test_is_long_command_returns_true_for_long_commands(self):
|
||||||
|
"""_is_long_command should return True for commands in LONG_COMMANDS."""
|
||||||
|
executor = CommandExecutor()
|
||||||
|
|
||||||
|
assert executor._is_long_command('/gsd:execute-phase') is True
|
||||||
|
assert executor._is_long_command('/gsd:plan-phase') is True
|
||||||
|
assert executor._is_long_command('/gsd:discuss-phase') is True
|
||||||
|
assert executor._is_long_command('/gsd:new-project') is True
|
||||||
|
|
||||||
|
def test_is_long_command_returns_false_for_regular_commands(self):
|
||||||
|
"""_is_long_command should return False for commands not in LONG_COMMANDS."""
|
||||||
|
executor = CommandExecutor()
|
||||||
|
|
||||||
|
assert executor._is_long_command('/gsd:status') is False
|
||||||
|
assert executor._is_long_command('/gsd:list') is False
|
||||||
|
assert executor._is_long_command('/gsd:help') is False
|
||||||
|
assert executor._is_long_command('/gsd:use') is False
|
||||||
|
|
||||||
|
def test_is_long_command_handles_commands_with_arguments(self):
|
||||||
|
"""_is_long_command should handle commands with arguments correctly."""
|
||||||
|
executor = CommandExecutor()
|
||||||
|
|
||||||
|
# Long commands with args should still be detected
|
||||||
|
assert executor._is_long_command('/gsd:execute-phase 01-core') is True
|
||||||
|
assert executor._is_long_command('/gsd:plan-phase feature-x') is True
|
||||||
|
assert executor._is_long_command('/gsd:discuss-phase about architecture') is True
|
||||||
|
assert executor._is_long_command('/gsd:new-project my-project') is True
|
||||||
|
|
||||||
|
# Regular commands with args should not be detected as long
|
||||||
|
assert executor._is_long_command('/gsd:status --verbose') is False
|
||||||
|
assert executor._is_long_command('/gsd:use my-project') is False
|
||||||
|
|
||||||
|
def test_is_long_command_returns_false_for_non_gsd_commands(self):
|
||||||
|
"""_is_long_command should return False for non-/gsd: commands."""
|
||||||
|
executor = CommandExecutor()
|
||||||
|
|
||||||
|
assert executor._is_long_command('status') is False
|
||||||
|
assert executor._is_long_command('/help') is False
|
||||||
|
assert executor._is_long_command('regular message') is False
|
||||||
|
|||||||
Reference in New Issue
Block a user