This repository has been archived on 2026-08-07. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
openclaw/skills/gsd-openclaw/.planning/phases/01-core-infrastructure/01-04-PLAN.md
T

14 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, user_setup, must_haves
phase plan type wave depends_on files_modified autonomous requirements user_setup must_haves
01-core-infrastructure 04 execute 3
01-03
src/gsd_openclaw/cli.py
src/gsd_openclaw/tmux_session.py
tests/test_cli.py
pyproject.toml
true
SESS-05
truths artifacts key_links
User can resume an interrupted session with context restoration
User can invoke create/list/destroy/resume from command line
Orphan sessions are detected and can be cleaned up
State is validated before session resumption
path provides exports min_lines
src/gsd_openclaw/cli.py CLI entry point for session management
main
cli
100
path provides contains
tests/test_cli.py Test coverage for CLI commands test_cli_create
from to via pattern
src/gsd_openclaw/cli.py tmux_session.TmuxSessionManager CLI invokes session operations TmuxSessionManager
from to via pattern
cli resume state_manager.load State validation before resumption load_state
Implement CLI entry point with session recovery and orphan cleanup.

Purpose: Provides the user-facing interface for session management. Includes resume functionality that restores context from persisted state and cleanup for orphan sessions.

Output: Working CLI with create/list/destroy/resume/cleanup commands.

<execution_context> @/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md @/home/starmen1265/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/01-core-infrastructure/01-CONTEXT.md @.planning/phases/01-core-infrastructure/01-RESEARCH.md

TmuxSessionManager:

class TmuxSessionManager:
    def create_session(self, project_name: str, ...) -> TmuxSession: ...
    def destroy_session(self, project_name: str, cleanup_workspace: bool) -> bool: ...
    def list_gsd_sessions(self) -> List[SessionInfo]: ...

SessionInfo:

@dataclass
class SessionInfo:
    tmux_name: str
    project_name: str
    session_id: str
    created_at: str
    last_active_at: str
    command_count: int
    display_name: Optional[str]
    is_orphan: bool

StateManager:

class StateManager:
    def save(self, state: SessionState) -> None: ...
    def load(self) -> Optional[SessionState]: ...
Task 1: Implement session resume with state validation src/gsd_openclaw/tmux_session.py, tests/test_tmux_session.py - Test 1: resume_session returns existing session if state valid and tmux exists - Test 2: resume_session recreates tmux session if state exists but tmux missing - Test 3: resume_session raises error if no state file exists - Test 4: resume_session validates schema version before resuming Add resume_session and get_session methods to TmuxSessionManager:
class TmuxSessionError(Exception):
    """Errors related to tmux session management."""
    pass


class TmuxSessionManager:
    # ... existing code ...

    def get_session(self, project_name: str) -> Optional[TmuxSession]:
        """Get existing session without creating new one."""
        state_manager = StateManager(project_name)
        state = state_manager.load()

        if state is None:
            return None

        # Find tmux session
        try:
            tmux_session = self.server.sessions.get(
                session_name=state.tmux_session_name
            )
        except libtmux.exc.LibTmuxException:
            tmux_session = None

        return TmuxSession(
            session_id=state.session_id,
            project_name=project_name,
            tmux_session_name=state.tmux_session_name,
            tmux_session=tmux_session
        )

    def resume_session(self, project_name: str) -> TmuxSession:
        """Resume an existing session, recreating tmux if needed."""
        state_manager = StateManager(project_name)
        state = state_manager.load()

        if state is None:
            raise TmuxSessionError(f"No session found for project: {project_name}")

        # Validate state
        if state.schema_version != "1.0":
            raise TmuxSessionError(
                f"Unsupported state version: {state.schema_version}"
            )

        # Check if tmux session exists
        try:
            tmux_session = self.server.sessions.get(
                session_name=state.tmux_session_name
            )
        except libtmux.exc.LibTmuxException:
            tmux_session = None

        # Recreate tmux session if missing
        if tmux_session is None:
            workspace_manager = WorkspaceManager(project_name)
            if not workspace_manager.exists():
                raise TmuxSessionError(
                    f"Workspace missing for project: {project_name}"
                )
            # Create new tmux session with same name
            tmux_session = self.server.new_session(
                session_name=state.tmux_session_name,
                attach=False,
                window_name='main',
                start_directory=str(workspace_manager.get_workspace().root)
            )
            # Update pane ID in state
            state.tmux_pane_id = tmux_session.active_pane.pane_id
            state.last_active_at = datetime.now().isoformat()
            state_manager.save(state)

        return TmuxSession(
            session_id=state.session_id,
            project_name=project_name,
            tmux_session_name=state.tmux_session_name,
            tmux_session=tmux_session
        )
pytest tests/test_tmux_session.py::test_resume_session -x pytest tests/test_tmux_session.py::test_resume_recreates_tmux -x resume_session validates state, recreates tmux if needed, raises clear errors Task 2: Implement orphan session cleanup src/gsd_openclaw/tmux_session.py, tests/test_tmux_session.py - Test 1: cleanup_orphans kills tmux sessions without state files - Test 2: cleanup_orphans returns list of cleaned session names - Test 3: cleanup_orphans handles sessions with corrupted state files Add cleanup_orphans method:
from typing import List

@dataclass
class CleanupResult:
    """Result of orphan cleanup."""
    cleaned_sessions: List[str]
    errors: List[str]


class TmuxSessionManager:
    # ... existing code ...

    def cleanup_orphans(self) -> CleanupResult:
        """Remove tmux sessions without corresponding state files."""
        cleaned = []
        errors = []

        # Get all gsd-* tmux sessions
        tmux_sessions = self.list_sessions()

        for tmux_session in tmux_sessions:
            tmux_name = tmux_session.name
            project_name = self._extract_project_name(tmux_name)

            # Check for state file
            state_manager = StateManager(project_name)
            state = state_manager.load()

            should_clean = False

            if state is None:
                # No state file - orphan
                should_clean = True
            elif state.tmux_session_name != tmux_name:
                # State exists but references different tmux session
                # This could be a collision case, treat as orphan
                should_clean = True

            if should_clean:
                try:
                    tmux_session.kill()
                    cleaned.append(tmux_name)
                except libtmux.exc.LibTmuxException as e:
                    errors.append(f"Failed to kill {tmux_name}: {e}")

        return CleanupResult(cleaned_sessions=cleaned, errors=errors)
pytest tests/test_tmux_session.py::test_cleanup_orphans -x cleanup_orphans removes tmux sessions without state files, reports results Task 3: Implement CLI with argparse src/gsd_openclaw/cli.py, tests/test_cli.py - Test 1: `gsd-ocl create ` creates session - Test 2: `gsd-ocl list` shows sessions - Test 3: `gsd-ocl destroy ` removes session - Test 4: `gsd-ocl resume ` resumes session - Test 5: `gsd-ocl cleanup` removes orphans - Test 6: CLI exits with code 1 on error Create CLI using argparse following dev-workflow pattern:
#!/usr/bin/env python3
"""CLI for gsd-openclaw session management."""

import argparse
import sys
from datetime import datetime

from gsd_openclaw.tmux_session import (
    TmuxSessionManager,
    TmuxSessionError,
    CleanupResult,
)


def cmd_create(args):
    """Create a new session for a project."""
    manager = TmuxSessionManager()
    try:
        session = manager.create_session(
            project_name=args.project,
            display_name=args.display_name
        )
        print(f"Created session: {session.tmux_session_name}")
        print(f"Session ID: {session.session_id}")
        print(f"Project: {session.project_name}")
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)


def cmd_list(args):
    """List all gsd sessions."""
    manager = TmuxSessionManager()
    sessions = manager.list_gsd_sessions()

    if not sessions:
        print("No active sessions.")
        return

    print(f"{'Project':<20} {'Session ID':<10} {'Last Active':<20} {'Status'}")
    print("-" * 70)
    for s in sessions:
        status = "ORPHAN" if s.is_orphan else "active"
        display = s.display_name or s.project_name
        print(f"{display:<20} {s.session_id:<10} {s.last_active_at:<20} {status}")


def cmd_destroy(args):
    """Destroy a session."""
    manager = TmuxSessionManager()
    result = manager.destroy_session(
        project_name=args.project,
        cleanup_workspace=args.cleanup
    )
    if result:
        print(f"Destroyed session for: {args.project}")
    else:
        print(f"No session found for: {args.project}")
        sys.exit(1)


def cmd_resume(args):
    """Resume an existing session."""
    manager = TmuxSessionManager()
    try:
        session = manager.resume_session(project_name=args.project)
        print(f"Resumed session: {session.tmux_session_name}")
        print(f"Session ID: {session.session_id}")
        print(f"Project: {session.project_name}")
    except TmuxSessionError as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)


def cmd_cleanup(args):
    """Clean up orphan sessions."""
    manager = TmuxSessionManager()
    result = manager.cleanup_orphans()

    if result.cleaned_sessions:
        print(f"Cleaned {len(result.cleaned_sessions)} orphan session(s):")
        for name in result.cleaned_sessions:
            print(f"  - {name}")

    if result.errors:
        print(f"Errors ({len(result.errors)}):")
        for err in result.errors:
            print(f"  - {err}")

    if not result.cleaned_sessions and not result.errors:
        print("No orphan sessions found.")


def main():
    parser = argparse.ArgumentParser(
        prog="gsd-ocl",
        description="GSD-OpenClaw session management"
    )
    subparsers = parser.add_subparsers(dest="command", required=True)

    # create
    p_create = subparsers.add_parser("create", help="Create a new session")
    p_create.add_argument("project", help="Project name")
    p_create.add_argument("--display-name", "-d", help="Friendly display name")
    p_create.set_defaults(func=cmd_create)

    # list
    p_list = subparsers.add_parser("list", help="List all sessions")
    p_list.set_defaults(func=cmd_list)

    # destroy
    p_destroy = subparsers.add_parser("destroy", help="Destroy a session")
    p_destroy.add_argument("project", help="Project name")
    p_destroy.add_argument("--cleanup", "-c", action="store_true",
                           help="Also remove workspace directory")
    p_destroy.set_defaults(func=cmd_destroy)

    # resume
    p_resume = subparsers.add_parser("resume", help="Resume a session")
    p_resume.add_argument("project", help="Project name")
    p_resume.set_defaults(func=cmd_resume)

    # cleanup
    p_cleanup = subparsers.add_parser("cleanup", help="Clean up orphan sessions")
    p_cleanup.set_defaults(func=cmd_cleanup)

    args = parser.parse_args()
    args.func(args)


if __name__ == "__main__":
    main()
pytest tests/test_cli.py -x CLI provides create/list/destroy/resume/cleanup commands via argparse Task 4: Configure pyproject.toml with CLI entry point pyproject.toml Create or update pyproject.toml with package configuration:
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

[project]
name = "gsd-openclaw"
version = "0.1.0"
description = "GSD to OpenClaw bridge skill"
requires-python = ">=3.9"
dependencies = [
    "libtmux>=0.53.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=8.0",
    "pytest-cov>=4.0",
]

[project.scripts]
gsd-ocl = "gsd_openclaw.cli:main"

[tool.setuptools.packages.find]
where = ["src"]

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = "-v --tb=short"

This makes the CLI installable via pip install -e . and accessible as gsd-ocl. pip install -e . && gsd-ocl --help Package installable, gsd-ocl command available in PATH

Run full test suite: ```bash pytest tests/ -v ```

Test CLI end-to-end:

pip install -e .
gsd-ocl create test-project
gsd-ocl list
gsd-ocl resume test-project
gsd-ocl destroy test-project
gsd-ocl cleanup

<success_criteria>

  • resume_session validates state and recreates tmux if needed
  • cleanup_orphans removes orphan tmux sessions
  • CLI provides all session management commands
  • CLI exits with appropriate error codes
  • Package installable via pip
  • gsd-ocl command available in PATH
  • All tests pass: pytest tests/ -v </success_criteria>
After completion, create `.planning/phases/01-core-infrastructure/01-04-SUMMARY.md`