- Add Celery app configuration with Redis broker/backend - Add task modules: agent, workflow, cost, git, sync - Add task stubs for: - Agent execution (spawn, heartbeat, terminate) - Workflow orchestration (start sprint, checkpoint, code review) - Cost tracking (record usage, calculate, generate report) - Git operations (clone, commit, push, sync) - External sync (import issues, export updates) - Add task tests directory structure - Configure for production-ready Celery setup Implements #18 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
199 lines
5.5 KiB
Python
199 lines
5.5 KiB
Python
# app/tasks/sync.py
|
|
"""
|
|
Issue synchronization tasks for Syndarix.
|
|
|
|
These tasks handle bidirectional issue synchronization:
|
|
- Incremental sync (polling for recent changes)
|
|
- Full reconciliation (daily comprehensive sync)
|
|
- Webhook event processing
|
|
- Pushing local changes to external trackers
|
|
|
|
Tasks are routed to the 'sync' queue for dedicated processing.
|
|
Per ADR-011, sync follows a master/replica model with configurable direction.
|
|
"""
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from app.celery_app import celery_app
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@celery_app.task(bind=True, name="app.tasks.sync.sync_issues_incremental")
|
|
def sync_issues_incremental(self) -> dict[str, Any]:
|
|
"""
|
|
Perform incremental issue synchronization across all projects.
|
|
|
|
This periodic task (runs every 5 minutes):
|
|
1. Query each project's external tracker for recent changes
|
|
2. Compare with local issue cache
|
|
3. Apply updates to local database
|
|
4. Handle conflicts based on sync direction config
|
|
|
|
Returns:
|
|
dict with status and type
|
|
"""
|
|
logger.info("Starting incremental issue sync across all projects")
|
|
|
|
# TODO: Implement incremental sync
|
|
# This will involve:
|
|
# 1. Loading all active projects with sync enabled
|
|
# 2. For each project, querying external tracker since last_sync_at
|
|
# 3. Upserting issues into local database
|
|
# 4. Updating last_sync_at timestamp
|
|
|
|
return {
|
|
"status": "pending",
|
|
"type": "incremental",
|
|
}
|
|
|
|
|
|
@celery_app.task(bind=True, name="app.tasks.sync.sync_issues_full")
|
|
def sync_issues_full(self) -> dict[str, Any]:
|
|
"""
|
|
Perform full issue reconciliation across all projects.
|
|
|
|
This periodic task (runs daily):
|
|
1. Fetch all issues from external trackers
|
|
2. Compare with local database
|
|
3. Handle orphaned issues
|
|
4. Resolve any drift between systems
|
|
|
|
Returns:
|
|
dict with status and type
|
|
"""
|
|
logger.info("Starting full issue reconciliation across all projects")
|
|
|
|
# TODO: Implement full sync
|
|
# This will involve:
|
|
# 1. Loading all active projects
|
|
# 2. Fetching complete issue lists from external trackers
|
|
# 3. Comparing with local database
|
|
# 4. Handling deletes and orphans
|
|
# 5. Resolving conflicts based on sync config
|
|
|
|
return {
|
|
"status": "pending",
|
|
"type": "full",
|
|
}
|
|
|
|
|
|
@celery_app.task(bind=True, name="app.tasks.sync.process_webhook_event")
|
|
def process_webhook_event(
|
|
self,
|
|
provider: str,
|
|
event_type: str,
|
|
payload: dict[str, Any],
|
|
) -> dict[str, Any]:
|
|
"""
|
|
Process a webhook event from an external Git provider.
|
|
|
|
This task handles real-time updates from:
|
|
- Gitea: issue.created, issue.updated, pull_request.*, etc.
|
|
- GitHub: issues, pull_request, push, etc.
|
|
- GitLab: issue events, merge request events, etc.
|
|
|
|
Args:
|
|
provider: Git provider name (gitea, github, gitlab)
|
|
event_type: Event type from provider
|
|
payload: Raw webhook payload
|
|
|
|
Returns:
|
|
dict with status, provider, and event_type
|
|
"""
|
|
logger.info(f"Processing webhook event from {provider}: {event_type}")
|
|
|
|
# TODO: Implement webhook processing
|
|
# This will involve:
|
|
# 1. Validating webhook signature
|
|
# 2. Parsing provider-specific payload
|
|
# 3. Mapping to internal event format
|
|
# 4. Updating local database
|
|
# 5. Triggering any dependent workflows
|
|
|
|
return {
|
|
"status": "pending",
|
|
"provider": provider,
|
|
"event_type": event_type,
|
|
}
|
|
|
|
|
|
@celery_app.task(bind=True, name="app.tasks.sync.sync_project_issues")
|
|
def sync_project_issues(
|
|
self,
|
|
project_id: str,
|
|
full: bool = False,
|
|
) -> dict[str, Any]:
|
|
"""
|
|
Synchronize issues for a specific project.
|
|
|
|
This task can be triggered manually or by webhooks:
|
|
1. Connect to project's external tracker
|
|
2. Fetch issues (incremental or full)
|
|
3. Update local database
|
|
|
|
Args:
|
|
project_id: UUID of the project
|
|
full: Whether to do full sync or incremental
|
|
|
|
Returns:
|
|
dict with status and project_id
|
|
"""
|
|
logger.info(
|
|
f"Syncing issues for project {project_id} (full={full})"
|
|
)
|
|
|
|
# TODO: Implement project-specific sync
|
|
# This will involve:
|
|
# 1. Loading project configuration
|
|
# 2. Connecting to external tracker
|
|
# 3. Fetching issues based on full flag
|
|
# 4. Upserting to database
|
|
|
|
return {
|
|
"status": "pending",
|
|
"project_id": project_id,
|
|
}
|
|
|
|
|
|
@celery_app.task(bind=True, name="app.tasks.sync.push_issue_to_external")
|
|
def push_issue_to_external(
|
|
self,
|
|
project_id: str,
|
|
issue_id: str,
|
|
operation: str,
|
|
) -> dict[str, Any]:
|
|
"""
|
|
Push a local issue change to the external tracker.
|
|
|
|
This task handles outbound sync when Syndarix is the master:
|
|
- create: Create new issue in external tracker
|
|
- update: Update existing issue
|
|
- close: Close issue in external tracker
|
|
|
|
Args:
|
|
project_id: UUID of the project
|
|
issue_id: UUID of the local issue
|
|
operation: Operation type (create, update, close)
|
|
|
|
Returns:
|
|
dict with status, issue_id, and operation
|
|
"""
|
|
logger.info(
|
|
f"Pushing {operation} for issue {issue_id} in project {project_id}"
|
|
)
|
|
|
|
# TODO: Implement outbound sync
|
|
# This will involve:
|
|
# 1. Loading issue and project config
|
|
# 2. Mapping to external tracker format
|
|
# 3. Calling provider API
|
|
# 4. Updating external_id mapping
|
|
|
|
return {
|
|
"status": "pending",
|
|
"issue_id": issue_id,
|
|
"operation": operation,
|
|
}
|