项目文件夹

文件
2026-07-13 12:29:52 +08:00

720 行
32 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
#!/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"macOSDarwin {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字符,格式 `开发环境: <OS>/开发工具: <IDE名称>`。",
"- 开发该软件的操作系统:≤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 <SKILL_DIR>/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()