#!/usr/bin/env python3 """Generate the Markdown draft for application form information.""" from __future__ import annotations import argparse import os import platform import re import shutil from pathlib import Path from typing import Any from common import ensure_dir, read_json, read_text MIN_MAIN_FUNCTION_CHARS = 500 MAX_MAIN_FUNCTION_CHARS = 1300 FIELD_ORDER = [ "软件全称", "软件简称", "版本号", "软件分类", "开发完成日期", "开发方式", "软件说明", "发表状态", "首次发表日期", "著作权人", "权利范围", "权利取得方式", "开发的硬件环境", "运行的硬件环境", "开发该软件的操作系统", "软件开发环境 / 开发工具", "该软件的运行平台 / 操作系统", "软件运行支撑环境 / 支持软件", "编程语言", "源程序量", "开发目的", "面向领域 / 行业", "软件的主要功能", "软件的技术特点", "页数", ] def effective_len(value: str) -> int: return len(str(value or "").replace(" ", "").replace("\n", "")) def clean_text(value: Any) -> str: text = str(value or "").strip() text = re.sub(r"\s+", " ", text) text = text.replace("`", "").replace("#", "").strip() return text def trim_effective(value: str, max_chars: int = MAX_MAIN_FUNCTION_CHARS) -> str: if effective_len(value) <= max_chars: return value result: list[str] = [] count = 0 for char in value: if char not in (" ", "\n"): count += 1 if count > max_chars: break result.append(char) return "".join(result).rstrip(",。;、 ") + "。" def business_feature_pairs(business: dict[str, Any] | None) -> list[tuple[str, str]]: if not business: return [] features = business.get("business_features") or [] details = business.get("business_feature_details") or {} if not isinstance(features, list): return [] if not isinstance(details, dict): details = {} pairs: list[tuple[str, str]] = [] for feature in features: name = clean_text(feature) if not name: continue detail = clean_text(details.get(name)) pairs.append((name, detail)) return pairs def summarize_business_features(software_name: str, business: dict[str, Any] | None) -> str: pairs = business_feature_pairs(business) if not business or not pairs: return "" industry = clean_text(business.get("industry")) target_users = business.get("target_users") or [] if isinstance(target_users, list): target_text = "、".join(clean_text(item) for item in target_users if clean_text(item)) else: target_text = clean_text(target_users) core_value = clean_text(business.get("core_value")) product_positioning = clean_text(business.get("product_positioning")) operation_flow = business.get("operation_flow") or [] if isinstance(operation_flow, list): flow_steps = [clean_text(item).rstrip("。") for item in operation_flow if clean_text(item)] flow_text = ",再".join(flow_steps) else: flow_text = clean_text(operation_flow) feature_names = "、".join(name for name, _ in pairs[:8]) parts: list[str] = [] if product_positioning: parts.append(product_positioning.rstrip("。") + "。") else: scope = f"面向{industry}" if industry else "面向实际业务场景" users = f",服务于{target_text}" if target_text else "" parts.append(f"{software_name}是一套{scope}{users}的应用软件。") parts.append(f"软件主要提供{feature_names}等功能。") if core_value: parts.append(core_value.rstrip("。") + "。") for name, detail in pairs[:8]: if detail: detail = detail.rstrip("。") if detail.startswith(("用户", "系统")): parts.append(f"在{name}功能中,{detail}。") else: parts.append(f"{name}功能主要{detail}。") else: parts.append(f"{name}功能支持用户完成相关业务操作,并在处理完成后返回对应结果。") if flow_text: parts.append(f"用户通常先{flow_text},系统在关键步骤提供状态反馈和结果展示。") result = "".join(parts) while effective_len(result) < MIN_MAIN_FUNCTION_CHARS: result += ( f"围绕{feature_names}等核心功能,软件将用户输入、过程处理、结果查看和资料管理组织在连续的操作流程中," "用户可以根据页面提示逐步完成业务处理,系统保存必要的数据记录并提供清晰的反馈信息," "便于用户后续继续查看、复核和调整相关内容。" ) return trim_effective(result) def summarize_features(analysis: dict[str, Any], software_name: str, business: dict[str, Any] | None = None) -> str: """Generate a substantive main-function description for the application form. When business context JSON provides main_functions it will be used upstream; this function is a fallback that assembles the best available evidence into a multi- paragraph description targeting the 500-1300 Chinese-character window required by the Chinese copyright office. """ business_summary = summarize_business_features(software_name, business) if business_summary: return business_summary features = analysis.get("feature_candidates") or [] readme = (analysis.get("readme_excerpt") or "").strip() routes = analysis.get("routes") or [] readable_features = [] for feature in features: name = humanize_feature(str(feature)) if re.search(r"[A-Za-z]", name): continue if name and name not in readable_features: readable_features.append(name) parts: list[str] = [] # Opening overview paragraph feature_list = "、".join(readable_features[:12]) if readable_features else "信息展示、业务处理、数据管理和系统交互" parts.append( f"{software_name}是一套面向用户业务场景的综合软件系统," f"主要提供{feature_list}等核心功能模块。" f"系统通过清晰的操作界面和合理的业务流程设计,帮助用户高效完成日常工作和业务协作。" ) # Module-by-module breakdown detail_parts: list[str] = [] for name in readable_features[:8]: skip = {"软件登录", "用户注册", "用户认证", "首页", "数据看板", "系统设置"} if name in skip: continue detail_parts.append(f"{name}模块支持用户进行相关数据的查看、录入和管理操作,提供完整的业务处理能力和结果反馈。") if not detail_parts: route_display = [r.strip("/") for r in routes[:6] if r != "/" and not r.startswith("/:")] if route_display: for route_name in route_display[:6]: label = route_name.replace("-", " ").replace("_", " ").title() detail_parts.append(f"{label}模块支持用户进行相关数据的查看、录入和管理操作,提供完整的业务处理能力和结果反馈。") else: detail_parts.append("用户可通过系统界面完成数据查询、信息录入、业务处理和结果导出等操作。") detail_parts.append("系统支持多角色用户的协同工作,不同权限用户可访问相应的功能模块。") detail_parts.append("系统提供数据持久化存储和历史记录追溯能力,保障业务数据的完整性和可审计性。") # Limit detail parts to avoid exceeding max length combined = "".join(detail_parts) if len(combined) + len("".join(parts)) > MAX_MAIN_FUNCTION_CHARS: while detail_parts and len("".join(detail_parts)) + len("".join(parts)) > MAX_MAIN_FUNCTION_CHARS: detail_parts.pop() parts.extend(detail_parts) # Closing paragraph if readme: first_line = readme.splitlines()[0][:80] parts.append(f"系统核心业务围绕{first_line}展开,覆盖从信息采集到结果呈现的完整操作链路。") result = "".join(parts) while effective_len(result) < MIN_MAIN_FUNCTION_CHARS: padding = ( "此外,系统还提供了配套的数据管理、用户操作记录、状态跟踪和系统配置等辅助功能模块," "各个功能模块之间通过统一的界面布局和操作规范协同运行,用户可以在不同模块间灵活切换和处理跨模块的业务流程。" "系统整体设计注重业务完整性和操作连续性,能够满足用户日常工作中对信息处理和业务管理的核心需求。" "系统界面设计遵循清晰直观的原则,主要操作入口集中展示,用户无需复杂培训即可上手使用。" "系统的数据管理能力包括数据的录入、存储、查询、修改和删除等基本操作,同时支持数据批量处理和导入导出功能。" "在业务处理方面,系统支持多步骤业务流程的串联执行,各环节之间数据自动流转,减少用户重复录入。" "系统还提供了灵活的配置选项,管理员可以根据实际业务需求调整系统参数和功能开关。" ) result += padding return trim_effective(result) def humanize_feature(name: str) -> str: value = re.sub(r"([a-z])([A-Z])", r"\1 \2", name) value = value.replace("-", " ").replace("_", " ").strip() key = value.lower().replace(" ", "") mapping = { "login": "软件登录", "register": "用户注册", "auth": "用户认证", "home": "首页", "dashboard": "数据看板", "project": "项目管理", "projects": "项目管理", "projectsettings": "项目设置", "projectssettings": "项目设置", "settings": "系统设置", "asset": "资源管理", "assets": "资源管理", "assethub": "资源中心", "billing": "费用管理", "agentstatusbar": "智能体状态展示", "messagebubble": "消息展示", "chatpanel": "对话面板", "chatinput": "对话输入", "assetpanel": "资源面板", } return mapping.get(key, value.title() if re.search(r"[A-Za-z]", value) else value) def build_fields( analysis: dict[str, Any], manifest: dict[str, Any], software_name: str, version: str, answers: dict[str, str], business: dict[str, Any] | None = None, ) -> dict[str, str]: frameworks = analysis.get("frameworks") or [] framework_text = "、".join(frameworks) if frameworks else "前端工程化框架" language = analysis.get("language") or "待用户确认" project = Path(analysis.get("project_root") or ".") hardware_hint = current_hardware_environment() dev_os_hint = current_operating_system() version_hint = version_confirmation_hint(analysis, version) software_name_hint = f"待用户确认(建议:{software_name};请确认最终软件全称)" defaults = { "软件全称": software_name_hint, "软件简称": "", "版本号": version_hint, "软件分类": (business.get("software_category") or "应用软件") if business else "应用软件", "开发完成日期": "待用户确认(YYYY-MM-DD)", "开发方式": (business.get("development_situation") or "单独开发") if business else "单独开发", "软件说明": "原创", "发表状态": "待用户确认(已发表/未发表)", "首次发表日期": "待用户确认(YYYY-MM-DD,未发表则留空)", "著作权人": "待用户确认", "权利范围": (business.get("rights_scope") or "全部权利") if business else "全部权利", "权利取得方式": (business.get("rights_acquisition") or "原始取得") if business else "原始取得", "开发的硬件环境": hardware_hint, "运行的硬件环境": hardware_hint, "开发该软件的操作系统": dev_os_hint, "软件开发环境 / 开发工具": f"开发环境: {dev_os_hint}/开发工具: {infer_ide_name(project)}", "该软件的运行平台 / 操作系统": infer_runtime_os(analysis), "软件运行支撑环境 / 支持软件": infer_runtime_support(analysis, project), "编程语言": language, "源程序量": str(manifest.get("source_line_count") or manifest.get("selected_source_line_count") or "待用户确认"), "开发目的": (business.get("application_purpose") or f"待用户确认(≤50字符,需说明开发目的,不能只写软件名称)") if business else "待用户确认(≤50字符,需说明开发目的,不能只写软件名称)", "面向领域 / 行业": (business.get("industry") or "待用户确认") if business else "待用户确认", "软件的主要功能": (business.get("main_functions") or summarize_features(analysis, software_name, business)) if business else summarize_features(analysis, software_name, business), "软件的技术特点": (business.get("technical_characteristics") or f"系统采用{framework_text}构建前端界面,结合模块化组件、路由组织、接口封装和状态管理实现业务功能") if business else f"系统采用{framework_text}构建前端界面,结合模块化组件、路由组织、接口封装和状态管理实现业务功能", "页数": str(manifest.get("total_pages") or "待用户确认"), } defaults.update({k: v for k, v in answers.items() if v}) # 未发表时首次发表日期应为空,不触发待确认门禁 publish_status = defaults.get("发表状态", "") if "未发表" in publish_status and "已发表" not in publish_status: if "待用户确认" in defaults.get("首次发表日期", "") or not defaults.get("首次发表日期", "").strip(): defaults["首次发表日期"] = "" # 软件的主要功能:确保业务理解提供的内容也满足最低字数 main_func = defaults.get("软件的主要功能", "") if main_func and "待用户确认" not in main_func and effective_len(main_func) < MIN_MAIN_FUNCTION_CHARS: defaults["软件的主要功能"] = summarize_features(analysis, software_name, business) return defaults def version_numbers(value: str) -> tuple[int, ...]: raw = str(value or "").strip() raw = raw.lstrip("vV") parts = re.findall(r"\d+", raw) return tuple(int(part) for part in parts[:3]) def version_less_than_1(value: str) -> bool: numbers = version_numbers(value) return bool(numbers) and numbers[0] < 1 def normalize_version_label(value: str) -> str: raw = str(value or "").strip() if not raw: return "" return raw if raw.upper().startswith("V") else f"V{raw}" def project_version_candidate(analysis: dict[str, Any]) -> str: value = str((analysis.get("package") or {}).get("version") or "").strip() if value and value.upper() != "V1.0": return normalize_version_label(value) return "" def version_confirmation_hint(analysis: dict[str, Any], requested_version: str) -> str: project_version = project_version_candidate(analysis) requested = normalize_version_label(requested_version or "V1.0") if project_version and version_less_than_1(project_version): return ( f"待用户确认(项目版本号为 {project_version},软著首次提交通常建议从 V1.0 开始;" f"请确认填写 V1.0 还是 {project_version})" ) if not project_version and version_less_than_1(requested): return ( f"待用户确认(当前建议版本号为 {requested},软著首次提交通常建议从 V1.0 开始;" f"请确认填写 V1.0 还是 {requested})" ) if project_version and project_version != requested: return f"待用户确认(项目版本号为 {project_version},当前建议为 {requested};请确认最终申报版本号)" return f"待用户确认(建议:{requested};请确认最终版本号)" def format_gb(size: int | None) -> str: if not size: return "" return f"{size / (1024 ** 3):.0f}GB" def total_memory_bytes() -> int | None: try: return os.sysconf("SC_PAGE_SIZE") * os.sysconf("SC_PHYS_PAGES") except (AttributeError, OSError, ValueError): return None def current_hardware_environment() -> str: parts: list[str] = [] cpu_count = os.cpu_count() machine = platform.machine() processor = platform.processor() if processor and processor != machine and processor.lower() != "arm": parts.append(f"CPU {processor}") if cpu_count: parts.append(f"CPU {cpu_count}核") if machine: parts.append(f"架构 {machine}") memory = format_gb(total_memory_bytes()) if memory: parts.append(f"内存 {memory}") try: disk = shutil.disk_usage(Path.home()) disk_total = format_gb(disk.total) if disk_total: parts.append(f"硬盘 {disk_total}") except OSError: pass if parts: return "、".join(parts) return "待用户确认" def current_operating_system() -> str: system = platform.system() if system == "Darwin": version = platform.mac_ver()[0] label = f"macOS {version}" if version else f"macOS(Darwin {platform.release()})" elif system == "Windows": label = f"Windows {platform.release()}" elif system == "Linux": label = f"Linux {platform.release()}" else: label = f"{system} {platform.release()}".strip() or "待用户确认" return label def infer_ide_name(project: Path) -> str: if (project / ".idea").exists(): return "WebStorm 或 IntelliJ IDEA" if (project / ".vscode").exists(): return "Visual Studio Code" if list(project.glob("*.code-workspace")): return "Visual Studio Code" return "Visual Studio Code" def infer_runtime_os(analysis: dict[str, Any]) -> str: frameworks = set(analysis.get("frameworks") or []) deps = set((analysis.get("package") or {}).get("dependency_names") or []) if "Electron" in frameworks or "electron" in deps or "Tauri" in frameworks or "@tauri-apps/api" in deps: return "Windows 10/11 或 macOS 13及以上版本" if frameworks & {"Vue", "React", "Vite", "Next.js", "Nuxt", "Svelte", "Astro", "Angular"}: return "Windows 10/11 或 macOS 13及以上版本" return "Windows 10/11 或 macOS 13及以上版本" def project_file(project: Path, relative: str) -> Path | None: if not relative: return None path = project / relative return path if path.exists() else None def load_project_package(project: Path, analysis: dict[str, Any]) -> dict[str, Any]: package_path = project_file(project, (analysis.get("package") or {}).get("path") or "") if package_path: try: return read_json(package_path) except Exception: return {} return {} def read_readme(project: Path) -> str: for name in ("README.md", "README.zh.md", "readme.md", "Readme.md"): path = project / name if path.exists(): try: return read_text(path, limit=12000) except Exception: return "" return "" def extract_requirement_bullets(text: str) -> list[str]: wanted = ("python", "node", "docker", "compose", "postgres", "redis", "chrome", "edge", "safari") # Patterns that indicate a feature description rather than a runtime requirement. _feature_start = re.compile(r"^(?:[一-鿿]|L\d|P\d|[A-Z]\d\s)") bullets: list[str] = [] for line in text.splitlines(): match = re.match(r"\s*[-*]\s+(.+)", line) if not match: continue item = match.group(1).strip() if any(key in item.lower() for key in wanted) and item not in bullets: if len(item) > 80: continue if _feature_start.match(item) and "(" in item: continue bullets.append(item) return bullets[:8] def detect_package_manager(project: Path, package_path: str) -> str: base = (project / package_path).parent if package_path else project checks = [ ("pnpm-lock.yaml", "pnpm"), ("yarn.lock", "Yarn"), ("bun.lock", "Bun"), ("bun.lockb", "Bun"), ("package-lock.json", "npm"), ] for filename, manager in checks: if (base / filename).exists() or (project / filename).exists(): return manager return "npm" def has_support_term(items: list[str], term: str) -> bool: return any(term.lower() in item.lower() for item in items) def infer_runtime_support(analysis: dict[str, Any], project: Path) -> str: """Infer runtime support environment (≤50 chars plain text).""" package_info = load_project_package(project, analysis) package_path = (analysis.get("package") or {}).get("path") or "" deps = set((analysis.get("package") or {}).get("dependency_names") or []) frameworks = set(analysis.get("frameworks") or []) support: list[str] = [] readme_requirements = extract_requirement_bullets(read_readme(project)) if package_info or deps or frameworks & {"Vue", "React", "Vite", "Next.js", "Nuxt", "Svelte", "Astro", "Angular"}: if not has_support_term(support, "node"): node_engine = str((package_info.get("engines") or {}).get("node") or "").strip() support.append(f"Node.js {node_engine}".strip() if node_engine else "Node.js") support.append(detect_package_manager(project, package_path)) support.append("现代浏览器") if ((project / "pyproject.toml").exists() or any(project.glob("*/pyproject.toml"))) and not has_support_term(support, "python"): support.append("Python") if ((project / "requirements.txt").exists() or list(project.glob("*/requirements*.txt"))) and not has_support_term(support, "python"): support.append("Python") if ((project / "docker-compose.yml").exists() or (project / "docker-compose.yaml").exists() or list(project.glob("docker-compose*.yml"))) and not has_support_term(support, "docker"): support.append("Docker") compose_text = "" for compose in list(project.glob("docker-compose*.yml")) + list(project.glob("docker-compose*.yaml")): try: compose_text += "\n" + read_text(compose, limit=20000).lower() except Exception: continue if "postgres" in compose_text: support.append("PostgreSQL") if "redis" in compose_text: support.append("Redis") # Also incorporate readme requirements into support software for req in readme_requirements: if not has_support_term(support, req.split()[0].lower()): support.append(req) # Deduplicate unique: list[str] = [] for item in support: clean = str(item).strip().rstrip(";;") if clean and clean not in unique: unique.append(clean) if not unique: return "待用户确认" # Enforce ≤50 char limit: trim items if needed result = "、".join(unique) if len(result) > 50: trimmed: list[str] = [] for item in unique: candidate = "、".join(trimmed + [item]) if len(candidate) <= 50: trimmed.append(item) else: break result = "、".join(trimmed) if trimmed else unique[0][:50] return result def write_application_md(path: Path, fields: dict[str, str], analysis: dict[str, Any], manifest: dict[str, Any], business: dict[str, Any] | None = None) -> None: # 兜底:未发表时首次发表日期不应输出"待用户确认",直接留空 publish_status = fields.get("发表状态", "") if "未发表" in publish_status and "已发表" not in publish_status: if "待用户确认" in (fields.get("首次发表日期") or "") or not (fields.get("首次发表日期") or "").strip(): fields["首次发表日期"] = "" lines = ["# 申请表信息", ""] for field in FIELD_ORDER: lines.append(f"➤{field}:{fields.get(field, '待用户确认')}") pending = [field for field in FIELD_ORDER if "待用户确认" in (fields.get(field) or "")] # Build warnings for common issues warnings: list[str] = [] soft_name = fields.get("软件全称", "") clean_name = str(soft_name).strip() raw_name = clean_name if "待用户确认" in clean_name and "建议:" in clean_name: try: raw_name = clean_name.split("建议:")[1].rstrip(")").split(";")[0].strip() except (IndexError, ValueError): raw_name = clean_name for suffix in ["软件", "平台"]: if raw_name.endswith(suffix): warnings.append(f"软件全称以「{suffix}」结尾,存在被驳回风险。建议考虑去掉「{suffix}」后缀或改用其他命名方式。") main_func = fields.get("软件的主要功能", "") if main_func and "待用户确认" not in main_func: func_len = len(str(main_func).replace(" ", "").replace("\n", "")) if func_len < MIN_MAIN_FUNCTION_CHARS: warnings.append(f"软件的主要功能仅有 {func_len} 字,应不少于 {MIN_MAIN_FUNCTION_CHARS} 字。请扩写功能说明。") elif func_len > MAX_MAIN_FUNCTION_CHARS: warnings.append(f"软件的主要功能共 {func_len} 字,超过建议上限 {MAX_MAIN_FUNCTION_CHARS} 字。请精简。") # Check character limits for fields with ≤50 or ≤100 constraints char_limit_fields = { "开发的硬件环境": 50, "运行的硬件环境": 50, "开发该软件的操作系统": 50, "软件开发环境 / 开发工具": 50, "该软件的运行平台 / 操作系统": 50, "软件运行支撑环境 / 支持软件": 50, "开发目的": 50, "面向领域 / 行业": 50, "软件的技术特点": 100, } for field_name, limit in char_limit_fields.items(): value = fields.get(field_name, "") if value and "待用户确认" not in value and len(value) > limit: warnings.append(f"{field_name}共 {len(value)} 字符,超过限制 {limit} 字符。请精简。") lines.extend( [ "", "## 字段填写口径", "", "- 软件全称:必须由用户确认;最终正式资料文件名、代码页眉和操作手册中的软件名称均以本字段为准。", "- 软件简称:可选;如有常用简称则填写。", "- 版本号:必须由用户确认;如果项目版本小于 V1.0,软著首次提交通常建议使用 V1.0,也可按实际项目版本填写,最终以本字段为准。", "- 软件分类:应用软件/嵌入式软件/中间件/系统软件/其他。", "- 开发完成日期、首次发表日期:必须使用 YYYY-MM-DD 格式。", "- 开发方式:单独开发/合作开发/委托开发/下达任务开发。", "- 软件说明:原创 / 修改(含翻译软件、合成软件)。", "- 发表状态:已发表或未发表;已发表需附首次发表日期。", "- 软件开发环境 / 开发工具:≤50字符,格式 `开发环境: /开发工具: `。", "- 开发该软件的操作系统:≤50字符,填写实际开发电脑的操作系统版本。", "- 该软件的运行平台 / 操作系统:≤50字符,填写软件运行所在的操作系统或浏览器环境。", "- 软件运行支撑环境 / 支持软件:≤50字符,直接列出运行依赖(如 Node.js、npm、浏览器),不加格式前缀。", "- 开发的硬件环境和运行的硬件环境:≤50字符,可使用检测到的电脑配置作为建议值。", "- 源程序量:纯数字(不含'行'字),指全部源程序的总行数。", "- 开发目的:≤50字符,用一句话说明目的,不能只写软件名称。", "- 面向领域 / 行业:≤50字符。", "- 软件的主要功能:500~1300字。", "- 软件的技术特点:多选标签(APP/游戏软件/教育软件/金融软件/医疗软件/地理信息软件/云计算软件/信息安全软件/大数据软件/人工智能软件/VR软件/5G软件/小程序/物联网软件/智慧城市软件)+ 文本描述≤100字符。", "", "## 项目分析摘要", "", f"- 项目目录:{analysis.get('project_root', '')}", f"- 框架:{'、'.join(analysis.get('frameworks') or []) or '未识别'}", f"- 源码文件数:{analysis.get('source', {}).get('total_file_count', analysis.get('source', {}).get('file_count', 0))}", f"- 源程序量(含空行):{analysis.get('source', {}).get('total_line_count', analysis.get('source', {}).get('line_count', 0))}", f"- 代码材料页数:{manifest.get('total_pages', 0)}", f"- 代码输出模式:{manifest.get('mode', '')}", f"- 业务理解:{'已读取 草稿/业务理解.json' if business else '未提供,使用项目分析兜底'}", "", "## 待确认字段", "", ] ) if warnings: lines.append("## 字段提醒") lines.append("") lines.extend(f"- {w}" for w in warnings) lines.append("") if pending: lines.extend(f"- {field}" for field in pending) else: lines.append("- 无") lines.extend( [ "", "```text", "STOP_FOR_USER", "NEXT_ACTION: 请补全并确认申请表字段;确认后运行 confirm_stage.py --stage application-fields。", "```", ] ) path.write_text("\n".join(lines) + "\n", encoding="utf-8") def require_confirmed_business(business: dict[str, Any] | None) -> None: if business is None: raise SystemExit( "STOP_FOR_USER\n" "NEXT_ACTION: 申请表信息必须基于已确认的业务理解生成。请先生成并确认 草稿/业务理解.md。" ) if business.get("confirmation_required") and not business.get("user_confirmed"): raise SystemExit( "STOP_FOR_USER\n" "NEXT_ACTION: 业务理解尚未确认。请先确认 草稿/业务理解.md," "再运行 `python3 /scripts/confirm_stage.py --workdir 软件著作权申请资料 --stage business --note \"<用户确认内容>\"`。" ) def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--analysis", required=True) parser.add_argument("--code-manifest", required=True) parser.add_argument("--software-name", required=True) parser.add_argument("--version", default="V1.0") parser.add_argument("--answers", help="Optional JSON object with confirmed field values") parser.add_argument("--business-context", help="Business context JSON generated before material drafting") parser.add_argument("--out-dir", default="软件著作权申请资料/草稿") args = parser.parse_args() analysis = read_json(Path(args.analysis)) manifest = read_json(Path(args.code_manifest)) answers = read_json(Path(args.answers)) if args.answers else {} business = read_json(Path(args.business_context)) if args.business_context else None require_confirmed_business(business) out_dir = ensure_dir(Path(args.out_dir)) fields = build_fields(analysis, manifest, args.software_name, args.version, answers, business) out_path = out_dir / "申请表信息.md" write_application_md(out_path, fields, analysis, manifest, business) print(f"OK application draft: {out_path}") print("STOP_FOR_USER") print("NEXT_ACTION: 请补全并确认申请表字段;确认后运行 confirm_stage.py --stage application-fields。") if __name__ == "__main__": main()