diff --git a/.github/workflows/codex-autofix.yml b/.github/workflows/codex-autofix.yml deleted file mode 100644 index 543f16db..00000000 --- a/.github/workflows/codex-autofix.yml +++ /dev/null @@ -1,109 +0,0 @@ -name: Codex Auto-Fix on Failure - -on: - workflow_run: - # Trigger this job after any run of the primary CI workflow completes - workflows: ["CI"] - types: [completed] - -permissions: - contents: write - pull-requests: write - -jobs: - auto-fix: - # Only run when the referenced workflow concluded with a failure - if: ${{ github.event.workflow_run.conclusion == 'failure' }} - runs-on: ubuntu-latest - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - FAILED_WORKFLOW_NAME: ${{ github.event.workflow_run.name }} - FAILED_RUN_URL: ${{ github.event.workflow_run.html_url }} - FAILED_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} - FAILED_HEAD_SHA: ${{ github.event.workflow_run.head_sha }} - steps: - - name: Ensure OpenAI secret is available - id: openai-secret-check - run: | - if [ -z "$OPENAI_API_KEY" ]; then - echo "OPENAI_API_KEY secret is not set. Skipping auto-fix." >&2 - echo "has-secret=false" >> "$GITHUB_OUTPUT" - else - echo "has-secret=true" >> "$GITHUB_OUTPUT" - fi - - name: Checkout Failing Ref - if: ${{ steps.openai-secret-check.outputs.has-secret == 'true' }} - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # ratchet:actions/checkout@v5 - with: - ref: ${{ env.FAILED_HEAD_SHA }} - fetch-depth: 0 - persist-credentials: false - - name: Install uv - if: ${{ steps.openai-secret-check.outputs.has-secret == 'true' }} - uses: astral-sh/setup-uv@5dbc9fba7434435c4cd0268139340fa3696d98f3 # ratchet:astral-sh/setup-uv@v7 - with: - enable-cache: true - cache-dependency-glob: "uv.lock" - - name: Set up Python 3.12 - if: ${{ steps.openai-secret-check.outputs.has-secret == 'true' }} - run: uv python install 3.12 - - name: Install project dependencies - if: ${{ steps.openai-secret-check.outputs.has-secret == 'true' }} - run: uv sync --all-extras - - name: Debug run identifiers - if: ${{ steps.openai-secret-check.outputs.has-secret == 'true' }} - run: | - echo "workflow_run.id=${{ github.event.workflow_run.id }}" - echo "github.run_id=${{ github.run_id }}" - - name: Prepare Codex server info file - if: ${{ steps.openai-secret-check.outputs.has-secret == 'true' }} - run: | - mkdir -p "$HOME/.codex" - rm -f "$HOME/.codex/${{ github.run_id }}.json" - touch "$HOME/.codex/${{ github.run_id }}.json" - - name: Run Codex - if: ${{ steps.openai-secret-check.outputs.has-secret == 'true' }} - uses: openai/codex-action@f3036cd34d4257a8df0c846c9c4988166860b304 - id: codex - with: - openai-api-key: ${{ secrets.OPENAI_API_KEY }} - prompt: >- - You are working in a Python repository that uses uv for dependency management and pytest for testing. Read the - repository, run the relevant uv-based test commands (for example `uv run python tests/test_config.py` and - `uv run pytest -q -m "not e2e"`), identify the minimal change needed to make the failing CI pass, implement only that change, - and stop. Note: E2E tests require a running server and are excluded from CI via the -m "not e2e" marker. Do not refactor unrelated code or files. Keep changes small and surgical. - codex-args: '["--config","sandbox_mode=\"workspace-write\""]' - - name: Re-sync project dependencies - if: ${{ steps.openai-secret-check.outputs.has-secret == 'true' }} - run: uv sync --all-extras - - name: Run configuration validation tests - if: ${{ steps.openai-secret-check.outputs.has-secret == 'true' }} - env: - AZURE_AI_PROJECT_ENDPOINT: ${{ secrets.AZURE_AI_PROJECT_ENDPOINT }} - AZURE_AI_SEARCH_ENDPOINT: ${{ secrets.AZURE_AI_SEARCH_ENDPOINT }} - AZURE_AI_SEARCH_KEY: ${{ secrets.AZURE_AI_SEARCH_KEY }} - AZURE_OPENAI_CHAT_COMPLETION_DEPLOYED_MODEL_NAME: ${{ secrets.AZURE_OPENAI_CHAT_COMPLETION_DEPLOYED_MODEL_NAME }} - AZURE_OPENAI_EMBEDDING_DEPLOYED_MODEL_NAME: ${{ secrets.AZURE_OPENAI_EMBEDDING_DEPLOYED_MODEL_NAME }} - run: uv run python tests/test_config.py - - name: Run pytest suite - if: ${{ steps.openai-secret-check.outputs.has-secret == 'true' }} - env: - AZURE_AI_PROJECT_ENDPOINT: ${{ secrets.AZURE_AI_PROJECT_ENDPOINT }} - AZURE_AI_SEARCH_ENDPOINT: ${{ secrets.AZURE_AI_SEARCH_ENDPOINT }} - AZURE_AI_SEARCH_KEY: ${{ secrets.AZURE_AI_SEARCH_KEY }} - AZURE_OPENAI_CHAT_COMPLETION_DEPLOYED_MODEL_NAME: ${{ secrets.AZURE_OPENAI_CHAT_COMPLETION_DEPLOYED_MODEL_NAME }} - AZURE_OPENAI_EMBEDDING_DEPLOYED_MODEL_NAME: ${{ secrets.AZURE_OPENAI_EMBEDDING_DEPLOYED_MODEL_NAME }} - run: uv run pytest -q -m "not e2e" - - name: Create pull request with fixes - if: ${{ success() && steps.openai-secret-check.outputs.has-secret == 'true' }} - uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # ratchet:peter-evans/create-pull-request@v7 - with: - commit-message: "fix(ci): auto-fix failing tests via Codex" - branch: codex/auto-fix-${{ github.event.workflow_run.id }} - base: ${{ env.FAILED_HEAD_BRANCH }} - title: "Auto-fix failing CI via Codex" - body: | - Codex automatically generated this PR in response to a CI failure on workflow `${{ env.FAILED_WORKFLOW_NAME }}`. - Failed run: ${{ env.FAILED_RUN_URL }} - Head branch: `${{ env.FAILED_HEAD_BRANCH }}` - This PR contains minimal changes intended solely to make the CI pass. diff --git a/playwright.config.ts b/playwright.config.ts deleted file mode 100644 index caaca66a..00000000 --- a/playwright.config.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { defineConfig, devices } from "@playwright/test"; - -/** - * Read environment variables from file. - * https://github.com/motdotla/dotenv - */ -// import dotenv from 'dotenv'; -// import path from 'path'; -// dotenv.config({ path: path.resolve(__dirname, '.env') }); - -/** - * See https://playwright.dev/docs/test-configuration. - */ -export default defineConfig({ - testDir: "./e2e", - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: "html", - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - /* Base URL to use in actions like `await page.goto('')`. */ - // baseURL: 'http://localhost:3000', - - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: "on-first-retry", - }, - - /* Configure projects for major browsers */ - projects: [ - { - name: "chromium", - use: { ...devices["Desktop Chrome"] }, - }, - - { - name: "firefox", - use: { ...devices["Desktop Firefox"] }, - }, - - { - name: "webkit", - use: { ...devices["Desktop Safari"] }, - }, - - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, - - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, - // }, - // { - // name: 'Google Chrome', - // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, - // }, - ], - - /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'npm run start', - // url: 'http://localhost:3000', - // reuseExistingServer: !process.env.CI, - // }, -}); diff --git a/playwright.service.config.ts b/playwright.service.config.ts deleted file mode 100644 index 329f1d6b..00000000 --- a/playwright.service.config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { defineConfig } from "@playwright/test"; -import { createAzurePlaywrightConfig, ServiceOS } from "@azure/playwright"; -import { DefaultAzureCredential } from "@azure/identity"; -import config from "./playwright.config"; - -/* Learn more about service configuration at https://aka.ms/pww/docs/config */ -export default defineConfig( - config, - createAzurePlaywrightConfig(config, { - exposeNetwork: "", - connectTimeout: 3 * 60 * 1000, // 3 minutes - os: ServiceOS.LINUX, - credential: new DefaultAzureCredential(), - }), -); - -require("dotenv").config(); diff --git a/tests/test_error_handling.py b/tests/test_error_handling.py index 5fa6cf1c..38a31ba7 100644 --- a/tests/test_error_handling.py +++ b/tests/test_error_handling.py @@ -7,6 +7,10 @@ from agentic_fleet.api.app import create_app +# Use shared constants for model names used in tests +MODEL_NAME = "agentic_fleet" +INVALID_ENTITY_ID = "invalid_entity_id" + @pytest.mark.asyncio async def test_invalid_entity_id() -> None: @@ -16,7 +20,7 @@ async def test_invalid_entity_id() -> None: resp = await client.post( "/v1/responses", json={ - "model": "invalid_entity_id", + "model": INVALID_ENTITY_ID, "input": "Test", "stream": False, }, @@ -25,7 +29,7 @@ async def test_invalid_entity_id() -> None: data = resp.json() assert "error" in data assert data["error"]["code"] == "entity_not_found" - assert "invalid_entity_id" in data["error"]["message"] + assert INVALID_ENTITY_ID in data["error"]["message"] @pytest.mark.asyncio @@ -36,7 +40,7 @@ async def test_invalid_request_body() -> None: resp = await client.post( "/v1/responses", json={ - "model": "magentic_fleet", + "model": MODEL_NAME, # Missing required "input" field }, ) @@ -61,7 +65,7 @@ async def test_missing_required_fields() -> None: resp = await client.post( "/v1/responses", json={ - "model": "magentic_fleet", + "model": MODEL_NAME, }, ) assert resp.status_code == 422 @@ -75,7 +79,7 @@ async def test_workflow_error_propagation() -> None: resp = await client.post( "/v1/responses", json={ - "model": "magentic_fleet", + "model": MODEL_NAME, "input": "Test", "stream": True, }, @@ -103,14 +107,13 @@ async def test_malformed_sse_events() -> None: resp = await client.post( "/v1/responses", json={ - "model": "magentic_fleet", + "model": MODEL_NAME, "input": "Test", "stream": True, }, ) assert resp.status_code == 200 # Server should handle stream properly - assert True @pytest.mark.asyncio diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 94eafd75..11f7562d 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -37,17 +37,24 @@ def db_manager(temp_db): @pytest.fixture def persistence_service(db_manager): - """Create persistence service.""" - settings = PersistenceSettings() - settings.enabled = True - settings.summary_threshold = 10 # Lower threshold for testing - return ConversationPersistenceService(db_manager, settings) + """Factory fixture to create persistence service with customizable settings.""" + + def _factory(summary_threshold=10, summary_keep_recent=None): + settings = PersistenceSettings() + settings.enabled = True + settings.summary_threshold = summary_threshold + if summary_keep_recent is not None: + settings.summary_keep_recent = summary_keep_recent + return ConversationPersistenceService(db_manager, settings) + + return _factory @pytest.mark.asyncio async def test_create_conversation(persistence_service): """Test conversation creation.""" - conv_id = await persistence_service.create_conversation( + svc = persistence_service() + conv_id = await svc.create_conversation( workflow_id="test-workflow", metadata={"test": "data"}, ) @@ -59,10 +66,11 @@ async def test_create_conversation(persistence_service): @pytest.mark.asyncio async def test_add_message_with_sequence(persistence_service): """Test adding message with automatic sequencing.""" - conv_id = await persistence_service.create_conversation(workflow_id="test-workflow") + svc = persistence_service() + conv_id = await svc.create_conversation(workflow_id="test-workflow") # Add first message - msg1 = await persistence_service.add_message( + msg1 = await svc.add_message( conversation_id=conv_id, role="user", content="Hello", @@ -70,7 +78,7 @@ async def test_add_message_with_sequence(persistence_service): assert msg1["sequence"] == 0 # Add second message - msg2 = await persistence_service.add_message( + msg2 = await svc.add_message( conversation_id=conv_id, role="assistant", content="Hi there", @@ -125,18 +133,19 @@ async def get_seq(): @pytest.mark.asyncio async def test_message_history_ordering(persistence_service): """Test that message history maintains sequence order.""" - conv_id = await persistence_service.create_conversation(workflow_id="test-workflow") + svc = persistence_service() + conv_id = await svc.create_conversation(workflow_id="test-workflow") # Add messages for i in range(5): - await persistence_service.add_message( + await svc.add_message( conversation_id=conv_id, role="user" if i % 2 == 0 else "assistant", content=f"Message {i}", ) # Get history - history = await persistence_service.get_conversation_history(conv_id) + history = await svc.get_conversation_history(conv_id) # Verify order assert len(history) == 5 @@ -148,10 +157,11 @@ async def test_message_history_ordering(persistence_service): @pytest.mark.asyncio async def test_ledger_snapshot(persistence_service): """Test ledger snapshot storage.""" - conv_id = await persistence_service.create_conversation(workflow_id="test-workflow") + svc = persistence_service() + conv_id = await svc.create_conversation(workflow_id="test-workflow") # Add ledger snapshots - await persistence_service.add_ledger_snapshot( + await svc.add_ledger_snapshot( conversation_id=conv_id, task_id="task-1", goal="Complete task 1", @@ -159,7 +169,7 @@ async def test_ledger_snapshot(persistence_service): snapshot_data={"progress": 50}, ) - await persistence_service.add_ledger_snapshot( + await svc.add_ledger_snapshot( conversation_id=conv_id, task_id="task-1", goal="Complete task 1", @@ -168,7 +178,7 @@ async def test_ledger_snapshot(persistence_service): ) # Get latest state - ledger = await persistence_service.get_ledger_state(conv_id) + ledger = await svc.get_ledger_state(conv_id) # Should only get latest snapshot per task assert len(ledger) == 1 @@ -179,16 +189,17 @@ async def test_ledger_snapshot(persistence_service): @pytest.mark.asyncio async def test_event_storage(persistence_service): """Test event storage with sequencing.""" - conv_id = await persistence_service.create_conversation(workflow_id="test-workflow") + svc = persistence_service() + conv_id = await svc.create_conversation(workflow_id="test-workflow") # Add events - seq1 = await persistence_service.add_event( + seq1 = await svc.add_event( conversation_id=conv_id, event_type="workflow.start", event_data={"workflow_id": "test"}, ) - seq2 = await persistence_service.add_event( + seq2 = await svc.add_event( conversation_id=conv_id, event_type="agent.message", event_data={"agent": "planner"}, @@ -199,7 +210,7 @@ async def test_event_storage(persistence_service): assert seq2 == 1 # Get event history - events = await persistence_service.get_event_history(conv_id) + events = await svc.get_event_history(conv_id) assert len(events) == 2 assert events[0]["event_type"] == "workflow.start" assert events[1]["event_type"] == "agent.message" @@ -208,10 +219,11 @@ async def test_event_storage(persistence_service): @pytest.mark.asyncio async def test_reasoning_trace_storage(persistence_service): """Test reasoning trace storage separate from messages.""" - conv_id = await persistence_service.create_conversation(workflow_id="test-workflow") + svc = persistence_service() + conv_id = await svc.create_conversation(workflow_id="test-workflow") # Add message with reasoning - await persistence_service.add_message( + await svc.add_message( conversation_id=conv_id, role="assistant", content="Answer", @@ -219,7 +231,7 @@ async def test_reasoning_trace_storage(persistence_service): ) # Get history with reasoning - history = await persistence_service.get_conversation_history(conv_id, include_reasoning=True) + history = await svc.get_conversation_history(conv_id, include_reasoning=True) assert len(history) == 1 assert history[0]["reasoning"] == "Detailed reasoning trace" @@ -229,32 +241,37 @@ async def test_reasoning_trace_storage(persistence_service): @pytest.mark.asyncio async def test_summarization_threshold(persistence_service): """Test automatic summarization at threshold.""" - # Use low threshold - need to update both settings and policy - persistence_service.settings.summary_threshold = 8 - persistence_service.settings.summary_keep_recent = 2 - persistence_service.summarization_policy.threshold = 8 - persistence_service.summarization_policy.keep_recent = 2 + # Use custom configuration via factory, do not mutate internals directly + svc = persistence_service(summary_threshold=8, summary_keep_recent=2) - conv_id = await persistence_service.create_conversation(workflow_id="test-workflow") + conv_id = await svc.create_conversation(workflow_id="test-workflow") - # Add messages up to threshold + # Add messages beyond threshold to trigger summarization (10 > 8) for i in range(10): - await persistence_service.add_message( + await svc.add_message( conversation_id=conv_id, role="user" if i % 2 == 0 else "assistant", content=f"Message {i}", ) # Get history - should have summary + recent messages - history = await persistence_service.get_conversation_history(conv_id) + history = await svc.get_conversation_history(conv_id) # Should have triggered summarization: summary message + 2 recent messages = 3 # (8 messages summarized, keeping last 2) - assert len(history) <= 4 # Summary + up to 2 recent messages + margin + assert len(history) == 3 # Summary + 2 recent messages # Check if a summary message exists + # Prefer robust field checks: 'type' or 'is_summary' if they exist, fallback to strict matching summary_messages = [ - m for m in history if m["role"] == "system" and "summarized" in m["content"].lower() + m + for m in history + if m["role"] == "system" + and ( + m.get("type") == "summary" + or m.get("is_summary") is True + or m["content"].lower().startswith("summary:") + ) ] assert len(summary_messages) >= 1, "Expected at least one summary message" @@ -262,18 +279,19 @@ async def test_summarization_threshold(persistence_service): @pytest.mark.asyncio async def test_event_replay(persistence_service): """Test event replay from specific sequence.""" - conv_id = await persistence_service.create_conversation(workflow_id="test-workflow") + svc = persistence_service() + conv_id = await svc.create_conversation(workflow_id="test-workflow") # Add events for i in range(5): - await persistence_service.add_event( + await svc.add_event( conversation_id=conv_id, event_type=f"event-{i}", event_data={"index": i}, ) # Replay from sequence 2 - events = await persistence_service.get_event_history(conv_id, from_sequence=2) + events = await svc.get_event_history(conv_id, from_sequence=2) assert len(events) == 3 assert events[0]["event_type"] == "event-2" @@ -309,7 +327,7 @@ async def test_reasoning_repository(db_manager): reasoning_text="Detailed trace", effort="high", verbosity="verbose", - model="gpt-5-mini", + model="test-model", metadata={"tokens": 1000}, ) @@ -318,7 +336,7 @@ async def test_reasoning_repository(db_manager): assert trace is not None assert trace["reasoning_text"] == "Detailed trace" assert trace["effort"] == "high" - assert trace["model"] == "gpt-5-mini" + assert trace["model"] == "test-model" if __name__ == "__main__": diff --git a/tests/test_workflow_factory.py b/tests/test_workflow_factory.py index 154553f5..a694e7b2 100644 --- a/tests/test_workflow_factory.py +++ b/tests/test_workflow_factory.py @@ -11,6 +11,14 @@ from agentic_fleet.workflow.magentic_workflow import MagenticFleetWorkflow +def _validate_workflow_dict(workflow: dict) -> None: + assert "id" in workflow + assert "name" in workflow + assert "description" in workflow + assert "factory" in workflow + assert "agent_count" in workflow + + def test_workflow_factory_initialization() -> None: """Test WorkflowFactory initializes correctly.""" factory = WorkflowFactory() @@ -24,15 +32,11 @@ def test_list_available_workflows() -> None: workflows = factory.list_available_workflows() assert isinstance(workflows, list) - workflow_ids = {w["id"] for w in workflows} + workflow_ids = {workflow["id"] for workflow in workflows} assert workflow_ids == {"collaboration", "magentic_fleet"} for workflow in workflows: - assert "id" in workflow - assert "name" in workflow - assert "description" in workflow - assert "factory" in workflow - assert "agent_count" in workflow + _validate_workflow_dict(workflow) def test_get_workflow_config_magentic_fleet() -> None: @@ -65,22 +69,13 @@ def test_create_from_yaml_magentic_fleet(monkeypatch: pytest.MonkeyPatch) -> Non @pytest.mark.skip(reason="Legacy test for removed api.workflow_factory implementation") def test_build_magentic_fleet_args() -> None: """Test building arguments for magentic fleet workflow.""" - factory = WorkflowFactory() - config = factory.get_workflow_config("magentic_fleet") - - kwargs = factory._build_magentic_fleet_args(config) - - assert isinstance(kwargs, dict) + pass @pytest.mark.skip(reason="utils.factory.WorkflowFactory doesn't check config/ directory anymore") def test_workflow_factory_with_custom_path() -> None: """Test WorkflowFactory with custom config path to repo override.""" - config_path = Path(__file__).parent.parent / "config" / "workflows.yaml" - factory = WorkflowFactory(config_path=config_path) - - assert factory.config_path == config_path - assert factory._config is not None + pass def test_workflow_factory_missing_config_file() -> None: @@ -126,12 +121,4 @@ def test_workflow_factory_env_override(tmp_path: Path, monkeypatch: pytest.Monke @pytest.mark.skip(reason="utils.factory.WorkflowFactory uses package default, not config/ fallback") def test_workflow_factory_env_invalid_path_falls_back(monkeypatch: pytest.MonkeyPatch) -> None: """Invalid AF_WORKFLOW_CONFIG should fall back to repo configuration.""" - - repo_config = Path(__file__).parent.parent / "config" / "workflows.yaml" - monkeypatch.setenv("AF_WORKFLOW_CONFIG", str(Path("/definitely/missing.yaml"))) - - factory = WorkflowFactory() - - assert factory.config_path == repo_config - workflows = {workflow["id"] for workflow in factory.list_available_workflows()} - assert workflows == {"collaboration", "magentic_fleet"} + pass diff --git a/tests/validate_test_improvements.py b/tests/validate_test_improvements.py index e7370773..d4659795 100644 --- a/tests/validate_test_improvements.py +++ b/tests/validate_test_improvements.py @@ -18,8 +18,7 @@ import time from pathlib import Path -# Add current directory to path for imports -sys.path.append(str(Path(__file__).parent)) +# If you encounter import errors, run this script with 'PYTHONPATH=.' or install the package in editable mode. def validate_config_testing(): diff --git a/tools/scripts/setup-pypi-environment.sh b/tools/scripts/setup-pypi-environment.sh deleted file mode 100755 index d0c255bc..00000000 --- a/tools/scripts/setup-pypi-environment.sh +++ /dev/null @@ -1,159 +0,0 @@ -#!/bin/bash -# Setup PyPI Environment for GitHub Actions -# This script provides instructions and checks for setting up PyPI publishing - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" -cd "${REPO_ROOT}" - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -echo -e "${BLUE}================================${NC}" -echo -e "${BLUE}PyPI Environment Setup for AgenticFleet${NC}" -echo -e "${BLUE}================================${NC}" -echo "" - -# Check if gh CLI is installed -if ! command -v gh &> /dev/null; then - echo -e "${RED}Error: GitHub CLI (gh) is not installed${NC}" - echo "Please install it from: https://cli.github.com/" - exit 1 -fi - -# Check if logged in -if ! gh auth status &> /dev/null; then - echo -e "${RED}Error: Not logged in to GitHub CLI${NC}" - echo "Please run: gh auth login" - exit 1 -fi - -REPO="Qredence/AgenticFleet" - -echo -e "${GREEN}✓${NC} GitHub CLI is installed and authenticated" -echo "" - -echo -e "${YELLOW}Step 1: PyPI Environment Setup${NC}" -echo "GitHub environments must be created through the web UI." -echo "" -echo -e "${BLUE}To create the 'pypi' environment:${NC}" -echo "" -echo "1. Go to: https://github.com/${REPO}/settings/environments" -echo "2. Click 'New environment'" -echo "3. Name: ${GREEN}pypi${NC}" -echo "4. Click 'Configure environment'" -echo "" -echo -e "${YELLOW}Step 2: Configure Deployment Protection${NC}" -echo "" -echo "5. Under 'Deployment branches and tags', select: ${GREEN}Selected tags${NC}" -echo "6. Click 'Add deployment branch or tag rule'" -echo "7. Enter pattern: ${GREEN}v[0-9]+.[0-9]+.[0-9]+*${NC}" -echo " ${YELLOW}Note: Use this exact pattern, not 'v*.*.*' (causes 'Name is invalid' error)${NC}" -echo "8. Click 'Add rule'" -echo "" -echo -e "${YELLOW}Step 3: Set Up PyPI Trusted Publishing (Recommended)${NC}" -echo "" -echo "Instead of using API tokens, set up trusted publishing:" -echo "" -echo "1. Go to: ${BLUE}https://pypi.org/manage/account/publishing/${NC}" -echo "2. Scroll to 'Add a new pending publisher'" -echo "3. Fill in:" -echo " - PyPI Project Name: ${GREEN}agentic-fleet${NC}" -echo " - Owner: ${GREEN}Qredence${NC}" -echo " - Repository name: ${GREEN}AgenticFleet${NC}" -echo " - Workflow name: ${GREEN}release.yml${NC}" -echo " - Environment name: ${GREEN}pypi${NC}" -echo "4. Click 'Add'" -echo "" -echo -e "${YELLOW}Alternative: Using API Token${NC}" -echo "" -echo "If you prefer using an API token instead:" -echo "" -echo "1. Generate token at: ${BLUE}https://pypi.org/manage/account/token/${NC}" -echo "2. In GitHub, go to: https://github.com/${REPO}/settings/environments" -echo "3. Click on 'pypi' environment" -echo "4. Under 'Environment secrets', click 'Add secret'" -echo "5. Name: ${GREEN}PYPI_API_TOKEN${NC}" -echo "6. Paste your PyPI token" -echo "7. Update .github/workflows/release.yml to use the token" -echo "" - -echo -e "${YELLOW}Step 4: Optional - TestPyPI Environment${NC}" -echo "" -echo "To test releases before publishing to production:" -echo "" -echo "1. Create another environment named: ${GREEN}testpypi${NC}" -echo "2. Set up trusted publishing at: ${BLUE}https://test.pypi.org/manage/account/publishing/${NC}" -echo "3. Use the same details but for TestPyPI" -echo "" - -echo -e "${GREEN}================================${NC}" -echo -e "${GREEN}Verification${NC}" -echo -e "${GREEN}================================${NC}" -echo "" - -# Check if release workflow exists -if [ -f ".github/workflows/release.yml" ]; then - echo -e "${GREEN}✓${NC} Release workflow exists" -else - echo -e "${RED}✗${NC} Release workflow not found" -fi - -# Check pyproject.toml -if [ -f "pyproject.toml" ]; then - echo -e "${GREEN}✓${NC} pyproject.toml exists" - - # Check package name - if grep -q 'name = "agentic-fleet"' pyproject.toml; then - echo -e "${GREEN}✓${NC} Package name: agentic-fleet" - else - echo -e "${YELLOW}!${NC} Package name not found or different" - fi - - # Check version - version=$(grep -E '^version = ' pyproject.toml | head -1 | cut -d'"' -f2) - if [ -n "$version" ]; then - echo -e "${GREEN}✓${NC} Current version: $version" - fi -else - echo -e "${RED}✗${NC} pyproject.toml not found" -fi - -echo "" -echo -e "${YELLOW}================================${NC}" -echo -e "${YELLOW}Testing Your Setup${NC}" -echo -e "${YELLOW}================================${NC}" -echo "" -echo "Once environment is configured, test the release workflow:" -echo "" -echo "1. Manual trigger (without publishing):" -echo " ${BLUE}gh workflow run release.yml${NC}" -echo "" -echo "2. Create a test tag:" -echo " ${BLUE}git tag v0.5.5-test${NC}" -echo " ${BLUE}git push origin v0.5.5-test${NC}" -echo "" -echo "3. For production release:" -echo " ${BLUE}git tag v0.5.5${NC}" -echo " ${BLUE}git push origin v0.5.5${NC}" -echo "" - -echo -e "${GREEN}================================${NC}" -echo -e "${GREEN}Quick Links${NC}" -echo -e "${GREEN}================================${NC}" -echo "" -echo "Repository Settings: https://github.com/${REPO}/settings" -echo "Environments: https://github.com/${REPO}/settings/environments" -echo "PyPI Publishing: https://pypi.org/manage/account/publishing/" -echo "TestPyPI Publishing: https://test.pypi.org/manage/account/publishing/" -echo "Workflow Runs: https://github.com/${REPO}/actions/workflows/release.yml" -echo "" - -echo -e "${BLUE}For more details, see: docs/GITHUB_ACTIONS_SETUP.md${NC}" -echo ""