diff --git a/.github/workflows/strategies-smoke.yml b/.github/workflows/strategies-smoke.yml index 3f80540..d7daa02 100644 --- a/.github/workflows/strategies-smoke.yml +++ b/.github/workflows/strategies-smoke.yml @@ -226,13 +226,13 @@ jobs: if: steps.secrets_ok.outputs.ok == 'true' run: | set -euo pipefail - RUN_DIR=$(ls -td results/run_* 2>/dev/null | head -n1 || true) + RUN_DIR=$(ls -td .pitaya/results/run_* 2>/dev/null | head -n1 || true) if [ -z "$RUN_DIR" ]; then echo "No results directory found" >&2 exit 1 fi RUN_ID=$(basename "$RUN_DIR") - STATE_JSON="pitaya_state/$RUN_ID/state.json" + STATE_JSON=".pitaya/state/$RUN_ID/state.json" echo "Results: $RUN_DIR" >> $GITHUB_STEP_SUMMARY if [ ! -f "$STATE_JSON" ]; then diff --git a/.gitignore b/.gitignore index 23eda0c..7f44be7 100644 --- a/.gitignore +++ b/.gitignore @@ -54,13 +54,6 @@ dmypy.json .spyproject .ropeproject -# Logs -logs/ -*.log -log/ -*.log.* -test_logs/ - # Environment variables .env .env.* @@ -81,8 +74,5 @@ desktop.ini *.backup *.orig -# Output directories -output/ -results/ -runs/ -pitaya_state/ \ No newline at end of file +# Runtime/output +.pitaya/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b399e6..608e801 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Persist per-instance metadata to results and logs artifacts. ([#79](https://github.com/tact-lang/pitaya/pull/79)) - CLI flags: `--override-config` and `--resume-key-policy {strict|suffix}` to control how resume applies overrides and durable key behavior. ([#81](https://github.com/tact-lang/pitaya/pull/81)) -- Persist the effective run configuration to `pitaya_state//config.json` and a redacted copy to `logs//config.json` to improve resume fidelity. ([#81](https://github.com/tact-lang/pitaya/pull/81)) +- Persist the effective run configuration to `.pitaya/state//config.json` and a redacted copy to `logs//config.json` to improve resume fidelity. ([#81](https://github.com/tact-lang/pitaya/pull/81)) - Strategy: `pr-review` — N reviewers, validator per reviewer, and a composer; CI‑friendly with JSON trailer parsing and fail gating. - Workspace: Optional `--include-branches` (CSV/JSON) or config `runner.include_branches` to materialize extra read‑only branches in the isolated workspace for all strategies. Also supported per-task via `workspace_include_branches` metadata. ([#95](https://github.com/tact-lang/pitaya/pull/95)) diff --git a/README.md b/README.md index 9f123ac..311217e 100644 --- a/README.md +++ b/README.md @@ -117,8 +117,8 @@ CLI overrides config; `-S key=value` only affects the selected strategy. ## Results & Logs -- Logs: `logs//events.jsonl` and JSONL component logs (orchestration.jsonl, runner.jsonl, tui.jsonl, other.jsonl) -- Results: `results//` (summary.json, metadata.json, branches.txt, instance_metrics.csv, instances/.json) +- Logs: `.pitaya/logs//events.jsonl` and JSONL component logs (orchestration.jsonl, runner.jsonl, tui.jsonl, other.jsonl) +- Results: `.pitaya/results//` (summary.json, metadata.json, branches.txt, instance_metrics.csv, instances/.json) - Branches: `pitaya///k` (hierarchical namespace) - Resume: `pitaya --resume ` diff --git a/docs/cli.md b/docs/cli.md index dc22624..c3a2df7 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -169,8 +169,8 @@ pitaya "task" --force-commit # force a commit if changes exist - Precedence: CLI > env > .env > project config > defaults. - Directories: - - State: `--state-dir ./pitaya_state` (default) - - Logs: `--logs-dir ./logs` (default) + - State: `--state-dir ./.pitaya/state` (default) + - Logs: `--logs-dir .pitaya/logs` (default) ## Maintenance diff --git a/docs/configuration.md b/docs/configuration.md index 78a4b18..cb9de22 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -38,7 +38,7 @@ strategies: iterations: 3 logging: - # JSONL component logs under logs// + # JSONL component logs under .pitaya/logs// max_file_size: 10485760 # 10MB per component file before rotation retention_days: 7 # cleanup old run dirs (component logs) console_verbose: false # headless-only: surface agent/tool steps to console (same as --verbose) @@ -81,13 +81,13 @@ Most strategies leave these at defaults. To force an import even when there are ## Directories -- State: `pitaya_state/` (can be changed with `--state-dir`) -- Logs: `logs//` (change with `--logs-dir`) -- Results: `results//` (summary.json, metadata.json, branches.txt, instance_metrics.csv, instances/.json) +- State: `.pitaya/state/` (can be changed with `--state-dir`) +- Logs: `.pitaya/logs//` (change with `--logs-dir`) +- Results: `.pitaya/results//` (summary.json, metadata.json, branches.txt, instance_metrics.csv, instances/.json) Structured logs -- Pitaya writes JSON Lines component logs in `logs//` (orchestration.jsonl, runner.jsonl, tui.jsonl, other.jsonl). +- Pitaya writes JSON Lines component logs in `.pitaya/logs//` (orchestration.jsonl, runner.jsonl, tui.jsonl, other.jsonl). - Old log directories are cleaned up periodically; component files rotate by size. - Custom redaction patterns from `logging.redaction.custom_patterns` are applied to both logs and emitted events. @@ -95,8 +95,8 @@ Structured logs On a fresh run, Pitaya writes the fully merged configuration (CLI + env + .env + file + defaults): -- Unredacted copy to `pitaya_state//config.json` (for fidelity on resume). The `pitaya_state/` directory is git‑ignored by default. -- Redacted copy to `logs//config.json` (tokens/API keys masked) for auditability alongside logs. +- Unredacted copy to `.pitaya/state//config.json` (for fidelity on resume). The `.pitaya/state/` directory is git‑ignored by default. +- Redacted copy to `.pitaya/logs//config.json` (tokens/API keys masked) for auditability alongside logs. On `--resume `, Pitaya loads the saved effective config by default. This preserves durable keys and behavior. CLI overrides on resume are applied as follows: diff --git a/docs/custom-strategies.md b/docs/custom-strategies.md index d652b77..978c51a 100644 --- a/docs/custom-strategies.md +++ b/docs/custom-strategies.md @@ -40,7 +40,7 @@ Create a file `my_strategy.py` in your repo: ```python from dataclasses import dataclass from typing import List -from pitaya.orchestration.strategies.base import Strategy, StrategyConfig +from pitaya.orchestration.strategy.base import Strategy, StrategyConfig from pitaya.shared import InstanceResult @dataclass @@ -80,7 +80,7 @@ Spawn N parallel tasks and wait for all, tolerating failures: ```python from dataclasses import dataclass from typing import List -from pitaya.orchestration.strategies.base import Strategy, StrategyConfig +from pitaya.orchestration.strategy.base import Strategy, StrategyConfig from pitaya.shared import InstanceResult @dataclass diff --git a/docs/quickstart.md b/docs/quickstart.md index 2a3a708..0f7bddc 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -59,7 +59,7 @@ cd pitaya uv run pitaya --version # Or directly via Python module -python -m src.cli --version +python -m pitaya --version ``` ## Authenticate @@ -108,7 +108,7 @@ uv run pitaya "Create a HELLO.txt file with 'Hello from Pitaya' text in it and c Tips - Press Ctrl+C to stop the run. Pitaya shuts down gracefully (containers stopped, state saved) and prints a resume hint with the run ID. -- After completion, results are written under `results//` and logs under `logs//`. +- After completion, results are written under `.pitaya/results//` and logs under `.pitaya/logs//`. - Branches are created only if the agent commits changes. ## Explore More @@ -135,8 +135,8 @@ pitaya "Write the funniest and most original joke possible" --strategy iterative ## Where to Find Results -- Logs: `logs//events.jsonl` and JSONL component logs (orchestration.jsonl, runner.jsonl, tui.jsonl, other.jsonl) -- Results: `results//` (summary.json, metadata.json, branches.txt, metrics, instances/.json) +- Logs: `.pitaya/logs//events.jsonl` and JSONL component logs (orchestration.jsonl, runner.jsonl, tui.jsonl, other.jsonl) +- Results: `.pitaya/results//` (summary.json, metadata.json, branches.txt, metrics, instances/.json) - Branches: `pitaya///k` (hierarchical namespace) List branches: diff --git a/docs/tui.md b/docs/tui.md index a5a5307..7926296 100644 --- a/docs/tui.md +++ b/docs/tui.md @@ -44,7 +44,7 @@ Inspect a previous run without the orchestrator running: ```bash pitaya-tui --run-id run_20250114_123456 # or from an events file -pitaya-tui --events-file logs/run_20250114_123456/events.jsonl --output streaming +pitaya-tui --events-file .pitaya/logs/run_20250114_123456/events.jsonl --output streaming ``` Filters: @@ -54,5 +54,5 @@ Filters: ## Tips - Cancel with Ctrl+C; a graceful shutdown stops containers, saves state, and prints a resume hint with the run ID. -- Logs live at `logs//`; results at `results//`. +- Logs live at `.pitaya/logs//`; results at `.pitaya/results//`. - In compact mode, long branches shorten to their unique suffix with a leading `/` so the `k` tail is visible. diff --git a/examples/custom_simple.py b/examples/custom_simple.py index c83bb7a..6b3e310 100644 --- a/examples/custom_simple.py +++ b/examples/custom_simple.py @@ -11,9 +11,9 @@ from dataclasses import dataclass from typing import List -from src.orchestration.strategies.base import Strategy, StrategyConfig -from src.orchestration.strategy_context import StrategyContext -from src.shared import InstanceResult +from pitaya.orchestration.strategy.base import Strategy, StrategyConfig +from pitaya.orchestration.strategy.context import StrategyContext +from pitaya.shared import InstanceResult @dataclass diff --git a/examples/fanout_two.py b/examples/fanout_two.py index 8e2f616..0af9444 100644 --- a/examples/fanout_two.py +++ b/examples/fanout_two.py @@ -12,9 +12,9 @@ from dataclasses import dataclass from typing import List -from src.orchestration.strategies.base import Strategy, StrategyConfig -from src.orchestration.strategy_context import StrategyContext -from src.shared import InstanceResult +from pitaya.orchestration.strategy.base import Strategy, StrategyConfig +from pitaya.orchestration.strategy.context import StrategyContext +from pitaya.shared import InstanceResult @dataclass diff --git a/examples/propose_refine.py b/examples/propose_refine.py index c028092..7f482fc 100644 --- a/examples/propose_refine.py +++ b/examples/propose_refine.py @@ -14,9 +14,9 @@ from dataclasses import dataclass from typing import List -from src.orchestration.strategies.base import Strategy, StrategyConfig -from src.orchestration.strategy_context import StrategyContext -from src.shared import InstanceResult +from pitaya.orchestration.strategy.base import Strategy, StrategyConfig +from pitaya.orchestration.strategy.context import StrategyContext +from pitaya.shared import InstanceResult @dataclass diff --git a/pyproject.toml b/pyproject.toml index 51d18c4..a749bf0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,21 +35,21 @@ maintainers = [ urls = { "Repository" = "https://github.com/tact-lang/pitaya", "Issues" = "https://github.com/tact-lang/pitaya/issues" } [project.scripts] -pitaya = "src.cli:main" -pitaya-tui = "src.tui.cli:main" +pitaya = "pitaya.cli.main:main" +pitaya-tui = "pitaya.tui.cli:main" [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] -packages = ["src"] +packages = ["src/pitaya"] [tool.hatch.build.targets.sdist] exclude = [ "logs", "results", - "pitaya_state", + ".pitaya", ".venv", ".ruff_cache", ".mypy_cache", diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index 493f741..0000000 --- a/src/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.3.0" diff --git a/src/__main__.py b/src/__main__.py deleted file mode 100644 index bbe8ae8..0000000 --- a/src/__main__.py +++ /dev/null @@ -1,11 +0,0 @@ -""" -Main entry point for Pitaya. - -This module allows Pitaya to be run as: - python -m src -""" - -from .cli import main - -if __name__ == "__main__": - main() diff --git a/src/instance_runner/__init__.py b/src/instance_runner/__init__.py deleted file mode 100644 index 642d938..0000000 --- a/src/instance_runner/__init__.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -Instance Runner — executes AI coding agent instances in isolated Docker containers. - -Used by Pitaya to orchestrate tools such as Claude Code and Codex CLI. -""" - -__version__ = "0.3.0" - -# Import exceptions from common module -from ..exceptions import ( - OrchestratorError, - DockerError, - GitError, - AgentError, - TimeoutError, - ValidationError, -) - -# Import from public API -from .api import run_instance - -# Import shared types instead of local ones -from ..shared import ( - InstanceResult, - ContainerLimits, - AuthConfig, - RetryConfig, - RunnerPlugin, - PluginCapabilities, -) - -__all__ = [ - # Main function - "run_instance", - # Data classes - "InstanceResult", - "ContainerLimits", - "AuthConfig", - "RetryConfig", - # Plugin system - "RunnerPlugin", - "PluginCapabilities", - # Exceptions - "OrchestratorError", - "DockerError", - "GitError", - "AgentError", - "TimeoutError", - "ValidationError", -] diff --git a/src/instance_runner/plugin_interface.py b/src/instance_runner/plugin_interface.py deleted file mode 100644 index 4656bc6..0000000 --- a/src/instance_runner/plugin_interface.py +++ /dev/null @@ -1,11 +0,0 @@ -""" -Runner Plugin Interface for supporting multiple AI coding tools. - -This module defines the abstract interface that all AI tool plugins must implement, -as specified in section 3.8 of the Pitaya specification. -""" - -# Re-export from shared types to maintain compatibility -from ..shared import PluginCapabilities, RunnerPlugin - -__all__ = ["PluginCapabilities", "RunnerPlugin"] diff --git a/src/instance_runner/types.py b/src/instance_runner/types.py deleted file mode 100644 index a01dcdb..0000000 --- a/src/instance_runner/types.py +++ /dev/null @@ -1,80 +0,0 @@ -""" -Common types and data classes for the instance runner. -""" - -from dataclasses import dataclass, field -from typing import Any, Dict, Optional - - -@dataclass -class ContainerLimits: - """Resource limits for Docker containers.""" - - cpu_count: int = 2 - memory_gb: int = 4 - memory_swap_gb: int = 4 # Total memory + swap - - -@dataclass -class AuthConfig: - """Authentication configuration for AI tools.""" - - oauth_token: Optional[str] = None - api_key: Optional[str] = None - base_url: Optional[str] = None - - -@dataclass -class RetryConfig: - """Configuration for retry mechanism.""" - - max_attempts: int = 3 - initial_delay_seconds: float = 10.0 # Per spec: 10s, 60s, 360s - max_delay_seconds: float = 360.0 - exponential_base: float = 6.0 # To get 10s -> 60s -> 360s progression - # Pattern-based retry logic as per spec - docker_error_patterns: tuple = ( - "connection refused", - "no such host", - "timeout", - "Cannot connect to the Docker daemon", - "already in use", # name conflict - "409 Client Error", # explicit status hint - 'Conflict ("Conflict.', # docker's overlap wording - ) - agent_error_patterns: tuple = ( - "rate limit", - "API error", - "connection reset", - "429", # Rate limit status code - ) - general_error_patterns: tuple = ( - "ECONNREFUSED", - "ETIMEDOUT", - "ENETUNREACH", - "Connection timed out", - ) - - -@dataclass -class InstanceResult: - """Result from running a single instance.""" - - success: bool - branch_name: Optional[str] = None - has_changes: bool = False - final_message: Optional[str] = None - session_id: Optional[str] = None - container_name: Optional[str] = None - metrics: Dict[str, Any] = field(default_factory=dict) - error: Optional[str] = None - error_type: Optional[str] = None - duration_seconds: Optional[float] = None - # Additional fields from specification - commit_statistics: Optional[Dict[str, Any]] = None # count, lines added/deleted - started_at: Optional[str] = None # ISO timestamp - completed_at: Optional[str] = None # ISO timestamp - retry_attempts: int = 0 - log_path: Optional[str] = None - workspace_path: Optional[str] = None # Until cleanup - status: str = "unknown" # success/failed/timeout/canceled diff --git a/src/orchestration/__init__.py b/src/orchestration/__init__.py deleted file mode 100644 index a2565a7..0000000 --- a/src/orchestration/__init__.py +++ /dev/null @@ -1,45 +0,0 @@ -""" -Orchestration component for coordinating multiple AI coding agents. - -Pitaya orchestrates agents such as Claude Code and Codex CLI with pluggable -and custom strategies. This component manages parallel execution, events, and -state, providing the intelligence for complex multi-instance workflows. -""" - -from .orchestrator import Orchestrator -from .event_bus import EventBus -from .state import RunState, StateManager -from .strategies import ( - Strategy, - StrategyConfig, - AVAILABLE_STRATEGIES, -) -from ..exceptions import ( - OrchestratorError, - DockerError, - GitError, - StrategyError, - ValidationError, - TimeoutError, - AgentError, -) - -__version__ = "0.3.0" - -__all__ = [ - "Orchestrator", - "EventBus", - "RunState", - "StateManager", - "Strategy", - "StrategyConfig", - "AVAILABLE_STRATEGIES", - # Exceptions - "OrchestratorError", - "DockerError", - "GitError", - "StrategyError", - "ValidationError", - "TimeoutError", - "AgentError", -] diff --git a/src/orchestration/cli/__init__.py b/src/orchestration/cli/__init__.py deleted file mode 100644 index 4316d8a..0000000 --- a/src/orchestration/cli/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -"""CLI subpackage for orchestrator-facing commands and helpers. - -Each module is kept small and focused to comply with the handbook budgets. -Public surface is explicit via __all__. -""" - -from __future__ import annotations - -__all__ = [ - "doctor", - "config_print", - "runs", - "results_display", - "config_loader", - "auth", - "strategy_config", - "validation", - "preflight", - "orchestrator_runner", - "headless", - "tui_runner", - "headless_stream", - "headless_handlers", - "runner_setup", -] diff --git a/src/orchestration/state.py b/src/orchestration/state.py deleted file mode 100644 index 3f1e8c8..0000000 --- a/src/orchestration/state.py +++ /dev/null @@ -1,6 +0,0 @@ -"""State management exports.""" - -from .state_manager import StateManager -from .state_models import InstanceInfo, RunState, StrategyExecution - -__all__ = ["InstanceInfo", "RunState", "StrategyExecution", "StateManager"] diff --git a/src/orchestration/strategies/__init__.py b/src/orchestration/strategies/__init__.py deleted file mode 100644 index bf33b39..0000000 --- a/src/orchestration/strategies/__init__.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -Orchestration strategies for coordinating multiple AI coding instances. -""" - -from .base import Strategy, StrategyConfig -from .simple import SimpleStrategy -from .scoring import ScoringStrategy, ScoringConfig -from .best_of_n import BestOfNStrategy, BestOfNConfig -from .iterative import IterativeStrategy, IterativeConfig -from .bug_finding import BugFindingStrategy, BugFindingConfig -from .doc_review import DocReviewStrategy, DocReviewConfig -from .pr_review import PRReviewStrategy, PRReviewConfig - -# Registry of available strategies -AVAILABLE_STRATEGIES = { - "simple": SimpleStrategy, - "scoring": ScoringStrategy, - "best-of-n": BestOfNStrategy, - "iterative": IterativeStrategy, - "bug-finding": BugFindingStrategy, - "doc-review": DocReviewStrategy, - "pr-review": PRReviewStrategy, -} - -__all__ = [ - "Strategy", - "StrategyConfig", - "SimpleStrategy", - "ScoringStrategy", - "ScoringConfig", - "BestOfNStrategy", - "BestOfNConfig", - "IterativeStrategy", - "IterativeConfig", - "BugFindingStrategy", - "BugFindingConfig", - "DocReviewStrategy", - "DocReviewConfig", - "PRReviewStrategy", - "PRReviewConfig", - "AVAILABLE_STRATEGIES", -] diff --git a/src/pitaya/__init__.py b/src/pitaya/__init__.py new file mode 100644 index 0000000..03a6931 --- /dev/null +++ b/src/pitaya/__init__.py @@ -0,0 +1,12 @@ +"""Pitaya public API surface. + +This package intentionally exposes only the stable entry points needed by +consumers; everything else should be considered internal and may change. +""" + +from .orchestration.orchestrator import Orchestrator +from .runner.api import run_instance +from .shared.results import InstanceResult +from .version import __version__ + +__all__ = ["Orchestrator", "run_instance", "InstanceResult", "__version__"] diff --git a/src/pitaya/__main__.py b/src/pitaya/__main__.py new file mode 100644 index 0000000..d4d01b0 --- /dev/null +++ b/src/pitaya/__main__.py @@ -0,0 +1,6 @@ +"""Allow ``python -m pitaya`` to invoke the CLI.""" + +from pitaya.cli.main import main + +if __name__ == "__main__": + main() diff --git a/src/pitaya/cli/__init__.py b/src/pitaya/cli/__init__.py new file mode 100644 index 0000000..e45ec11 --- /dev/null +++ b/src/pitaya/cli/__init__.py @@ -0,0 +1,18 @@ +"""CLI layer for the Pitaya command-line interface.""" + +__all__ = [ + "main", + "parser", + "sections", + "auth", + "preflight", + "headless", + "headless_handlers", + "headless_stream", + "orchestrator_runner", + "runner_setup", + "runs", + "results_display", + "config_print", + "tui_runner", +] diff --git a/src/orchestration/cli/auth.py b/src/pitaya/cli/auth.py similarity index 98% rename from src/orchestration/cli/auth.py rename to src/pitaya/cli/auth.py index 4acc141..e29394f 100644 --- a/src/orchestration/cli/auth.py +++ b/src/pitaya/cli/auth.py @@ -9,13 +9,13 @@ import argparse import os -from ...config import ( +from pitaya.config import ( get_default_config, load_dotenv_config, load_env_config, merge_config, ) -from ...shared import AuthConfig +from pitaya.shared import AuthConfig __all__ = ["get_auth_config"] diff --git a/src/orchestration/cli/config_print.py b/src/pitaya/cli/config_print.py similarity index 99% rename from src/orchestration/cli/config_print.py rename to src/pitaya/cli/config_print.py index ccd7c9a..c9d6e8e 100644 --- a/src/orchestration/cli/config_print.py +++ b/src/pitaya/cli/config_print.py @@ -13,7 +13,7 @@ from rich.console import Console -from ...config import ( +from pitaya.config import ( get_default_config, load_dotenv_config, load_env_config, diff --git a/src/orchestration/cli/doctor.py b/src/pitaya/cli/doctor.py similarity index 97% rename from src/orchestration/cli/doctor.py rename to src/pitaya/cli/doctor.py index 8c9ad8e..d388f0f 100644 --- a/src/orchestration/cli/doctor.py +++ b/src/pitaya/cli/doctor.py @@ -12,8 +12,8 @@ from rich.console import Console -from ...config import load_dotenv_config, load_env_config -from ...utils.platform_utils import get_temp_dir, validate_docker_setup +from pitaya.config import load_dotenv_config, load_env_config +from pitaya.utils.platform_utils import get_temp_dir, validate_docker_setup __all__ = ["run_doctor"] diff --git a/src/orchestration/cli/headless.py b/src/pitaya/cli/headless.py similarity index 95% rename from src/orchestration/cli/headless.py rename to src/pitaya/cli/headless.py index 91df57b..2c9cc74 100644 --- a/src/orchestration/cli/headless.py +++ b/src/pitaya/cli/headless.py @@ -7,10 +7,10 @@ from rich.console import Console -from ...orchestration import Orchestrator +from pitaya.orchestration import Orchestrator from .headless_stream import subscribe_json, subscribe_streaming from .results_display import display_detailed_results -from .strategy_config import get_strategy_config +from pitaya.config.strategy import get_strategy_config __all__ = ["run_headless"] diff --git a/src/orchestration/cli/headless_handlers.py b/src/pitaya/cli/headless_handlers.py similarity index 100% rename from src/orchestration/cli/headless_handlers.py rename to src/pitaya/cli/headless_handlers.py diff --git a/src/orchestration/cli/headless_stream.py b/src/pitaya/cli/headless_stream.py similarity index 96% rename from src/orchestration/cli/headless_stream.py rename to src/pitaya/cli/headless_stream.py index 8c85da9..bd94c76 100644 --- a/src/orchestration/cli/headless_stream.py +++ b/src/pitaya/cli/headless_stream.py @@ -7,7 +7,7 @@ from rich.console import Console -from ...orchestration import Orchestrator +from pitaya.orchestration import Orchestrator from .headless_handlers import build_streaming_handlers, make_prefix __all__ = ["subscribe_streaming", "subscribe_json"] diff --git a/src/cli.py b/src/pitaya/cli/main.py similarity index 88% rename from src/cli.py rename to src/pitaya/cli/main.py index ce6f7d7..60d779a 100644 --- a/src/cli.py +++ b/src/pitaya/cli/main.py @@ -2,7 +2,7 @@ """Pitaya CLI entrypoint (lean, testable, handbook-compliant). Provides a thin shell that delegates to small, cohesive helpers under -`src/orchestration/cli/` and a separate parser builder. +``pitaya.cli`` and a separate parser builder. """ from __future__ import annotations @@ -14,8 +14,8 @@ from rich.console import Console -from .orchestration.cli_parser import create_parser -from .orchestration.cli import config_print, doctor, orchestrator_runner, runs +from pitaya.cli.parser import create_parser +from pitaya.cli import config_print, doctor, orchestrator_runner, runs __all__: Final = ["main"] diff --git a/src/orchestration/cli/orchestrator_runner.py b/src/pitaya/cli/orchestrator_runner.py similarity index 94% rename from src/orchestration/cli/orchestrator_runner.py rename to src/pitaya/cli/orchestrator_runner.py index cb30457..743d2eb 100644 --- a/src/orchestration/cli/orchestrator_runner.py +++ b/src/pitaya/cli/orchestrator_runner.py @@ -11,9 +11,9 @@ from rich.console import Console -from ...exceptions import DockerError, OrchestratorError, ValidationError -from ...orchestration import Orchestrator -from ...shared import AuthConfig, ContainerLimits, RetryConfig +from pitaya.exceptions import DockerError, OrchestratorError, ValidationError +from pitaya.orchestration import Orchestrator +from pitaya.shared import AuthConfig, ContainerLimits, RetryConfig from . import headless as headless_run from . import tui_runner from .auth import get_auth_config @@ -25,7 +25,7 @@ propagate_resume_paths, setup_logging, ) -from .validation import validate_full_config +from pitaya.config.validation import validate_full_config __all__ = ["run"] @@ -67,7 +67,7 @@ def _validate_docker( console: Console, args: argparse.Namespace, run_id: str ) -> Optional[int]: try: - from ...utils.platform_utils import validate_docker_setup + from pitaya.utils.platform_utils import validate_docker_setup ok, _err = validate_docker_setup() if not ok: @@ -157,8 +157,9 @@ def _build_orchestrator( return Orchestrator( max_parallel_instances=total, max_parallel_startup=start, - state_dir=Path(orch_cfg.get("state_dir", Path("./pitaya_state"))), - logs_dir=Path(orch_cfg.get("logs_dir", Path("./logs"))), + state_dir=Path(orch_cfg.get("state_dir", Path(".pitaya/state"))), + logs_dir=Path(orch_cfg.get("logs_dir", Path(".pitaya/logs"))), + results_dir=Path(cfg.get("results_dir", Path(".pitaya/results"))), container_limits=_container_limits(cfg), retry_config=RetryConfig(max_attempts=3), auth_config=auth, diff --git a/src/orchestration/cli_parser.py b/src/pitaya/cli/parser.py similarity index 94% rename from src/orchestration/cli_parser.py rename to src/pitaya/cli/parser.py index 4cab0f6..a2010cb 100644 --- a/src/orchestration/cli_parser.py +++ b/src/pitaya/cli/parser.py @@ -7,9 +7,9 @@ from __future__ import annotations import argparse -from . import __version__ -from .strategies import AVAILABLE_STRATEGIES -from .cli.parser_sections import ( +from pitaya.version import __version__ +from pitaya.orchestration.strategy.builtin import AVAILABLE_STRATEGIES +from pitaya.cli.sections import ( add_global_and_positional, add_strategy_args, add_model_plugin_args, diff --git a/src/orchestration/cli/preflight.py b/src/pitaya/cli/preflight.py similarity index 100% rename from src/orchestration/cli/preflight.py rename to src/pitaya/cli/preflight.py diff --git a/src/orchestration/cli/results_display.py b/src/pitaya/cli/results_display.py similarity index 99% rename from src/orchestration/cli/results_display.py rename to src/pitaya/cli/results_display.py index 7633892..88d16ad 100644 --- a/src/orchestration/cli/results_display.py +++ b/src/pitaya/cli/results_display.py @@ -4,7 +4,7 @@ from dataclasses import dataclass from typing import Any, Iterable, List -from ...shared import InstanceResult, InstanceStatus +from pitaya.shared import InstanceResult, InstanceStatus from rich.console import Console diff --git a/src/orchestration/cli/runner_setup.py b/src/pitaya/cli/runner_setup.py similarity index 94% rename from src/orchestration/cli/runner_setup.py rename to src/pitaya/cli/runner_setup.py index 531ec11..44909c0 100644 --- a/src/orchestration/cli/runner_setup.py +++ b/src/pitaya/cli/runner_setup.py @@ -10,14 +10,14 @@ from rich.console import Console -from ...config import ( +from pitaya.config import ( get_default_config, load_dotenv_config, load_env_config, load_global_config, merge_config, ) -from .config_loader import build_cli_config, load_project_config +from pitaya.config.loader import build_cli_config, load_project_config __all__ = [ "apply_resume_overrides", @@ -67,7 +67,7 @@ def load_effective_config(args: argparse.Namespace, run_id: str) -> Dict[str, An if getattr(args, "resume", None): try: - cfg_path = Path(getattr(args, "state_dir", Path("./pitaya_state"))) + cfg_path = Path(getattr(args, "state_dir", Path(".pitaya/state"))) cfg_file = cfg_path / run_id / "config.json" with open(cfg_file, "r", encoding="utf-8") as fh: return json.load(fh) @@ -83,13 +83,13 @@ def setup_logging( """Configure structured logging and background log rotation if available.""" try: - from ...utils.log_rotation import cleanup_old_logs, setup_log_rotation_task - from ...utils.structured_logging import setup_structured_logging + from pitaya.utils.log_rotation import cleanup_old_logs, setup_log_rotation_task + from pitaya.utils.structured_logging import setup_structured_logging except ImportError: return logs_dir_cfg = full_config.get("orchestration", {}).get("logs_dir") or getattr( - args, "logs_dir", Path("./logs") + args, "logs_dir", Path(".pitaya/logs") ) logs_dir = Path(logs_dir_cfg) logging_cfg = full_config.get("logging", {}) or {} @@ -166,12 +166,12 @@ def persist_config_snapshots( try: sdir = Path( full_config.get("orchestration", {}).get( - "state_dir", getattr(args, "state_dir", Path("./pitaya_state")) + "state_dir", getattr(args, "state_dir", Path(".pitaya/state")) ) ) ldir = Path( full_config.get("orchestration", {}).get( - "logs_dir", getattr(args, "logs_dir", Path("./logs")) + "logs_dir", getattr(args, "logs_dir", Path(".pitaya/logs")) ) ) (sdir / run_id).mkdir(parents=True, exist_ok=True) @@ -226,7 +226,7 @@ def _apply_unsafe_overrides(cfg: Dict[str, Any], overrides: Dict[str, Any]) -> N def _explicit_cli_overrides(args: argparse.Namespace) -> Dict[str, Any]: try: - from ..cli_parser import create_parser + from pitaya.cli.parser import create_parser except Exception: return build_cli_config(args) diff --git a/src/orchestration/cli/runs.py b/src/pitaya/cli/runs.py similarity index 100% rename from src/orchestration/cli/runs.py rename to src/pitaya/cli/runs.py diff --git a/src/orchestration/cli/parser_sections.py b/src/pitaya/cli/sections.py similarity index 94% rename from src/orchestration/cli/parser_sections.py rename to src/pitaya/cli/sections.py index ff50591..97cf891 100644 --- a/src/orchestration/cli/parser_sections.py +++ b/src/pitaya/cli/sections.py @@ -32,7 +32,7 @@ def add_global_and_positional(parser: argparse.ArgumentParser) -> None: def add_strategy_args(parser: argparse.ArgumentParser) -> None: - from ..strategies import AVAILABLE_STRATEGIES + from pitaya.orchestration.strategy.builtin import AVAILABLE_STRATEGIES g = parser.add_argument_group("Strategy") g.add_argument( @@ -55,7 +55,7 @@ def add_strategy_args(parser: argparse.ArgumentParser) -> None: def _plugin_choices() -> list[str]: try: - from ...instance_runner.plugins import AVAILABLE_PLUGINS as _APLUG + from pitaya.runner.plugins import AVAILABLE_PLUGINS as _APLUG return sorted(list(_APLUG.keys())) except (ImportError, Exception): @@ -178,8 +178,9 @@ def add_limits_args(parser: argparse.ArgumentParser) -> None: def add_state_args(parser: argparse.ArgumentParser) -> None: g = parser.add_argument_group("Config, State & Logs") g.add_argument("--config", type=Path, help="Config file (default pitaya.yaml)") - g.add_argument("--state-dir", type=Path, default=Path("./pitaya_state")) - g.add_argument("--logs-dir", type=Path, default=Path("./logs")) + g.add_argument("--state-dir", type=Path, default=Path(".pitaya/state")) + g.add_argument("--logs-dir", type=Path, default=Path(".pitaya/logs")) + g.add_argument("--results-dir", type=Path, default=Path(".pitaya/results")) g.add_argument( "--redact", choices=["true", "false"], diff --git a/src/orchestration/cli/tui_runner.py b/src/pitaya/cli/tui_runner.py similarity index 97% rename from src/orchestration/cli/tui_runner.py rename to src/pitaya/cli/tui_runner.py index 00fc023..a203e27 100644 --- a/src/orchestration/cli/tui_runner.py +++ b/src/pitaya/cli/tui_runner.py @@ -8,9 +8,9 @@ from rich.console import Console -from ...tui.display import TUIDisplay -from ...orchestration import Orchestrator -from .strategy_config import get_strategy_config +from pitaya.tui.display import TUIDisplay +from pitaya.orchestration import Orchestrator +from pitaya.config.strategy import get_strategy_config __all__ = ["run_tui"] diff --git a/src/pitaya/config/__init__.py b/src/pitaya/config/__init__.py new file mode 100644 index 0000000..2782c63 --- /dev/null +++ b/src/pitaya/config/__init__.py @@ -0,0 +1,44 @@ +"""Configuration models, loading, validation, and defaults.""" + +from pitaya.config.models import AuthConfig, ContainerLimits, RetryConfig +from pitaya.config.loader import load_project_config, build_cli_config +from pitaya.config.strategy import get_strategy_config +from pitaya.config.defaults import ( + merge_config, + deep_merge, + load_yaml_config, + load_global_config, + load_dotenv_config, + load_env_config, + get_default_config, + select_auth_mode, + validate_auth_config, + load_config, +) + +__all__ = [ + "AuthConfig", + "ContainerLimits", + "RetryConfig", + "load_project_config", + "build_cli_config", + "get_strategy_config", + "merge_config", + "deep_merge", + "load_yaml_config", + "load_global_config", + "load_dotenv_config", + "load_env_config", + "get_default_config", + "select_auth_mode", + "validate_auth_config", + "load_config", + "validate_full_config", +] + + +def validate_full_config(*args, **kwargs): + """Lazily import validation to avoid circular imports at package import.""" + from pitaya.config.validation import validate_full_config as _vf + + return _vf(*args, **kwargs) diff --git a/src/config.py b/src/pitaya/config/defaults.py similarity index 97% rename from src/config.py rename to src/pitaya/config/defaults.py index 84c180c..956e7fd 100644 --- a/src/config.py +++ b/src/pitaya/config/defaults.py @@ -132,12 +132,15 @@ def load_env_config() -> Dict[str, Any]: def get_default_config() -> Dict[str, Any]: """Return a fresh copy of Pitaya's default configuration.""" + runtime_root = Path("./.pitaya") return { "model": "sonnet", "strategy": "simple", "output": "tui", - "state_dir": Path("./pitaya_state"), - "logs_dir": Path("./logs"), + "state_dir": runtime_root / "state", + "logs_dir": runtime_root / "logs", + "results_dir": runtime_root / "results", + "artifacts_dir": runtime_root / "artifacts", "import_policy": "auto", "import_conflict_policy": "fail", "skip_empty_import": True, diff --git a/src/orchestration/cli/config_loader.py b/src/pitaya/config/loader.py similarity index 97% rename from src/orchestration/cli/config_loader.py rename to src/pitaya/config/loader.py index f81485d..96128d7 100644 --- a/src/orchestration/cli/config_loader.py +++ b/src/pitaya/config/loader.py @@ -94,6 +94,8 @@ def build_cli_config(args) -> Dict[str, Any]: cfg.setdefault("orchestration", {})["state_dir"] = args.state_dir if getattr(args, "logs_dir", None): cfg.setdefault("orchestration", {})["logs_dir"] = args.logs_dir + if getattr(args, "results_dir", None): + cfg["results_dir"] = args.results_dir if getattr(args, "output", None): cfg["output"] = args.output if getattr(args, "oauth_token", None): diff --git a/src/shared/config.py b/src/pitaya/config/models.py similarity index 96% rename from src/shared/config.py rename to src/pitaya/config/models.py index 6395930..7b26400 100644 --- a/src/shared/config.py +++ b/src/pitaya/config/models.py @@ -3,7 +3,7 @@ from dataclasses import dataclass from typing import Optional -from .type_aliases import ErrorPatterns +from pitaya.shared.type_aliases import ErrorPatterns @dataclass diff --git a/src/orchestration/cli/strategy_config.py b/src/pitaya/config/strategy.py similarity index 100% rename from src/orchestration/cli/strategy_config.py rename to src/pitaya/config/strategy.py diff --git a/src/orchestration/cli/validation.py b/src/pitaya/config/validation.py similarity index 98% rename from src/orchestration/cli/validation.py rename to src/pitaya/config/validation.py index a4c7fb6..fac999b 100644 --- a/src/orchestration/cli/validation.py +++ b/src/pitaya/config/validation.py @@ -5,7 +5,7 @@ from pathlib import Path from typing import Any, Dict -from ...orchestration.strategies import AVAILABLE_STRATEGIES +from pitaya.orchestration.strategy.builtin import AVAILABLE_STRATEGIES from rich.console import Console __all__ = ["validate_full_config"] diff --git a/src/exceptions.py b/src/pitaya/exceptions.py similarity index 100% rename from src/exceptions.py rename to src/pitaya/exceptions.py diff --git a/src/pitaya/orchestration/__init__.py b/src/pitaya/orchestration/__init__.py new file mode 100644 index 0000000..0faf223 --- /dev/null +++ b/src/pitaya/orchestration/__init__.py @@ -0,0 +1,5 @@ +"""Orchestration core for Pitaya.""" + +from .orchestrator import Orchestrator + +__all__ = ["Orchestrator"] diff --git a/src/pitaya/orchestration/events/__init__.py b/src/pitaya/orchestration/events/__init__.py new file mode 100644 index 0000000..33c5d5d --- /dev/null +++ b/src/pitaya/orchestration/events/__init__.py @@ -0,0 +1,7 @@ +"""Event bus, persistence, and redaction helpers.""" + +from pitaya.orchestration.events.bus import EventBus +from pitaya.orchestration.events.persistence import EventPersistence +from pitaya.orchestration.events.redaction import EventRedactor + +__all__ = ["EventBus", "EventPersistence", "EventRedactor"] diff --git a/src/orchestration/event_bus.py b/src/pitaya/orchestration/events/bus.py similarity index 98% rename from src/orchestration/event_bus.py rename to src/pitaya/orchestration/events/bus.py index 3c5ec86..91afc02 100644 --- a/src/orchestration/event_bus.py +++ b/src/pitaya/orchestration/events/bus.py @@ -11,8 +11,8 @@ from pathlib import Path from typing import Any, Callable, Dict, List, Optional, Set, Tuple -from .event_persistence import EventPersistence -from .event_redaction import EventRedactor +from pitaya.orchestration.events.persistence import EventPersistence +from pitaya.orchestration.events.redaction import EventRedactor logger = logging.getLogger(__name__) diff --git a/src/orchestration/instance_events.py b/src/pitaya/orchestration/events/instance_events.py similarity index 100% rename from src/orchestration/instance_events.py rename to src/pitaya/orchestration/events/instance_events.py diff --git a/src/orchestration/event_persistence.py b/src/pitaya/orchestration/events/persistence.py similarity index 99% rename from src/orchestration/event_persistence.py rename to src/pitaya/orchestration/events/persistence.py index 18d770b..cfbd37c 100644 --- a/src/orchestration/event_persistence.py +++ b/src/pitaya/orchestration/events/persistence.py @@ -12,7 +12,7 @@ from pathlib import Path from typing import Any, Dict, List, Optional, Tuple, TextIO -from .event_redaction import EventRedactor +from pitaya.orchestration.events.redaction import EventRedactor logger = logging.getLogger(__name__) diff --git a/src/orchestration/event_redaction.py b/src/pitaya/orchestration/events/redaction.py similarity index 100% rename from src/orchestration/event_redaction.py rename to src/pitaya/orchestration/events/redaction.py diff --git a/src/pitaya/orchestration/instances/__init__.py b/src/pitaya/orchestration/instances/__init__.py new file mode 100644 index 0000000..a919b63 --- /dev/null +++ b/src/pitaya/orchestration/instances/__init__.py @@ -0,0 +1,3 @@ +"""Instance lifecycle management for Pitaya.""" + +__all__: list[str] = [] diff --git a/src/orchestration/instance_manager.py b/src/pitaya/orchestration/instances/manager.py similarity index 96% rename from src/orchestration/instance_manager.py rename to src/pitaya/orchestration/instances/manager.py index d321352..01c308c 100644 --- a/src/orchestration/instance_manager.py +++ b/src/pitaya/orchestration/instances/manager.py @@ -11,10 +11,10 @@ import random from typing import Any, Dict, List, Optional, Set -from ..exceptions import OrchestratorError -from ..shared import InstanceResult, InstanceStatus -from .instance_runner import execute_instance -from .instance_spawn import spawn_instance +from pitaya.exceptions import OrchestratorError +from pitaya.shared import InstanceResult, InstanceStatus +from pitaya.orchestration.instances.runner import execute_instance +from pitaya.orchestration.instances.spawn import spawn_instance logger = logging.getLogger(__name__) diff --git a/src/orchestration/instance_runner.py b/src/pitaya/orchestration/instances/runner.py similarity index 97% rename from src/orchestration/instance_runner.py rename to src/pitaya/orchestration/instances/runner.py index dd43847..91e0f63 100644 --- a/src/orchestration/instance_runner.py +++ b/src/pitaya/orchestration/instances/runner.py @@ -6,10 +6,10 @@ import logging from typing import Dict, Optional -from ..exceptions import DockerError, GitError, OrchestratorError -from ..instance_runner import run_instance -from ..shared import ContainerLimits, InstanceResult, InstanceStatus -from .instance_events import ( +from pitaya.exceptions import DockerError, GitError, OrchestratorError +from pitaya.runner.api import run_instance +from pitaya.shared import ContainerLimits, InstanceResult, InstanceStatus +from pitaya.orchestration.events.instance_events import ( build_event_callback, heartbeat_monitor, map_error_type, @@ -157,6 +157,7 @@ def _prepare_run_kwargs(orchestrator, info, strategy_exec_id, task_key, startup_ workspace_include_branches=(info.metadata or {}).get( "workspace_include_branches" ), + logs_dir=orchestrator.logs_dir, ) diff --git a/src/orchestration/instance_spawn.py b/src/pitaya/orchestration/instances/spawn.py similarity index 97% rename from src/orchestration/instance_spawn.py rename to src/pitaya/orchestration/instances/spawn.py index f3f1989..4c3f5a7 100644 --- a/src/orchestration/instance_spawn.py +++ b/src/pitaya/orchestration/instances/spawn.py @@ -10,9 +10,12 @@ from pathlib import Path from typing import Any, Dict, Optional -from ..shared import InstanceResult, InstanceStatus -from .instance_events import strategy_execution_id, truncate_final_message -from .state import InstanceInfo +from pitaya.shared import InstanceResult, InstanceStatus +from pitaya.orchestration.events.instance_events import ( + strategy_execution_id, + truncate_final_message, +) +from pitaya.orchestration.state import InstanceInfo logger = logging.getLogger(__name__) diff --git a/src/orchestration/orchestrator.py b/src/pitaya/orchestration/orchestrator.py similarity index 93% rename from src/orchestration/orchestrator.py rename to src/pitaya/orchestration/orchestrator.py index c04ea79..aef806d 100644 --- a/src/orchestration/orchestrator.py +++ b/src/pitaya/orchestration/orchestrator.py @@ -7,19 +7,19 @@ from pathlib import Path from typing import Any, Callable, Dict, List, Optional, Set, Tuple -from ..shared import ( +from pitaya.shared import ( AuthConfig, ContainerLimits, InstanceResult, RetryConfig, InstanceStatus, ) -from .event_bus import EventBus -from .instance_manager import InstanceManager -from .resume_manager import resume_run as resume_run_helper -from .results_writer import save_results as save_results_helper -from .state import InstanceInfo, RunState, StateManager -from .strategy_runner import run_strategy as run_strategy_helper +from pitaya.orchestration.events.bus import EventBus +from pitaya.orchestration.instances.manager import InstanceManager +from pitaya.orchestration.resume.manager import resume_run as resume_run_helper +from pitaya.orchestration.results.writer import save_results as save_results_helper +from pitaya.orchestration.state import InstanceInfo, RunState, StateManager +from pitaya.orchestration.strategy.runner import run_strategy as run_strategy_helper logger = logging.getLogger(__name__) @@ -31,8 +31,9 @@ def __init__( self, max_parallel_instances: Optional[int] = None, max_parallel_startup: Optional[int] = None, - state_dir: Path = Path("./pitaya_state"), - logs_dir: Path = Path("./logs"), + state_dir: Path = Path("./.pitaya/state"), + logs_dir: Path = Path("./.pitaya/logs"), + results_dir: Path = Path("./.pitaya/results"), container_limits: Optional[ContainerLimits] = None, retry_config: Optional[RetryConfig] = None, auth_config: Optional[AuthConfig] = None, @@ -56,6 +57,7 @@ def __init__( self.max_parallel_startup: Optional[int] = max_parallel_startup self.state_dir = state_dir self.logs_dir = logs_dir + self.results_dir = results_dir self.container_limits = container_limits or ContainerLimits() self.retry_config = retry_config or RetryConfig() self.auth_config = auth_config @@ -114,6 +116,7 @@ async def initialize(self) -> None: """Initialize state manager, event bus, and instance executors.""" self.state_dir.mkdir(parents=True, exist_ok=True) self.logs_dir.mkdir(parents=True, exist_ok=True) + self.results_dir.mkdir(parents=True, exist_ok=True) await self._check_disk_space() self.state_manager = StateManager( @@ -177,7 +180,7 @@ async def shutdown(self) -> None: "Stopping %s running containers in parallel...", len(running_instances), ) - from ..instance_runner.docker_manager import DockerManager + from pitaya.runner.docker.manager import DockerManager async def _stop(inst: InstanceInfo) -> None: try: diff --git a/src/pitaya/orchestration/results/__init__.py b/src/pitaya/orchestration/results/__init__.py new file mode 100644 index 0000000..f603146 --- /dev/null +++ b/src/pitaya/orchestration/results/__init__.py @@ -0,0 +1,3 @@ +"""Result processing utilities.""" + +__all__: list[str] = [] diff --git a/src/orchestration/results_utils.py b/src/pitaya/orchestration/results/utils.py similarity index 100% rename from src/orchestration/results_utils.py rename to src/pitaya/orchestration/results/utils.py diff --git a/src/orchestration/results_writer.py b/src/pitaya/orchestration/results/writer.py similarity index 98% rename from src/orchestration/results_writer.py rename to src/pitaya/orchestration/results/writer.py index c2d99b8..200496e 100644 --- a/src/orchestration/results_writer.py +++ b/src/pitaya/orchestration/results/writer.py @@ -4,11 +4,10 @@ import json import logging -from pathlib import Path from typing import Any, Dict, List -from ..shared import InstanceResult, InstanceStatus -from .results_utils import ( +from pitaya.shared import InstanceResult, InstanceStatus +from pitaya.orchestration.results.utils import ( compute_counts, persist_instance_metadata, resolve_instance_id, @@ -179,8 +178,8 @@ def _append_results(results, state, summary_data, branches: List[str]) -> None: async def save_results( orchestrator, run_id: str, results: List[InstanceResult] ) -> None: - """Persist run outputs to ./results and ./logs.""" - results_dir = Path("./results") / run_id + """Persist run outputs to configured results and logs directories.""" + results_dir = orchestrator.results_dir / run_id results_dir.mkdir(parents=True, exist_ok=True) run_logs_dir = orchestrator.logs_dir / run_id run_logs_dir.mkdir(parents=True, exist_ok=True) diff --git a/src/pitaya/orchestration/resume/__init__.py b/src/pitaya/orchestration/resume/__init__.py new file mode 100644 index 0000000..f5ae871 --- /dev/null +++ b/src/pitaya/orchestration/resume/__init__.py @@ -0,0 +1,3 @@ +"""Resume helpers for restoring orchestrator state.""" + +__all__: list[str] = [] diff --git a/src/orchestration/resume_helpers.py b/src/pitaya/orchestration/resume/helpers.py similarity index 97% rename from src/orchestration/resume_helpers.py rename to src/pitaya/orchestration/resume/helpers.py index cde582f..9a9c18e 100644 --- a/src/orchestration/resume_helpers.py +++ b/src/pitaya/orchestration/resume/helpers.py @@ -7,9 +7,9 @@ from datetime import datetime, timezone from typing import List -from ..shared import InstanceResult, InstanceStatus -from .strategies import AVAILABLE_STRATEGIES as STRATEGIES -from .strategy_context import StrategyContext +from pitaya.shared import InstanceResult, InstanceStatus +from pitaya.orchestration.strategy.builtin import AVAILABLE_STRATEGIES as STRATEGIES +from pitaya.orchestration.strategy.context import StrategyContext logger = logging.getLogger(__name__) @@ -18,7 +18,7 @@ def prepare_event_bus(orchestrator, run_id: str) -> None: """Point event bus to the resumed run and apply redaction.""" event_log_path = orchestrator.logs_dir / run_id / "events.jsonl" if not orchestrator.event_bus: - from .event_bus import EventBus + from pitaya.orchestration.events.bus import EventBus orchestrator.event_bus = EventBus( max_events=orchestrator.event_buffer_size, persist_path=event_log_path @@ -173,7 +173,7 @@ def reopen_strategies(orchestrator, run_id: str) -> None: async def schedule_instances(orchestrator, saved_state) -> List[str]: - from ..instance_runner.plugins import AVAILABLE_PLUGINS + from pitaya.runner.plugins import AVAILABLE_PLUGINS scheduled: List[str] = [] manager = orchestrator.instance_manager diff --git a/src/orchestration/resume_manager.py b/src/pitaya/orchestration/resume/manager.py similarity index 96% rename from src/orchestration/resume_manager.py rename to src/pitaya/orchestration/resume/manager.py index e7ed871..bec9324 100644 --- a/src/orchestration/resume_manager.py +++ b/src/pitaya/orchestration/resume/manager.py @@ -5,8 +5,8 @@ import logging from typing import List -from ..shared import InstanceResult -from .resume_helpers import ( +from pitaya.shared import InstanceResult +from pitaya.orchestration.resume.helpers import ( backfill_terminal, mark_run_completed, prepare_event_bus, diff --git a/src/pitaya/orchestration/state/__init__.py b/src/pitaya/orchestration/state/__init__.py new file mode 100644 index 0000000..fdfe665 --- /dev/null +++ b/src/pitaya/orchestration/state/__init__.py @@ -0,0 +1,6 @@ +"""State models and manager for orchestration.""" + +from pitaya.orchestration.state.manager import StateManager +from pitaya.orchestration.state.models import RunState, InstanceInfo, StrategyExecution + +__all__ = ["StateManager", "RunState", "InstanceInfo", "StrategyExecution"] diff --git a/src/orchestration/state_manager.py b/src/pitaya/orchestration/state/manager.py similarity index 96% rename from src/orchestration/state_manager.py rename to src/pitaya/orchestration/state/manager.py index 3d5322c..296edab 100644 --- a/src/orchestration/state_manager.py +++ b/src/pitaya/orchestration/state/manager.py @@ -9,16 +9,16 @@ from pathlib import Path from typing import Any, Dict, List, Optional -from ..shared import InstanceResult, InstanceStatus -from .state_models import RunState -from .state_rebuilder import apply_event, rebuild_from_events -from .state_snapshot import ( +from pitaya.shared import InstanceResult, InstanceStatus +from pitaya.orchestration.state.models import RunState +from pitaya.orchestration.state.rebuilder import apply_event, rebuild_from_events +from pitaya.orchestration.state.snapshot import ( maybe_snapshot, save_snapshot, start_periodic_snapshots, stop_periodic_snapshots, ) -from .state_updates import ( +from pitaya.orchestration.state.updates import ( register_instance, register_strategy, register_task, diff --git a/src/orchestration/state_models.py b/src/pitaya/orchestration/state/models.py similarity index 99% rename from src/orchestration/state_models.py rename to src/pitaya/orchestration/state/models.py index 62c09b8..e72de2e 100644 --- a/src/orchestration/state_models.py +++ b/src/pitaya/orchestration/state/models.py @@ -7,7 +7,7 @@ from pathlib import Path from typing import Any, Dict, List, Optional -from ..shared import InstanceResult, InstanceStatus +from pitaya.shared import InstanceResult, InstanceStatus @dataclass diff --git a/src/orchestration/state_rebuilder.py b/src/pitaya/orchestration/state/rebuilder.py similarity index 97% rename from src/orchestration/state_rebuilder.py rename to src/pitaya/orchestration/state/rebuilder.py index ce6ca13..cbd0cfb 100644 --- a/src/orchestration/state_rebuilder.py +++ b/src/pitaya/orchestration/state/rebuilder.py @@ -6,8 +6,8 @@ from datetime import datetime, timezone from typing import Any, Dict, Iterable, Optional -from ..shared import InstanceResult, InstanceStatus -from .state_models import InstanceInfo, RunState, StrategyExecution +from pitaya.shared import InstanceResult, InstanceStatus +from pitaya.orchestration.state.models import InstanceInfo, RunState, StrategyExecution logger = logging.getLogger(__name__) diff --git a/src/orchestration/state_snapshot.py b/src/pitaya/orchestration/state/snapshot.py similarity index 98% rename from src/orchestration/state_snapshot.py rename to src/pitaya/orchestration/state/snapshot.py index a7e4e82..6064ccd 100644 --- a/src/orchestration/state_snapshot.py +++ b/src/pitaya/orchestration/state/snapshot.py @@ -22,7 +22,7 @@ async def save_snapshot(state_manager) -> None: pass try: - from ..shared import InstanceStatus as _IS + from pitaya.shared import InstanceStatus as _IS insts = list(state.instances.values()) state.total_instances = len(insts) diff --git a/src/orchestration/state_updates.py b/src/pitaya/orchestration/state/updates.py similarity index 98% rename from src/orchestration/state_updates.py rename to src/pitaya/orchestration/state/updates.py index 627970b..319a44c 100644 --- a/src/orchestration/state_updates.py +++ b/src/pitaya/orchestration/state/updates.py @@ -6,8 +6,8 @@ from datetime import datetime, timezone from typing import Any, Dict, Optional -from ..shared import InstanceResult, InstanceStatus -from .state_models import InstanceInfo, StrategyExecution +from pitaya.shared import InstanceResult, InstanceStatus +from pitaya.orchestration.state.models import InstanceInfo, StrategyExecution logger = logging.getLogger(__name__) diff --git a/src/pitaya/orchestration/strategy/__init__.py b/src/pitaya/orchestration/strategy/__init__.py new file mode 100644 index 0000000..7f1b3ef --- /dev/null +++ b/src/pitaya/orchestration/strategy/__init__.py @@ -0,0 +1,3 @@ +"""Strategy interfaces and built-in strategies.""" + +__all__: list[str] = [] diff --git a/src/orchestration/strategies/base.py b/src/pitaya/orchestration/strategy/base.py similarity index 97% rename from src/orchestration/strategies/base.py rename to src/pitaya/orchestration/strategy/base.py index 0205d53..7e64487 100644 --- a/src/orchestration/strategies/base.py +++ b/src/pitaya/orchestration/strategy/base.py @@ -15,10 +15,10 @@ import re from typing import TYPE_CHECKING, Any, Dict, List, Optional -from ...shared import InstanceResult +from pitaya.shared import InstanceResult if TYPE_CHECKING: - from ..strategy_context import StrategyContext + from pitaya.orchestration.strategy.context import StrategyContext __all__ = ["Strategy", "StrategyConfig"] diff --git a/src/pitaya/orchestration/strategy/builtin/__init__.py b/src/pitaya/orchestration/strategy/builtin/__init__.py new file mode 100644 index 0000000..09b6cee --- /dev/null +++ b/src/pitaya/orchestration/strategy/builtin/__init__.py @@ -0,0 +1,30 @@ +"""Built-in orchestration strategies.""" + +from pitaya.orchestration.strategy.builtin.simple import SimpleStrategy +from pitaya.orchestration.strategy.builtin.scoring import ScoringStrategy +from pitaya.orchestration.strategy.builtin.best_of_n import BestOfNStrategy +from pitaya.orchestration.strategy.builtin.iterative import IterativeStrategy +from pitaya.orchestration.strategy.builtin.bug_finding import BugFindingStrategy +from pitaya.orchestration.strategy.builtin.doc_review import DocReviewStrategy +from pitaya.orchestration.strategy.builtin.pr_review import PRReviewStrategy + +AVAILABLE_STRATEGIES = { + "simple": SimpleStrategy, + "scoring": ScoringStrategy, + "best-of-n": BestOfNStrategy, + "iterative": IterativeStrategy, + "bug-finding": BugFindingStrategy, + "doc-review": DocReviewStrategy, + "pr-review": PRReviewStrategy, +} + +__all__ = [ + "AVAILABLE_STRATEGIES", + "SimpleStrategy", + "ScoringStrategy", + "BestOfNStrategy", + "IterativeStrategy", + "BugFindingStrategy", + "DocReviewStrategy", + "PRReviewStrategy", +] diff --git a/src/orchestration/strategies/best_of_n.py b/src/pitaya/orchestration/strategy/builtin/best_of_n.py similarity index 95% rename from src/orchestration/strategies/best_of_n.py rename to src/pitaya/orchestration/strategy/builtin/best_of_n.py index 8b2f346..f1010a5 100644 --- a/src/orchestration/strategies/best_of_n.py +++ b/src/pitaya/orchestration/strategy/builtin/best_of_n.py @@ -11,13 +11,13 @@ from dataclasses import dataclass, field from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple -from ...shared import InstanceResult -from ...exceptions import StrategyError -from .base import Strategy, StrategyConfig +from pitaya.shared import InstanceResult +from pitaya.exceptions import StrategyError +from pitaya.orchestration.strategy.base import Strategy, StrategyConfig from .scoring import ScoringStrategy if TYPE_CHECKING: - from ..strategy_context import StrategyContext + from pitaya.orchestration.strategy.context import StrategyContext logger = logging.getLogger(__name__) @@ -162,7 +162,7 @@ async def run_candidate(idx: int) -> List[InstanceResult]: if not candidates: logger.error(f"All {cfg.n} candidates failed") try: - from ...exceptions import NoViableCandidates + from pitaya.exceptions import NoViableCandidates raise NoViableCandidates() except Exception: @@ -192,7 +192,7 @@ async def run_candidate(idx: int) -> List[InstanceResult]: f"successful candidates {len(successful)} < require_min_success={cfg.require_min_success}" ) try: - from ...exceptions import NoViableCandidates + from pitaya.exceptions import NoViableCandidates raise NoViableCandidates() except Exception: @@ -212,7 +212,7 @@ async def run_candidate(idx: int) -> List[InstanceResult]: return [r0] # All failed try: - from ...exceptions import NoViableCandidates + from pitaya.exceptions import NoViableCandidates raise NoViableCandidates() except Exception: diff --git a/src/orchestration/strategies/bug_finding.py b/src/pitaya/orchestration/strategy/builtin/bug_finding.py similarity index 97% rename from src/orchestration/strategies/bug_finding.py rename to src/pitaya/orchestration/strategy/builtin/bug_finding.py index 5210d5f..9cc43c7 100644 --- a/src/orchestration/strategies/bug_finding.py +++ b/src/pitaya/orchestration/strategy/builtin/bug_finding.py @@ -14,11 +14,11 @@ from pathlib import Path from typing import TYPE_CHECKING, Any, Dict, List, Optional -from ...shared import InstanceResult -from .base import Strategy, StrategyConfig +from pitaya.shared import InstanceResult +from pitaya.orchestration.strategy.base import Strategy, StrategyConfig if TYPE_CHECKING: - from ..strategy_context import StrategyContext + from pitaya.orchestration.strategy.context import StrategyContext logger = logging.getLogger(__name__) @@ -145,7 +145,7 @@ async def execute( if not val_result: # Surface validation failure explicitly try: - from ...shared import InstanceResult as _IR + from pitaya.shared import InstanceResult as _IR failed_val = _IR( success=False, diff --git a/src/orchestration/strategies/doc_review.py b/src/pitaya/orchestration/strategy/builtin/doc_review.py similarity index 99% rename from src/orchestration/strategies/doc_review.py rename to src/pitaya/orchestration/strategy/builtin/doc_review.py index 87a71b6..0e014ad 100644 --- a/src/orchestration/strategies/doc_review.py +++ b/src/pitaya/orchestration/strategy/builtin/doc_review.py @@ -28,11 +28,11 @@ import yaml -from ...shared import InstanceResult -from .base import Strategy, StrategyConfig +from pitaya.shared import InstanceResult +from pitaya.orchestration.strategy.base import Strategy, StrategyConfig if TYPE_CHECKING: - from ..strategy_context import StrategyContext + from pitaya.orchestration.strategy.context import StrategyContext __all__ = ["DocReviewConfig", "DocReviewStrategy"] diff --git a/src/orchestration/strategies/iterative.py b/src/pitaya/orchestration/strategy/builtin/iterative.py similarity index 96% rename from src/orchestration/strategies/iterative.py rename to src/pitaya/orchestration/strategy/builtin/iterative.py index 8bc0bb8..f79045c 100644 --- a/src/orchestration/strategies/iterative.py +++ b/src/pitaya/orchestration/strategy/builtin/iterative.py @@ -13,12 +13,12 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Dict, List, Optional -from ...shared import InstanceResult -from ...exceptions import StrategyError -from .base import Strategy, StrategyConfig +from pitaya.shared import InstanceResult +from pitaya.exceptions import StrategyError +from pitaya.orchestration.strategy.base import Strategy, StrategyConfig if TYPE_CHECKING: - from ..strategy_context import StrategyContext + from pitaya.orchestration.strategy.context import StrategyContext __all__ = ["IterativeConfig", "IterativeStrategy"] @@ -92,7 +92,7 @@ async def execute( h = await ctx.run(iter_task, key=iter_key) res = await ctx.wait(h) except Exception as e: - from ...shared import InstanceResult as _IR + from pitaya.shared import InstanceResult as _IR err_type = getattr(e, "error_type", "unknown") msg = getattr(e, "message", str(e)) diff --git a/src/orchestration/strategies/pr_review.py b/src/pitaya/orchestration/strategy/builtin/pr_review.py similarity index 98% rename from src/orchestration/strategies/pr_review.py rename to src/pitaya/orchestration/strategy/builtin/pr_review.py index bd1ab0e..5a590c3 100644 --- a/src/orchestration/strategies/pr_review.py +++ b/src/pitaya/orchestration/strategy/builtin/pr_review.py @@ -25,11 +25,11 @@ from pathlib import Path from typing import TYPE_CHECKING, Dict, List, Optional, Tuple -from ...shared import InstanceResult -from .base import Strategy, StrategyConfig +from pitaya.shared import InstanceResult +from pitaya.orchestration.strategy.base import Strategy, StrategyConfig if TYPE_CHECKING: - from ..strategy_context import StrategyContext + from pitaya.orchestration.strategy.context import StrategyContext __all__ = ["PRReviewConfig", "PRReviewStrategy"] @@ -521,6 +521,11 @@ async def _review_then_validate( if run_id: sidecar_path = _write_review_sidecar( run_id=str(run_id), + base_dir=getattr( + getattr(ctx, "_orchestrator", None), + "results_dir", + Path("./results"), + ), intro=(comp_res.final_message or "").strip(), event=event, selected_ids=selected_ids, @@ -1277,6 +1282,7 @@ def _resolve_commit_id( def _write_review_sidecar( *, run_id: str, + base_dir: Path, intro: str, event: str, selected_ids: List[str], @@ -1285,8 +1291,8 @@ def _write_review_sidecar( ) -> str: """Write results//review/index.json and return its relative path.""" try: - base_dir = Path("./results") / run_id / "review" - base_dir.mkdir(parents=True, exist_ok=True) + review_dir = Path(base_dir) / run_id / "review" + review_dir.mkdir(parents=True, exist_ok=True) payload = { "intro": intro, "event": event, @@ -1294,7 +1300,7 @@ def _write_review_sidecar( "selected_details": selected_details, "commit_id": commit_id, } - out_path = base_dir / "index.json" + out_path = review_dir / "index.json" out_path.write_text(json.dumps(payload, indent=2), encoding="utf-8") # Return path relative to run results dir return "review/index.json" diff --git a/src/orchestration/strategies/scoring.py b/src/pitaya/orchestration/strategy/builtin/scoring.py similarity index 97% rename from src/orchestration/strategies/scoring.py rename to src/pitaya/orchestration/strategy/builtin/scoring.py index a392fde..c8e2486 100644 --- a/src/orchestration/strategies/scoring.py +++ b/src/pitaya/orchestration/strategy/builtin/scoring.py @@ -14,12 +14,12 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple -from ...shared import InstanceResult -from ...exceptions import StrategyError -from .base import Strategy, StrategyConfig +from pitaya.shared import InstanceResult +from pitaya.exceptions import StrategyError +from pitaya.orchestration.strategy.base import Strategy, StrategyConfig if TYPE_CHECKING: - from ..strategy_context import StrategyContext + from pitaya.orchestration.strategy.context import StrategyContext __all__ = ["ScoringConfig", "ScoringStrategy"] @@ -110,14 +110,14 @@ async def execute( except Exception as e: # Prefer rich result from TaskFailed (includes final_message/metrics/log_path) try: - from ...exceptions import TaskFailed + from pitaya.exceptions import TaskFailed if isinstance(e, TaskFailed) and e.failure.result is not None: gen_result = e.failure.result # type: ignore[assignment] else: raise e except Exception: - from ...shared import InstanceResult as _IR + from pitaya.shared import InstanceResult as _IR err_type = getattr(e, "error_type", "unknown") msg = getattr(e, "message", str(e)) diff --git a/src/orchestration/strategies/simple.py b/src/pitaya/orchestration/strategy/builtin/simple.py similarity index 88% rename from src/orchestration/strategies/simple.py rename to src/pitaya/orchestration/strategy/builtin/simple.py index 2509240..d04d31c 100644 --- a/src/orchestration/strategies/simple.py +++ b/src/pitaya/orchestration/strategy/builtin/simple.py @@ -7,12 +7,12 @@ import logging from typing import TYPE_CHECKING, List -from ...shared import InstanceResult -from ...exceptions import StrategyError -from .base import Strategy, StrategyConfig +from pitaya.shared import InstanceResult +from pitaya.exceptions import StrategyError +from pitaya.orchestration.strategy.base import Strategy, StrategyConfig if TYPE_CHECKING: - from ..strategy_context import StrategyContext + from pitaya.orchestration.strategy.context import StrategyContext logger = logging.getLogger(__name__) diff --git a/src/orchestration/strategy_context.py b/src/pitaya/orchestration/strategy/context.py similarity index 92% rename from src/orchestration/strategy_context.py rename to src/pitaya/orchestration/strategy/context.py index ce22a35..da8f2a3 100644 --- a/src/orchestration/strategy_context.py +++ b/src/pitaya/orchestration/strategy/context.py @@ -5,10 +5,10 @@ import time from typing import Any, Dict, List, Optional -from ..shared import InstanceResult -from .strategy_handles import Handle, InstanceHandle -from .strategy_random import DeterministicRand -from .strategy_task import _schedule_async +from pitaya.shared import InstanceResult +from pitaya.orchestration.strategy.handles import Handle, InstanceHandle +from pitaya.orchestration.strategy.random import DeterministicRand +from pitaya.orchestration.strategy.task import _schedule_async class StrategyContext: @@ -90,7 +90,7 @@ async def wait(self, handle: Handle) -> InstanceResult: r = results[handle.instance_id] if not getattr(r, "success", False): try: - from ..exceptions import TaskFailed + from pitaya.exceptions import TaskFailed raise TaskFailed( handle.key, @@ -114,7 +114,7 @@ async def wait_all( return successes, failures if any(not getattr(r, "success", False) for r in out): try: - from ..exceptions import AggregateTaskFailed + from pitaya.exceptions import AggregateTaskFailed failed_keys = [ h.key diff --git a/src/orchestration/strategy_handles.py b/src/pitaya/orchestration/strategy/handles.py similarity index 94% rename from src/orchestration/strategy_handles.py rename to src/pitaya/orchestration/strategy/handles.py index 5c4750b..a3197a1 100644 --- a/src/orchestration/strategy_handles.py +++ b/src/pitaya/orchestration/strategy/handles.py @@ -2,7 +2,7 @@ from __future__ import annotations -from ..shared import InstanceResult +from pitaya.shared import InstanceResult class InstanceHandle: diff --git a/src/orchestration/strategies/loader.py b/src/pitaya/orchestration/strategy/loader.py similarity index 100% rename from src/orchestration/strategies/loader.py rename to src/pitaya/orchestration/strategy/loader.py diff --git a/src/orchestration/strategy_random.py b/src/pitaya/orchestration/strategy/random.py similarity index 100% rename from src/orchestration/strategy_random.py rename to src/pitaya/orchestration/strategy/random.py diff --git a/src/orchestration/strategy_runner.py b/src/pitaya/orchestration/strategy/runner.py similarity index 97% rename from src/orchestration/strategy_runner.py rename to src/pitaya/orchestration/strategy/runner.py index bec43d9..6c2ae79 100644 --- a/src/orchestration/strategy_runner.py +++ b/src/pitaya/orchestration/strategy/runner.py @@ -9,10 +9,10 @@ from pathlib import Path from typing import Any, Dict, List, Optional -from ..exceptions import OrchestratorError, StrategyError -from ..shared import InstanceResult -from .strategy_context import StrategyContext -from .strategy_utils import ( +from pitaya.exceptions import OrchestratorError, StrategyError +from pitaya.shared import InstanceResult +from pitaya.orchestration.strategy.context import StrategyContext +from pitaya.orchestration.strategy.utils import ( detect_default_workspace_branches, emit_strategy_completed, emit_strategy_failed, diff --git a/src/orchestration/strategy_task.py b/src/pitaya/orchestration/strategy/task.py similarity index 99% rename from src/orchestration/strategy_task.py rename to src/pitaya/orchestration/strategy/task.py index b5b4967..c8e7706 100644 --- a/src/orchestration/strategy_task.py +++ b/src/pitaya/orchestration/strategy/task.py @@ -8,7 +8,7 @@ import time from typing import Any, Dict, Optional, Tuple -from .strategy_handles import Handle +from pitaya.orchestration.strategy.handles import Handle def schedule_task( diff --git a/src/orchestration/strategy_utils.py b/src/pitaya/orchestration/strategy/utils.py similarity index 95% rename from src/orchestration/strategy_utils.py rename to src/pitaya/orchestration/strategy/utils.py index 59b0a25..29285b8 100644 --- a/src/orchestration/strategy_utils.py +++ b/src/pitaya/orchestration/strategy/utils.py @@ -8,9 +8,9 @@ from pathlib import Path from typing import Any, Dict, Optional, Tuple -from .event_bus import EventBus -from .strategies import AVAILABLE_STRATEGIES -from .strategies.loader import load_strategy +from pitaya.orchestration.events.bus import EventBus +from pitaya.orchestration.strategy.builtin import AVAILABLE_STRATEGIES +from pitaya.orchestration.strategy.loader import load_strategy logger = logging.getLogger(__name__) diff --git a/src/pitaya/runner/__init__.py b/src/pitaya/runner/__init__.py new file mode 100644 index 0000000..db25cff --- /dev/null +++ b/src/pitaya/runner/__init__.py @@ -0,0 +1,16 @@ +"""Pitaya runner executes agent instances and plugins.""" + +from pitaya.runner.api import run_instance +from pitaya.shared.results import InstanceResult +from pitaya.shared.plugin import RunnerPlugin, PluginCapabilities +from pitaya.shared.types import AuthConfig, ContainerLimits, RetryConfig + +__all__ = [ + "run_instance", + "InstanceResult", + "RunnerPlugin", + "PluginCapabilities", + "AuthConfig", + "ContainerLimits", + "RetryConfig", +] diff --git a/src/instance_runner/agent_runner.py b/src/pitaya/runner/agent_runner.py similarity index 98% rename from src/instance_runner/agent_runner.py rename to src/pitaya/runner/agent_runner.py index e33fdde..c09d3bb 100644 --- a/src/instance_runner/agent_runner.py +++ b/src/pitaya/runner/agent_runner.py @@ -4,7 +4,7 @@ from typing import Any, Callable, Dict, Optional -from .plugin_interface import RunnerPlugin +from pitaya.shared.plugin import RunnerPlugin from .runner_params import RunnerParams diff --git a/src/instance_runner/api.py b/src/pitaya/runner/api.py similarity index 97% rename from src/instance_runner/api.py rename to src/pitaya/runner/api.py index fdde73a..64602af 100644 --- a/src/instance_runner/api.py +++ b/src/pitaya/runner/api.py @@ -10,7 +10,7 @@ from pathlib import Path from typing import Any, Callable, Dict, Optional -from ..shared import ( +from pitaya.shared import ( AuthConfig, ContainerLimits, InstanceResult, @@ -59,6 +59,7 @@ async def run_instance( force_commit: bool = False, # Workspace preparation options workspace_include_branches: Optional[list[str]] = None, + logs_dir: Path = Path(".pitaya/logs"), ) -> InstanceResult: """Run a single AI coding instance; thin wrapper over `runner.run_instance`.""" return await _run_instance( @@ -97,6 +98,7 @@ async def run_instance( agent_cli_args=agent_cli_args, force_commit=force_commit, workspace_include_branches=workspace_include_branches, + logs_dir=logs_dir, ) diff --git a/src/pitaya/runner/attempt/__init__.py b/src/pitaya/runner/attempt/__init__.py new file mode 100644 index 0000000..c64a721 --- /dev/null +++ b/src/pitaya/runner/attempt/__init__.py @@ -0,0 +1,3 @@ +"""Attempt execution helpers for runner.""" + +__all__: list[str] = [] diff --git a/src/instance_runner/attempt_executor.py b/src/pitaya/runner/attempt/executor.py similarity index 92% rename from src/instance_runner/attempt_executor.py rename to src/pitaya/runner/attempt/executor.py index 2fcc93f..c8f5675 100644 --- a/src/instance_runner/attempt_executor.py +++ b/src/pitaya/runner/attempt/executor.py @@ -8,23 +8,23 @@ from datetime import datetime, timezone from typing import Any, Dict, Optional -from ..exceptions import ( +from pitaya.exceptions import ( AgentError, DockerError, GitError, TimeoutError, ValidationError, ) -from .agent_runner import AgentRunner -from .container_manager import ContainerManager -from .git_operations import GitOperations -from .import_manager import ImportManager -from .attempt_mixins import FailureHandlingMixin -from .attempt_timeout import TimeoutCleanupMixin -from .runner_params import RunnerParams -from .workspace_manager import WorkspaceManager -from ..shared import InstanceResult -from .docker_manager import DockerManager +from pitaya.runner.agent_runner import AgentRunner +from pitaya.runner.docker.container_manager import ContainerManager +from pitaya.runner.workspace.git_operations import GitOperations +from pitaya.runner.workspace.import_manager import ImportManager +from pitaya.runner.attempt.mixins import FailureHandlingMixin +from pitaya.runner.attempt.timeout import TimeoutCleanupMixin +from pitaya.runner.runner_params import RunnerParams +from pitaya.runner.workspace.manager import WorkspaceManager +from pitaya.shared import InstanceResult +from pitaya.runner.docker.manager import DockerManager logger = logging.getLogger(__name__) @@ -60,11 +60,11 @@ def __init__( self.metrics: Dict[str, Any] = {} self.started_at = datetime.now(timezone.utc).isoformat() self.start_time = time.time() - self.log_path = ( - f"./logs/{params.run_id}/instance_{params.instance_id}.log" - if params.run_id and params.instance_id - else None - ) + self.log_path = None + if params.run_id and params.instance_id: + self.log_path = str( + params.logs_dir / params.run_id / f"instance_{params.instance_id}.log" + ) async def run(self) -> InstanceResult: try: diff --git a/src/instance_runner/attempt_mixins.py b/src/pitaya/runner/attempt/mixins.py similarity index 98% rename from src/instance_runner/attempt_mixins.py rename to src/pitaya/runner/attempt/mixins.py index 03329a0..f4cd2bd 100644 --- a/src/instance_runner/attempt_mixins.py +++ b/src/pitaya/runner/attempt/mixins.py @@ -6,8 +6,8 @@ from datetime import datetime, timezone from typing import Any, Dict, Optional -from .runner_utils import is_retryable_error -from ..shared import InstanceResult +from pitaya.runner.runner_utils import is_retryable_error +from pitaya.shared import InstanceResult class EventEmitterMixin: diff --git a/src/instance_runner/attempt_timeout.py b/src/pitaya/runner/attempt/timeout.py similarity index 100% rename from src/instance_runner/attempt_timeout.py rename to src/pitaya/runner/attempt/timeout.py diff --git a/src/pitaya/runner/docker/__init__.py b/src/pitaya/runner/docker/__init__.py new file mode 100644 index 0000000..a9d301f --- /dev/null +++ b/src/pitaya/runner/docker/__init__.py @@ -0,0 +1,5 @@ +"""Docker lifecycle helpers for Pitaya runner.""" + +from pitaya.exceptions import DockerError, TimeoutError + +__all__ = ["DockerError", "TimeoutError"] diff --git a/src/instance_runner/container_manager.py b/src/pitaya/runner/docker/container_manager.py similarity index 96% rename from src/instance_runner/container_manager.py rename to src/pitaya/runner/docker/container_manager.py index b77dfd8..d3b80b0 100644 --- a/src/instance_runner/container_manager.py +++ b/src/pitaya/runner/docker/container_manager.py @@ -8,10 +8,10 @@ from dataclasses import asdict from typing import Any, Callable, Dict, Optional -from ..exceptions import DockerError -from .docker_manager import DockerManager -from .plugin_interface import RunnerPlugin -from .runner_params import RunnerParams +from pitaya.exceptions import DockerError +from pitaya.runner.docker.manager import DockerManager +from pitaya.shared.plugin import RunnerPlugin +from pitaya.runner.runner_params import RunnerParams logger = logging.getLogger(__name__) diff --git a/src/instance_runner/docker_manager.py b/src/pitaya/runner/docker/manager.py similarity index 97% rename from src/instance_runner/docker_manager.py rename to src/pitaya/runner/docker/manager.py index 7ad77fc..a625a60 100644 --- a/src/instance_runner/docker_manager.py +++ b/src/pitaya/runner/docker/manager.py @@ -25,9 +25,9 @@ except ImportError: # pragma: no cover - very unlikely Container = Any -from . import DockerError, TimeoutError -from .types import AuthConfig -from .docker_ops import cleanup, container_create, exec_runner, heartbeat +from pitaya.config.models import AuthConfig +from pitaya.runner.docker.ops import cleanup, container_create, exec_runner, heartbeat +from pitaya.exceptions import DockerError, TimeoutError # Suppress the urllib3 exception on close that happens with docker-py warnings.filterwarnings("ignore", message=".*I/O operation on closed file.*") diff --git a/src/instance_runner/docker_ops/__init__.py b/src/pitaya/runner/docker/ops/__init__.py similarity index 100% rename from src/instance_runner/docker_ops/__init__.py rename to src/pitaya/runner/docker/ops/__init__.py diff --git a/src/instance_runner/docker_ops/cleanup.py b/src/pitaya/runner/docker/ops/cleanup.py similarity index 100% rename from src/instance_runner/docker_ops/cleanup.py rename to src/pitaya/runner/docker/ops/cleanup.py diff --git a/src/instance_runner/docker_ops/config_builder.py b/src/pitaya/runner/docker/ops/config_builder.py similarity index 99% rename from src/instance_runner/docker_ops/config_builder.py rename to src/pitaya/runner/docker/ops/config_builder.py index b77cf81..27f0c56 100644 --- a/src/instance_runner/docker_ops/config_builder.py +++ b/src/pitaya/runner/docker/ops/config_builder.py @@ -10,7 +10,7 @@ from docker.types import Ulimit -from ..types import AuthConfig +from pitaya.config.models import AuthConfig async def build_container_config( diff --git a/src/instance_runner/docker_ops/container_create.py b/src/pitaya/runner/docker/ops/container_create.py similarity index 98% rename from src/instance_runner/docker_ops/container_create.py rename to src/pitaya/runner/docker/ops/container_create.py index 0e0953e..8984fef 100644 --- a/src/instance_runner/docker_ops/container_create.py +++ b/src/pitaya/runner/docker/ops/container_create.py @@ -7,7 +7,7 @@ from pathlib import Path from typing import Any, Callable, Dict, Optional -from ..types import AuthConfig +from pitaya.config.models import AuthConfig from .config_builder import build_container_config from .image_check import ensure_image_exists from .mount_utils import ( diff --git a/src/instance_runner/docker_ops/exec_logging.py b/src/pitaya/runner/docker/ops/exec_logging.py similarity index 100% rename from src/instance_runner/docker_ops/exec_logging.py rename to src/pitaya/runner/docker/ops/exec_logging.py diff --git a/src/instance_runner/docker_ops/exec_prep.py b/src/pitaya/runner/docker/ops/exec_prep.py similarity index 100% rename from src/instance_runner/docker_ops/exec_prep.py rename to src/pitaya/runner/docker/ops/exec_prep.py diff --git a/src/instance_runner/docker_ops/exec_runner.py b/src/pitaya/runner/docker/ops/exec_runner.py similarity index 100% rename from src/instance_runner/docker_ops/exec_runner.py rename to src/pitaya/runner/docker/ops/exec_runner.py diff --git a/src/instance_runner/docker_ops/heartbeat.py b/src/pitaya/runner/docker/ops/heartbeat.py similarity index 100% rename from src/instance_runner/docker_ops/heartbeat.py rename to src/pitaya/runner/docker/ops/heartbeat.py diff --git a/src/instance_runner/docker_ops/image_check.py b/src/pitaya/runner/docker/ops/image_check.py similarity index 100% rename from src/instance_runner/docker_ops/image_check.py rename to src/pitaya/runner/docker/ops/image_check.py diff --git a/src/instance_runner/docker_ops/mount_utils.py b/src/pitaya/runner/docker/ops/mount_utils.py similarity index 98% rename from src/instance_runner/docker_ops/mount_utils.py rename to src/pitaya/runner/docker/ops/mount_utils.py index 5ddb898..02007d8 100644 --- a/src/instance_runner/docker_ops/mount_utils.py +++ b/src/pitaya/runner/docker/ops/mount_utils.py @@ -11,7 +11,7 @@ from docker.types import Mount -from ...utils.platform_utils import normalize_path_for_docker +from pitaya.utils.platform_utils import normalize_path_for_docker def extract_strategy_index(container_name: str) -> str: diff --git a/src/instance_runner/docker_ops/reuse.py b/src/pitaya/runner/docker/ops/reuse.py similarity index 100% rename from src/instance_runner/docker_ops/reuse.py rename to src/pitaya/runner/docker/ops/reuse.py diff --git a/src/pitaya/runner/parsing/__init__.py b/src/pitaya/runner/parsing/__init__.py new file mode 100644 index 0000000..08d1497 --- /dev/null +++ b/src/pitaya/runner/parsing/__init__.py @@ -0,0 +1,3 @@ +"""Parsing helpers for runner plugin outputs.""" + +__all__: list[str] = [] diff --git a/src/instance_runner/claude_helpers.py b/src/pitaya/runner/parsing/claude_helpers.py similarity index 100% rename from src/instance_runner/claude_helpers.py rename to src/pitaya/runner/parsing/claude_helpers.py diff --git a/src/instance_runner/claude_parser.py b/src/pitaya/runner/parsing/claude_parser.py similarity index 100% rename from src/instance_runner/claude_parser.py rename to src/pitaya/runner/parsing/claude_parser.py diff --git a/src/instance_runner/codex_item_handlers.py b/src/pitaya/runner/parsing/codex_item_handlers.py similarity index 100% rename from src/instance_runner/codex_item_handlers.py rename to src/pitaya/runner/parsing/codex_item_handlers.py diff --git a/src/instance_runner/codex_parser.py b/src/pitaya/runner/parsing/codex_parser.py similarity index 100% rename from src/instance_runner/codex_parser.py rename to src/pitaya/runner/parsing/codex_parser.py diff --git a/src/instance_runner/plugins/__init__.py b/src/pitaya/runner/plugins/__init__.py similarity index 100% rename from src/instance_runner/plugins/__init__.py rename to src/pitaya/runner/plugins/__init__.py diff --git a/src/instance_runner/plugins/claude_code.py b/src/pitaya/runner/plugins/claude_code.py similarity index 98% rename from src/instance_runner/plugins/claude_code.py rename to src/pitaya/runner/plugins/claude_code.py index 000d3b3..de63721 100644 --- a/src/instance_runner/plugins/claude_code.py +++ b/src/pitaya/runner/plugins/claude_code.py @@ -6,8 +6,8 @@ import os from typing import Any, Callable, Dict, List, Optional, Tuple, TYPE_CHECKING -from ..claude_parser import ClaudeOutputParser -from ..plugin_interface import PluginCapabilities, RunnerPlugin +from pitaya.runner.parsing.claude_parser import ClaudeOutputParser +from pitaya.shared.plugin import PluginCapabilities, RunnerPlugin if TYPE_CHECKING: from docker.models.containers import Container diff --git a/src/instance_runner/plugins/codex.py b/src/pitaya/runner/plugins/codex.py similarity index 98% rename from src/instance_runner/plugins/codex.py rename to src/pitaya/runner/plugins/codex.py index 22e1779..f2d69e7 100644 --- a/src/instance_runner/plugins/codex.py +++ b/src/pitaya/runner/plugins/codex.py @@ -5,9 +5,9 @@ import logging from typing import Any, Callable, Dict, List, Optional, Tuple, TYPE_CHECKING -from ..codex_parser import CodexOutputParser -from ..plugin_interface import PluginCapabilities, RunnerPlugin -from ...exceptions import AgentError +from pitaya.runner.parsing.codex_parser import CodexOutputParser +from pitaya.shared.plugin import PluginCapabilities, RunnerPlugin +from pitaya.exceptions import AgentError from .codex_env import ( ENV_API_KEY, collect_codex_env, diff --git a/src/instance_runner/plugins/codex_env.py b/src/pitaya/runner/plugins/codex_env.py similarity index 100% rename from src/instance_runner/plugins/codex_env.py rename to src/pitaya/runner/plugins/codex_env.py diff --git a/src/instance_runner/runner.py b/src/pitaya/runner/runner.py similarity index 95% rename from src/instance_runner/runner.py rename to src/pitaya/runner/runner.py index 826486d..b9ab0dd 100644 --- a/src/instance_runner/runner.py +++ b/src/pitaya/runner/runner.py @@ -8,7 +8,7 @@ from .runner_coordinator import RunnerCoordinator, build_runner_params from .runner_params import RunnerParams -from ..shared import AuthConfig, ContainerLimits, InstanceResult, RetryConfig +from pitaya.shared import AuthConfig, ContainerLimits, InstanceResult, RetryConfig async def run_instance( @@ -47,6 +47,7 @@ async def run_instance( agent_cli_args: Optional[list[str]] = None, force_commit: bool = False, workspace_include_branches: Optional[list[str]] = None, + logs_dir: Path = Path(".pitaya/logs"), ) -> InstanceResult: """Execute a single AI coding instance in Docker (retry-aware).""" params = _build_params_from_args(locals()) @@ -92,4 +93,5 @@ def _build_params_from_args(args: Dict[str, Any]) -> RunnerParams: force_commit=args["force_commit"], workspace_include_branches=args["workspace_include_branches"], task_key=args["task_key"], + logs_dir=args["logs_dir"], ) diff --git a/src/instance_runner/runner_coordinator.py b/src/pitaya/runner/runner_coordinator.py similarity index 94% rename from src/instance_runner/runner_coordinator.py rename to src/pitaya/runner/runner_coordinator.py index e3d6e2a..49dc767 100644 --- a/src/instance_runner/runner_coordinator.py +++ b/src/pitaya/runner/runner_coordinator.py @@ -8,13 +8,13 @@ from dataclasses import replace from datetime import datetime, timezone -from .attempt_executor import AttemptExecutor -from .plugin_interface import RunnerPlugin -from .plugins import AVAILABLE_PLUGINS -from .runner_params import RunnerParams -from .runner_utils import is_retryable_error -from ..exceptions import ValidationError -from ..shared import ContainerLimits, InstanceResult, RetryConfig +from pitaya.runner.attempt.executor import AttemptExecutor +from pitaya.shared.plugin import RunnerPlugin +from pitaya.runner.plugins import AVAILABLE_PLUGINS +from pitaya.runner.runner_params import RunnerParams +from pitaya.runner.runner_utils import is_retryable_error +from pitaya.exceptions import ValidationError +from pitaya.shared import ContainerLimits, InstanceResult, RetryConfig logger = logging.getLogger(__name__) @@ -188,6 +188,7 @@ def build_runner_params( force_commit, workspace_include_branches, task_key, + logs_dir, ) -> RunnerParams: """Create RunnerParams with defaults for missing identifiers.""" instance = instance_id or str(uuid.uuid4()) @@ -230,5 +231,6 @@ def build_runner_params( "force_commit": force_commit, "workspace_include_branches": workspace_include_branches, "task_key": task_key, + "logs_dir": logs_dir, } return RunnerParams(**params) diff --git a/src/instance_runner/runner_params.py b/src/pitaya/runner/runner_params.py similarity index 94% rename from src/instance_runner/runner_params.py rename to src/pitaya/runner/runner_params.py index 86ee761..0933701 100644 --- a/src/instance_runner/runner_params.py +++ b/src/pitaya/runner/runner_params.py @@ -7,7 +7,7 @@ from pathlib import Path from typing import Any, Callable, Dict, Optional -from ..shared import AuthConfig, ContainerLimits, RetryConfig +from pitaya.shared import AuthConfig, ContainerLimits, RetryConfig @dataclass(frozen=True, slots=True) @@ -49,3 +49,4 @@ class RunnerParams: force_commit: bool workspace_include_branches: Optional[list[str]] task_key: Optional[str] + logs_dir: Path diff --git a/src/instance_runner/runner_utils.py b/src/pitaya/runner/runner_utils.py similarity index 95% rename from src/instance_runner/runner_utils.py rename to src/pitaya/runner/runner_utils.py index 79e2f77..7c78574 100644 --- a/src/instance_runner/runner_utils.py +++ b/src/pitaya/runner/runner_utils.py @@ -2,7 +2,7 @@ from __future__ import annotations -from ..shared import RetryConfig +from pitaya.shared import RetryConfig def is_retryable_error( diff --git a/src/pitaya/runner/workspace/__init__.py b/src/pitaya/runner/workspace/__init__.py new file mode 100644 index 0000000..86b3547 --- /dev/null +++ b/src/pitaya/runner/workspace/__init__.py @@ -0,0 +1,5 @@ +"""Workspace and git import helpers for runner.""" + +from pitaya.exceptions import GitError + +__all__ = ["GitError"] diff --git a/src/instance_runner/git_branch_importer.py b/src/pitaya/runner/workspace/git_branch_importer.py similarity index 100% rename from src/instance_runner/git_branch_importer.py rename to src/pitaya/runner/workspace/git_branch_importer.py diff --git a/src/instance_runner/git_common.py b/src/pitaya/runner/workspace/git_common.py similarity index 100% rename from src/instance_runner/git_common.py rename to src/pitaya/runner/workspace/git_common.py diff --git a/src/instance_runner/git_import_dedupe.py b/src/pitaya/runner/workspace/git_import_dedupe.py similarity index 100% rename from src/instance_runner/git_import_dedupe.py rename to src/pitaya/runner/workspace/git_import_dedupe.py diff --git a/src/instance_runner/git_import_types.py b/src/pitaya/runner/workspace/git_import_types.py similarity index 100% rename from src/instance_runner/git_import_types.py rename to src/pitaya/runner/workspace/git_import_types.py diff --git a/src/instance_runner/git_import_utils.py b/src/pitaya/runner/workspace/git_import_utils.py similarity index 100% rename from src/instance_runner/git_import_utils.py rename to src/pitaya/runner/workspace/git_import_utils.py diff --git a/src/instance_runner/git_importer.py b/src/pitaya/runner/workspace/git_importer.py similarity index 100% rename from src/instance_runner/git_importer.py rename to src/pitaya/runner/workspace/git_importer.py diff --git a/src/instance_runner/git_lock.py b/src/pitaya/runner/workspace/git_lock.py similarity index 100% rename from src/instance_runner/git_lock.py rename to src/pitaya/runner/workspace/git_lock.py diff --git a/src/instance_runner/git_operations.py b/src/pitaya/runner/workspace/git_operations.py similarity index 100% rename from src/instance_runner/git_operations.py rename to src/pitaya/runner/workspace/git_operations.py diff --git a/src/instance_runner/git_workspace.py b/src/pitaya/runner/workspace/git_workspace.py similarity index 99% rename from src/instance_runner/git_workspace.py rename to src/pitaya/runner/workspace/git_workspace.py index d220e86..9098075 100644 --- a/src/instance_runner/git_workspace.py +++ b/src/pitaya/runner/workspace/git_workspace.py @@ -12,7 +12,7 @@ from . import GitError from .git_common import is_valid_branch_name, relax_workspace_permissions, run_command -from ..utils.platform_utils import get_temp_dir +from pitaya.utils.platform_utils import get_temp_dir logger = logging.getLogger(__name__) diff --git a/src/instance_runner/import_manager.py b/src/pitaya/runner/workspace/import_manager.py similarity index 96% rename from src/instance_runner/import_manager.py rename to src/pitaya/runner/workspace/import_manager.py index 9777504..ef94e65 100644 --- a/src/instance_runner/import_manager.py +++ b/src/pitaya/runner/workspace/import_manager.py @@ -6,9 +6,9 @@ import re from typing import Dict, Optional -from ..exceptions import GitError -from .git_operations import GitOperations -from .runner_params import RunnerParams +from pitaya.exceptions import GitError +from pitaya.runner.workspace.git_operations import GitOperations +from pitaya.runner.runner_params import RunnerParams logger = logging.getLogger(__name__) diff --git a/src/instance_runner/workspace_manager.py b/src/pitaya/runner/workspace/manager.py similarity index 98% rename from src/instance_runner/workspace_manager.py rename to src/pitaya/runner/workspace/manager.py index c2bfd3b..d104fb6 100644 --- a/src/instance_runner/workspace_manager.py +++ b/src/pitaya/runner/workspace/manager.py @@ -7,7 +7,7 @@ from typing import Callable, Dict, Optional from .git_operations import GitOperations -from .runner_params import RunnerParams +from pitaya.runner.runner_params import RunnerParams logger = logging.getLogger(__name__) diff --git a/src/shared/__init__.py b/src/pitaya/shared/__init__.py similarity index 66% rename from src/shared/__init__.py rename to src/pitaya/shared/__init__.py index 0f3c313..9827405 100644 --- a/src/shared/__init__.py +++ b/src/pitaya/shared/__init__.py @@ -7,15 +7,10 @@ import from each other directly. """ -from .types import ( - InstanceResult, - InstanceStatus, - ContainerLimits, - AuthConfig, - RetryConfig, - RunnerPlugin, - PluginCapabilities, -) +from pitaya.shared.results import InstanceResult +from pitaya.shared.status import InstanceStatus +from pitaya.shared.plugin import RunnerPlugin, PluginCapabilities +from pitaya.config.models import AuthConfig, ContainerLimits, RetryConfig __all__ = [ "InstanceResult", diff --git a/src/shared/events.py b/src/pitaya/shared/events.py similarity index 100% rename from src/shared/events.py rename to src/pitaya/shared/events.py diff --git a/src/shared/plugin.py b/src/pitaya/shared/plugin.py similarity index 100% rename from src/shared/plugin.py rename to src/pitaya/shared/plugin.py diff --git a/src/shared/results.py b/src/pitaya/shared/results.py similarity index 100% rename from src/shared/results.py rename to src/pitaya/shared/results.py diff --git a/src/shared/status.py b/src/pitaya/shared/status.py similarity index 100% rename from src/shared/status.py rename to src/pitaya/shared/status.py diff --git a/src/shared/type_aliases.py b/src/pitaya/shared/type_aliases.py similarity index 100% rename from src/shared/type_aliases.py rename to src/pitaya/shared/type_aliases.py diff --git a/src/shared/types.py b/src/pitaya/shared/types.py similarity index 52% rename from src/shared/types.py rename to src/pitaya/shared/types.py index bc3ff2b..4e250b8 100644 --- a/src/shared/types.py +++ b/src/pitaya/shared/types.py @@ -1,16 +1,11 @@ -"""Aggregated shared types and interfaces. +"""Aggregated shared types and interfaces.""" -This module re-exports shared dataclasses, enums, and type aliases to keep -callers stable while allowing the underlying implementations to live in -smaller, focused modules. -""" - -from .config import AuthConfig, ContainerLimits, RetryConfig -from .events import Event, EventTypes -from .plugin import PluginCapabilities, RunnerPlugin -from .results import InstanceResult -from .status import InstanceStatus -from .type_aliases import ( +from pitaya.config.models import AuthConfig, ContainerLimits, RetryConfig +from pitaya.shared.events import Event, EventTypes +from pitaya.shared.plugin import PluginCapabilities, RunnerPlugin +from pitaya.shared.results import InstanceResult +from pitaya.shared.status import InstanceStatus +from pitaya.shared.type_aliases import ( AuthParams, Command, ContainerConfig, diff --git a/src/tui/__init__.py b/src/pitaya/tui/__init__.py similarity index 97% rename from src/tui/__init__.py rename to src/pitaya/tui/__init__.py index 546c891..c0c11f6 100644 --- a/src/tui/__init__.py +++ b/src/pitaya/tui/__init__.py @@ -5,8 +5,6 @@ adaptive display modes based on instance count. """ -__version__ = "0.3.0" - from .display import TUIDisplay from .models import ( TUIState, @@ -26,9 +24,12 @@ "StrategyDisplay", "InstanceDisplay", "InstanceStatus", + "__version__", "EventProcessor", "AsyncEventStream", "AdaptiveDisplay", "OrchestratorTUI", "main", ] + +__version__ = "0.3.0" diff --git a/src/tui/__main__.py b/src/pitaya/tui/__main__.py similarity index 85% rename from src/tui/__main__.py rename to src/pitaya/tui/__main__.py index 127b2a7..ea3274b 100644 --- a/src/tui/__main__.py +++ b/src/pitaya/tui/__main__.py @@ -2,7 +2,7 @@ Main entry point for Pitaya TUI. This module allows the TUI to be run as: - python -m src.tui + python -m pitaya.tui """ from .cli import main diff --git a/src/tui/adaptive.py b/src/pitaya/tui/adaptive.py similarity index 100% rename from src/tui/adaptive.py rename to src/pitaya/tui/adaptive.py diff --git a/src/tui/adaptive_renderers/__init__.py b/src/pitaya/tui/adaptive_renderers/__init__.py similarity index 100% rename from src/tui/adaptive_renderers/__init__.py rename to src/pitaya/tui/adaptive_renderers/__init__.py diff --git a/src/tui/adaptive_renderers/base.py b/src/pitaya/tui/adaptive_renderers/base.py similarity index 100% rename from src/tui/adaptive_renderers/base.py rename to src/pitaya/tui/adaptive_renderers/base.py diff --git a/src/tui/adaptive_renderers/compact.py b/src/pitaya/tui/adaptive_renderers/compact.py similarity index 100% rename from src/tui/adaptive_renderers/compact.py rename to src/pitaya/tui/adaptive_renderers/compact.py diff --git a/src/tui/adaptive_renderers/dense.py b/src/pitaya/tui/adaptive_renderers/dense.py similarity index 100% rename from src/tui/adaptive_renderers/dense.py rename to src/pitaya/tui/adaptive_renderers/dense.py diff --git a/src/tui/adaptive_renderers/detailed.py b/src/pitaya/tui/adaptive_renderers/detailed.py similarity index 100% rename from src/tui/adaptive_renderers/detailed.py rename to src/pitaya/tui/adaptive_renderers/detailed.py diff --git a/src/tui/cli.py b/src/pitaya/tui/cli.py similarity index 100% rename from src/tui/cli.py rename to src/pitaya/tui/cli.py diff --git a/src/tui/cli_modes.py b/src/pitaya/tui/cli_modes.py similarity index 100% rename from src/tui/cli_modes.py rename to src/pitaya/tui/cli_modes.py diff --git a/src/tui/cli_parser.py b/src/pitaya/tui/cli_parser.py similarity index 95% rename from src/tui/cli_parser.py rename to src/pitaya/tui/cli_parser.py index 894e5c6..2419077 100644 --- a/src/tui/cli_parser.py +++ b/src/pitaya/tui/cli_parser.py @@ -19,7 +19,7 @@ def create_tui_parser() -> argparse.ArgumentParser: " # Watch a run by ID\n" " pitaya-tui --run-id run_20250114_123456\n\n" " # Stream as text from a file\n" - " pitaya-tui --events-file logs/run_20250114_123456/events.jsonl --output streaming\n\n" + " pitaya-tui --events-file .pitaya/logs/run_20250114_123456/events.jsonl --output streaming\n\n" " # Emit JSON events (NDJSON, no TUI)\n" " pitaya-tui --events-file events.jsonl --output json\n" ), diff --git a/src/tui/display.py b/src/pitaya/tui/display.py similarity index 100% rename from src/tui/display.py rename to src/pitaya/tui/display.py diff --git a/src/tui/display_components/__init__.py b/src/pitaya/tui/display_components/__init__.py similarity index 100% rename from src/tui/display_components/__init__.py rename to src/pitaya/tui/display_components/__init__.py diff --git a/src/tui/display_components/body.py b/src/pitaya/tui/display_components/body.py similarity index 100% rename from src/tui/display_components/body.py rename to src/pitaya/tui/display_components/body.py diff --git a/src/tui/display_components/footer.py b/src/pitaya/tui/display_components/footer.py similarity index 86% rename from src/tui/display_components/footer.py rename to src/pitaya/tui/display_components/footer.py index 2f84f7c..afb9642 100644 --- a/src/tui/display_components/footer.py +++ b/src/pitaya/tui/display_components/footer.py @@ -3,6 +3,7 @@ from __future__ import annotations from datetime import datetime, timezone +from pathlib import Path from rich.console import Group from rich.panel import Panel @@ -96,20 +97,24 @@ def _build_footer_lines(self, run_src): lines.append(line1) try: - logs_path = ( - str(self._events_file.parent) - if getattr(self, "_events_file", None) - else f"logs/{run.run_id}" - ) + if getattr(self, "_events_file", None): + run_logs_dir = self._events_file.parent + logs_root = run_logs_dir.parent + results_path = logs_root.parent / "results" / run.run_id + logs_path = str(run_logs_dir) + else: + logs_root = Path(".pitaya/logs") + logs_path = str(logs_root / run.run_id) + results_path = Path(".pitaya/results") / run.run_id except Exception: - logs_path = f"logs/{run.run_id}" - results_path = f"results/{run.run_id}" + logs_path = f".pitaya/logs/{run.run_id}" + results_path = Path(".pitaya/results") / run.run_id line2 = Text() line2.append("Logs: ", style="bold white") line2.append(str(logs_path), style="bright_blue") line2.append(" • ", style="white") line2.append("Results: ", style="bold white") - line2.append(results_path, style="bright_blue") + line2.append(str(results_path), style="bright_blue") lines.append(line2) return lines diff --git a/src/tui/display_components/formatting.py b/src/pitaya/tui/display_components/formatting.py similarity index 100% rename from src/tui/display_components/formatting.py rename to src/pitaya/tui/display_components/formatting.py diff --git a/src/tui/display_components/header.py b/src/pitaya/tui/display_components/header.py similarity index 100% rename from src/tui/display_components/header.py rename to src/pitaya/tui/display_components/header.py diff --git a/src/tui/display_components/layout.py b/src/pitaya/tui/display_components/layout.py similarity index 100% rename from src/tui/display_components/layout.py rename to src/pitaya/tui/display_components/layout.py diff --git a/src/tui/display_components/render_loop.py b/src/pitaya/tui/display_components/render_loop.py similarity index 100% rename from src/tui/display_components/render_loop.py rename to src/pitaya/tui/display_components/render_loop.py diff --git a/src/tui/display_components/snapshot.py b/src/pitaya/tui/display_components/snapshot.py similarity index 100% rename from src/tui/display_components/snapshot.py rename to src/pitaya/tui/display_components/snapshot.py diff --git a/src/tui/display_main.py b/src/pitaya/tui/display_main.py similarity index 100% rename from src/tui/display_main.py rename to src/pitaya/tui/display_main.py diff --git a/src/tui/event_handler.py b/src/pitaya/tui/event_handler.py similarity index 100% rename from src/tui/event_handler.py rename to src/pitaya/tui/event_handler.py diff --git a/src/tui/event_processing/__init__.py b/src/pitaya/tui/event_processing/__init__.py similarity index 100% rename from src/tui/event_processing/__init__.py rename to src/pitaya/tui/event_processing/__init__.py diff --git a/src/tui/event_processing/agent.py b/src/pitaya/tui/event_processing/agent.py similarity index 100% rename from src/tui/event_processing/agent.py rename to src/pitaya/tui/event_processing/agent.py diff --git a/src/tui/event_processing/base.py b/src/pitaya/tui/event_processing/base.py similarity index 100% rename from src/tui/event_processing/base.py rename to src/pitaya/tui/event_processing/base.py diff --git a/src/tui/event_processing/file_watcher.py b/src/pitaya/tui/event_processing/file_watcher.py similarity index 100% rename from src/tui/event_processing/file_watcher.py rename to src/pitaya/tui/event_processing/file_watcher.py diff --git a/src/tui/event_processing/instance_lifecycle.py b/src/pitaya/tui/event_processing/instance_lifecycle.py similarity index 100% rename from src/tui/event_processing/instance_lifecycle.py rename to src/pitaya/tui/event_processing/instance_lifecycle.py diff --git a/src/tui/event_processing/instance_phases.py b/src/pitaya/tui/event_processing/instance_phases.py similarity index 100% rename from src/tui/event_processing/instance_phases.py rename to src/pitaya/tui/event_processing/instance_phases.py diff --git a/src/tui/event_processing/logging_config.py b/src/pitaya/tui/event_processing/logging_config.py similarity index 84% rename from src/tui/event_processing/logging_config.py rename to src/pitaya/tui/event_processing/logging_config.py index 5b89c0f..d21c263 100644 --- a/src/tui/event_processing/logging_config.py +++ b/src/pitaya/tui/event_processing/logging_config.py @@ -3,7 +3,7 @@ import logging # Preserve the original logger name for consistent output -LOGGER_NAME = "src.tui.event_handler" +LOGGER_NAME = "pitaya.tui.event_handler" logger = logging.getLogger(LOGGER_NAME) __all__ = ["logger", "LOGGER_NAME"] diff --git a/src/tui/event_processing/processor.py b/src/pitaya/tui/event_processing/processor.py similarity index 100% rename from src/tui/event_processing/processor.py rename to src/pitaya/tui/event_processing/processor.py diff --git a/src/tui/event_processing/run_strategy.py b/src/pitaya/tui/event_processing/run_strategy.py similarity index 100% rename from src/tui/event_processing/run_strategy.py rename to src/pitaya/tui/event_processing/run_strategy.py diff --git a/src/tui/event_processing/stream.py b/src/pitaya/tui/event_processing/stream.py similarity index 100% rename from src/tui/event_processing/stream.py rename to src/pitaya/tui/event_processing/stream.py diff --git a/src/tui/event_processing/stream_file_ops.py b/src/pitaya/tui/event_processing/stream_file_ops.py similarity index 100% rename from src/tui/event_processing/stream_file_ops.py rename to src/pitaya/tui/event_processing/stream_file_ops.py diff --git a/src/tui/event_processing/task_lifecycle.py b/src/pitaya/tui/event_processing/task_lifecycle.py similarity index 100% rename from src/tui/event_processing/task_lifecycle.py rename to src/pitaya/tui/event_processing/task_lifecycle.py diff --git a/src/tui/event_processing/task_progress.py b/src/pitaya/tui/event_processing/task_progress.py similarity index 100% rename from src/tui/event_processing/task_progress.py rename to src/pitaya/tui/event_processing/task_progress.py diff --git a/src/tui/models.py b/src/pitaya/tui/models.py similarity index 100% rename from src/tui/models.py rename to src/pitaya/tui/models.py diff --git a/src/utils/__init__.py b/src/pitaya/utils/__init__.py similarity index 100% rename from src/utils/__init__.py rename to src/pitaya/utils/__init__.py diff --git a/src/utils/log_rotation.py b/src/pitaya/utils/log_rotation.py similarity index 100% rename from src/utils/log_rotation.py rename to src/pitaya/utils/log_rotation.py diff --git a/src/utils/platform_utils.py b/src/pitaya/utils/platform_utils.py similarity index 100% rename from src/utils/platform_utils.py rename to src/pitaya/utils/platform_utils.py diff --git a/src/utils/structured_logging.py b/src/pitaya/utils/structured_logging.py similarity index 94% rename from src/utils/structured_logging.py rename to src/pitaya/utils/structured_logging.py index 35ec1dd..7e03653 100644 --- a/src/utils/structured_logging.py +++ b/src/pitaya/utils/structured_logging.py @@ -12,9 +12,9 @@ __all__ = ["JSONFormatter", "get_component_logger", "setup_structured_logging"] _COMPONENT_TO_MODULES: Dict[str, Tuple[str, ...]] = { - "orchestration.jsonl": ("src.orchestration",), - "runner.jsonl": ("src.instance_runner",), - "tui.jsonl": ("src.tui",), + "orchestration.jsonl": ("pitaya.orchestration",), + "runner.jsonl": ("pitaya.runner",), + "tui.jsonl": ("pitaya.tui",), } _NOISY_THIRD_PARTY_LOGGERS = ( "urllib3", @@ -104,9 +104,9 @@ def setup_structured_logging( def get_component_logger(component: str) -> logging.Logger: - """Return logger name-spaced under ``src.`` when missing the prefix.""" - if not component.startswith("src."): - component = f"src.{component}" + """Return logger name-spaced under ``pitaya.`` when missing the prefix.""" + if not component.startswith("pitaya."): + component = f"pitaya.{component}" return logging.getLogger(component) @@ -152,7 +152,7 @@ def _configure_existing_loggers( console_handlers: Iterable[logging.Handler], ) -> None: for logger_name in list(logging.Logger.manager.loggerDict.keys()): - if not logger_name.startswith("src."): + if not logger_name.startswith("pitaya."): continue logger = logging.getLogger(logger_name) diff --git a/src/pitaya/version.py b/src/pitaya/version.py new file mode 100644 index 0000000..c73b3b8 --- /dev/null +++ b/src/pitaya/version.py @@ -0,0 +1,5 @@ +"""Central version definition for Pitaya.""" + +__version__ = "0.3.0" + +__all__ = ["__version__"] diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index 2a855d9..0000000 --- a/tests/conftest.py +++ /dev/null @@ -1,6 +0,0 @@ -import sys -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[1] -if str(ROOT) not in sys.path: - sys.path.insert(0, str(ROOT)) diff --git a/tests/test_git_operations.py b/tests/integration/test_git_operations.py similarity index 98% rename from tests/test_git_operations.py rename to tests/integration/test_git_operations.py index aca280f..1559e48 100644 --- a/tests/test_git_operations.py +++ b/tests/integration/test_git_operations.py @@ -3,7 +3,7 @@ import pytest -from src.instance_runner.git_operations import GitOperations +from pitaya.runner.workspace.git_operations import GitOperations def run_git(repo: Path, *args: str) -> str: diff --git a/tests/test_cli_helpers.py b/tests/unit/test_cli_helpers.py similarity index 95% rename from tests/test_cli_helpers.py rename to tests/unit/test_cli_helpers.py index 8dca2e6..6519305 100644 --- a/tests/test_cli_helpers.py +++ b/tests/unit/test_cli_helpers.py @@ -7,10 +7,10 @@ from rich.console import Console -from src.orchestration.cli import headless -from src.orchestration.cli import orchestrator_runner -from src.orchestration.cli.runner_setup import apply_resume_overrides -from src.orchestration.cli.results_display import ( +from pitaya.cli import headless +from pitaya.cli import orchestrator_runner +from pitaya.cli.runner_setup import apply_resume_overrides +from pitaya.cli.results_display import ( Totals, _coalesce_totals, _totals_from_results, diff --git a/tests/test_codex_parser.py b/tests/unit/test_codex_parser.py similarity index 96% rename from tests/test_codex_parser.py rename to tests/unit/test_codex_parser.py index 5a5c686..6d156a5 100644 --- a/tests/test_codex_parser.py +++ b/tests/unit/test_codex_parser.py @@ -1,4 +1,4 @@ -from src.instance_runner.codex_parser import CodexOutputParser +from pitaya.runner.parsing.codex_parser import CodexOutputParser def test_codex_parser_handles_thread_and_turn_events() -> None: diff --git a/tests/test_codex_plugin.py b/tests/unit/test_codex_plugin.py similarity index 96% rename from tests/test_codex_plugin.py rename to tests/unit/test_codex_plugin.py index 6e1e5c2..d294043 100644 --- a/tests/test_codex_plugin.py +++ b/tests/unit/test_codex_plugin.py @@ -1,6 +1,6 @@ import pytest -from src.instance_runner.plugins.codex import ( +from pitaya.runner.plugins.codex import ( CodexPlugin, _collect_codex_env, _select_provider_env_key, diff --git a/tests/test_event_bus_redaction.py b/tests/unit/test_event_bus_redaction.py similarity index 94% rename from tests/test_event_bus_redaction.py rename to tests/unit/test_event_bus_redaction.py index 00ed122..7e2de94 100644 --- a/tests/test_event_bus_redaction.py +++ b/tests/unit/test_event_bus_redaction.py @@ -1,7 +1,7 @@ import base64 -from src.orchestration.event_bus import EventBus +from pitaya.orchestration.events.bus import EventBus def _make_jwt(payload: bytes = b'{"sub":"123"}') -> str: diff --git a/tests/test_failure_cleanup_volume.py b/tests/unit/test_failure_cleanup_volume.py similarity index 90% rename from tests/test_failure_cleanup_volume.py rename to tests/unit/test_failure_cleanup_volume.py index 2127644..74e2cca 100644 --- a/tests/test_failure_cleanup_volume.py +++ b/tests/unit/test_failure_cleanup_volume.py @@ -1,10 +1,11 @@ from typing import Any, Dict +from pathlib import Path import pytest -from src.instance_runner.attempt_mixins import FailureHandlingMixin -from src.instance_runner.runner_params import RunnerParams -from src.shared import ContainerLimits, RetryConfig +from pitaya.runner.attempt.mixins import FailureHandlingMixin +from pitaya.runner.runner_params import RunnerParams +from pitaya.shared import ContainerLimits, RetryConfig class _FakeContainerMgr: @@ -55,6 +56,7 @@ def __init__(self, cmgr: _FakeContainerMgr) -> None: force_commit=False, workspace_include_branches=None, task_key=None, + logs_dir=Path(".pitaya/logs"), ) self.attempt_number = 1 self.total_attempts = 1 diff --git a/tests/test_runner_cancellation_event.py b/tests/unit/test_runner_cancellation_event.py similarity index 90% rename from tests/test_runner_cancellation_event.py rename to tests/unit/test_runner_cancellation_event.py index bf78700..9e5bb3a 100644 --- a/tests/test_runner_cancellation_event.py +++ b/tests/unit/test_runner_cancellation_event.py @@ -1,9 +1,9 @@ from pathlib import Path from typing import Any, Dict, List -from src.instance_runner.attempt_mixins import FailureHandlingMixin -from src.instance_runner.runner_params import RunnerParams -from src.shared import ContainerLimits, RetryConfig +from pitaya.runner.attempt.mixins import FailureHandlingMixin +from pitaya.runner.runner_params import RunnerParams +from pitaya.shared import ContainerLimits, RetryConfig class _Dummy(FailureHandlingMixin): @@ -44,6 +44,7 @@ def __init__(self, callback_list: List[Dict[str, Any]]) -> None: force_commit=False, workspace_include_branches=None, task_key=None, + logs_dir=Path(".pitaya/logs"), ) self.attempt_number = 1 self.total_attempts = 1