feat: Agentic QE Fleet v2.0.0 - 14-agent quality engineering platform
- 14 specialized AI agents across 5 battle zones (Prepare/Analyze/Design/Review/Monitor) - New: risk-assessor, test-strategist, data-builder, coverage-auditor, quality-gatekeeper, execution-analyst, knowledge-curator - New: fleet_runner.py orchestrator with multi-zone manifest pipeline - New: fleet_config.yml for centralized configuration - New: knowledge activation system (keyword + semantic matching) - New: semantic conflict detection with severity grading (P0-P3) - New: three-tier quality gate (PASS/PASS_WITH_FIX/BLOCKED) - New: monitor zone for test execution analysis and auto knowledge curation - Backward compatible: /case_generate alias, case_pipeline.py preserved - Comprehensive docs: USER_GUIDE.md + MAINTENANCE_GUIDE.md
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
"""
|
||||
Agentic QE Fleet — Manifest 管理模块
|
||||
|
||||
负责跨战区 manifest JSON 的读写、合并、验证和迁移。
|
||||
每个战区产出一个 manifest_<zone>.json,fleet_runner.py 消费。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
MANIFESTS_DIR = REPO_ROOT / "output" / "manifests"
|
||||
|
||||
# 战区顺序
|
||||
ZONE_ORDER = ["prepare", "analyze", "design", "review", "monitor"]
|
||||
|
||||
|
||||
def ensure_manifests_dir() -> Path:
|
||||
MANIFESTS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
return MANIFESTS_DIR
|
||||
|
||||
|
||||
def manifest_path(base_name: str, zone: str) -> Path:
|
||||
"""返回战区 manifest 文件路径。"""
|
||||
return MANIFESTS_DIR / f"{base_name}_{zone}.json"
|
||||
|
||||
|
||||
def load_manifest(base_name: str, zone: str) -> dict[str, Any]:
|
||||
"""加载指定战区的 manifest。"""
|
||||
path = manifest_path(base_name, zone)
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f"Manifest 不存在: {path}")
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def load_manifest_safe(base_name: str, zone: str) -> dict[str, Any] | None:
|
||||
"""安全加载 manifest,不存在时返回 None。"""
|
||||
path = manifest_path(base_name, zone)
|
||||
if not path.exists():
|
||||
return None
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def save_manifest(base_name: str, zone: str, data: dict[str, Any]) -> Path:
|
||||
"""保存战区 manifest。"""
|
||||
ensure_manifests_dir()
|
||||
path = manifest_path(base_name, zone)
|
||||
data.setdefault("_meta", {})
|
||||
data["_meta"]["zone"] = zone
|
||||
data["_meta"]["base_name"] = base_name
|
||||
data["_meta"]["updated_at"] = datetime.now(timezone.utc).isoformat()
|
||||
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
return path
|
||||
|
||||
|
||||
def build_merged_manifest(base_name: str, zones: list[str] | None = None) -> dict[str, Any]:
|
||||
"""合并多个战区的 manifest 为统一视图。"""
|
||||
if zones is None:
|
||||
zones = ZONE_ORDER
|
||||
|
||||
merged: dict[str, Any] = {
|
||||
"_meta": {
|
||||
"base_name": base_name,
|
||||
"merged_zones": [],
|
||||
"merged_at": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
}
|
||||
|
||||
for zone in zones:
|
||||
manifest = load_manifest_safe(base_name, zone)
|
||||
if manifest is None:
|
||||
continue
|
||||
merged["_meta"]["merged_zones"].append(zone)
|
||||
# 战区数据按 zone 命名空间隔离
|
||||
merged[zone] = manifest
|
||||
# 同时摊平顶层便捷字段(后者覆盖前者)
|
||||
for key, value in manifest.items():
|
||||
if key.startswith("_"):
|
||||
continue
|
||||
merged[key] = value
|
||||
|
||||
return merged
|
||||
|
||||
|
||||
def get_zone_status(base_name: str) -> dict[str, str]:
|
||||
"""返回各战区完成状态。"""
|
||||
status: dict[str, str] = {}
|
||||
for zone in ZONE_ORDER:
|
||||
manifest = load_manifest_safe(base_name, zone)
|
||||
if manifest is None:
|
||||
status[zone] = "pending"
|
||||
elif manifest.get("_meta", {}).get("status") == "completed":
|
||||
status[zone] = "completed"
|
||||
else:
|
||||
status[zone] = "in_progress"
|
||||
return status
|
||||
|
||||
|
||||
def mark_zone_completed(base_name: str, zone: str) -> Path:
|
||||
"""标记战区为已完成。"""
|
||||
manifest = load_manifest(base_name, zone)
|
||||
manifest.setdefault("_meta", {})["status"] = "completed"
|
||||
return save_manifest(base_name, zone, manifest)
|
||||
|
||||
|
||||
def get_confirmation_gate(base_name: str) -> dict[str, Any] | None:
|
||||
"""获取当前确认门禁状态(分析战区产出)。"""
|
||||
manifest = load_manifest_safe(base_name, "analyze")
|
||||
if manifest is None:
|
||||
return None
|
||||
return manifest.get("confirmation_gate")
|
||||
|
||||
|
||||
def get_quality_verdict(base_name: str) -> dict[str, Any] | None:
|
||||
"""获取质量裁决(评审战区产出)。"""
|
||||
manifest = load_manifest_safe(base_name, "review")
|
||||
if manifest is None:
|
||||
return None
|
||||
return manifest.get("quality_verdict")
|
||||
|
||||
|
||||
def get_latest_zone(base_name: str) -> str | None:
|
||||
"""返回最新已完成的战区名称。"""
|
||||
for zone in reversed(ZONE_ORDER):
|
||||
manifest = load_manifest_safe(base_name, zone)
|
||||
if manifest and manifest.get("_meta", {}).get("status") == "completed":
|
||||
return zone
|
||||
return None
|
||||
|
||||
|
||||
def get_next_zone(base_name: str) -> str | None:
|
||||
"""返回下一个待执行的战区名称。"""
|
||||
for zone in ZONE_ORDER:
|
||||
manifest = load_manifest_safe(base_name, zone)
|
||||
if manifest is None or manifest.get("_meta", {}).get("status") != "completed":
|
||||
return zone
|
||||
# 全部完成
|
||||
return None
|
||||
|
||||
|
||||
def migrate_legacy_manifest(base_name: str) -> dict[str, Any]:
|
||||
"""从旧的单 manifest JSON 迁移到新的多战区 manifest 结构。"""
|
||||
legacy_path = MANIFESTS_DIR / f"{base_name}.json"
|
||||
if not legacy_path.exists():
|
||||
raise FileNotFoundError(f"旧版 manifest 不存在: {legacy_path}")
|
||||
|
||||
legacy = json.loads(legacy_path.read_text(encoding="utf-8"))
|
||||
|
||||
# 从旧 manifest 推断 prepare 战区数据
|
||||
prepare_data = {
|
||||
"_meta": {"zone": "prepare", "base_name": base_name, "status": "completed",
|
||||
"migrated_from_legacy": True},
|
||||
"base_name": legacy.get("base_name", base_name),
|
||||
"normalized_requirement_file": legacy.get("normalized_requirement_file"),
|
||||
"normalized_technical_solution_files": legacy.get("normalized_technical_solution_files", []),
|
||||
"technical_solution_files": legacy.get("technical_solution_files", []),
|
||||
"project_profile_file": legacy.get("project_profile_file"),
|
||||
"effective_terminology_files": legacy.get("effective_terminology_files", []),
|
||||
"optional_terminology_files": legacy.get("optional_terminology_files", []),
|
||||
"knowledge_base_files": legacy.get("knowledge_base_files", []),
|
||||
}
|
||||
save_manifest(base_name, "prepare", prepare_data)
|
||||
|
||||
# 从旧 manifest 推断 analyze 战区数据
|
||||
analyze_data = {
|
||||
"_meta": {"zone": "analyze", "base_name": base_name, "status": "completed",
|
||||
"migrated_from_legacy": True},
|
||||
"related_requirements": legacy.get("related_requirements", []),
|
||||
"conflict_candidates_count": legacy.get("conflict_candidates_count", 0),
|
||||
"confirmation_gate": legacy.get("confirmation_gate", {}),
|
||||
"analysis_file": legacy.get("analysis_file"),
|
||||
"relation_report_file": legacy.get("relation_report_file"),
|
||||
}
|
||||
save_manifest(base_name, "analyze", analyze_data)
|
||||
|
||||
return build_merged_manifest(base_name)
|
||||
Reference in New Issue
Block a user