mnemo_cards/.forgejo/workflows/agent-planning.yml

381 lines
15 KiB
YAML
Raw Normal View History

2025-11-20 21:28:55 +00:00
name: AI Agent - Planning
on:
workflow_dispatch:
inputs:
2025-11-21 11:13:03 +00:00
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
2025-11-22 19:08:15 +00:00
# schedule:
# - cron: '0 */4 * * *'
2025-11-20 21:28:55 +00:00
jobs:
planning:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
2025-11-20 22:15:47 +00:00
- name: Set up Python and bash
2025-11-20 21:55:20 +00:00
run: |
apt-get update -qq
2025-11-21 23:26:25 +00:00
apt-get install -y -qq python3 python3-pip python3-venv bash jq
2025-11-20 21:55:20 +00:00
python3 --version
pip3 --version
2025-11-20 22:15:47 +00:00
bash --version || echo "bash check failed"
2025-11-21 23:26:25 +00:00
jq --version || echo "jq check failed"
2025-11-20 21:28:55 +00:00
- name: Install Cursor CLI
run: |
2025-11-20 22:15:47 +00:00
# bash should already be installed in previous step
2025-11-20 21:28:55 +00:00
curl https://cursor.com/install -fsS | bash
2025-11-20 22:12:32 +00:00
# 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
2025-11-20 22:04:17 +00:00
# Verify installation
2025-11-20 22:12:32 +00:00
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
2025-11-20 22:04:17 +00:00
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
2025-11-20 22:12:32 +00:00
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"
2025-11-20 22:04:17 +00:00
exit 1
fi
2025-11-20 21:28:55 +00:00
- name: Configure Git
run: |
git config --global user.name "AI Agent"
git config --global user.email "ai-agent@mnemo-cards.com"
2025-11-21 23:26:25 +00:00
- name: Run Planning Agent for All Components
2025-11-20 21:28:55 +00:00
env:
CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}
2025-11-21 11:13:03 +00:00
CURSOR_MODEL: ${{ inputs.llm_model || secrets.CURSOR_MODEL }}
2025-11-20 21:28:55 +00:00
PROJECT_ROOT: ${{ github.workspace }}
MAX_ITERATIONS: 10
2025-11-20 22:22:34 +00:00
PATH: /usr/bin:/bin:$HOME/.local/bin:$HOME/.cursor/bin:${{ env.PATH }}
2025-11-20 21:28:55 +00:00
run: |
2025-11-20 22:22:34 +00:00
# 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
2025-11-20 22:04:17 +00:00
# Verify cursor-agent is accessible
if ! command -v cursor-agent &> /dev/null; then
echo "❌ cursor-agent not found in PATH"
echo "PATH: $PATH"
2025-11-20 22:12:32 +00:00
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
2025-11-20 22:04:17 +00:00
export PATH="$HOME/.cursor/bin:$PATH"
2025-11-20 22:12:32 +00:00
echo "✅ Found at $HOME/.cursor/bin/cursor-agent"
2025-11-20 22:04:17 +00:00
else
2025-11-20 22:12:32 +00:00
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"
2025-11-20 22:04:17 +00:00
exit 1
fi
2025-11-20 22:22:34 +00:00
else
echo "✅ cursor-agent found at: $(command -v cursor-agent)"
2025-11-20 22:04:17 +00:00
fi
2025-11-20 21:28:55 +00:00
cd ${{ github.workspace }}
2025-11-21 23:26:25 +00:00
# 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"
2025-11-20 21:28:55 +00:00
2025-11-21 23:26:25 +00:00
- name: Parse Task Lists and Create Summaries
2025-11-20 21:28:55 +00:00
if: success()
id: parse_tasks
run: |
2025-11-21 23:26:25 +00:00
COMPONENTS=("web_v2" "backend" "common")
2025-11-20 21:28:55 +00:00
2025-11-21 23:26:25 +00:00
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
2025-11-20 21:28:55 +00:00
import json
import os
2025-11-21 23:26:25 +00:00
component = "$COMPONENT"
2025-11-20 21:28:55 +00:00
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*"
2025-11-21 23:26:25 +00:00
# Save to file for next step (component-specific)
with open(f'/tmp/issue_body_{component}.txt', 'w') as f:
2025-11-20 21:28:55 +00:00
f.write(body)
title = f"📋 Planning: {component} - {len(tasks)} tasks ({total_hours:.1f}h)"
2025-11-21 23:26:25 +00:00
with open(f'/tmp/issue_title_{component}.txt', 'w') as f:
2025-11-20 21:28:55 +00:00
f.write(title)
2025-11-21 23:26:25 +00:00
print(f"✅ {component}: Tasks: {len(tasks)}, High: {high}, Medium: {medium}, Low: {low}")
2025-11-20 21:28:55 +00:00
except Exception as e:
2025-11-21 23:26:25 +00:00
print(f"❌ Error processing {component}: {e}")
import traceback
traceback.print_exc()
2025-11-20 21:28:55 +00:00
EOF
2025-11-21 23:26:25 +00:00
done
echo "✅ All task lists parsed"
2025-11-20 21:28:55 +00:00
2025-11-21 23:26:25 +00:00
- name: Create Issues via Forgejo API
2025-11-20 21:28:55 +00:00
if: success()
2025-11-21 23:26:25 +00:00
env:
FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }}
API_URL: ${{ github.api_url }}/repos/${{ github.repository }}/issues
2025-11-20 21:28:55 +00:00
run: |
2025-11-21 23:26:25 +00:00
COMPONENTS=("web_v2" "backend" "common")
2025-11-20 21:28:55 +00:00
2025-11-21 23:26:25 +00:00
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)
2025-11-20 21:28:55 +00:00
2025-11-21 23:33:31 +00:00
# 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)
2025-11-21 23:26:25 +00:00
payload = {
2025-11-21 23:33:31 +00:00
"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 = {
2025-11-21 23:26:25 +00:00
"title": title,
"body": body,
"labels": ["ai-agent", "planning", component]
2025-11-20 21:28:55 +00:00
}
2025-11-21 23:26:25 +00:00
2025-11-21 23:33:31 +00:00
# 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")
2025-11-21 23:26:25 +00:00
curl_cmd = [
"curl", "-X", "POST", api_url,
"-H", f"Authorization: token {token}",
"-H", "Content-Type: application/json",
"-d", json_payload,
2025-11-21 23:33:31 +00:00
"-w", "\nHTTP Status: %{http_code}\n",
"-s", "-S"
2025-11-21 23:26:25 +00:00
]
result = subprocess.run(curl_cmd, capture_output=True, text=True)
2025-11-21 23:33:31 +00:00
# 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):
2025-11-21 23:26:25 +00:00
print(f"✅ Issue created for {component}")
2025-11-21 23:33:31 +00:00
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)
2025-11-21 23:26:25 +00:00
else:
print(f"❌ Failed to create issue for {component}")
2025-11-21 23:33:31 +00:00
print(f"HTTP Status: {http_status}")
print(f"Response: {response_body[:500]}")
if result.stderr:
print(f"Error: {result.stderr}")
2025-11-21 23:26:25 +00:00
sys.exit(1)
PYTHON_SCRIPT
if [ $? -ne 0 ]; then
echo "⚠️ Failed to create issue for $COMPONENT, continuing..."
fi
done
2025-11-20 21:28:55 +00:00
2025-11-21 23:26:25 +00:00
- name: Trigger Development Workflow for All Components
2025-11-20 21:28:55 +00:00
if: success()
run: |
2025-11-21 23:26:25 +00:00
COMPONENTS=("web_v2" "backend" "common")
2025-11-20 21:28:55 +00:00
API_URL="${{ github.api_url }}/repos/${{ github.repository }}/actions/workflows/agent-development.yml/dispatches"
2025-11-21 23:26:25 +00:00
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
2025-11-20 21:28:55 +00:00