name: AI Agent - Planning on: workflow_dispatch: inputs: llm_model: description: 'LLM Model' required: true default: 'auto' type: choice options: - auto - composer-1 - sonnet-4.5 - sonnet-4.5-thinking - gemini-3-pro - gpt-5 - gpt-5.1 - gpt-5-high - gpt-5.1-high - gpt-5-codex - gpt-5-codex-high - gpt-5.1-codex - gpt-5.1-codex-high - opus-4.1 - grok # schedule: # - cron: '0 */4 * * *' jobs: planning: runs-on: ubuntu-latest timeout-minutes: 30 steps: - name: Checkout repository uses: actions/checkout@v3 with: fetch-depth: 0 - name: Set up Python and bash run: | apt-get update -qq apt-get install -y -qq python3 python3-pip python3-venv bash jq python3 --version pip3 --version bash --version || echo "bash check failed" jq --version || echo "jq check failed" - name: Install Cursor CLI run: | # bash should already be installed in previous step curl https://cursor.com/install -fsS | bash # Cursor installs to ~/.local/bin by default export PATH="$HOME/.local/bin:$PATH" echo "$HOME/.local/bin" >> $GITHUB_PATH # Also check ~/.cursor/bin (older location) if [ -d "$HOME/.cursor/bin" ]; then export PATH="$HOME/.cursor/bin:$PATH" echo "$HOME/.cursor/bin" >> $GITHUB_PATH fi # Verify installation if [ -f "$HOME/.local/bin/cursor-agent" ]; then echo "✅ Cursor CLI installed at $HOME/.local/bin/cursor-agent" chmod +x "$HOME/.local/bin/cursor-agent" "$HOME/.local/bin/cursor-agent" --version || echo "cursor-agent executable found but version check failed" elif [ -f "$HOME/.cursor/bin/cursor-agent" ]; then echo "✅ Cursor CLI installed at $HOME/.cursor/bin/cursor-agent" chmod +x "$HOME/.cursor/bin/cursor-agent" "$HOME/.cursor/bin/cursor-agent" --version || echo "cursor-agent executable found but version check failed" else echo "❌ Cursor CLI not found in expected locations" echo "Checking PATH..." command -v cursor-agent || echo "cursor-agent not in PATH" find "$HOME" -name "cursor-agent" -type f 2>/dev/null | head -5 || echo "No cursor-agent found in $HOME" exit 1 fi - name: Configure Git run: | git config --global user.name "AI Agent" git config --global user.email "ai-agent@mnemo-cards.com" - name: Run Planning Agent for All Components env: CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }} CURSOR_MODEL: ${{ inputs.llm_model || secrets.CURSOR_MODEL }} PROJECT_ROOT: ${{ github.workspace }} MAX_ITERATIONS: 10 PATH: /usr/bin:/bin:$HOME/.local/bin:$HOME/.cursor/bin:${{ env.PATH }} run: | # Ensure bash and cursor-agent are in PATH for this step export PATH="/usr/bin:/bin:$HOME/.local/bin:$HOME/.cursor/bin:$PATH" # Verify bash is accessible if ! command -v bash &> /dev/null; then echo "❌ bash not found in PATH" echo "PATH: $PATH" echo "Looking for bash..." find /usr /bin -name "bash" -type f 2>/dev/null | head -5 || echo "No bash found" exit 1 else echo "✅ bash found at: $(command -v bash)" bash --version || echo "bash version check failed" fi # Verify cursor-agent is accessible if ! command -v cursor-agent &> /dev/null; then echo "❌ cursor-agent not found in PATH" echo "PATH: $PATH" echo "Trying full paths..." if [ -f "$HOME/.local/bin/cursor-agent" ]; then export PATH="$HOME/.local/bin:$PATH" echo "✅ Found at $HOME/.local/bin/cursor-agent" elif [ -f "$HOME/.cursor/bin/cursor-agent" ]; then export PATH="$HOME/.cursor/bin:$PATH" echo "✅ Found at $HOME/.cursor/bin/cursor-agent" else echo "❌ cursor-agent not found in any expected location" find "$HOME" -name "cursor-agent" -type f 2>/dev/null | head -5 || echo "No cursor-agent found" exit 1 fi else echo "✅ cursor-agent found at: $(command -v cursor-agent)" fi cd ${{ github.workspace }} # Plan for all components COMPONENTS=("web_v2" "backend" "common") for COMPONENT in "${COMPONENTS[@]}"; do echo "📋 Planning for component: $COMPONENT" python3 tools/agent/planning_agent.py "$COMPONENT" || { echo "⚠️ Planning failed for $COMPONENT, continuing with other components..." } done echo "✅ Planning completed for all components" - name: Parse Task Lists and Create Summaries if: success() id: parse_tasks run: | COMPONENTS=("web_v2" "backend" "common") for COMPONENT in "${COMPONENTS[@]}"; do TASK_LIST_PATH="ai_docs/agent/${COMPONENT}/task_list.json" if [ ! -f "$TASK_LIST_PATH" ]; then echo "⚠️ Task list not found for $COMPONENT, skipping..." continue fi echo "📊 Parsing task list for $COMPONENT..." # Parse JSON with Python python3 << EOF import json import os component = "$COMPONENT" task_list_path = f"ai_docs/agent/{component}/task_list.json" try: with open(task_list_path, 'r') as f: data = json.load(f) tasks = data.get('tasks', []) high = sum(1 for t in tasks if t.get('priority') == 'high') medium = sum(1 for t in tasks if t.get('priority') == 'medium') low = sum(1 for t in tasks if t.get('priority') == 'low') total_hours = sum(t.get('estimated_hours', 0) for t in tasks) # Create issue body body = f"# 🎯 Planning Summary: {component}\\n\\n" body += f"**Generated:** {data.get('generated_at', 'N/A')}\\n" body += f"**Total Tasks:** {len(tasks)}\\n" body += f"**Estimated Hours:** {total_hours:.1f}h\\n\\n" body += "## Priority Breakdown\\n\\n" body += f"- 🔴 **HIGH:** {high} tasks\\n" body += f"- 🟡 **MEDIUM:** {medium} tasks\\n" body += f"- 🟢 **LOW:** {low} tasks\\n\\n" high_tasks = [t for t in tasks if t.get('priority') == 'high'] if high_tasks: body += "## 🔴 High Priority Tasks\\n\\n" for task in high_tasks: body += f"### {task['id']}: {task['title']}\\n" body += f"- **Estimated:** {task['estimated_hours']}h\\n" body += f"- **Status:** {task['status']}\\n" desc = task['description'][:200] body += f"- **Description:** {desc}...\\n\\n" body += "\\n---\\n*Generated by AI Planning Agent*" # Save to file for next step (component-specific) with open(f'/tmp/issue_body_{component}.txt', 'w') as f: f.write(body) title = f"📋 Planning: {component} - {len(tasks)} tasks ({total_hours:.1f}h)" with open(f'/tmp/issue_title_{component}.txt', 'w') as f: f.write(title) print(f"✅ {component}: Tasks: {len(tasks)}, High: {high}, Medium: {medium}, Low: {low}") except Exception as e: print(f"❌ Error processing {component}: {e}") import traceback traceback.print_exc() EOF done echo "✅ All task lists parsed" - name: Create Issues via Forgejo API if: success() env: FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }} API_URL: ${{ github.api_url }}/repos/${{ github.repository }}/issues run: | COMPONENTS=("web_v2" "backend" "common") for COMPONENT in "${COMPONENTS[@]}"; do if [ ! -f "/tmp/issue_body_${COMPONENT}.txt" ] || [ ! -f "/tmp/issue_title_${COMPONENT}.txt" ]; then echo "⚠️ Issue files not found for $COMPONENT, skipping..." continue fi echo "📝 Creating issue for $COMPONENT..." # Create issue using Python for proper JSON escaping python3 << PYTHON_SCRIPT import json import sys import os import subprocess component = "$COMPONENT" api_url = os.environ.get('API_URL') token = os.environ.get('FORGEJO_TOKEN') if not all([component, api_url, token]): print("❌ Missing required environment variables") sys.exit(1) # Read title and body try: with open(f'/tmp/issue_title_{component}.txt', 'r') as f: title = f.read().strip() with open(f'/tmp/issue_body_{component}.txt', 'r') as f: body = f.read() except Exception as e: print(f"❌ Error reading files: {e}") sys.exit(1) # Validate title and body if not title: print("❌ Title is empty") sys.exit(1) if len(title) > 255: print(f"⚠️ Title too long ({len(title)} chars), truncating...") title = title[:255] # Create JSON payload (labels are optional, may not exist in repo) payload = { "title": title, "body": body } # Try to add labels, but don't fail if they don't exist # Labels will be added only if they exist in the repository payload_with_labels = { "title": title, "body": body, "labels": ["ai-agent", "planning", component] } # Make API request - try with labels first json_payload = json.dumps(payload_with_labels) print(f"📤 Sending request to: {api_url}") print(f"📋 Title: {title[:50]}...") print(f"📝 Body length: {len(body)} chars") curl_cmd = [ "curl", "-X", "POST", api_url, "-H", f"Authorization: token {token}", "-H", "Content-Type: application/json", "-d", json_payload, "-w", "\nHTTP Status: %{http_code}\n", "-s", "-S" ] result = subprocess.run(curl_cmd, capture_output=True, text=True) # Parse HTTP status code from output http_status = None response_body = result.stdout if "HTTP Status:" in result.stdout: parts = result.stdout.rsplit("HTTP Status:", 1) response_body = parts[0].strip() http_status = parts[1].strip() if len(parts) > 1 else None # Check HTTP status code if http_status == "201" or (result.returncode == 0 and "HTTP Status: 201" in result.stdout): print(f"✅ Issue created for {component}") if response_body: try: response = json.loads(response_body) if "number" in response: print(f" Issue #{response['number']} created") except: pass elif http_status in ["422", "400"] or "HTTP Status: 422" in result.stdout or "HTTP Status: 400" in result.stdout: print(f"⚠️ Labels may not exist or validation error, trying without labels...") print(f" First attempt response: {response_body[:200]}") # Retry without labels json_payload = json.dumps(payload) curl_cmd = [ "curl", "-X", "POST", api_url, "-H", f"Authorization: token {token}", "-H", "Content-Type: application/json", "-d", json_payload, "-w", "\nHTTP Status: %{http_code}\n", "-s", "-S" ] result = subprocess.run(curl_cmd, capture_output=True, text=True) # Parse status again http_status = None response_body = result.stdout if "HTTP Status:" in result.stdout: parts = result.stdout.rsplit("HTTP Status:", 1) response_body = parts[0].strip() http_status = parts[1].strip() if len(parts) > 1 else None if http_status == "201" or "HTTP Status: 201" in result.stdout: print(f"✅ Issue created for {component} (without labels)") else: print(f"❌ Failed to create issue for {component}") print(f"HTTP Status: {http_status}") print(f"Response: {response_body[:500]}") if result.stderr: print(f"Error details: {result.stderr}") sys.exit(1) else: print(f"❌ Failed to create issue for {component}") print(f"HTTP Status: {http_status}") print(f"Response: {response_body[:500]}") if result.stderr: print(f"Error: {result.stderr}") sys.exit(1) PYTHON_SCRIPT if [ $? -ne 0 ]; then echo "⚠️ Failed to create issue for $COMPONENT, continuing..." fi done - name: Trigger Development Workflow for All Components if: success() run: | COMPONENTS=("web_v2" "backend" "common") API_URL="${{ github.api_url }}/repos/${{ github.repository }}/actions/workflows/agent-development.yml/dispatches" for COMPONENT in "${COMPONENTS[@]}"; do echo "🚀 Triggering development workflow for $COMPONENT..." curl -X POST "$API_URL" \ -H "Authorization: token ${{ secrets.FORGEJO_TOKEN }}" \ -H "Content-Type: application/json" \ -d "{\"ref\":\"master\",\"inputs\":{\"component\":\"$COMPONENT\"}}" || { echo "⚠️ Failed to trigger workflow for $COMPONENT, continuing..." } echo "✅ Development workflow triggered for $COMPONENT" done