Files
QaAutomationHub/scripts/fleet_manifest.py
T
xst b2a035c4f9 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
2026-07-09 14:29:11 +08:00

181 lines
6.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Agentic QE Fleet — Manifest 管理模块
负责跨战区 manifest JSON 的读写、合并、验证和迁移。
每个战区产出一个 manifest_<zone>.jsonfleet_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)