PMFlow/backend/app/services/task_brief/mixin_generate_status.py

120 lines
4.4 KiB
Python

from __future__ import annotations
from .shared import *
class TaskBriefGenerateStatusMixin:
@classmethod
async def generate_brief(
cls,
db: AsyncSession,
*,
tenant_id: int,
operator_user_id: int,
title: str,
template_id: Optional[str],
changed_files: list[str],
scope_summary: Optional[str],
) -> dict[str, Any]:
row, bundle = await cls._get_or_init_bundle(db, tenant_id=tenant_id)
now_text = now_cn_naive().isoformat()
templates = bundle.get("templates") if isinstance(bundle.get("templates"), list) else []
template_obj = None
if template_id:
for item in templates:
if isinstance(item, dict) and str(item.get("id") or "") == str(template_id):
template_obj = item
break
if template_obj is None:
template_obj = next((item for item in templates if isinstance(item, dict)), None)
brief_id = f"brief_{uuid4().hex[:12]}"
run_id = f"run_{uuid4().hex[:12]}"
file_lines = [str(path).strip() for path in changed_files if str(path).strip()]
summary = str(scope_summary or "").strip() or f"共 {len(file_lines)} 个文件发生变更"
risks = (
[
"跨文件改动可能引发契约/类型不一致,请补充联动核对。",
"合入前需完成 build/lint/test 并记录结果。",
]
if file_lines
else ["未提供变更文件列表,风险评估信息不足,请人工补充。"]
)
content = cls._render_template_content(
template_content=str((template_obj or {}).get("content") or "").strip(),
generated_at=now_text,
base_ref=cls._resolve_git_ref("HEAD~1"),
head_ref=cls._resolve_git_ref("HEAD"),
changed_files=file_lines,
scope_summary=summary,
risks=risks,
)
run = {
"id": run_id,
"brief_id": brief_id,
"title": str(title or "").strip()[:160] or "增量任务包",
"template_id": str((template_obj or {}).get("id") or "") or None,
"template_name": str((template_obj or {}).get("name") or "") or None,
"generated_at": now_text,
"generated_by": int(operator_user_id),
"status": "completed",
"changed_files": file_lines,
"scope_summary": summary,
}
brief = {
"id": brief_id,
"title": run["title"],
"status": "ready",
"created_at": now_text,
"updated_at": now_text,
"latest_run_id": run_id,
"template_id": run["template_id"],
"template_name": run["template_name"],
"summary": summary,
"content": content,
"changed_files": file_lines,
}
runs = bundle.get("runs") if isinstance(bundle.get("runs"), list) else []
briefs = bundle.get("briefs") if isinstance(bundle.get("briefs"), list) else []
runs.append(run)
briefs = [item for item in briefs if isinstance(item, dict) and str(item.get("id") or "") != brief_id]
briefs.append(brief)
bundle["runs"] = runs
bundle["briefs"] = briefs
row.config = bundle
await db.flush()
return {"run": run, "brief": brief}
@classmethod
async def get_contract_sync_status(cls, db: AsyncSession, *, tenant_id: int) -> dict[str, Any]:
_ = db
_ = tenant_id
root = Path(__file__).resolve().parents[3]
contract_files = [
root / "docs/architecture/MASTER_CONTRACT.md",
root / "docs/architecture/API_CONTRACTS.md",
root / "docs/architecture/DATAFLOW.md",
]
statuses = []
for file_path in contract_files:
exists = file_path.exists()
statuses.append(
{
"file": str(file_path.relative_to(root)),
"exists": exists,
"updated_at": (
datetime.fromtimestamp(file_path.stat().st_mtime).isoformat()
if exists
else None
),
}
)
all_synced = all(bool(item.get("exists")) for item in statuses)
return {
"all_synced": all_synced,
"files": statuses,
}