Taiga integration
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user