This commit is contained in:
Dmitry 2025-11-21 14:13:03 +03:00
parent 3ca6b4d5f8
commit 07176f9b79
8 changed files with 749 additions and 57 deletions

View file

@ -11,6 +11,35 @@ on:
- web_v2
- backend
- common
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
commit_mode:
description: 'Commit mode: master (direct commit with PR fallback) or pr (always create PR)'
required: false
default: 'master'
type: choice
options:
- master
- pr
schedule:
# Run every 30 minutes
- cron: '*/20 * * * *'
@ -76,11 +105,13 @@ jobs:
- name: Run Development Agent
env:
CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}
CURSOR_MODEL: ${{ secrets.CURSOR_MODEL }}
CURSOR_MODEL: ${{ inputs.llm_model || secrets.CURSOR_MODEL }}
PROJECT_ROOT: ${{ github.workspace }}
MAX_ITERATIONS: 10
MAX_RETRIES: 3
TIMEOUT_HOURS: 6
FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }}
COMMIT_MODE: ${{ inputs.commit_mode || 'master' }}
run: |
# Verify cursor-agent is accessible
echo "Current PATH: $PATH"

View file

@ -0,0 +1,86 @@
name: AI Agent - Documentation Update
on:
workflow_dispatch:
inputs:
component:
description: 'Component to document'
required: true
type: choice
options:
- web_v2
- backend
- common
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
jobs:
update-docs:
runs-on: ubuntu-latest
container: code.mnemo-cards.online/cinnabarflower/cards/agent:latest
timeout-minutes: 60
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
submodules: recursive
- name: Configure Git
run: |
git config --global user.name "AI Agent"
git config --global user.email "ai-agent@mnemo-cards.com"
- name: Install Dependencies
run: |
COMPONENT="${{ inputs.component }}"
if [ "$COMPONENT" = "web_v2" ]; then
cd mnemo_cards_web_v2
flutter pub get
elif [ "$COMPONENT" = "backend" ]; then
cd mnemo_cards_backend
dart pub get
elif [ "$COMPONENT" = "common" ]; then
cd mnemo_cards_common
dart pub get
fi
- name: Run Documentation Agent
env:
CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}
CURSOR_MODEL: ${{ inputs.llm_model }}
PROJECT_ROOT: ${{ github.workspace }}
run: |
# Verify cursor-agent is accessible
if ! command -v cursor-agent &> /dev/null; then
echo "❌ cursor-agent not found in PATH"
# Try to find it in common locations
if [ -f "$HOME/.local/bin/cursor-agent" ]; then
export PATH="$HOME/.local/bin:$PATH"
elif [ -f "$HOME/.cursor/bin/cursor-agent" ]; then
export PATH="$HOME/.cursor/bin:$PATH"
fi
fi
cd ${{ github.workspace }}
python3 tools/agent/doc_updater.py ${{ inputs.component }} --model ${{ inputs.llm_model }}

View file

@ -11,6 +11,27 @@ on:
- web_v2
- backend
- common
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 * * *'
@ -70,7 +91,7 @@ jobs:
- name: Run Planning Agent
env:
CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}
CURSOR_MODEL: ${{ secrets.CURSOR_MODEL }}
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 }}

View file

@ -5,16 +5,46 @@ You are an autonomous AI software engineer working on the mnemo_cards project. Y
## Project Context
This is a language learning application consisting of multiple components:
- **mnemo_cards_web_v2**: Flutter web frontend (main user interface)
- **mnemo_cards_web_v2**: Flutter web frontend (main user interface). **WEB ONLY**.
- **mnemo_cards_backend**: Dart backend server (API, data storage)
- **mnemo_cards_common**: Shared code between frontend and backend
## Autonomous Workflow Rules
### 1. Operating Mode
- **Act as an IMPLEMENTER**: Execute changes directly. Do not ask for confirmation unless a critical "Denylisted" action is required.
- **State Files**: You must maintain and read from these files at the repo root:
- `project_config.md`: Goals, constraints, stack specifics. **Read First**.
- `workflow_state.md`: Volatile loop state (Plan, Next Actions, Progress). **Update Constantly**.
- `PROGRESS.md` & `TODO.md`: High-level project tracking. **Update on Completion**.
- **Context Budget**: Summarize code context. Do not paste whole files.
### 2. The Autonomous Loop
1. **Read**: Load only necessary snippets.
2. **Plan**: Update `workflow_state.md` (Plan & Next Actions).
3. **Act**:
- Apply targeted diffs.
- Run fast checks (lint, test).
4. **Verify**:
- Parse outputs.
- Update `workflow_state.md` (Progress Log).
- If failure: Analyze -> Fix -> Loop.
- **Auto-Fix**: If `mnemo_cards_web` errors occur, run `mnemo_cards_web/complete_auto_debug.sh`, wait, read report, fix, repeat.
5. **Trim**: Prune chat context. Rely on state files.
6. **Repeat** until acceptance criteria met.
### 3. Web Specifics (mnemo_cards_web_v2)
- **WEB ONLY**: This app runs ONLY on web. No mobile, no macOS.
- **Tests**: Run tests ONLY for web platform.
- **Access**: Check `.access` for credentials if needed.
## Your Responsibilities
1. **Read and understand the task** - Analyze the requirements and acceptance criteria
2. **Implement the solution** - Write clean, maintainable code following project conventions
3. **Write comprehensive tests** - Ensure all new code has unit tests
4. **Verify your work** - Make sure tests pass and linting is clean
1. **Read and understand the task** - Analyze requirements from `task.md` or user input.
2. **Manage State** - Create/Update `workflow_state.md`, `PROGRESS.md`, `TODO.md`.
3. **Implement the solution** - Write clean code, following `project_config.md`.
4. **Write comprehensive tests** - Unit tests are mandatory.
5. **Verify your work** - Tests, Lints, Auto-Debug.
## Code Standards
@ -244,12 +274,11 @@ try {
## Git Workflow
Your commits will be automatically created. Make sure your changes are:
- **Atomic**: One logical change per commit
- **Complete**: All files needed for the feature
- **Tested**: All tests pass
- **Clean**: Linter passes
- **Size-limited**: Maximum 500 changed lines per commit
- If changes exceed this limit, split the work into multiple commits/tasks
- **Atomic**: One logical change per commit.
- **Complete**: All files needed for the feature.
- **Tested**: All tests pass.
- **Clean**: Linter passes.
- **Targeted**: Prefer surgical refactors over massive rewrites.
## Reference Materials

View file

@ -60,6 +60,9 @@ Each task should have:
**CRITICAL: Tasks must be small and focused. Large tasks MUST be broken down into smaller subtasks.**
> [!IMPORTANT]
> **Agent Timeout**: The agent performing the task will be automatically interrupted if the task takes longer than 20 minutes.
- **Small tasks** (1-3 hours): Single feature or bug fix - **PREFERRED SIZE**
- **Medium tasks** (4-5 hours): Feature with multiple files - **ACCEPTABLE, but prefer smaller**
- **Large tasks** (3+ hours): **MUST be broken down** into smaller subtasks before adding to task list

View file

@ -5,9 +5,13 @@ Executes a single task per run: read task -> execute -> test -> commit
import sys
import subprocess
import time
import json
import re
import urllib.request
import urllib.error
from pathlib import Path
from datetime import datetime, timezone
from typing import Optional, List
from typing import Optional, List, Tuple
from config import AgentConfig, get_test_command, get_lint_command
from task_manager import TaskManager, GlobalLock, Task
@ -293,15 +297,16 @@ class AgentOrchestrator:
print("⚠️ No changes to commit")
# Push changes (with pull before push to avoid conflicts)
# If push fails, _git_push() will automatically create a PR
print("\n📤 Pushing changes...")
if not self._git_push():
print("⚠️ Push failed, pulling latest changes and retrying...")
# Pull latest changes before retrying push
if self._git_pull():
print("✅ Pulled latest changes, retrying push...")
self._git_push()
else:
print("❌ Failed to pull latest changes")
push_success = self._git_push()
if not push_success:
print("⚠️ Push failed, attempting to create PR...")
# _git_push() already handles PR creation, but if it still failed,
# we'll continue anyway as the changes are committed locally
print("⚠️ Changes are committed locally but not pushed")
print(" You may need to manually push or create a PR")
return True
@ -488,8 +493,363 @@ Changes:
# No changes or error
return False
def _get_git_repo_info(self) -> Tuple[Optional[str], Optional[str]]:
"""Get repository owner and name from git remote."""
result = subprocess.run(
["git", "remote", "get-url", "origin"],
cwd=self.config.project_root,
capture_output=True,
text=True
)
if result.returncode != 0:
return None, None
remote_url = result.stdout.strip()
# Parse different URL formats:
# https://forgejo.example.com/owner/repo.git
# https://forgejo.example.com/owner/repo
# git@forgejo.example.com:owner/repo.git
# https://github.com/owner/repo.git
patterns = [
r'https?://[^/]+/([^/]+)/([^/]+?)(?:\.git)?/?$',
r'git@[^:]+:([^/]+)/([^/]+?)(?:\.git)?$',
]
for pattern in patterns:
match = re.search(pattern, remote_url)
if match:
owner = match.group(1)
repo = match.group(2)
return owner, repo
return None, None
def _get_forgejo_api_url(self) -> Optional[str]:
"""Get Forgejo/GitHub API URL from git remote."""
if self.config.forgejo_api_url:
return self.config.forgejo_api_url
result = subprocess.run(
["git", "remote", "get-url", "origin"],
cwd=self.config.project_root,
capture_output=True,
text=True
)
if result.returncode != 0:
return None
remote_url = result.stdout.strip()
# Extract base URL
# https://forgejo.example.com/owner/repo.git -> https://forgejo.example.com
# https://github.com/owner/repo.git -> https://api.github.com
# git@forgejo.example.com:owner/repo.git -> https://forgejo.example.com
if 'github.com' in remote_url:
return 'https://api.github.com'
# For Forgejo, extract the base URL
match = re.search(r'https?://([^/]+)', remote_url)
if match:
base_url = f"https://{match.group(1)}"
return f"{base_url}/api/v1"
match = re.search(r'git@([^:]+)', remote_url)
if match:
base_url = f"https://{match.group(1)}"
return f"{base_url}/api/v1"
return None
def _check_existing_pr(self, branch_name: str) -> Optional[str]:
"""Check if PR already exists for this branch. Returns PR URL if found."""
if not self.config.forgejo_token:
return None
owner, repo = self._get_git_repo_info()
if not owner or not repo:
return None
api_url = self._get_forgejo_api_url()
if not api_url:
return None
# List open PRs for this branch
pr_url = f"{api_url}/repos/{owner}/{repo}/pulls?head={owner}:{branch_name}&state=open"
try:
req = urllib.request.Request(
pr_url,
headers={
"Authorization": f"token {self.config.forgejo_token}",
"Accept": "application/json"
}
)
with urllib.request.urlopen(req) as response:
if response.status == 200:
prs = json.loads(response.read().decode('utf-8'))
if prs and len(prs) > 0:
return prs[0].get('html_url')
except Exception:
# If check fails, continue anyway
pass
return None
def _create_pull_request(self, task: Task, branch_name: str) -> bool:
"""Create a pull request via Forgejo/GitHub API."""
if not self.config.forgejo_token:
print("⚠️ FORGEJO_TOKEN not set, cannot create PR")
return False
owner, repo = self._get_git_repo_info()
if not owner or not repo:
print("⚠️ Could not determine repository owner/name from git remote")
return False
api_url = self._get_forgejo_api_url()
if not api_url:
print("⚠️ Could not determine API URL from git remote")
return False
# Check if PR already exists
existing_pr = self._check_existing_pr(branch_name)
if existing_pr:
print(f"✅ Pull Request already exists: {existing_pr}")
return True
# Get current branch name (should match branch_name, but verify)
branch_result = subprocess.run(
["git", "rev-parse", "--abbrev-ref", "HEAD"],
cwd=self.config.project_root,
capture_output=True,
text=True
)
if branch_result.returncode != 0:
print("⚠️ Could not determine current branch")
return False
current_branch = branch_result.stdout.strip()
# Use the provided branch_name, not current_branch (they should match)
if current_branch != branch_name:
print(f"⚠️ Branch mismatch: current={current_branch}, expected={branch_name}")
# Use current_branch for PR creation
branch_name = current_branch
# PR title and body
pr_title = f"feat({self.config.component}): {task.title}"
pr_body = f"""## Task: {task.id}
**Priority:** {task.priority}
### Description
{task.description}
### Changes
This PR contains changes for task {task.id} completed by AI Agent.
### Acceptance Criteria
"""
for i, criterion in enumerate(task.acceptance_criteria, 1):
pr_body += f"{i}. {criterion}\n"
pr_body += f"""
---
*Created automatically by AI Agent*
*Component: {self.config.component}*
"""
# Create PR via API
pr_url = f"{api_url}/repos/{owner}/{repo}/pulls"
data = {
"title": pr_title,
"body": pr_body,
"head": current_branch,
"base": "master"
}
try:
req = urllib.request.Request(
pr_url,
data=json.dumps(data).encode('utf-8'),
headers={
"Authorization": f"token {self.config.forgejo_token}",
"Content-Type": "application/json",
"Accept": "application/json"
}
)
with urllib.request.urlopen(req) as response:
if response.status in [200, 201]:
result = json.loads(response.read().decode('utf-8'))
pr_number = result.get('number', 'unknown')
pr_html_url = result.get('html_url', pr_url)
print(f"✅ Created Pull Request #{pr_number}")
print(f" URL: {pr_html_url}")
return True
else:
error_body = response.read().decode('utf-8')
print(f"❌ Failed to create PR: HTTP {response.status}")
print(f" Response: {error_body}")
return False
except urllib.error.HTTPError as e:
error_body = e.read().decode('utf-8')
print(f"❌ Failed to create PR: HTTP {e.code}")
print(f" Response: {error_body}")
return False
except Exception as e:
print(f"❌ Failed to create PR: {e}")
return False
def _create_branch_and_pr(self, task: Task) -> bool:
"""Create a feature branch and PR for the task."""
# Get current task if not provided
if not task:
state = self.task_manager.load_state()
if not state.current_task_id:
print("⚠️ No current task ID, cannot create PR")
return False
tasks = self.task_manager.load_tasks()
task = next((t for t in tasks if t.id == state.current_task_id), None)
if not task:
print("⚠️ Current task not found, cannot create PR")
return False
# Create branch name from task ID
branch_name = f"agent/{self.config.component}/{task.id}"
branch_name = re.sub(r'[^a-zA-Z0-9/-]', '-', branch_name)
# Get current branch name
branch_result = subprocess.run(
["git", "rev-parse", "--abbrev-ref", "HEAD"],
cwd=self.config.project_root,
capture_output=True,
text=True
)
current_branch = branch_result.stdout.strip() if branch_result.returncode == 0 else "unknown"
# If we're already on the target branch, just push it
if current_branch == branch_name:
print(f"✅ Already on branch: {branch_name}")
else:
# Check if branch already exists locally
check_branch = subprocess.run(
["git", "show-ref", "--verify", "--quiet", f"refs/heads/{branch_name}"],
cwd=self.config.project_root,
capture_output=True,
text=True
)
if check_branch.returncode == 0:
# Branch exists locally, just checkout
print(f"⚠️ Branch {branch_name} already exists locally, checking out...")
branch_result = subprocess.run(
["git", "checkout", branch_name],
cwd=self.config.project_root,
capture_output=True,
text=True
)
else:
# Create and checkout new branch
branch_result = subprocess.run(
["git", "checkout", "-b", branch_name],
cwd=self.config.project_root,
capture_output=True,
text=True
)
if branch_result.returncode != 0:
print(f"⚠️ Failed to create/checkout branch: {branch_result.stderr}")
return False
print(f"✅ Created/checked out branch: {branch_name}")
# Push branch to remote
push_branch_result = subprocess.run(
["git", "push", "-u", "origin", branch_name],
cwd=self.config.project_root,
capture_output=True,
text=True
)
if push_branch_result.returncode != 0:
print(f"⚠️ Failed to push branch: {push_branch_result.stderr}")
return False
print(f"✅ Pushed branch to remote")
# Create PR
if self._create_pull_request(task, branch_name):
print("✅ Successfully created PR")
return True
else:
print("⚠️ Failed to create PR, but branch was pushed")
return False
def _git_push(self) -> bool:
"""Push changes to remote. Pulls first if needed to avoid conflicts."""
"""Push changes to remote. Behavior depends on commit_mode:
- 'pr': Always create PR (don't try to push to master)
- 'master': Try to push to master, create PR as fallback if push fails
"""
# Get current branch name
branch_result = subprocess.run(
["git", "rev-parse", "--abbrev-ref", "HEAD"],
cwd=self.config.project_root,
capture_output=True,
text=True
)
current_branch = branch_result.stdout.strip() if branch_result.returncode == 0 else "unknown"
# If commit_mode is 'pr', always create PR (don't try to push to master)
if self.config.commit_mode == "pr":
print("\n📝 Commit mode is 'pr', creating PR instead of pushing to master...")
# Get current task
state = self.task_manager.load_state()
if not state.current_task_id:
print("⚠️ No current task ID, cannot create PR")
return False
tasks = self.task_manager.load_tasks()
current_task = next((t for t in tasks if t.id == state.current_task_id), None)
if not current_task:
print("⚠️ Current task not found, cannot create PR")
return False
# If we're on master, create branch and PR
if current_branch == "master":
return self._create_branch_and_pr(current_task)
else:
# We're already on a feature branch, just push it and create PR
push_branch_result = subprocess.run(
["git", "push", "-u", "origin", current_branch],
cwd=self.config.project_root,
capture_output=True,
text=True
)
if push_branch_result.returncode == 0:
print(f"✅ Pushed branch to remote")
if self._create_pull_request(current_task, current_branch):
return True
else:
print("⚠️ Failed to create PR, but branch was pushed")
return True # Still consider it success
else:
print(f"⚠️ Push failed: {push_branch_result.stderr}")
return False
# commit_mode is 'master' - try to push to master, fallback to PR if fails
# First, fetch to check if there are remote changes
fetch_result = subprocess.run(
["git", "fetch"],
@ -518,6 +878,8 @@ Changes:
if not self._git_pull():
print("❌ Failed to pull, push may fail")
# If we're on master, try to push directly
if current_branch == "master":
# Now try to push
result = subprocess.run(
["git", "push"],
@ -547,12 +909,51 @@ Changes:
return True
else:
print(f"❌ Push failed after pull: {retry_result.stderr}")
return False
# Fall through to create PR (fallback)
else:
print("❌ Failed to pull before retry")
# Fall through to create PR (fallback)
# Fallback: create branch and PR
print("\n📝 Push to master failed, creating branch and PR as fallback...")
# Get current task
state = self.task_manager.load_state()
if not state.current_task_id:
print("⚠️ No current task ID, cannot create PR")
return False
tasks = self.task_manager.load_tasks()
current_task = next((t for t in tasks if t.id == state.current_task_id), None)
if not current_task:
print("⚠️ Current task not found, cannot create PR")
return False
return self._create_branch_and_pr(current_task)
else:
print(f"⚠️ Push failed: {result.stderr}")
# We're already on a feature branch, just push it
print(f"\n📝 Pushing to feature branch: {current_branch}")
push_branch_result = subprocess.run(
["git", "push", "-u", "origin", current_branch],
cwd=self.config.project_root,
capture_output=True,
text=True
)
if push_branch_result.returncode == 0:
print(f"✅ Pushed branch to remote")
# Try to create PR if we have task info
state = self.task_manager.load_state()
if state.current_task_id:
tasks = self.task_manager.load_tasks()
current_task = next((t for t in tasks if t.id == state.current_task_id), None)
if current_task:
self._create_pull_request(current_task, current_branch)
return True
else:
print(f"⚠️ Push failed: {push_branch_result.stderr}")
return False
def _create_issue_for_failed_task(self, task: Task) -> None:

View file

@ -24,6 +24,14 @@ class AgentConfig:
cursor_api_key: Optional[str] = None
cursor_model: str = "claude-3-5-sonnet-20241022"
# Forgejo/GitHub API settings
forgejo_token: Optional[str] = None
forgejo_api_url: Optional[str] = None # Will be auto-detected from git remote
forgejo_repo: Optional[str] = None # Will be auto-detected from git remote
# Commit mode: 'master' (direct commit with PR fallback) or 'pr' (always create PR)
commit_mode: str = "master"
# State file paths
@property
def agent_dir(self) -> Path:
@ -70,6 +78,9 @@ class AgentConfig:
timeout_hours=int(os.getenv("TIMEOUT_HOURS", "6")),
cursor_api_key=os.getenv("CURSOR_API_KEY"),
cursor_model=os.getenv("CURSOR_MODEL", "claude-3-5-sonnet-20241022"),
forgejo_token=os.getenv("FORGEJO_TOKEN"),
forgejo_api_url=os.getenv("FORGEJO_API_URL") or os.getenv("GITHUB_API_URL"),
commit_mode=os.getenv("COMMIT_MODE", "master"),
)
def validate(self) -> None:

110
tools/agent/doc_updater.py Normal file
View file

@ -0,0 +1,110 @@
"""
Script to update documentation for a component using AI agent.
"""
import sys
import argparse
import subprocess
from pathlib import Path
from config import AgentConfig
from cursor_cli_wrapper import CursorCLI, CursorResultStatus
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(description="Update documentation for a component")
parser.add_argument("component", choices=["web_v2", "backend", "common"], help="Component to document")
parser.add_argument("--model", default="claude-3-5-sonnet-20241022", help="LLM model to use")
args = parser.parse_args()
# Load configuration
try:
config = AgentConfig.from_env(args.component)
# Override model if provided in args (though config loads from env, we want to support CLI override if needed,
# but here we primarily rely on env vars set by workflow, so we'll stick to config or args)
# Actually, AgentConfig.from_env reads CURSOR_MODEL.
# If we pass it via args, we should update it.
if args.model:
config.cursor_model = args.model
except ValueError as e:
print(f"❌ Configuration error: {e}")
sys.exit(1)
print(f"🚀 Starting Documentation Update for: {args.component}")
print(f"🤖 Model: {config.cursor_model}")
# Initialize Cursor CLI
cursor_cli = CursorCLI(
project_root=config.project_root,
api_key=config.cursor_api_key,
model=config.cursor_model,
verbose=True
)
# Define the task
task_description = f"""
You are an expert technical writer and software engineer.
Your task is to update the documentation for the '{args.component}' component.
1. Analyze the current codebase in '{config.component_root}'.
2. Check existing documentation in 'ai_docs/{args.component}' and 'README.md' files.
3. Identify missing or outdated documentation.
4. Update or create documentation files to reflect the current state of the code.
- Focus on architecture, API endpoints, setup instructions, and key features.
- Ensure 'README.md' in the component root is up to date.
- If there are significant changes, update 'ai_docs/{args.component}/architecture.md' or similar.
Do NOT modify any code files. ONLY modify markdown documentation files.
"""
print("\n📝 Task Description:")
print(task_description)
print(f"\n{'='*80}\n")
# Run agent
result = cursor_cli.run_agent(
task_description=task_description,
force=True,
max_iterations=5
)
if result.status != CursorResultStatus.SUCCESS:
print(f"\n❌ Documentation update failed: {result.error}")
sys.exit(1)
print(f"\n✅ Agent completed successfully")
print(f" - Files created: {len(result.files_created)}")
print(f" - Files modified: {len(result.files_modified)}")
if not result.files_created and not result.files_modified:
print("\n⚠️ No changes made to documentation.")
sys.exit(0)
# Commit changes
print("\n💾 Committing changes...")
# Git config
subprocess.run(["git", "config", "--global", "user.name", "AI Agent"], check=False)
subprocess.run(["git", "config", "--global", "user.email", "ai-agent@mnemo-cards.com"], check=False)
# Add changes
subprocess.run(["git", "add", "."], cwd=config.project_root, check=True)
# Commit
commit_msg = f"docs({args.component}): Update documentation via AI Agent\n\nModel: {config.cursor_model}"
subprocess.run(["git", "commit", "-m", commit_msg], cwd=config.project_root, check=False)
# Push
print("\n📤 Pushing changes...")
# Pull first
subprocess.run(["git", "pull", "--rebase"], cwd=config.project_root, check=False)
push_result = subprocess.run(["git", "push"], cwd=config.project_root, check=False)
if push_result.returncode == 0:
print("✅ Changes pushed successfully")
else:
print("❌ Failed to push changes")
sys.exit(1)
if __name__ == "__main__":
main()