Taiga integration

This commit is contained in:
2026-06-09 12:47:13 +03:00
parent c8599b3d13
commit 1f83dcb574
30 changed files with 1543 additions and 115 deletions
+104
View File
@@ -0,0 +1,104 @@
import json
import re
from typing import Any
from app.llm.client import LLMClient
def strip_markdown_json(text: str) -> str:
text = text.strip()
fenced = re.search(r"```(?:json)?\s*(.*?)\s*```", text, re.DOTALL | re.IGNORECASE)
if fenced:
return fenced.group(1).strip()
return text
def slugify_branch(title: str, max_len: int = 40) -> str:
text = title.lower()
text = re.sub(r"[^a-z0-9а-яё]+", "-", text, flags=re.IGNORECASE)
text = re.sub(r"-+", "-", text).strip("-")
return text[:max_len] or "task"
async def structure_work_item(
raw_text: str,
projects: list[dict[str, Any]],
) -> dict[str, Any]:
project_lines = "\n".join(
f"- {p['slug']}: {p['name']} (id={p['taiga_id']})" for p in projects
)
system_prompt = f"""
Ты технический ассистент. Преобразуй сырое описание фичи или бага в строгий JSON.
Отвечай только JSON, без markdown.
Доступные проекты Taiga:
{project_lines}
Схема:
{{
"project_slug": "slug проекта из списка",
"title": "короткое название",
"description": "понятное описание",
"issue_type": "feature|bug",
"priority": "low|normal|high",
"tags": ["tag1"],
"acceptance_criteria": ["критерий 1"],
"children": [
{{"title": "подзадача", "description": "описание", "type": "Task"}}
],
"questions": ["уточняющий вопрос если данных мало"]
}}
Правила:
- Пиши на русском.
- project_slug выбери из списка; если неясно — первый подходящий или спроси в questions.
- acceptance_criteria проверяемые.
- children — технические подзадачи.
""".strip()
llm = LLMClient()
result = await llm.complete(
[
{"role": "system", "content": system_prompt},
{"role": "user", "content": raw_text},
]
)
content = strip_markdown_json(result.get("content") or "")
return json.loads(content)
def format_story_description(task: dict[str, Any], raw_text: str) -> str:
lines = [task.get("description") or raw_text]
acceptance = task.get("acceptance_criteria") or []
if acceptance:
lines.append("")
lines.append("## Acceptance criteria")
for item in acceptance:
lines.append(f"- {item}")
questions = task.get("questions") or []
if questions:
lines.append("")
lines.append("## Вопросы")
for item in questions:
lines.append(f"- {item}")
lines.append("")
lines.append("## Исходное описание")
lines.append(raw_text)
return "\n".join(lines).strip()
def format_gitea_body(
task: dict[str, Any],
raw_text: str,
taiga_ref: int,
taiga_url: str,
branch: str,
) -> str:
body = format_story_description(task, raw_text)
body += f"\n\n---\n**Taiga:** #{taiga_ref}{taiga_url}\n"
body += f"**Ветка:** `{branch}`\n"
body += "\nЗакрытие: `Closes gitea #N, taiga #REF` в коммите"
return body