8.5 KiB
8.5 KiB
JIRA AI Fixer - Developer Guide
Overview
JIRA AI Fixer is a universal AI-powered issue analysis engine that integrates with multiple issue tracking systems to automatically analyze support cases and suggest code fixes.
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ JIRA AI Fixer │
├─────────────────────────────────────────────────────────────────┤
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │TicketHub │ │ JIRA │ │ServiceNow│ │ Zendesk │ ... │
│ │ Webhook │ │ Webhook │ │ Webhook │ │ Webhook │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ └─────────────┴──────┬──────┴─────────────┘ │
│ │ │
│ ┌───────▼───────┐ │
│ │ Normalizer │ (Adapter Pattern) │
│ └───────┬───────┘ │
│ │ │
│ ┌───────▼───────┐ │
│ │ Analyzer │ (LLM + Code Analysis) │
│ └───────┬───────┘ │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ │ │ │ │
│ ┌──────▼─────┐ ┌─────▼─────┐ ┌────▼────┐ │
│ │ Database │ │ PR Gen │ │Callback │ │
│ │ PostgreSQL │ │ Gitea │ │ to Src │ │
│ └────────────┘ └───────────┘ └─────────┘ │
└─────────────────────────────────────────────────────────────────┘
Tech Stack
- Language: Python 3.11
- Framework: FastAPI (async)
- Database: PostgreSQL 15
- LLM: OpenRouter API (Llama 3.3 70B free tier)
- Code Hosting: Gitea (self-hosted)
Project Structure
jira-ai-fixer/
├── api/
│ └── main_v3.py # Main application (monolith)
├── docs/
│ ├── DEVELOPER_GUIDE.md
│ ├── USER_GUIDE.md
│ └── ARCHITECTURE.md
└── README.md
Key Components
1. Webhook Adapters
Each supported system has a dedicated adapter that normalizes payloads:
def normalize_jira(payload: dict) -> Optional[NormalizedIssue]:
"""Normalize JIRA webhook payload"""
issue = payload.get("issue", {})
fields = issue.get("fields", {})
return NormalizedIssue(
external_id=str(issue.get("id")),
external_key=issue.get("key"),
source="jira",
title=fields.get("summary"),
description=fields.get("description"),
callback_url=f"{base_url}/rest/api/2/issue/{issue.get('key')}/comment"
)
2. Analysis Pipeline
async def analyze_issue(issue_id: int, issue: NormalizedIssue):
# 1. Fetch source code from repositories
cobol_files = await fetch_cobol_files()
# 2. Build LLM prompt
prompt = build_analysis_prompt(issue, cobol_files)
# 3. Call LLM API
analysis = await call_llm(prompt)
# 4. Parse response
result = parse_analysis(analysis)
# 5. Create fix branch and PR
pr_info = await create_fix_branch_and_pr(issue, result)
# 6. Post back to source system
await post_analysis_to_source(issue, result, pr_info)
3. Callback System
Results are posted back to the source system in their native format:
| System | Method | Format |
|---|---|---|
| TicketHub | POST /tickets/{id}/comments | {"author": "...", "content": "..."} |
| JIRA | POST /rest/api/2/issue/{key}/comment | {"body": "..."} |
| ServiceNow | PATCH /api/now/table/incident/{sys_id} | {"work_notes": "..."} |
| Zendesk | PUT /api/v2/tickets/{id}.json | {"ticket": {"comment": {...}}} |
| Azure DevOps | POST /workitems/{id}/comments | {"text": "..."} |
| GitHub | POST /repos/.../issues/{n}/comments | {"body": "..."} |
Adding a New Integration
- Create normalizer function:
def normalize_newsystem(payload: dict) -> Optional[NormalizedIssue]:
# Extract fields from payload
return NormalizedIssue(
external_id=...,
external_key=...,
source="newsystem",
title=...,
description=...,
callback_url=...
)
- Add webhook endpoint:
@app.post("/api/webhook/newsystem")
async def webhook_newsystem(payload: dict, background_tasks: BackgroundTasks):
issue = normalize_newsystem(payload)
if not issue:
return WebhookResponse(status="ignored", message="Event not handled")
issue_id = await save_and_queue_issue(issue, background_tasks)
return WebhookResponse(status="accepted", issue_id=issue_id)
- Add callback format in
post_analysis_to_source():
elif issue.source == "newsystem":
await client.post(issue.callback_url, json={...})
Environment Variables
| Variable | Description | Default |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string | postgresql://jira:jira_secret_2026@postgres:5432/jira_fixer |
OPENROUTER_API_KEY |
OpenRouter API key for LLM | (empty = mock mode) |
GITEA_URL |
Gitea instance URL | https://gitea.startdata.com.br |
GITEA_TOKEN |
Gitea API token | (empty) |
COBOL_REPO |
Default repository to analyze | startdata/cobol-sample-app |
API Endpoints
Webhooks
| Endpoint | Description |
|---|---|
POST /api/webhook/tickethub |
TicketHub webhooks |
POST /api/webhook/jira |
JIRA webhooks |
POST /api/webhook/servicenow |
ServiceNow webhooks |
POST /api/webhook/zendesk |
Zendesk webhooks |
POST /api/webhook/azure-devops |
Azure DevOps webhooks |
POST /api/webhook/github |
GitHub Issues webhooks |
POST /api/webhook/gitlab |
GitLab Issues webhooks |
POST /api/webhook/generic |
Generic webhook format |
Management
| Endpoint | Description |
|---|---|
GET /api/health |
Health check |
GET /api/issues |
List issues |
GET /api/issues/{id} |
Get issue details |
GET /api/integrations |
List integrations |
GET /api/stats |
Dashboard statistics |
Running Locally
# Install dependencies
pip install fastapi uvicorn httpx asyncpg pydantic
# Run with PostgreSQL
export DATABASE_URL="postgresql://user:pass@localhost:5432/jira_fixer"
uvicorn main:app --reload --port 8000
Testing Webhooks
# Test TicketHub webhook
curl -X POST http://localhost:8000/api/webhook/tickethub \
-H "Content-Type: application/json" \
-d '{
"event": "ticket.created",
"timestamp": "2026-02-18T18:00:00Z",
"data": {
"id": 1,
"key": "SUPP-1",
"title": "Test issue",
"description": "Test description"
}
}'
# Test generic webhook
curl -X POST http://localhost:8000/api/webhook/generic \
-H "Content-Type: application/json" \
-d '{
"id": "123",
"key": "CUSTOM-1",
"title": "Custom issue",
"description": "From custom system",
"source": "my-system",
"callback_url": "https://my-system.com/api/issues/123/comments"
}'
License
MIT License - StartData 2026