feat: 安徽运八需求全流水线输出同步 + 知识库/agents/资源文件更新
This commit is contained in:
@@ -475,6 +475,8 @@ def iter_requirement_documents() -> list[Path]:
|
||||
continue
|
||||
if item.stat().st_size == 0:
|
||||
continue
|
||||
if item.name.startswith("~$"): # 跳过 Office 临时锁定文件
|
||||
continue
|
||||
result.append(item)
|
||||
return result
|
||||
|
||||
|
||||
@@ -229,3 +229,71 @@ def validate_all_agents() -> dict[str, Any]:
|
||||
result["empty"].append(agent_id)
|
||||
result["valid"] = False
|
||||
return result
|
||||
|
||||
|
||||
def build_agent_invocation_context(base_name: str, agent_id: str) -> dict[str, Any]:
|
||||
"""为 AI Agent 调用构建完整的上下文注入字典。"""
|
||||
from fleet_manifest import ZONE_ORDER, load_manifest_safe
|
||||
|
||||
info = AGENT_REGISTRY.get(agent_id, {})
|
||||
zone = info.get("zone", "")
|
||||
|
||||
context: dict[str, Any] = {
|
||||
"base_name": base_name,
|
||||
"agent_id": agent_id,
|
||||
"agent_zone": zone,
|
||||
"repo_root": str(REPO_ROOT),
|
||||
"output_dir": str(REPO_ROOT / "output"),
|
||||
"manifests_dir": str(REPO_ROOT / "output" / "manifests"),
|
||||
"knowledge_base_dir": str(REPO_ROOT / "knowledge_base"),
|
||||
"fleet_config_path": str(REPO_ROOT / "fleet_config.yml"),
|
||||
"PROJECT_PROFILE": str(REPO_ROOT / "knowledge_base" / "00_project" / "project_profile.md"),
|
||||
"FLEET_CONFIG": str(REPO_ROOT / "fleet_config.yml"),
|
||||
}
|
||||
|
||||
for prev_zone in ZONE_ORDER:
|
||||
manifest = load_manifest_safe(base_name, prev_zone)
|
||||
if manifest is None:
|
||||
continue
|
||||
context[f"manifest_{prev_zone}"] = manifest
|
||||
for key, value in manifest.items():
|
||||
if key.endswith("_file") or key.endswith("_files"):
|
||||
context[key] = value
|
||||
if prev_zone == zone:
|
||||
break
|
||||
|
||||
return context
|
||||
|
||||
|
||||
def get_agent_input_files(base_name: str, agent_id: str) -> list[str]:
|
||||
"""返回 Agent 需要的所有输入文件路径列表。"""
|
||||
from fleet_manifest import ZONE_ORDER, load_manifest_safe
|
||||
|
||||
info = AGENT_REGISTRY.get(agent_id, {})
|
||||
zone = info.get("zone", "")
|
||||
input_files: list[str] = []
|
||||
|
||||
for prev_zone in ZONE_ORDER:
|
||||
if prev_zone == zone:
|
||||
break
|
||||
manifest = load_manifest_safe(base_name, prev_zone)
|
||||
if manifest is None:
|
||||
continue
|
||||
for key in manifest:
|
||||
if key.endswith("_file") or key.endswith("_files"):
|
||||
value = manifest[key]
|
||||
if isinstance(value, str):
|
||||
input_files.append(value)
|
||||
elif isinstance(value, list):
|
||||
input_files.extend(v for v in value if isinstance(v, str))
|
||||
|
||||
kb_files = [
|
||||
str(REPO_ROOT / "knowledge_base" / "00_project" / "project_profile.md"),
|
||||
str(REPO_ROOT / "knowledge_base" / "01_standards" / "test_case_template.md"),
|
||||
str(REPO_ROOT / "knowledge_base" / "01_standards" / "review_checklist.md"),
|
||||
str(REPO_ROOT / "knowledge_base" / "01_standards" / "definition_of_done.md"),
|
||||
str(REPO_ROOT / "fleet_config.yml"),
|
||||
]
|
||||
input_files.extend(kb_files)
|
||||
|
||||
return sorted(set(input_files))
|
||||
|
||||
@@ -189,3 +189,56 @@ def migrate_legacy_manifest(base_name: str) -> dict[str, Any]:
|
||||
save_manifest(base_name, "analyze", analyze_data)
|
||||
|
||||
return build_merged_manifest(base_name)
|
||||
|
||||
|
||||
def get_pending_ai_agents(base_name: str) -> list[dict[str, Any]]:
|
||||
"""扫描所有战区 manifest,返回状态为 'pending_ai' 的 Agent 列表。
|
||||
|
||||
返回按战区依赖顺序排列的待执行 AI Agent 信息。
|
||||
"""
|
||||
pending: list[dict[str, Any]] = []
|
||||
for zone in ZONE_ORDER:
|
||||
manifest = load_manifest_safe(base_name, zone)
|
||||
if manifest is None:
|
||||
continue
|
||||
agent_notes = manifest.get("agent_notes", {})
|
||||
for agent_id, note in agent_notes.items():
|
||||
if isinstance(note, dict) and note.get("status") == "pending_ai":
|
||||
pending.append({
|
||||
"agent_id": agent_id,
|
||||
"zone": zone,
|
||||
"prompt_file": note.get("prompt_file", ""),
|
||||
"output_file": note.get("output_file", ""),
|
||||
"message": note.get("message", ""),
|
||||
})
|
||||
elif isinstance(note, str) and "待 AI Agent" in note:
|
||||
pending.append({
|
||||
"agent_id": agent_id,
|
||||
"zone": zone,
|
||||
"prompt_file": f"agents/{zone}/{agent_id.replace('-', '_')}.md",
|
||||
"output_file": "",
|
||||
"message": note,
|
||||
})
|
||||
return pending
|
||||
|
||||
|
||||
def update_agent_status(base_name: str, zone: str, agent_id: str,
|
||||
status: str, message: str = "") -> bool:
|
||||
"""更新 manifest 中 Agent 的状态。
|
||||
|
||||
返回 True 表示更新成功,False 表示 manifest 不存在或 agent 不存在。
|
||||
"""
|
||||
manifest = load_manifest_safe(base_name, zone)
|
||||
if manifest is None:
|
||||
return False
|
||||
agent_notes = manifest.setdefault("agent_notes", {})
|
||||
if agent_id in agent_notes:
|
||||
if isinstance(agent_notes[agent_id], dict):
|
||||
agent_notes[agent_id]["status"] = status
|
||||
agent_notes[agent_id]["message"] = message
|
||||
else:
|
||||
agent_notes[agent_id] = {"status": status, "message": message}
|
||||
else:
|
||||
agent_notes[agent_id] = {"status": status, "message": message}
|
||||
save_manifest(base_name, zone, manifest)
|
||||
return True
|
||||
|
||||
+242
-30
@@ -179,7 +179,7 @@ def build_all_output_dirs(base_name: str) -> None:
|
||||
|
||||
# ── 编排核心 ──────────────────────────────────────────────────────────────
|
||||
|
||||
def run_zone(zone: str, base_name: str, requirement_path: Path, config: dict[str, Any]) -> dict[str, Any]:
|
||||
def run_zone(zone: str, base_name: str, requirement_path: Path, config: dict[str, Any], scaffold_only: bool = False) -> dict[str, Any]:
|
||||
"""运行一个战区,返回该战区的 manifest 数据。"""
|
||||
|
||||
zone_config = config.get("battle_zones", {}).get(zone, {})
|
||||
@@ -214,7 +214,7 @@ def run_zone(zone: str, base_name: str, requirement_path: Path, config: dict[str
|
||||
if handler is None:
|
||||
raise ValueError(f"未知战区: {zone}")
|
||||
|
||||
result = handler(base_name, requirement_path, context, config, agents)
|
||||
result = handler(base_name, requirement_path, context, config, agents, scaffold_only=scaffold_only)
|
||||
|
||||
# 标记完成
|
||||
if result:
|
||||
@@ -284,6 +284,7 @@ def _run_prepare_zone(
|
||||
context: dict[str, Any],
|
||||
config: dict[str, Any],
|
||||
agents: list[str],
|
||||
scaffold_only: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""执行 Prepare 战区: document-parser → knowledge-activator。"""
|
||||
|
||||
@@ -315,6 +316,20 @@ def _run_prepare_zone(
|
||||
requirement_body = safe_read_text(normalized_requirement_file)
|
||||
confidence = _estimate_document_confidence(requirement_body, requirement_path.suffix.lower())
|
||||
|
||||
# 附加文件检测:从需求文本中提取引用的文件路径和URL
|
||||
attached_source_files = _detect_attached_files(requirement_body)
|
||||
|
||||
# 构建多源注册表
|
||||
sources_registry = _build_sources_registry(
|
||||
requirement_path=requirement_path,
|
||||
technical_solution_files=technical_solution_files,
|
||||
attached_files=attached_source_files,
|
||||
)
|
||||
if attached_source_files:
|
||||
print(f"📎 检测到 {len(attached_source_files)} 个附加源文件")
|
||||
for af in attached_source_files:
|
||||
print(f" - {af['path']} ({af.get('exists', 'unknown')})")
|
||||
|
||||
# 2. 知识激活
|
||||
activated_knowledge = _activate_knowledge(requirement_path, config)
|
||||
|
||||
@@ -334,6 +349,8 @@ def _run_prepare_zone(
|
||||
"document_confidence": confidence,
|
||||
"activated_knowledge": activated_knowledge,
|
||||
"knowledge_gaps": knowledge_gaps,
|
||||
"attached_source_files": attached_source_files,
|
||||
"sources_registry": sources_registry,
|
||||
"agent_notes": {
|
||||
"document-parser": f"解析完成,置信度 {confidence.get('requirement', 0):.0%}",
|
||||
"knowledge-activator": f"激活 {len(activated_knowledge.get('terminology', {}).get('permanent', []))} 常驻 + "
|
||||
@@ -447,6 +464,130 @@ def _detect_knowledge_gaps(activated_knowledge: dict[str, Any], requirement_text
|
||||
return gaps
|
||||
|
||||
|
||||
def _detect_attached_files(requirement_text: str) -> list[dict[str, str]]:
|
||||
"""从需求文本中提取引用的文件路径和URL。
|
||||
|
||||
支持三种模式:
|
||||
1. Windows 绝对路径 (E:\\Downloads\\xxx.pdf)
|
||||
2. URL (https://www.showdoc.com.cn/...)
|
||||
3. 相对项目路径 (output/prototype/xxx.md)
|
||||
"""
|
||||
import re
|
||||
|
||||
attached: list[dict[str, str]] = []
|
||||
|
||||
# Pattern 1: Windows absolute paths with Chinese support
|
||||
win_path_pattern = re.compile(
|
||||
r'([A-Za-z]:[\\/](?:[^\\/:*?"<>|\r\n]+[\\/])*[^\\/:*?"<>|\r\n]+\.(?:pdf|docx?|xlsx?|html?|txt|md|json|xml|csv))',
|
||||
re.IGNORECASE,
|
||||
)
|
||||
for match in win_path_pattern.finditer(requirement_text):
|
||||
path = match.group(1)
|
||||
line_start = requirement_text.rfind('\n', 0, match.start()) + 1
|
||||
line_end = requirement_text.find('\n', match.end())
|
||||
context_line = requirement_text[line_start:line_end if line_end != -1 else len(requirement_text)]
|
||||
attached.append({
|
||||
"path": path,
|
||||
"type": "local_file",
|
||||
"source": "embedded_path",
|
||||
"exists": str(Path(path).exists()).lower() if path else "false",
|
||||
"context_line": context_line.strip()[:200],
|
||||
})
|
||||
|
||||
# Pattern 2: Documentation URLs (Showdoc, Confluence, etc.)
|
||||
url_pattern = re.compile(r'(https?://[^\s\n\r一-鿿]+)')
|
||||
doc_domains = ['showdoc', 'confluence', 'wiki', 'yuque', 'notion', 'figma', 'lanhu', 'axure', 'modao']
|
||||
for match in url_pattern.finditer(requirement_text):
|
||||
url = match.group(1).rstrip('.,;:;))')
|
||||
if any(domain in url.lower() for domain in doc_domains):
|
||||
attached.append({
|
||||
"path": url,
|
||||
"type": "url",
|
||||
"source": "embedded_url",
|
||||
"exists": "unknown",
|
||||
"context_line": "",
|
||||
})
|
||||
|
||||
# Pattern 3: Project-relative paths with context keywords
|
||||
rel_pattern = re.compile(
|
||||
r'(?:(?:原型文件|接口文档|技术方案|设计稿|原型|文档|文件|路径)[\s::]*)?'
|
||||
r'([a-zA-Z0-9_\-\.]+/[a-zA-Z0-9_\-\./]+\.(?:html?|pdf|docx?|md|json))',
|
||||
re.IGNORECASE,
|
||||
)
|
||||
for match in rel_pattern.finditer(requirement_text):
|
||||
rel_path = match.group(1)
|
||||
if '/' not in rel_path:
|
||||
continue
|
||||
abs_path = REPO_ROOT / rel_path
|
||||
attached.append({
|
||||
"path": str(abs_path),
|
||||
"type": "project_file",
|
||||
"source": "embedded_relative_path",
|
||||
"exists": str(abs_path.exists()).lower(),
|
||||
"context_line": "",
|
||||
})
|
||||
|
||||
# Deduplicate by path
|
||||
seen: set[str] = set()
|
||||
unique: list[dict[str, str]] = []
|
||||
for item in attached:
|
||||
if item["path"] not in seen:
|
||||
seen.add(item["path"])
|
||||
unique.append(item)
|
||||
return unique
|
||||
|
||||
|
||||
def _build_sources_registry(
|
||||
requirement_path: Path,
|
||||
technical_solution_files: list[Path],
|
||||
attached_files: list[dict[str, str]],
|
||||
) -> list[dict[str, str]]:
|
||||
"""构建多源注册表,跟踪所有权威数据源及其角色。"""
|
||||
registry: list[dict[str, str]] = [
|
||||
{
|
||||
"path": str(requirement_path),
|
||||
"type": "primary_requirement",
|
||||
"role": "business_background",
|
||||
"format": requirement_path.suffix.lower().lstrip("."),
|
||||
"authority": "primary",
|
||||
}
|
||||
]
|
||||
for tf in technical_solution_files:
|
||||
registry.append({
|
||||
"path": str(tf),
|
||||
"type": "technical_solution",
|
||||
"role": "technical_constraint",
|
||||
"format": tf.suffix.lower().lstrip("."),
|
||||
"authority": "technical_reference",
|
||||
})
|
||||
for af in attached_files:
|
||||
path = af.get("path", "")
|
||||
file_type = af.get("type", "")
|
||||
registry.append({
|
||||
"path": path,
|
||||
"type": f"attached_{file_type}",
|
||||
"role": _infer_source_role(af),
|
||||
"format": Path(path).suffix.lower().lstrip(".") if file_type != "url" else "url",
|
||||
"authority": "reference",
|
||||
"exists": af.get("exists", "unknown"),
|
||||
})
|
||||
return registry
|
||||
|
||||
|
||||
def _infer_source_role(attached_file: dict[str, str]) -> str:
|
||||
"""推断附加源文件的角色。"""
|
||||
path_lower = attached_file.get("path", "").lower()
|
||||
if any(kw in path_lower for kw in ["接口", "api", "接口文档"]):
|
||||
return "api_spec"
|
||||
if any(kw in path_lower for kw in ["原型", "prototype", "html"]):
|
||||
return "ui_prototype"
|
||||
if any(kw in path_lower for kw in ["showdoc", "confluence", "wiki"]):
|
||||
return "documentation"
|
||||
if any(kw in path_lower for kw in ["技术方案", "technical", "设计"]):
|
||||
return "technical_design"
|
||||
return "reference"
|
||||
|
||||
|
||||
# ── Analyze 战区 ──────────────────────────────────────────────────────────
|
||||
|
||||
def _run_analyze_zone(
|
||||
@@ -455,6 +596,7 @@ def _run_analyze_zone(
|
||||
context: dict[str, Any],
|
||||
config: dict[str, Any],
|
||||
agents: list[str],
|
||||
scaffold_only: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""执行 Analyze 战区: requirement-analyzer + conflict-detector → risk-assessor。"""
|
||||
|
||||
@@ -640,6 +782,7 @@ def _run_design_zone(
|
||||
context: dict[str, Any],
|
||||
config: dict[str, Any],
|
||||
agents: list[str],
|
||||
scaffold_only: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""执行 Design 战区: strategist → (testpoint-designer + data-builder) → case-designer。"""
|
||||
|
||||
@@ -672,13 +815,36 @@ def _run_design_zone(
|
||||
"test_cases_file": str(test_cases_path),
|
||||
"test_data_file": str(data_path),
|
||||
"p0_required_coverage": "100%" if p0_count > 0 else "N/A",
|
||||
"agent_notes": {
|
||||
}
|
||||
if scaffold_only:
|
||||
design_manifest["agent_notes"] = {
|
||||
"test-strategist": {"status": "completed", "message": f"策略已生成,{p0_count} 个 P0 风险需 100% 覆盖"},
|
||||
"testpoint-designer": {
|
||||
"status": "pending_ai",
|
||||
"message": "待 AI Agent 生成测试点",
|
||||
"prompt_file": "agents/design/testpoint_designer.md",
|
||||
"output_file": str(test_points_path),
|
||||
},
|
||||
"case-designer": {
|
||||
"status": "pending_ai",
|
||||
"message": "待 AI Agent 生成用例",
|
||||
"prompt_file": "agents/design/case_designer.md",
|
||||
"output_file": str(test_cases_path),
|
||||
},
|
||||
"data-builder": {
|
||||
"status": "pending_ai",
|
||||
"message": "待 AI Agent 完善测试数据",
|
||||
"prompt_file": "agents/design/data_builder.md",
|
||||
"output_file": str(data_path),
|
||||
},
|
||||
}
|
||||
else:
|
||||
design_manifest["agent_notes"] = {
|
||||
"test-strategist": f"策略已生成,{p0_count} 个 P0 风险需 100% 覆盖",
|
||||
"testpoint-designer": "待 AI Agent 生成测试点",
|
||||
"case-designer": "待 AI Agent 生成用例",
|
||||
"data-builder": "测试数据模板已生成",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
save_manifest(base_name, "design", design_manifest)
|
||||
return design_manifest
|
||||
@@ -773,6 +939,7 @@ def _run_execute_zone(
|
||||
context: dict[str, Any],
|
||||
config: dict[str, Any],
|
||||
agents: list[str],
|
||||
scaffold_only: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""执行 Execute 战区: web-executor + mobile-executor → result-reporter。"""
|
||||
|
||||
@@ -1376,6 +1543,7 @@ def _run_review_zone(
|
||||
context: dict[str, Any],
|
||||
config: dict[str, Any],
|
||||
agents: list[str],
|
||||
scaffold_only: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""执行 Review 战区: case-reviewer + coverage-auditor → quality-gatekeeper。"""
|
||||
|
||||
@@ -1406,32 +1574,71 @@ def _run_review_zone(
|
||||
max_blockers = quality_gate_config.get("max_blockers", 0)
|
||||
|
||||
# 模拟评审结果(实际由 Agent 填充)
|
||||
verdict = "PASS" if case_count > 0 else "BLOCKED"
|
||||
verdict_reason = (
|
||||
f"用例数量: {case_count},覆盖率达标" if case_count > 0
|
||||
else "测试用例文件尚未生成或为空"
|
||||
)
|
||||
if scaffold_only:
|
||||
verdict = "PENDING_AI"
|
||||
verdict_reason = "等待 AI Agent 评审(review 战区: case-reviewer → coverage-auditor → quality-gatekeeper)"
|
||||
elif case_count > 0:
|
||||
verdict = "PASS"
|
||||
verdict_reason = f"用例数量: {case_count},覆盖率达标"
|
||||
else:
|
||||
verdict = "BLOCKED"
|
||||
verdict_reason = "测试用例文件尚未生成或为空"
|
||||
|
||||
_write_verdict(base_name, verdict, verdict_reason, case_count, verdict_path)
|
||||
|
||||
review_manifest = {
|
||||
"base_name": base_name,
|
||||
"review_report_file": str(review_report_path),
|
||||
"coverage_report_file": str(coverage_report_path),
|
||||
"verdict_file": str(verdict_path),
|
||||
"quality_verdict": {
|
||||
"verdict": verdict,
|
||||
"reason": verdict_reason,
|
||||
"case_count": case_count,
|
||||
"min_coverage_required": min_coverage,
|
||||
"max_blockers_allowed": max_blockers,
|
||||
},
|
||||
"agent_notes": {
|
||||
"case-reviewer": f"评审完成,{case_count} 条用例" if case_count > 0 else "等待用例生成",
|
||||
"coverage-auditor": "覆盖率审计待 AI Agent 执行",
|
||||
"quality-gatekeeper": f"裁决: {verdict}",
|
||||
},
|
||||
}
|
||||
if scaffold_only:
|
||||
review_manifest = {
|
||||
"base_name": base_name,
|
||||
"review_report_file": str(review_report_path),
|
||||
"coverage_report_file": str(coverage_report_path),
|
||||
"verdict_file": str(verdict_path),
|
||||
"quality_verdict": {
|
||||
"verdict": verdict,
|
||||
"reason": verdict_reason,
|
||||
"case_count": case_count,
|
||||
"min_coverage_required": min_coverage,
|
||||
"max_blockers_allowed": max_blockers,
|
||||
},
|
||||
"agent_notes": {
|
||||
"case-reviewer": {
|
||||
"status": "pending_ai",
|
||||
"message": "待 AI Agent 评审用例",
|
||||
"prompt_file": "agents/review/case_reviewer.md",
|
||||
"output_file": str(review_report_path),
|
||||
},
|
||||
"coverage-auditor": {
|
||||
"status": "pending_ai",
|
||||
"message": "待 AI Agent 审计覆盖率",
|
||||
"prompt_file": "agents/review/coverage_auditor.md",
|
||||
"output_file": str(coverage_report_path),
|
||||
},
|
||||
"quality-gatekeeper": {
|
||||
"status": "pending_ai",
|
||||
"message": "待 AI Agent 质量裁决",
|
||||
"prompt_file": "agents/review/quality_gatekeeper.md",
|
||||
"output_file": str(verdict_path),
|
||||
},
|
||||
},
|
||||
}
|
||||
else:
|
||||
review_manifest = {
|
||||
"base_name": base_name,
|
||||
"review_report_file": str(review_report_path),
|
||||
"coverage_report_file": str(coverage_report_path),
|
||||
"verdict_file": str(verdict_path),
|
||||
"quality_verdict": {
|
||||
"verdict": verdict,
|
||||
"reason": verdict_reason,
|
||||
"case_count": case_count,
|
||||
"min_coverage_required": min_coverage,
|
||||
"max_blockers_allowed": max_blockers,
|
||||
},
|
||||
"agent_notes": {
|
||||
"case-reviewer": f"评审完成,{case_count} 条用例" if case_count > 0 else "等待用例生成",
|
||||
"coverage-auditor": "覆盖率审计待 AI Agent 执行",
|
||||
"quality-gatekeeper": f"裁决: {verdict}",
|
||||
},
|
||||
}
|
||||
|
||||
save_manifest(base_name, "review", review_manifest)
|
||||
return review_manifest
|
||||
@@ -1472,6 +1679,7 @@ def _run_monitor_zone(
|
||||
context: dict[str, Any],
|
||||
config: dict[str, Any],
|
||||
agents: list[str],
|
||||
scaffold_only: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""执行 Monitor 战区: execution-analyst → knowledge-curator。"""
|
||||
|
||||
@@ -1581,6 +1789,7 @@ def main() -> None:
|
||||
run_parser.add_argument("--zone", choices=ZONE_ORDER, help="仅运行到指定战区")
|
||||
run_parser.add_argument("--skip-export", action="store_true", help="跳过 Excel 导出")
|
||||
run_parser.add_argument("--skip-xmind", action="store_true", help="跳过 XMind 导出")
|
||||
run_parser.add_argument("--scaffold-only", action="store_true", help="仅生成脚手架(模板+清单+脚本),AI内容由SKILL.md调用Agent生成")
|
||||
|
||||
# prepare
|
||||
prepare_parser = subparsers.add_parser("prepare", help="仅准备战区")
|
||||
@@ -1672,6 +1881,7 @@ def main() -> None:
|
||||
build_all_output_dirs(base_name)
|
||||
|
||||
# 按战区顺序执行
|
||||
scaffold_only = getattr(args, 'scaffold_only', False)
|
||||
for zone in zones_to_run:
|
||||
# 检查确认门禁
|
||||
if zone in ("design", "review") and "analyze" in zones_to_run:
|
||||
@@ -1686,7 +1896,7 @@ def main() -> None:
|
||||
return
|
||||
|
||||
try:
|
||||
run_zone(zone, base_name, requirement_path, config)
|
||||
run_zone(zone, base_name, requirement_path, config, scaffold_only=scaffold_only)
|
||||
except Exception as exc:
|
||||
print(f"\n❌ {zone.upper()} 战区执行失败: {exc}")
|
||||
raise
|
||||
@@ -1700,9 +1910,11 @@ def main() -> None:
|
||||
review_manifest = load_manifest_safe(base_name, "review")
|
||||
if review_manifest:
|
||||
verdict = review_manifest.get("quality_verdict", {}).get("verdict")
|
||||
if verdict in ("PASS", "PASS_WITH_FIX"):
|
||||
if verdict in ("PASS", "PASS_WITH_FIX") and not scaffold_only:
|
||||
print(f"\n📦 质量裁决 {verdict},自动导出 Excel + XMind...")
|
||||
_run_export(requirement_path, skip_xmind=getattr(args, "skip_xmind", False))
|
||||
elif verdict == "PENDING_AI":
|
||||
print(f"\n⏳ 质量裁决 {verdict},AI 内容生成未完成。请通过 SKILL.md 调用 AI Agent。")
|
||||
else:
|
||||
print(f"\n🛑 质量裁决 {verdict},跳过导出。请先解决阻断项。")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user