
Aj Copyright Writer
- 4 installs
- Updated July 27, 2026
- zuoa/aj-skills
Expands a software name into full China software-copyright registration materials: spec, module docs, HTML prototypes with screenshots, source-code docs, and operation manuals.
About
A skill that produces the full set of Chinese software copyright (软著) registration materials from a software name, following a fixed pipeline of spec, module descriptions, HTML prototype screenshots, code documents, and operation manuals. A developer uses it to prepare a complete copyright registration application package.
- Fixed numbered outputs from spec to application-info txt, including code docx and manuals
- HTML prototypes screenshotted headless, covering login plus 10 module homepages
Aj Copyright Writer by the numbers
- 4 all-time installs (skills.sh)
- Ranked #1,241 of 1,879 Documentation skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/zuoa/aj-skills --skill aj-copyright-writerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 4 |
|---|---|
| Last updated | July 27, 2026 |
| Repository | zuoa/aj-skills ↗ |
What it does
Expands a software name into full China software-copyright registration materials: spec, module docs, HTML prototypes with screenshots, source-code docs, and operation manuals.
Files
AJ Copyright Writer
面向软件著作权申请材料的全流程写作技能。目标不是生成宣传稿,而是把一个软件名称扩展成可用于申请准备的操作手册、核心代码材料和配套界面图。
先读什么
- 执行完整流程前,读 references/workflow-contract.md。
- 写
spec、模块、手册和代码前,读 references/material-writing-rules.md。 - 生成界面原型前,读 references/prototype-ui-style.md。
- 生成操作手册前,读 references/operation-manual-template.md。
- 操作手册初稿完成后,读 references/operation-manual-humanizer.md,做保守的书面专业化去 AI 味处理。
- 写申请材料格式和边界前,读 references/software-registration-rules.md。
- 使用国家版权局参考材料时,读 references/ncac-2025-copyright-policy.md,并优先联网读取原始 URL。
- HTML 原型截图时,运行 scripts/screenshot_html_prototypes.py。
- Gemini 生图时,运行 scripts/generate_gemini_prototypes.py。
- 校验编号产物时,运行 scripts/validate_outputs.py。
- 生成 Word 文档时,运行 scripts/markdown_to_docx.py 和 scripts/build_code_docx.py。
Script Directory
{baseDir} = 当前 SKILL.md 所在目录。执行脚本时先解析 {baseDir},命令中始终使用 {baseDir}/scripts/...,不要假设当前工作目录一定是仓库根目录。
输入和默认值
必需输入:
SOFTWARE_NAME:软件名称。
可选输入:
- 输出目录:默认创建
copyright-materials/{software_slug}-{YYYYMMDD-HHMM}/,所有指定路径都位于该目录内。 - 软件版本号:默认
V1.0;如果用户或申请表提供了版本号,源代码文档页眉必须使用与申请表完全一致的软件名称和版本号。 - 本地参考目录:默认在输出目录、当前目录和
~/aj-skills中依次查找reference/、refence/、refrence/。 - 图片风格文件:默认查找
reference/style.md、refence/style.md、refrence/style.md。 - 操作手册模板:默认查找
reference/操作手册模版.docx、refrence/操作手册模版.docx、reference/操作手册模板.docx、refrence/操作手册模板.docx。 - 原型图生成模式:默认
html,生成03.prototype.html/*.html后通过 headless 浏览器截图;用户明确要求 AI 生图、Gemini 生图、图片模型、纯 prompt 时才使用image模式。 - Gemini 配置:默认读取
~/aj-skills/.env中的GEMINI_API_KEY;可用GEMINI_IMAGE_MODEL或GOOGLE_IMAGE_MODEL覆盖图片模型。默认模型为gemini-3.1-flash-image-preview,失败时依次尝试gemini-3-pro-image-preview、gemini-2.5-flash-image和imagen-4.0-generate-001。
如果用户只给软件名称,不要反复追问;按软件名称、公开资料和合理行业假设继续,但必须在 01.spec/spec.md 标明“公开资料事实”和“基于名称的扩展设定”。
全局生成限制
- 生成界面原型、HTML 原型、原型图片或原型 prompt 时,页面内不要标注版权信息、著作权归属、开发公司、技术支持单位、出品方、承建单位或类似公司署名;页脚、登录页、系统标题区、关于弹窗和水印中都不得出现这类信息。只展示软件名称、业务模块、角色、数据和操作控件。
输出结构
在输出目录内生成:
01.spec/spec.md
02.modules/01.md ... 10.md
03.prototype.html/00-login.html
03.prototype.html/01.html ... 10.html
03.prototype.html/01-01.html ... # 默认 html 模式,按需覆盖功能点
03.prototype.style/selection.md
03.prototype.prompt/00-login.md
03.prototype.prompt/01.md ... 10.md
03.prototype.prompt/01-01.md ... # image 模式,按需覆盖功能点
04.prototype/batch.json
04.prototype/00-login.jpg
04.prototype/01.jpg ... 10.jpg
04.prototype/01-01.jpg ... # 与功能点原型对应
05.code/01-模块名称.txt ... 10-模块名称.txt
06.manual/${SOFTWARE_NAME}_操作手册.draft.md
06.manual/${SOFTWARE_NAME}_操作手册.md
06.manual/${SOFTWARE_NAME}_操作手册.docx
07.code.full/${SOFTWARE_NAME}_代码.docx
08.application-info/${SOFTWARE_NAME}_软著申请表信息.txt可额外生成 source-log.md、qa.md 等辅助文件,但不要改变上述核心文件名。
工作流
Step 1: 调研并生成 01.spec/spec.md
1. 用软件名称联网检索公开资料,优先官网、产品文档、应用商店、公司公告、政府/行业网站和权威媒体。 2. 联网读取国家版权局参考页:https://www.ncac.gov.cn/xxfb/tzgg/202507/t20250723_923374.html。如果访问失败,使用内置参考摘要并记录失败原因。 3. 按 references/software-registration-rules.md 固定材料边界:申请表、软件鉴别材料、证明文件;源程序和文档普通交存按前后各连续 30 页、不足 60 页全部提交等规则理解。 4. 生成 01.spec/spec.md,内容包括:软件定位、目标用户、使用场景、业务流程、角色权限、系统架构、数据对象、功能总览、非功能要求、边界假设和材料写作口径。 5. 对没有公开证据的内容,用“为形成申请材料,本文按以下业务设定扩展”这类表述,不要伪装成已核验事实。 6. 面向交付的规格说明不要出现“模块01”“模块 01”“01模块”“功能点01”等内部编号标签;只能使用真实模块名称、章节号和自然语言说明。
生成后运行:
python {baseDir}/scripts/validate_outputs.py \
--spec-md 01.spec/spec.mdStep 2: 生成 10 个核心功能模块
基于 01.spec/spec.md 选出最能体现软件独创性和业务复杂度的 10 个模块,生成:
02.modules/01.md
...
02.modules/10.md每个模块必须拆出 3-5 个功能点,并写清:模块名称、用户目标、功能点清单、入口页面、关键流程、输入输出、数据字段、业务规则、异常处理、权限控制、界面要点、可生成代码的核心逻辑。
Step 3: 生成界面原型
遍历 02.modules/*.md,挑选复杂功能或最能展示产品能力的界面。优先使用 html 模式;只有用户明确要求 AI 生图、Gemini 生图或只要 prompt 时,才使用 image 模式。
生成原型前必须先读取 references/prototype-ui-style.md,根据软件名称、01.spec/spec.md 和 02.modules/*.md 判断软件类型,推荐一个最佳页面风格和 1-2 个备选风格,并让用户确认。用户确认后生成 03.prototype.style/selection.md,记录推断类型、推荐理由、用户确认的 style_id、本地 style.md 是否参与覆盖。除非用户明确要求无人值守生成,否则不要在未确认风格时开始写 HTML 或 prompt。
默认:HTML 原型模式
使用用户确认后的风格,再结合本地 style.md 的视觉覆盖,生成自包含 HTML 原型。不同软件类型应使用不同视觉体系,例如监管/预警类可使用 custom-command-system,政务审批类可使用 gov-service-light,企业运营类可使用 enterprise-data-station,医疗科研类可使用 medical-research-clean。
03.prototype.html/00-login.html
03.prototype.html/01.html
...
03.prototype.html/10.html
03.prototype.html/01-01.html
03.prototype.html/01-02.html
...每个 HTML 文件必须:
- 包含完整
<!doctype html>、内联 CSS、内联 JS 和 mock 数据。 - 在
<head>中写入<meta name="prototype-style" content="{confirmed_style_id}">,取值必须来自03.prototype.style/selection.md。 - 使用中文界面文案、真实业务字段、合理的统计卡片、表格、表单、弹窗或详情区域。
- 固定 16:9、1920x1080 首屏布局,截图时不能出现文字重叠、滚动条遮挡、空白主区域或外部资源加载失败。
- 在
<head>中写入<meta name="module" content="02.modules/01.md">,便于脚本记录 module 映射。 - 不依赖 CDN、远程字体、远程图片或后端接口;所有 mock 数据写在页面内。
- 页面应符合用户确认的风格;若选择
custom-command-system或industrial-iot-cockpit,可参考用户提供示例图的顶部系统标题和导航、技术框架、查询/操作区、主内容表格或业务面板、发光边框和状态芯片;若选择轻量政务、医疗、教育或企业风格,则应按对应行业的清晰、正式、可读风格实现,不要强行套深色指挥舱。 - 不要在任何页面区域写入版权信息、开发公司信息、技术支持单位、出品方、承建单位或类似公司署名,也不要把这类信息放进页脚、水印或关于弹窗。
必须额外生成 00-login.html,截图为 04.prototype/00-login.jpg,只用于手册 2.1、登录。不要用任意模块截图替代登录截图。
截图覆盖策略:
- 每个模块必须至少有一个模块首页截图:
01.html到10.html。 - 统计全部模块的功能点数量后,额外为其中
40%-60%的功能点生成页面截图,命名为模块编号-功能点编号.html,例如03-02.html。 - 优先覆盖复杂流程、数据录入、图表分析、监控态势、审核处理、告警处置、配置管理、报表导出等更容易体现软件能力的功能点。
- 简单查询、帮助、普通详情查看等低复杂度功能点可以只在模块首页截图中覆盖。
界面文案一致性是硬要求:Step 6 操作手册将提到的菜单、按钮、字段、状态、筛选条件和提示语,必须在本步骤生成的 HTML 页面中真实可见。若页面中没有「新增」「保存」「导出」「审核」等按钮,就不要在操作手册中写用户点击这些按钮;应先补齐原型页面,再写手册。
可选:Gemini 生图模式
使用用户确认后的风格,再结合本地 style.md 的视觉覆盖生成:
03.prototype.prompt/00-login.md
03.prototype.prompt/01.md
...
03.prototype.prompt/10.md
03.prototype.prompt/01-01.md
...每个 prompt 都要固定 16:9、1920x1080、中文界面、真实业务数据、清晰层级、无重叠文字、可辨认控件和页面状态。prompt 必须明确写入用户确认的 style_id、风格名称和视觉要求,避免普通后台管理界面,并明确要求画面中不要出现版权信息、开发公司信息、技术支持单位、出品方、承建单位或类似公司署名。编号要与后续图片和手册引用保持一致。必须额外生成 00-login.md,并要求生成真实登录页面,不得用模块首页代替。功能点截图覆盖同样遵循 40%-60% 规则。
Step 4: 生成原型图片
默认运行 HTML 截图模式:
python {baseDir}/scripts/validate_outputs.py \
--module-dir 02.modules \
--style-selection 03.prototype.style/selection.md \
--html-dir 03.prototype.html
python {baseDir}/scripts/screenshot_html_prototypes.py \
--html-dir 03.prototype.html \
--output-dir 04.prototype \
--module-dir 02.modules \
--batch-file 04.prototype/batch.json \
--viewport 1920x1080
python {baseDir}/scripts/validate_outputs.py \
--style-selection 03.prototype.style/selection.md \
--html-dir 03.prototype.html \
--prototype-dir 04.prototype \
--batch-file 04.prototype/batch.jsonHTML 截图模式依赖 Playwright:
pip install playwright pillow
python -m playwright install chromium脚本会先生成 04.prototype/batch.json,每个任务包含 module、html、output、retry、status 和错误信息;重复运行时会跳过 manifest 中已成功且文件仍存在的图片。
可选 Gemini/Google API 生图模式:
python {baseDir}/scripts/validate_outputs.py \
--module-dir 02.modules \
--style-selection 03.prototype.style/selection.md \
--prompt-dir 03.prototype.prompt
python {baseDir}/scripts/generate_gemini_prototypes.py \
--prompt-dir 03.prototype.prompt \
--output-dir 04.prototype \
--module-dir 02.modules \
--batch-file 04.prototype/batch.json \
--env ~/aj-skills/.env \
--size 1920x1080
python {baseDir}/scripts/validate_outputs.py \
--style-selection 03.prototype.style/selection.md \
--prototype-dir 04.prototype \
--batch-file 04.prototype/batch.jsonGemini 生图脚本会先生成 04.prototype/batch.json,每个任务包含 module、prompt、output、retry、status 和错误信息。两种模式最终生成结果都必须是:
04.prototype/00-login.jpg
04.prototype/01.jpg
...
04.prototype/10.jpg
04.prototype/01-01.jpg
...若缺少 GEMINI_API_KEY、Playwright、浏览器依赖或接口报错,先明确阻塞点,不要用空白占位图冒充生成结果。
Step 5: 生成核心业务代码
对每个模块生成一个代码文件:
05.code/01-模块名称.txt
...
05.code/10-模块名称.txt每个文件包含:
- Java 后端核心业务类、DTO 或控制器片段。
- React 前端核心页面或组件片段。
- 中文注释率不少于
10%,按cloc输出的comment / (comment + code)计算;注释应解释业务口径、校验规则、状态流转、权限判断和非显而易见的处理逻辑。 - 具体业务逻辑、校验、状态流转、异常处理和数据转换。
代码要像项目中的人工业务代码:不过度分层,不堆模式名,不只写空壳接口,不使用完全相同的模板反复复制。每个代码文件应有充分业务逻辑,且中文注释率不得低于 10%;文件名必须使用 编号-模块名称.txt,例如 01-用户权限管理.txt,不要只命名为 01.txt。 10 个代码文件去除空行后的总行数不得低于 4000 行;单个代码文件建议保持 380-520 个非空行,复杂模块可适度上浮。代码行数和注释率必须用 cloc --by-file --force-lang=JavaScript,txt 05.code 统计确认,非空行数按 comment + code 计算,blank 不计入。不得用空行、无意义重复代码或无业务含义的注释凑行数。 源代码全文不得出现 copyright 字样,大小写都不允许;05.code/*.txt 不要包含空行,生成源代码文档前必须清理所有空白行。
Step 6: 生成 Markdown 操作手册
基于 02.modules/*.md 改写成人工操作说明,先生成初稿,再做保守去 AI 味处理后生成最终稿:
06.manual/${SOFTWARE_NAME}_操作手册.draft.md
06.manual/${SOFTWARE_NAME}_操作手册.md手册必须穿插对应图片:


手册口吻面向最终用户,按 references/operation-manual-template.md 的政企交付文档结构组织,但正文不要写成模板填空。每个功能点用自然段融合功能用途、操作路径、字段含义、按钮作用、系统反馈和异常处理,不要把需求分析、数据库字段和代码实现原样搬进去。1.4、术语定义 需要保留结构化条目,按 术语:定义 写,例如 个体识别:指通过图像特征提取与比对技术,唯一标识每只种鹅的身份信息。;该示例只说明格式,实际生成时必须替换为本软件的业务或技术术语,不要改成连续自然段。插图说明写在图片 alt 文本里,转换 Word 时会作为图片下方居中的图注。手册中提到的按钮、字段和提示文字必须能在对应截图中找到,否则先回到 Step 3 修改原型页面。
手册是交付给最终用户和登记材料审查人员阅读的文档,不允许出现“模块01”“模块 01”“01模块”“功能点01”这类内部文件编号或标记说明。章节标题应写成 ### 2.2、用户权限管理,不要写成 ### 2.2、模块01 用户权限管理;正文也应写“用户在左侧导航栏点击「用户权限管理」”,不要写“进入模块01”。
操作说明不能只写简单点击步骤,也不要在正文中出现 页面内容说明:、页面区域说明:、功能说明:、操作前提:、字段说明:、按钮说明:、操作步骤:、操作过程:、预期结果:、异常提示: 这类硬性标签。写作时把这些内容合并成 2-4 个连贯段落:先说明用户从哪里进入、页面展示哪些业务区域和关键字段,再说明用户如何筛选、录入、点击按钮或查看结果,最后自然交代系统如何校验、成功后如何提示和刷新,以及必填缺失、重复数据、权限不足、无查询结果等情况如何处理。
正文不要大量使用 1、2、3、 或 a)、b)、c) 列表。章节标题保留 1、、1.1、、2.2.1、 这类层级编号即可,功能说明正文优先使用自然段,必要时才使用少量无序短句。
初稿完成后,读取 references/operation-manual-humanizer.md,对 06.manual/${SOFTWARE_NAME}_操作手册.draft.md 做一次保守的书面专业化去 AI 味处理,并输出最终 06.manual/${SOFTWARE_NAME}_操作手册.md。去 AI 味只能调整句式、连接方式和模板痕迹,不能改变软件名称、模块名称、功能名称、页面按钮、字段、状态标签、提示语、章节标题、图注和图片路径。所有 「...」 中的页面文字必须原样保留。
处理完成后运行:
python {baseDir}/scripts/validate_outputs.py \
--manual-draft-md "06.manual/${SOFTWARE_NAME}_操作手册.draft.md" \
--manual-md "06.manual/${SOFTWARE_NAME}_操作手册.md"Step 7: 生成操作手册 docx
优先使用用户提供的模板。运行:
python {baseDir}/scripts/validate_outputs.py \
--manual-draft-md "06.manual/${SOFTWARE_NAME}_操作手册.draft.md" \
--manual-md "06.manual/${SOFTWARE_NAME}_操作手册.md"
python {baseDir}/scripts/markdown_to_docx.py \
--input "06.manual/${SOFTWARE_NAME}_操作手册.md" \
--output "06.manual/${SOFTWARE_NAME}_操作手册.docx" \
--template "refrence/操作手册模版.docx"
python {baseDir}/scripts/validate_outputs.py \
--manual-docx "06.manual/${SOFTWARE_NAME}_操作手册.docx"markdown_to_docx.py 默认会把 Markdown 列表转为普通文本,避免 Word 自动编号在长手册中累积出大量序号。只有用户明确要求保留 Word 自动列表时,才追加 --auto-lists。
模板路径不存在时,自动查找 reference/、refence/、refrence/ 下的同名模板;仍找不到时生成无模板 docx 并在最终说明中注明。
Step 8: 生成代码 docx
将 05.code/*.txt 合并为:
07.code.full/${SOFTWARE_NAME}_代码.docx运行:
cloc --by-file --force-lang=JavaScript,txt 05.code
python {baseDir}/scripts/validate_outputs.py \
--code-dir 05.code
python {baseDir}/scripts/build_code_docx.py \
--code-dir 05.code \
--output "07.code.full/${SOFTWARE_NAME}_代码.docx" \
--software-name "${SOFTWARE_NAME}" \
--software-version "${SOFTWARE_VERSION:-V1.0}"
python {baseDir}/scripts/validate_outputs.py \
--code-dir 05.code \
--code-docx "07.code.full/${SOFTWARE_NAME}_代码.docx" \
--software-name "${SOFTWARE_NAME}" \
--software-version "${SOFTWARE_VERSION:-V1.0}"如果使用 --root 做整体验证,必须同时传入 --software-name "${SOFTWARE_NAME}",这样脚本会检查 07.code.full/${SOFTWARE_NAME}_代码.docx,而不是旧的 code.docx。
如果用户提供了代码模板,用 --template 指定。代码 docx 不要生成封面、目录、模块标题页或“本文档由 05.code 目录下……”这类生成说明;正文直接从源程序开始。源代码文档必须共 60 页代码页,每页不少于 50 行,脚本默认每页输出 50 行;字体使用宋体、小五号,左对齐,单倍行距。页眉必须标注所申请软件名称和版本号,并与申请表中的相应内容完全一致;右上角标注页码。超过 60 页的源程序按普通交存口径取前 30 页和后 30 页连续代码,保证最后一页是程序结束页。源代码文档中不得出现 copyright 字样,不得保留空行。
Step 9: 生成申请表信息 txt
在所有核心材料完成后,基于 01.spec/spec.md、02.modules/*.md、05.code/*.txt 和代码行数统计,生成:
08.application-info/${SOFTWARE_NAME}_软著申请表信息.txt该文件用于辅助填写软件著作权登记申请表。不要保留括号中的提示语,也不要把字段写成解释性模板;每一项都要给出可直接填表的具体内容。字段值应简短、确定,并满足申请表字数限制:硬件环境、操作系统、开发工具、运行平台、支撑环境、开发目的、面向领域/行业均不超过 50 个字符;软件主要功能不超过 200 个字;技术特点描述不超过 100 个字。
源程序量 填写 cloc --by-file --force-lang=JavaScript,txt 05.code 统计得到的 comment + code 总行数,或在无法运行 cloc 时填写已生成 05.code/*.txt 去除空行后的总行数,并在最终回复中说明统计口径。编程语言和版本要与 05.code 的实际内容一致;本技能默认代码材料包含 Java 与 React/JavaScript,可写成 Java、JavaScript,版本可写成 Java 17、ES2022,除非用户提供了真实技术栈。
软件技术特点必须先从以下类型中选择一种最贴近的软件类别,再用一句话描述技术特点:APP、游戏软件、教育软件、金融软件、医疗软件、地理信息软件、云计算软件、信息安全软件、大数据软件、人工智能软件、VR软件、5G软件、小程序、物联网软件、智慧城市软件。不要多选;若软件不明显属于某一类,选择最接近业务场景的类型。
使用以下字段顺序和格式:
开发的硬件环境 通用 x86_64 开发工作站或笔记本电脑
运行的硬件环境 通用服务器、云主机或用户终端设备
开发该软件的
操作系统 Windows 11、macOS 14 或 Linux
软件开发环境 / 开发工具 JDK 17、Node.js 20、IntelliJ IDEA、VS Code
该软件的运行平台 / 操作系统 Linux 服务器、Windows 客户端或移动端系统
软件运行支撑环境 / 支持软件 JRE 17、Nginx、PostgreSQL、Redis
编程语言 语言:Java、JavaScript 版本:Java 17、ES2022 源程序量 4000 行
开发目的
提升业务监管、处理和统计分析效率。
面向领域 / 行业 智慧监管行业
软件的主要功能 提供用户登录、权限管理、业务数据采集、流程审核、监测预警、统计分析、报表导出和系统配置等功能。
软件的技术特点 类型:智慧城市软件。采用分层业务服务、可视化统计和规则预警机制,实现多角色协同处理与数据闭环管理。示例只说明格式,生成时必须替换为本软件匹配的硬件环境、操作系统、支撑软件、行业、主要功能和技术特点;源程序量 不要保留示例数字。
生成后运行:
python {baseDir}/scripts/validate_outputs.py \
--application-info-txt "08.application-info/${SOFTWARE_NAME}_软著申请表信息.txt"质量检查
交付前检查:
- 核心文件和目录是否全部存在。
02.modules是否有 10 个模块文件,05.code是否有 10 个代码文件。- 原型模式为
html时,03.prototype.html是否有00-login.html、01.html到10.html,并额外覆盖40%-60%功能点;原型模式为image时同理检查03.prototype.prompt。 04.prototype是否有00-login.jpg、01.jpg到10.jpg,并额外包含功能点截图。04.prototype/batch.json是否存在,且所有截图任务最终均为success。- 手册中的图片链接是否都能对应到
04.prototype/*.jpg。 - 手册中的按钮、字段和提示语是否能在对应截图中看到。
- 操作手册
1.4、术语定义是否按术语:定义写成结构化条目,而不是连续自然段。 - 操作手册是否用自然段充分解释页面内容、操作前提、操作过程、预期结果和异常提示,且没有写成固定标签分段。
- 操作手册正文是否避免大量
1、2、3、或a)、b)、c)列表;编号主要保留在章节标题。 - 操作手册最终稿是否已经按
operation-manual-humanizer.md做过保守去 AI 味,且没有改动专有名称、页面按钮、字段、状态标签、图注和图片路径。 - 规格说明和操作手册是否没有“模块01”“模块 01”“01模块”“功能点01”等内部编号标签。
- 是否已生成
03.prototype.style/selection.md,并记录推荐风格、备选风格和用户确认结果。 - 原型截图是否符合用户确认的页面风格,不能是普通后台管理页面,也不能把所有软件都套成同一种深色指挥舱。
spec是否区分公开事实、推断和扩展设定。- 代码文件是否使用
01-模块名称.txt命名,是否包含 Java 和 React 两部分,是否有实际业务逻辑;是否已用cloc --by-file --force-lang=JavaScript,txt 05.code确认 10 个代码文件comment + code总行数不少于4000行,且中文注释率不少于10%。 - 源代码文件和源代码 docx 是否不含空行、不含
copyright字样;代码 docx 是否为 60 页代码页、每页 50 行、宋体小五、左对齐、单倍行距,页眉名称和版本号是否与申请表一致,右上角是否有页码。 08.application-info/${SOFTWARE_NAME}_软著申请表信息.txt是否已生成,字段是否完整,字数是否符合 50/100/200 字限制,源程序量是否与代码统计口径一致,技术特点是否从指定软件类型中单选。- docx 是否成功生成;能渲染检查时,抽查首页、目录附近、图片页和代码页。
交付说明
最终回复只列出输出目录、核心文件、申请表信息 txt、使用的模板或缺失模板、未完成项和验证结果。不要把整份手册或全部代码粘贴到聊天中。
{
"skill_name": "aj-copyright-writer",
"evals": [
{
"id": 1,
"prompt": "为“城市餐厨垃圾智慧监管平台”生成软件著作权申请材料,输出完整目录,默认用 HTML 原型截图生成界面图片。",
"expected_output": "生成 01.spec、02.modules、03.prototype.style、03.prototype.html、04.prototype、05.code、06.manual、07.code.full、08.application-info 目录;模块和代码各 10 个编号文件;先根据软件类型推荐页面风格并经用户确认,selection.md 记录确认风格;HTML 和图片包含 00-login、01-10 模块首页,并额外覆盖 40%-60% 功能点;HTML 原型使用确认后的行业匹配风格而不是通用后台;每个模块包含 3-5 个功能点;04.prototype/batch.json 记录 HTML 截图任务状态;手册按政企操作手册模板组织,登录页引用 00-login.jpg,包含图X截图引用;先生成操作手册 draft,再保守去 AI 味生成最终 md 和 docx;最后生成 08.application-info/${SOFTWARE_NAME}_软著申请表信息.txt,包含硬件环境、操作系统、开发工具、运行平台、支撑环境、编程语言、源程序量、开发目的、面向领域、主要功能和技术特点。"
},
{
"id": 2,
"prompt": "我只有软件名称“AI 合同风险审查系统”,请按软著申请材料要求扩展出规格说明、操作手册和代码文档,没有公开资料就按合理业务设定继续。",
"expected_output": "spec 明确区分公开资料和扩展设定;生成 10 个核心功能模块且每个模块有 3-5 个功能点;先按合同风险审查场景推荐 finance-risk-terminal 等匹配风格并等待确认;默认生成 00-login、10 个模块首页和 40%-60% 功能点自包含 HTML 原型并截图,视觉上不是普通后台页面;代码文件按 01-模块名称.txt 命名,使用 cloc 统计确认 comment + code 总行数不少于 4000 行,中文注释率不少于 10%,且同时包含 Java 后端和 React 前端;代码文件和代码 docx 不含空行和 copyright 字样;代码 docx 为 60 页代码页、每页 50 行、宋体小五、左对齐、单倍行距,页眉包含软件名称和版本号,右上角有页码,最后一页为程序结束页;最后生成 08.application-info/${SOFTWARE_NAME}_软著申请表信息.txt,源程序量来自 cloc 或明确说明的备用统计口径,技术特点从指定软件类型中单选;缺少 Playwright、浏览器或模板时清楚说明。"
},
{
"id": 3,
"prompt": "根据现有 reference/style.md 和 refrence/操作手册模版.docx,为“校园资产巡检管理软件”制作软著操作手册和源代码 docx。",
"expected_output": "读取本地风格文件和 Word 模板,先按校园资产巡检场景推荐 education-campus-portal、enterprise-data-station 或 mobile-business-console 等风格并等待确认,生成登录页、模块首页与复杂功能点 HTML 原型、batch.json、截图图片引用、下方居中图注、操作手册 draft、保守去 AI 味后的操作手册 docx、07.code.full/${SOFTWARE_NAME}_代码.docx 和 08.application-info/${SOFTWARE_NAME}_软著申请表信息.txt。"
}
]
}
Material Writing Rules
Use these rules when writing the spec, modules, prompts, source-code files and operation manual.
01.spec/spec.md
Recommended structure:
# {SOFTWARE_NAME} 软件规格说明
## 1. 编写说明
## 2. 资料来源与事实边界
## 3. 软件概述
## 4. 目标用户与使用场景
## 5. 总体业务流程
## 6. 角色与权限
## 7. 系统架构
## 8. 核心功能总览
## 9. 数据对象与业务口径
## 10. 非功能要求
## 11. 申请材料扩展设定Rules:
- Separate verified public facts from inferred product design.
- Use stable module names that can flow into manual chapters and code files.
- Prefer specific business verbs: submit, review, dispatch, reconcile, archive, export, remind, verify.
- Avoid pure marketing claims such as "industry-leading" unless a cited public source supports them.
- Because this is a user-visible application material, do not write internal labels such as
模块01,模块 01,01模块or功能点01. Use the real module/function name and normal chapter numbering instead.
02.modules/*.md
Each module file should use:
# 01. 模块名称
## 模块定位
## 使用角色
## 功能点清单
## 入口与前置条件
## 主流程
## 输入与输出
## 核心数据
## 业务规则
## 异常与边界
## 权限与审计
## 界面要点
## 代码生成提示Rules:
- Module count is exactly 10.
- Each module must include exactly 3 to 5 function points under
## 功能点清单. - Each function point should have a short name and 1 to 2 sentences explaining user action, system behavior and output.
- Select modules that show business complexity, not merely "首页", "设置", "帮助".
- Each module should contain enough logic to generate Java and React code.
- Keep names consistent across all later files.
Function point format:
## 功能点清单
1. 功能点名称:说明用户如何触发、系统如何处理、产生什么结果。
2. 功能点名称:说明用户如何触发、系统如何处理、产生什么结果。
3. 功能点名称:说明用户如何触发、系统如何处理、产生什么结果。03.prototype.style/selection.md
Before writing prototype HTML or image prompts, read prototype-ui-style.md, recommend the best style for the software type, ask the user to confirm, and save the confirmed choice:
03.prototype.style/selection.mdThe selected style id must be one of:
custom-command-system
gov-service-light
enterprise-data-station
industrial-iot-cockpit
medical-research-clean
education-campus-portal
finance-risk-terminal
mobile-business-consoleUse the confirmed style consistently across login, module overview pages and function-point pages. Local style.md can adjust colors or brand details, but should not replace the confirmed style selection.
03.prototype.html/*.html
Default prototype mode is HTML screenshot. Read prototype-ui-style.md before writing these files. Each HTML file should be self-contained and use:
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="prototype-style" content="{confirmed_style_id}">
<meta name="module" content="02.modules/01.md">
<title>页面名称</title>
<style>...</style>
</head>
<body>
...
<script>
const mockData = [...];
</script>
</body>
</html>HTML prototype rules:
- Generate
00-login.htmlfor the login page, 10 module overview files01.htmlto10.html, and dedicated function-point files named模块编号-功能点编号.htmlfor40%-60%of all function points. - The login page is a required screenshot source for the manual and must not be replaced by a module screenshot.
- Match each file to the same-numbered module.
- Use inline CSS and inline JavaScript only.
- Use the confirmed style from
03.prototype.style/selection.mdand the matching guidance in prototype-ui-style.md. - Do not use CDN, external images, remote fonts or backend requests.
- Include realistic mock data: names, dates, statuses, counts, amounts, organization names and records that fit the software domain.
- Design for a 1920x1080 screenshot. Keep the primary UI within the first viewport.
- Use stable dimensions for nav, toolbar, cards, tables, forms and dialogs.
- Avoid text overlap, tiny unreadable labels, empty panels and decorative-only layouts.
- Follow local
style.mdwhen available. - Do not produce a generic admin page, default dashboard, plain card table, Ant Design clone or Bootstrap-like CRUD screen. Do not force every software type into the dark command-system style.
- All buttons, fields, menus, status labels and prompts later described in the operation manual must be visible in the corresponding HTML screenshot. This consistency requirement belongs here, before screenshots are captured.
- Prefer dedicated function-point screenshots for complex flows, data-entry forms, chart dashboards, monitoring/alert pages, audit handling, export/report features and configuration screens.
03.prototype.prompt/*.md
Use this only when the user selects Gemini/API image generation mode. Read prototype-ui-style.md first. Generate 00-login.md, module overview prompts 01.md to 10.md, and function-point prompts named 模块编号-功能点编号.md for 40%-60% of all function points.
Prompt structure:
# Prototype Prompt 01
目标文件: 04.prototype/01.jpg
对应模块: 02.modules/01.md
界面名称: ...
## 生成提示
...Prompt content must specify:
- 16:9, 1920x1080, clean product UI screenshot.
- Chinese UI labels.
- The screen's role, selected menu item, main workflow state and primary data.
- Exact layout: navigation, toolbar, filters, table/form/chart/detail panel, modal or status drawer when needed.
- The user-confirmed style id and visual language from
03.prototype.style/selection.md, not a generic admin dashboard. - Visual style from local
style.mdif present. - No text overlap, no unreadable tiny text, no meaningless placeholder blocks.
05.code/*.txt
Each file should be named with its module number and module name, such as 01-用户权限管理.txt. Do not use bare names like 01.txt.
Each file should contain:
// 模块: 01. 模块名称
// 说明: ...
===== Java Backend =====
...
===== React Frontend =====
...Code style:
- Make the Java code concrete: a controller, service-like class, DTO, enum or repository stub can appear in the same file when useful.
- Include validation, state transitions, permission checks, duplicate checks, calculations, sorting, filtering or audit logging where the module needs them.
- Keep React code as a real component with state, effects, event handlers and conditional rendering.
- Do not over-abstract into many empty layers.
- Keep the comment ratio no lower than 10%, calculated from
clocas comment lines divided bycomment + codelines. - Use Chinese comments to explain business intent, validation rules, state transitions, permission checks and non-obvious logic. Do not pad the ratio with meaningless line-by-line comments.
- It is acceptable that the code is illustrative rather than directly compilable, but it must look like source code, not pseudocode.
- Confirm line counts with
cloc --by-file --force-lang=JavaScript,txt 05.code; usecomment + codeas the non-empty line count and excludeblank. - The 10 files under
05.code/must contain at least 4000clocnon-empty lines in total after removing blank lines. - Target 380-520 non-empty lines per code file; complex modules may be longer when needed. Do not pad the line count with blank lines, meaningless duplicate code or comments that do not explain real business logic.
- Do not leave blank lines in
05.code/*.txt. - Do not use the word
copyrightanywhere in source-code files, regardless of case.
06.manual/*_操作手册.md
Use operation-manual-template.md as the detailed template. The shorter structure below is only a navigation summary.
# {SOFTWARE_NAME} V1.0
## 1、系统阐述
### 1.1、系统说明
### 1.2、主要功能
### 1.3、目标用户
### 1.4、术语定义
### 1.5、软件开发目的
## 2、实操指引
### 2.1、登录
### 2.2、模块名称
#### 2.2.1、子功能名称Manual style:
- Write for end users: "点击", "选择", "输入", "提交", "查看", "导出".
- Use the formal project-delivery style, but blend function description, operation path, field explanation, button explanation, operation flow, system feedback and exceptions into natural paragraphs.
1.4、术语定义must keep a structured术语:定义shape, such as个体识别:指通过图像特征提取与比对技术,唯一标识每只种鹅的身份信息。The example only shows the format; generated manuals must replace it with software-specific business or technical terms. Do not turn this section into continuous natural prose.- After drafting the manual, apply operation-manual-humanizer.md as a conservative professional humanizing pass. This should reduce formulaic AI phrasing without making the manual casual.
- Preserve software names, module names, function names, UI labels, button names, field names, status tags, prompts, figure captions and image paths exactly during the humanizing pass.
- Do not expose internal file labels or planning labels such as
模块01,模块 01,01模块,功能点01,第01功能点. Use business names such as用户权限管理and告警处置instead. - Do not write rigid labels such as
页面内容说明:,页面区域说明:,功能说明:,操作前提:,字段说明:,按钮说明:,操作步骤:,操作过程:,预期结果:or异常提示:in final manual text. - The rigid-label rule does not apply to
1.4、术语定义; that section should intentionally use术语:定义entries. - Avoid filling the body with repeated numbered or lettered lists. Keep numbering mainly in headings; use 2-4 connected paragraphs for each function point.
- Include one image per module when available.
- Reserve figure captions as
图X 页面名称, and keep figure numbers increasing from图1. - Convert business rules into operation tips, not implementation details.
- Keep paragraphs short and procedural.
- Use tables for field descriptions only when they improve clarity.
- Each main module must include 3-5 child function sections derived from
## 功能点清单. - The manual should include login screenshot, every module overview screenshot, and dedicated function-point screenshots for
40%-60%of all function points. Prefer screenshots for complex and data-heavy functions. - Each function point explanation should cover page content, operation prerequisites, operation process, expected result and common exception prompts in prose, without rigid subsection labels.
Image insertion:


The image alt text is the figure caption. In Word output it must appear below the image and centered.
07.code.full/${SOFTWARE_NAME}_代码.docx
The code document should:
- Contain exactly 60 code pages.
- Use 50 source lines per page, with no blank source lines.
- Use SimSun/宋体, 9 pt (小五), left alignment and single line spacing.
- Put the applied software name and version in the header exactly as they appear in the application form.
- Put page numbers in the upper-right header area.
- Preserve module order in the source stream.
- For source streams longer than 60 pages, use the first 30 pages and last 30 pages so the final page is the program ending page.
- Avoid adding explanatory prose, cover pages, directories, module heading pages or generated-material notes between code lines.
- Do not add generated-material explanations such as
软件源代码文档or本文档由 05.code 目录下的核心业务代码文件合并生成. - Do not include the word
copyrightanywhere in the code document.
08.application-info/${SOFTWARE_NAME}_软著申请表信息.txt
Generate this final text file after the manual and code document are complete. It supports application-form entry, so keep it concise and directly fillable rather than explanatory.
- Use the field order defined in
SKILL.mdStep 9. - Remove parenthetical prompt text from the final file.
- Keep hardware environments, operating systems, development tools, runtime support, development purpose and target industry within 50 characters each.
- Keep the main function summary within 200 Chinese characters.
- Pick exactly one software type from the Step 9 list, then describe technical characteristics within 100 Chinese characters.
- Set
源程序量fromcloccomment + codetotals for05.codewhen available; otherwise use the nonblank generated source-line count and record that fallback in the delivery note.
NCAC 2025 Copyright Policy Reference
Primary source:
- 国家版权局:《关于加快推进版权事业高质量发展的意见》,国版发〔2025〕2号,发布页日期 2025-07-23。
- URL: https://www.ncac.gov.cn/xxfb/tzgg/202507/t20250723_923374.html
How to Use This Reference
When creating software copyright application materials, this source is not a form template and does not replace the rules in software-registration-rules.md. Use it as policy background for copyright protection, registration standardization and software legalization. Prefer reading the live URL during execution; use this file only as a fallback or summary.
Relevant Points for Software Copyright Materials
- The policy emphasizes improving copyright creation, use, protection, management and services.
- It calls for a more standardized national copyright registration system, including computer software copyright registration.
- It mentions unified registration standards, standardized registration processes, better registration quality and online handling.
- It highlights copyright protection in emerging fields such as blockchain, big data, cloud computing and artificial intelligence.
- It includes continued promotion of genuine software use and governance mechanisms for software legalization.
Writing Implications
For 01.spec/spec.md:
- Make the software boundary, functions, data objects and original business logic clear.
- Avoid vague descriptions that cannot support registration review.
- Keep module names and documentation consistent.
For the operation manual:
- Show that the software can be operated as a concrete product, not just a concept.
- Use screenshots and step-by-step instructions to demonstrate functional completeness.
For source-code materials:
- Provide source-like code with stable module ordering and visible business logic.
- Do not use placeholder-only snippets that fail to show software expression.
Citation Note
If mentioning this source in generated materials, cite it briefly as a policy reference from the National Copyright Administration of China. Do not overstate it as a software registration application rule or a substitute for the current requirements of the Copyright Protection Center of China.
Operation Manual Humanizer
Use this reference after drafting 06.manual/${SOFTWARE_NAME}_操作手册.draft.md and before writing the final 06.manual/${SOFTWARE_NAME}_操作手册.md.
This is a conservative, professional humanizing pass for software copyright operation manuals. It borrows the useful parts of Humanizer-zh: remove formulaic AI phrasing, reduce inflated claims, vary sentence rhythm and avoid repeated templates. Do not make the manual casual. The final document should still read like a government or enterprise delivery document.
Goal
Rewrite the draft so it sounds like a careful technical writer prepared it from the actual screens. The degree of rewriting should be moderate:
- Keep the document structure, chapter headings, image links and business coverage stable.
- Keep
1.4、术语定义as structured术语:定义entries; improve wording only inside the definition text when necessary. - Rewrite repetitive paragraphs, stiff transitions and obvious template traces.
- Preserve professional wording, legal/material formality and operational clarity.
- Do not add product features, buttons, fields, screenshots or outcomes that are not present in the modules or prototypes.
Protected Text
Do not change these strings unless the draft contains an obvious typo and the same corrected wording is already used in the module or prototype:
- Software name and version.
- Module names and function names from
02.modules/*.md. - UI labels from prototypes, including menu names, tab names, button names, field names, status tags and prompt text.
- Any text inside Chinese corner quotes such as
「新增」,「保存」,「告警中心」. - Figure captions and image paths, for example
. - Chapter numbers and heading hierarchy, such as
### 2.4、告警中心and#### 2.4.2、告警处置. - Legal and application-material terms, such as
计算机软件著作权登记,操作手册,源程序,鉴别材料. - Numbers, dates, percentages, filenames and directory paths.
Before rewriting, make a protected glossary from headings, image markdown, corner-quoted UI labels, code spans, module names and function names. After rewriting, check that protected strings still appear unchanged.
What To Rewrite
Reduce these AI-like patterns:
- Repeated sentence frames, especially every function starting with
用户点击...后,系统进入...页面. - Overly complete but hollow statements, such as
便于用户完成全流程闭环管理. - Inflated adjectives, such as
全面,显著,高效,智能化,一体化,多维度, when they do not add concrete operational meaning. - Mechanical triads, such as
查询、录入、审核、导出repeated in every section. - Stacked transitions, such as
同时,此外,进一步,通过以上操作. - Hard labels in the body, such as
页面内容说明:,操作过程:,预期结果:,异常提示:.
The 术语:定义 entries under 1.4、术语定义 are not considered hard labels and should not be merged into natural paragraphs.
How To Rewrite
Use a restrained editorial pass:
- Prefer concrete screen language: say what the page shows and what the user does.
- Keep paragraphs short, usually 2 to 5 sentences.
- Vary openings across adjacent sections. Use
进入页面后,在列表中,选择记录后,完成填写后,系统校验通过后as needed, but avoid repeating the same sequence everywhere. - Merge field, button, result and exception information into the operation narrative.
- Replace vague claims with observable results: list refreshes, status changes, export file appears, processing record is added, validation message is shown.
- Keep formal terms, but remove unnecessary intensifiers.
- If a sentence sounds promotional, make it operational.
Do Not Do
- Do not use first person, jokes, casual commentary or colloquial filler.
- Do not simplify the manual into a blog-like explanation.
- Do not change exact UI text, button names, field names, module names or figure captions.
- Do not change screenshot paths.
- Do not rewrite
1.4、术语定义into continuous prose; keep one structured term entry per term. - Do not remove required coverage for login, module pages or function-point pages.
- Do not add unsupported success messages. If the screenshot or module does not contain the text, use a generic but professional description such as
系统显示处理结果并刷新列表.
Examples
Before:
用户点击功能区的「新增」按钮后,系统进入新增页面。页面内容说明:页面上方展示筛选条件,页面中部展示数据列表。预期结果:操作成功后系统显示保存成功并刷新列表。异常提示:必填信息未填写时系统显示提示。After:
用户在列表上方点击「新增」后,系统打开新增窗口。窗口中包含基础信息、业务属性和备注说明等内容,用户按页面要求填写后点击「保存」。系统会先校验必填项和重复数据,校验通过后提示处理完成,并将新增记录显示在列表中;如果信息缺失或用户权限不足,页面会在对应位置给出提示,用户可补充后重新提交。Before:
该模块可以全面提升业务处理效率,实现数据的统一管理和高效流转。After:
该模块用于集中维护业务记录,用户可在同一页面完成查询、录入、状态确认和结果查看。Final Check
The final manual must pass:
python {baseDir}/scripts/validate_outputs.py \
--manual-draft-md "06.manual/${SOFTWARE_NAME}_操作手册.draft.md" \
--manual-md "06.manual/${SOFTWARE_NAME}_操作手册.md"If validation reports missing protected terms, restore the exact original term and adjust only surrounding prose.
Operation Manual Template
Use this template when generating 06.manual/${SOFTWARE_NAME}_操作手册.md. The output should read like a formal government or enterprise project delivery document, and should be ready to convert into Word.
Writing Style
- Use a formal, standardized product manual tone.
- Prefer the pattern: function description → operation path → field explanation → button explanation → user steps.
- Every primary function module must describe purpose, page entry, key indicators, buttons and actual use flow.
- Function sections should not be too short. Explain what the user sees, why the operation matters, what should happen after the operation, and what common exception messages mean.
- Use numbered headings such as
1、,1.1、,2.2.1、only for chapters and sections. Avoid repeated numbered lists inside every function; body text should be natural paragraphs. 1.4、术语定义is an exception to the natural-paragraph rule: write structured term entries in术语:定义format.- Reserve screenshot positions for every main page and important child page using
图X 页面名称. - Write for operators, not developers. Avoid database, API and implementation details unless they directly affect user operation.
- Keep module names, button names and field names consistent with
02.modules/*.md,03.prototype.html/*.htmlor03.prototype.prompt/*.md, and screenshots. - Do not expose internal numbering labels from source files. Avoid wording such as
模块01,模块 01,01模块,功能点01and第01功能点; use the real module/function name with normal chapter numbering. - Do not use rigid labels such as
页面内容说明:,页面区域说明:,功能说明:,操作前提:,字段说明:,按钮说明:,操作步骤:,操作过程:,预期结果:or异常提示:in the final manual. These aspects must be blended into regular paragraphs. This restriction does not prohibit structured术语:定义entries in1.4、术语定义. - After the draft is complete, apply operation-manual-humanizer.md. Keep the tone formal and professional; only reduce repetitive AI-like phrasing and template traces. Do not change any software name, module name, UI label, button, field, status tag, prompt, figure caption or image path.
Required Structure
# {SOFTWARE_NAME} V1.0
## 1、系统阐述
### 1.1、系统说明
[说明系统面向的业务场景、建设目标、核心技术、解决的问题和总体价值。]
### 1.2、主要功能
[按模块列出系统主要功能,每个功能写一段说明。]
### 1.3、目标用户
[说明管理人员、业务人员、技术员、运维人员、科研人员等角色如何使用系统。]
### 1.4、术语定义
[以“术语:定义”的结构化条目解释系统中的重要业务术语和技术术语。]
### 1.5、软件开发目的
[说明业务价值、管理价值、技术价值和建设必要性。]
## 2、实操指引
### 2.1、登录
[说明登录入口、账号密码填写、验证码或组织选择、点击按钮、登录成功后的页面效果。]

### 2.2、{一级功能模块名称}
[先用一段话说明该模块的总体作用。]
用户在导航栏进入「{一级功能模块名称}」后,页面上方展示{字段A}、{字段B}、{字段C}等核心指标,便于用户快速判断当前业务状态;页面中部以列表、图表或业务面板展示{数据对象},右侧或底部展示详情、处理记录和系统提示。用户可根据{筛选条件}缩小范围,再通过页面中的主要按钮完成查询、录入、审核、导出或状态处理。

#### 2.2.1、{子功能名称}
用户点击功能区的「{子功能名称}」按钮后,系统进入{子功能名称}页面。该页面主要用于{业务作用},适用于已登录且具备{角色或权限}权限的用户;在操作前,用户需要确认系统中已有可处理的{业务对象},或已准备好需要录入、核对、审批、导出的业务信息。
进入页面后,顶部显示当前筛选条件、状态标签和常用操作按钮,中部展示{数据对象}列表、统计卡片或趋势图,右侧或底部显示选中记录的详情、处理记录和校验提示。{字段A}用于标识……,{字段B}用于反映……,{字段C}用于判断……,用户可结合这些字段完成核对和筛选。
用户可先按{筛选条件}查询目标数据,再点击「{按钮A}」进行{主要动作};系统会检查必填项、格式、重复数据和权限范围,校验通过后显示“保存成功”“提交成功”或相应业务提示,并刷新列表、更新状态标签或生成处理记录。需要批量处理时,用户可点击「{按钮B}」按当前筛选条件执行{辅助动作},完成后可在页面结果区查看导出文件、审批结果、告警处置记录或计算结果。
若用户未填写必填信息、选择了无效数据、权限不足或当前条件下暂无记录,系统会在字段旁、弹窗或页面右上角显示明确提示。用户应根据提示补充信息、调整筛选条件、重新选择记录或联系管理员处理。
Section Rules
1、系统阐述
1.1、系统说明 should include:
- Business scenario.
- Construction goal.
- Core technology or architecture in user-facing language.
- Problems solved.
- Overall value.
1.2、主要功能 should list the 10 modules from 02.modules, using this format:
{模块名称}用于{一段功能说明}。
{模块名称}用于{一段功能说明}。1.3、目标用户 should describe roles and purposes:
管理人员主要用于查看全局数据、审批关键流程并掌握系统运行状态。业务人员主要用于录入、处理、查询和导出日常业务数据。运维人员主要用于维护基础配置、查看运行异常并处理系统告警。1.4、术语定义 should use concise structured entries. Use one entry per term in 术语:定义 format, with a full-width colon :. Keep entries short, but do not force lettered numbering, tables or a continuous natural-language paragraph:
{术语}:指{定义}。
{术语}:{定义}。Examples of the expected shape:
个体识别:指通过图像特征提取与比对技术,唯一标识每只种鹅的身份信息。
行为分析:基于视频序列对种鹅的活动模式(如采食、饮水、交配、踱步、静卧等)进行自动分类与量化。When generating a manual for other software, replace the example terms with software-specific business and technical terms. Do not rewrite this section as a fully natural prose explanation.
1.5、软件开发目的 should cover:
- Why the software was developed.
- What management problem it solves.
- What efficiency, quality or traceability value it creates.
- What technical value it provides.
2、实操指引
2.1、登录 should include:
- Login address or entry.
- Account, password and optional verification input.
- Login button behavior.
- Successful login effect.
- Failed login tips.
For each module section:
- Start with one paragraph explaining the module's overall role.
- When the page has statistics, filters, status tags, list columns or card metrics, explain them in the opening paragraph instead of adding a separate rigid label.
- Add a screenshot caption and image link.
- Expand the module's 3-5 function points as child sections.
For each child function section:
- Use 2-4 natural paragraphs instead of rigid subheadings.
- Blend in where to click, what page opens, what the user sees, which fields matter, which buttons are available, and what business problem the function solves.
- Explain the operation flow in prose: user action, system validation, success feedback, data refresh, status change, file output or processing record.
- Mention common exceptions in the same narrative: missing required fields, duplicate data, insufficient permissions, no query results or network errors.
- Screenshot caption:
图X 子功能名称.
Screenshot Rules
- Use increasing figure numbers from
图1. - The figure caption is the image alt text and must appear below the image, centered, after Word conversion.
- Every main module should reference one prototype image if available.
- Cover
40%-60%of all function points with dedicated screenshots, in addition to login and module overview screenshots. - Prioritize screenshots for complex workflows, data-entry forms, chart/data visualization functions, monitoring/alert pages, audit handling, report export and configuration pages.
- Image markdown must use paths relative to the manual file:
- If a function point has no separate screenshot, reuse the module screenshot and write the caption for the visible area.
2.1、登录must use../04.prototype/00-login.jpg; never reuse01.jpgor another module screenshot as the login page.- Any button, field, tab, menu or prompt mentioned in the operation steps must be visible in the referenced screenshot. If it is not visible, revise the prototype first or remove the operation text.
Module Expansion Pattern
For 10 modules, write:
### 2.2、模块一名称
#### 2.2.1、功能点一
#### 2.2.2、功能点二
#### 2.2.3、功能点三
### 2.3、模块二名称
#### 2.3.1、功能点一
#### 2.3.2、功能点二
#### 2.3.3、功能点三Continue until all modules from 02.modules/01.md to 02.modules/10.md are covered.
Language Patterns
Use these phrases:
用户在左侧导航栏点击「{模块名称}」,进入{模块名称}页面。页面上方为条件筛选区,用户可按{字段}进行查询。页面中部为信息展示区,系统按列表形式展示{数据对象}。页面右侧展示当前选中记录的详情信息,便于用户核对处理结果。用户完成填写后,系统会对必填项、格式和重复数据进行校验。点击「新增」按钮后,系统弹出新增窗口,用户填写必填信息后点击「保存」。点击「导出」按钮后,系统按照当前筛选条件生成文件。操作完成后,系统在页面右上角显示处理结果,并刷新列表数据。若查询条件下暂无数据,系统显示空状态提示,用户可调整筛选条件后重新查询。
Avoid these patterns:
- Do not say "此处展示截图" without an image or figure number.
- Do not copy module analysis sections such as "代码生成提示" into the manual.
- Do not use developer-only wording such as "调用接口", "写入数据库", "返回 JSON".
- Do not leave generic placeholders like "字段A" in final output; replace them with software-specific fields.
- Do not write internal labels such as "模块01", "模块 01", "01模块", "功能点01" or "第01功能点" in headings, paragraphs, captions or tables.
- Do not write rigid labels such as "页面内容说明:", "页面区域说明:", "功能说明:", "操作前提:", "字段说明:", "按钮说明:", "操作步骤:", "操作过程:", "预期结果:" or "异常提示:".
- Do not fill the body with repeated
1、2、3、ora)、b)、c)lists. Keep numbering mainly in headings and use connected paragraphs for operation details.
Prototype UI Style
Use this reference when generating 03.prototype.html/*.html or 03.prototype.prompt/*.md.
The prototype should look like a bespoke software system made for the target industry. Do not default every project to the same dark cockpit UI. First infer the software type, recommend the best style, ask the user to confirm, and generate all prototype pages with the confirmed style.
Style Confirmation Workflow
Before creating 03.prototype.html/*.html or 03.prototype.prompt/*.md:
1. Read 01.spec/spec.md, the 10 files in 02.modules/, and any local reference/style.md. 2. Infer the software category from the software name, business objects, user roles and main workflows. 3. Recommend one best style and 1-2 alternatives. Explain the reason in 2-4 short sentences. 4. Ask the user to confirm the style before generating prototypes. Do not proceed to prototype generation until the user confirms, unless the user has explicitly requested unattended generation; in that case use the recommended style and record the reason. 5. After confirmation, create:
03.prototype.style/selection.mdUse this format:
# Prototype Style Selection
软件名称: {SOFTWARE_NAME}
推断类型: {software_category}
推荐风格: {style_id} - {style_name}
用户确认: {confirmed_style_id} - {confirmed_style_name}
本地风格参考: {style.md path or 未提供}
## 推荐理由
...
## 生成约束
- All HTML pages use `<meta name="prototype-style" content="{confirmed_style_id}">`.
- All prompts explicitly mention `{confirmed_style_name}` and its visual tokens.
- Local `style.md` is treated as an overlay, not as permission to ignore the confirmed style.
- Prototype screens must not include copyright notices, developer company names, technical support units, producer names, contractor names or similar company attribution.Style Library
custom-command-system - High-End Command System
Best for: public safety, monitoring, emergency dispatch, IoT supervision, environmental monitoring, logistics command, risk warning, smart city and systems that need a strong command-center feel.
Visual language:
- Deep navy or blue-black full-screen canvas.
- Angular technical frame, top title/navigation strip and dense command workspace.
- Cyan, teal and amber accent lights with luminous borders.
- Tables, maps, alarms, status chips, KPI panels and right-side detail drawers.
gov-service-light - Formal Government Service System
Best for: government affairs, approval handling, public service, administrative licensing, civil service, government reporting and formal institutional systems.
Visual language:
- Clean white and light-blue page background with restrained official blue accents.
- Strong header, breadcrumb-like path, clear module tabs and policy-style information grouping.
- Dense but readable forms, approval timelines, official status seals and document preview areas.
- Minimal decoration; the page should feel reliable, organized and suitable for Word screenshots.
enterprise-data-station - Enterprise Operations Workbench
Best for: CRM, ERP, asset management, inventory, contract management, procurement, inspection, after-sales, office workflow and general enterprise operations.
Visual language:
- Neutral light or soft dark workspace with compact top toolbar and data-first layout.
- Balanced table, filter, detail drawer and task panel composition.
- Subtle blue/green status indicators, clear action hierarchy and practical information density.
- Avoid decorative hero styling; this should feel like a daily work system.
industrial-iot-cockpit - Industrial IoT Cockpit
Best for: equipment monitoring, production scheduling, energy management, warehouse automation, safety inspection, sensor data and plant operations.
Visual language:
- Dark steel, graphite and electric cyan/orange accents.
- Equipment topology, sensor cards, trend charts, alarm strips and device state tables.
- Strong grid structure with left summary rail and central monitoring area.
- Use technical depth, but keep field labels and operation buttons readable.
medical-research-clean - Medical / Research Clean Terminal
Best for: medical management, laboratory systems, clinical data, health follow-up, research sample management and life-science workflows.
Visual language:
- Clean white, pale teal, blue-green and soft gray palette.
- Patient/sample cards, timeline panels, risk tags, record tables and structured forms.
- Clinical clarity over decoration; use generous spacing and calm status colors.
- Avoid overly futuristic dark visuals unless the user explicitly wants a command center.
education-campus-portal - Campus Service Portal
Best for: campus asset inspection, teaching affairs, student services, dormitory management, training platforms and school operations.
Visual language:
- Fresh blue, green and white palette with a friendly but still formal interface.
- Campus map/list split views, timetable-like panels, role-based cards and task lists.
- Softer corners and clearer navigation, without becoming a marketing site.
- Suitable for education administrators and campus operation staff.
finance-risk-terminal - Finance Risk Terminal
Best for: audit, finance, contract risk, credit review, compliance, invoice control, banking-like workflows and analysis-heavy systems.
Visual language:
- Dark graphite or deep ink background with restrained gold, cyan or red risk accents.
- Risk score cards, review queues, evidence panels, comparison tables and audit trail timelines.
- Conservative, precise and data-heavy; avoid flashy cyberpunk styling.
- Button labels and risk status must be highly legible.
mobile-business-console - Mobile / Field Work Console
Best for: mobile-first systems, field inspection, delivery, patrol, maintenance, collection, on-site reporting and apps where screenshots need to show mobile or tablet behavior.
Visual language:
- 1080p screenshot can show a tablet or mobile shell inside a clean operational background.
- Large touch controls, bottom actions, scan/upload widgets, GPS/task status and photo evidence blocks.
- Use mock device dimensions with stable layout; do not make a generic phone app landing page.
Category Recommendation Hints
- Software names containing
监管,监测,预警,指挥,态势,调度,应急,安防,物联网usually fitcustom-command-systemorindustrial-iot-cockpit. - Names containing
设备,传感,生产,能耗,仓储,巡检,工厂usually fitindustrial-iot-cockpit; campus or office inspection can fitenterprise-data-stationoreducation-campus-portal. - Names containing
政务,审批,公共服务,申报,档案,民政,住建usually fitgov-service-light. - Names containing
合同,审查,风控,审计,财务,发票,合规,授信usually fitfinance-risk-terminal. - Names containing
医院,医疗,健康,检验,实验,样本,科研usually fitmedical-research-clean. - Names containing
校园,教学,学生,教务,培训,宿舍usually fiteducation-campus-portal. - Names containing
客户,资产,库存,采购,工单,协同,办公usually fitenterprise-data-station. - Names containing
移动,外勤,巡检,采集,拍照,上报,现场may fitmobile-business-console, especially when the user wants mobile screenshots.
When multiple styles fit, recommend the one that best matches the most important workflow and list alternatives. For example, 校园资产巡检管理软件 can be education-campus-portal if the focus is campus service, or enterprise-data-station if the focus is asset ledger and work orders.
Mandatory HTML Markers
Every HTML prototype must include the confirmed style id:
<meta name="prototype-style" content="{confirmed_style_id}">Every module page must include:
<meta name="module" content="02.modules/01.md">The login page uses:
<meta name="module" content="login">Allowed prototype-style values are:
custom-command-system
gov-service-light
enterprise-data-station
industrial-iot-cockpit
medical-research-clean
education-campus-portal
finance-risk-terminal
mobile-business-consoleShared Layout Rules
Use the confirmed style, but keep these rules for every style:
- The system title must be prominent and domain-specific.
- Navigation, active tab, user/status area and primary action buttons must be visible.
- Screenshots must show operable product depth: filters, tables, forms, charts, status tags or detail panels.
- All buttons, fields, menus, status labels and prompts later described in the operation manual must be visible in the corresponding screenshot.
- Design for a 1920x1080 screenshot. Keep the primary UI within the first viewport.
- Use stable dimensions for nav, toolbar, cards, tables, forms and dialogs.
- Avoid text overlap, tiny unreadable labels, empty panels and decorative-only layouts.
- Do not use CDN, external images, remote fonts or backend requests.
- Do not use default Ant Design / Element / Bootstrap visual language.
- Do not place copyright notices, developer company names, technical support units, producer names, contractor names or similar company attribution anywhere in the prototype, including footer areas, login pages, title bars, watermarks and about dialogs.
- Show only the software name, business modules, roles, mock data and operation controls.
Login Rules
Every style needs a real login screenshot:
- Show the software name prominently.
- Include visible account, password, optional organization/code fields, remember-login checkbox and login button.
- Use the confirmed style's visual language.
- Do not use a generic centered white card unless the confirmed style is a formal light style and the card has domain-specific framing and details.
Local style.md
If local reference/style.md, refence/style.md or refrence/style.md exists:
- Treat it as an overlay for color, typography, spacing or brand preference.
- Still recommend and confirm one of the style ids above.
- If the local style conflicts with readability, screenshot quality or the confirmed style, prioritize readability and copyright-material usefulness.
Manual Consistency Rule
Before writing the operation manual, compare each module's intended operation text with its screenshot.
If the manual says:
- 点击「新增」
- 点击「保存」
- 选择「设备类型」
- 查看「状态」
- 点击「导出」
- 切换至「告警中心」
Then the corresponding screenshot must visibly contain these exact or very close labels. If it does not, revise the HTML prototype first.
Quality Bar
The final screenshot should pass these checks:
- At first glance it reads as a custom system matching the software type, not a generic backend.
- The confirmed style is consistent across login, module overview pages and function-point pages.
- Key operation buttons are visible.
- Field names and table headers are readable at 1920x1080.
- No text overlaps borders, icons or adjacent controls.
- The image can be inserted into a Word manual without looking like a placeholder.
Software Registration Rules
Use this reference when writing 01.spec/spec.md, operation manuals and source-code materials for Chinese computer software copyright registration.
Primary legal source:
- 《计算机软件著作权登记办法》,中华人民共和国国家版权局令第 1 号,2002-02-20 发布。
- Public mirror: https://www.sz.gov.cn/cn/xxgk/zfxxgj/zcfg/content/post_8965812.html
Hard Material Constraints
The application material set generally includes:
- Software copyright registration application form.
- Identification materials of the software.
- Relevant proof documents.
The software identification materials include:
- Program identification materials.
- Documentation identification materials.
For ordinary deposit:
- Source program and documentation are formed from the first and last continuous 30 pages.
- If the whole program or document is fewer than 60 pages, submit the whole program or document.
- Unless a special rule applies, each program page should have at least 50 lines, and each documentation page should have at least 30 lines.
Other relevant points:
- Registration software should be independently developed, or be an authorized derivative with important functional or performance improvements.
- Application documents should use Chinese; foreign-language certificates or proof documents should include Chinese translations.
- Application files use A4 paper format.
- Software name and right-holder names must stay consistent across application files unless supporting proof is provided.
Implications for This Skill
When the user has real code or manuals:
- Prefer extracting and formatting real materials instead of inventing replacement code.
- Preserve actual module names, version names and right-holder naming.
When the user only provides a software name:
- Generated code and manuals are draft application materials.
- State in
01.spec/spec.mdthat unverifiable content is an expanded business setting. - The applicant must verify the materials against the actual software before filing.
For 07.code.full/${SOFTWARE_NAME}_代码.docx:
- Keep module order stable and source-like.
- Include enough business logic to show software expression.
- Do not create placeholder-only source files.
- Generate 60 code pages when the source program is long enough, using the first 30 and last 30 pages under ordinary deposit practice.
- Each source-program page should contain at least 50 lines; this skill's code-docx script uses 50 lines per page.
- Use A4 source-code formatting: 宋体, 小五, left aligned, single line spacing.
- The header should show the applied software name and version exactly as in the application form, with page number at the upper right.
- The final source-program page should be the program ending page.
- Source code should not contain blank lines or the word
copyright.
For 06.manual/*_操作手册.docx:
- Use operation steps and screenshots to show a concrete operable product.
- Keep the document consistent with module names, screenshots and code files.
For 08.application-info/${SOFTWARE_NAME}_软著申请表信息.txt:
- Treat the file as concise application-form assistance, not a separate explanatory report.
- Keep software name, version, programming language, source-line count, functions and operating environments consistent with the generated specification, source code and document materials.
- Do not leave form hints or parenthetical instructions in the final text.
Workflow Contract
此文件定义 aj-copyright-writer 的产物约定。执行时以用户指定目录为准;用户未指定时创建独立输出目录,避免污染项目根目录。
Directory Layout
{output_root}/
01.spec/
spec.md
02.modules/
01.md
...
10.md
03.prototype.html/
00-login.html
01.html
...
10.html
01-01.html
...
03.prototype.style/
selection.md
03.prototype.prompt/
00-login.md
01.md
...
10.md
01-01.md
...
04.prototype/
batch.json
00-login.jpg
01.jpg
...
10.jpg
01-01.jpg
...
05.code/
01-模块名称.txt
...
10-模块名称.txt
06.manual/
{SOFTWARE_NAME}_操作手册.draft.md
{SOFTWARE_NAME}_操作手册.md
{SOFTWARE_NAME}_操作手册.docx
07.code.full/
{SOFTWARE_NAME}_代码.docx
08.application-info/
{SOFTWARE_NAME}_软著申请表信息.txtNumbering Rules
- Always use two-digit file names:
01to10. - Numbered directories must contain exactly the expected files:
02.modules:01.mdto10.md03.prototype.html:00-login.html,01.htmlto10.html, plus function-point pages in default HTML mode03.prototype.style:selection.mdrecording the recommended and confirmed prototype style03.prototype.prompt:00-login.md,01.mdto10.md, plus function-point prompts in image mode04.prototype:00-login.jpg,01.jpgto10.jpg, plus function-point screenshots05.code:01-模块名称.txtto10-模块名称.txt- Keep the same number across module, prompt, prototype image and code:
02.modules/03.md03.prototype.html/03.htmlor03.prototype.prompt/03.md04.prototype/03.jpg05.code/03-模块名称.txt- Function-point pages use
模块编号-功能点编号, for example03-02.html,03-02.md,03-02.jpg. - Every module needs a module overview screenshot. In addition, cover
40%-60%of all function points with dedicated screenshots. - Prioritize complex workflows, data-entry pages, chart/data visualization, monitoring/alert screens, audit flows, report export and configuration pages.
Batch Manifest
Prototype image generation uses 04.prototype/batch.json as a resumable manifest. Create or refresh it before taking screenshots or making API calls.
Each item must include:
{
"id": "00-login",
"mode": "html",
"module": "login",
"html": "/abs/path/03.prototype.html/00-login.html",
"output": "/abs/path/04.prototype/00-login.jpg",
"retry": {
"attempts": 0,
"max": 3
},
"status": "pending"
}In image mode, each item uses prompt instead of html. The manifest must contain 11 items: 00-login and 01 to 10. Allowed statuses: pending, running, success, failed. Re-running the prototype script should skip success items whose output file still exists, unless --force is passed.
Reference Directory Resolution
When the user mentions reference, refence or refrence, treat them as possible local reference directories. Search in this order:
1. {output_root}/reference 2. {output_root}/refence 3. {output_root}/refrence 4. {cwd}/reference 5. {cwd}/refence 6. {cwd}/refrence 7. ~/aj-skills/reference 8. ~/aj-skills/refence 9. ~/aj-skills/refrence
Do not rename the user's directories.
Required User-Visible Artifacts
The final answer should mention:
- Output root.
- Whether live web research succeeded.
- Whether
style.mdwas found. - Whether the manual template was found.
- Prototype generation mode:
htmlscreenshot orimageAPI. - Recommended prototype style, user-confirmed style and whether a user-provided
style.mdwas used as an overlay. - Whether the operation manual draft was humanized with the conservative copyright-manual humanizer.
- Whether docx files were generated.
- Whether
08.application-info/{SOFTWARE_NAME}_软著申请表信息.txtwas generated, and which line-count source was used for源程序量. - Any blocked step with the exact missing dependency, key or file.
Step Boundaries
Do not skip a numbered step silently. If a step cannot be completed:
1. Write the best possible upstream artifacts. 2. Stop before fabricating downstream artifacts that depend on the missing result. 3. Explain the blocker and the command or file needed to continue.
Examples:
- If
GEMINI_API_KEYis missing, still create prototype prompts, but do not create fake04.prototype/*.jpg. - If the Word template is missing, generate a docx without template and record that it is untemplated.
- If public information about the software is scarce, continue from reasonable assumptions and label them as assumptions.
#!/usr/bin/env python3
"""Build a source-code docx from numbered 05.code/*.txt files."""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
if str(SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPT_DIR))
from validate_outputs import ValidationError, validate_code_files, validate_file_exists
def import_docx():
try:
from docx import Document
from docx.enum.style import WD_STYLE_TYPE
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_BREAK, WD_TAB_ALIGNMENT
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Cm, Pt
except ImportError as exc:
raise SystemExit("Missing dependency: pip install python-docx") from exc
return Document, WD_STYLE_TYPE, WD_ALIGN_PARAGRAPH, WD_BREAK, WD_TAB_ALIGNMENT, OxmlElement, qn, Cm, Pt
def resolve_template(template: str | None, cwd: Path) -> Path | None:
if template:
path = Path(template).expanduser()
if path.exists():
return path
names = ["代码文档模版.docx", "代码文档模板.docx", "操作手册模版.docx", "操作手册模板.docx"]
roots = [
cwd / "reference",
cwd / "refence",
cwd / "refrence",
Path.home() / "aj-skills" / "reference",
Path.home() / "aj-skills" / "refence",
Path.home() / "aj-skills" / "refrence",
]
for root in roots:
for name in names:
candidate = root / name
if candidate.exists():
return candidate
return None
def set_run_font(run, qn, pt, font_name: str = "宋体", font_size: float = 9) -> None:
run.font.name = font_name
run.font.size = pt(font_size)
run._element.rPr.rFonts.set(qn("w:eastAsia"), font_name)
def ensure_code_style(document, style_type, align, qn, pt):
styles = document.styles
try:
style = styles["AJ Code"]
except KeyError:
style = styles.add_style("AJ Code", style_type.PARAGRAPH)
style.font.name = "宋体"
style.font.size = pt(9)
style._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体")
style.paragraph_format.alignment = align.LEFT
style.paragraph_format.line_spacing = 1
style.paragraph_format.space_before = pt(0)
style.paragraph_format.space_after = pt(0)
return style
def clear_body(document, qn) -> None:
body = document._body._element
for child in list(body):
if child.tag != qn("w:sectPr"):
body.remove(child)
def configure_page(document, cm) -> None:
section = document.sections[0]
section.page_width = cm(21)
section.page_height = cm(29.7)
section.top_margin = cm(1.8)
section.bottom_margin = cm(1.6)
section.left_margin = cm(2.0)
section.right_margin = cm(2.0)
section.header_distance = cm(0.8)
section.footer_distance = cm(0.8)
def clear_paragraph(paragraph) -> None:
paragraph._element.clear_content()
def add_page_number(paragraph, oxml_element, qn, pt) -> None:
run = paragraph.add_run("第 ")
set_run_font(run, qn, pt)
begin = oxml_element("w:fldChar")
begin.set(qn("w:fldCharType"), "begin")
instr = oxml_element("w:instrText")
instr.set(qn("xml:space"), "preserve")
instr.text = "PAGE"
separate = oxml_element("w:fldChar")
separate.set(qn("w:fldCharType"), "separate")
end = oxml_element("w:fldChar")
end.set(qn("w:fldCharType"), "end")
field_run = paragraph.add_run()
field_run._r.append(begin)
field_run._r.append(instr)
field_run._r.append(separate)
field_run._r.append(end)
run = paragraph.add_run(" 页")
set_run_font(run, qn, pt)
def configure_header(document, software_name: str, software_version: str, align, tab_align, oxml_element, qn, pt) -> None:
section = document.sections[0]
header = section.header
paragraph = header.paragraphs[0] if header.paragraphs else header.add_paragraph()
clear_paragraph(paragraph)
paragraph.paragraph_format.alignment = align.LEFT
paragraph.paragraph_format.line_spacing = 1
paragraph.paragraph_format.space_before = pt(0)
paragraph.paragraph_format.space_after = pt(0)
content_width = section.page_width - section.left_margin - section.right_margin
paragraph.paragraph_format.tab_stops.add_tab_stop(content_width, tab_align.RIGHT)
title_run = paragraph.add_run(f"{software_name} {software_version}")
set_run_font(title_run, qn, pt)
tab_run = paragraph.add_run("\t")
set_run_font(tab_run, qn, pt)
add_page_number(paragraph, oxml_element, qn, pt)
def code_files(code_dir: Path) -> list[Path]:
return validate_code_files(code_dir)
def infer_software_name(output_path: Path) -> str:
stem = output_path.stem
if stem.endswith("_代码"):
return stem[: -len("_代码")]
return stem
def source_lines_for_deposit(paths: list[Path], required_lines: int) -> list[str]:
lines: list[str] = []
errors: list[str] = []
for path in paths:
for line_number, raw_line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
if "copyright" in raw_line.lower():
errors.append(f"{path.name}:{line_number}: contains copyright")
if not raw_line.strip():
errors.append(f"{path.name}:{line_number}: blank source line")
continue
lines.append(raw_line.rstrip())
if errors:
preview = "; ".join(errors[:20])
if len(errors) > 20:
preview += f"; ... and {len(errors) - 20} more"
raise ValidationError("source code content: " + preview)
if len(lines) < required_lines:
raise ValidationError(f"source code content: expected at least {required_lines} non-empty source lines, found {len(lines)}")
if len(lines) == required_lines:
return lines
first_half = required_lines // 2
last_half = required_lines - first_half
return lines[:first_half] + lines[-last_half:]
def add_deposit_code_pages(document, lines: list[str], style_name: str, line_numbers: bool, lines_per_page: int, wd_break, qn, pt) -> None:
for index, line in enumerate(lines, start=1):
paragraph = document.add_paragraph(style=style_name)
paragraph.paragraph_format.left_indent = None
paragraph.paragraph_format.first_line_indent = None
if line_numbers:
number_run = paragraph.add_run(f"{index:04d} ")
set_run_font(number_run, qn, pt)
run = paragraph.add_run(line)
set_run_font(run, qn, pt)
if index % lines_per_page == 0 and index != len(lines):
run.add_break(wd_break.PAGE)
def build_docx(
code_dir: Path,
output_path: Path,
template_path: Path | None,
line_numbers: bool,
software_name: str | None,
software_version: str,
) -> None:
Document, style_type, align, wd_break, tab_align, oxml_element, qn, cm, pt = import_docx()
document = Document(str(template_path)) if template_path else Document()
clear_body(document, qn)
configure_page(document, cm)
ensure_code_style(document, style_type, align, qn, pt)
header_name = software_name or infer_software_name(output_path)
configure_header(document, header_name, software_version, align, tab_align, oxml_element, qn, pt)
lines_per_page = 50
total_pages = 60
selected_lines = source_lines_for_deposit(code_files(code_dir), lines_per_page * total_pages)
add_deposit_code_pages(document, selected_lines, "AJ Code", line_numbers, lines_per_page, wd_break, qn, pt)
output_path.parent.mkdir(parents=True, exist_ok=True)
document.save(str(output_path))
validate_file_exists(output_path, "code docx")
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--code-dir", required=True, type=Path)
parser.add_argument("--output", required=True, type=Path)
parser.add_argument("--template")
parser.add_argument("--software-name")
parser.add_argument("--software-version", default="V1.0")
parser.add_argument("--line-numbers", action="store_true")
args = parser.parse_args()
template_path = resolve_template(args.template, Path.cwd())
if template_path:
print(f"Using template: {template_path}")
else:
print("Template not found; generating code docx without template.")
try:
build_docx(args.code_dir, args.output, template_path, args.line_numbers, args.software_name, args.software_version)
except ValidationError as exc:
print(f"Validation failed: {exc}", file=sys.stderr)
return 1
print(f"Wrote {args.output}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
#!/usr/bin/env python3
"""Generate numbered prototype images from prompt markdown files.
Dependencies:
pip install google-genai pillow
Environment:
GEMINI_API_KEY is read from --env (default: ~/aj-skills/.env) or the process env.
GEMINI_IMAGE_MODEL or GOOGLE_IMAGE_MODEL is optional. If omitted, the script
tries current Gemini image models and then falls back to Imagen.
"""
from __future__ import annotations
import argparse
import base64
import hashlib
import json
import os
import re
import sys
import time
from datetime import datetime, timezone
from io import BytesIO
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
if str(SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPT_DIR))
from validate_outputs import ValidationError, validate_numbered_files, validate_prototype_files
DEFAULT_GEMINI_MODEL = "gemini-3.1-flash-image-preview"
FALLBACK_GEMINI_MODELS = ["gemini-3-pro-image-preview", "gemini-2.5-flash-image"]
DEFAULT_IMAGEN_MODEL = "imagen-4.0-generate-001"
def load_env_file(path: Path) -> None:
if not path.exists():
return
for raw_line in path.read_text(encoding="utf-8").splitlines():
line = raw_line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
key = key.strip()
value = value.strip().strip('"').strip("'")
if key and key not in os.environ:
os.environ[key] = value
def parse_size(value: str) -> tuple[int, int]:
normalized = value.lower().replace("*", "x")
if normalized in {"1080p", "fullhd", "fhd"}:
return 1920, 1080
if "x" not in normalized:
raise argparse.ArgumentTypeError("size must look like 1920x1080 or 1080p")
width, height = normalized.split("x", 1)
parsed = int(width), int(height)
if parsed[0] <= 0 or parsed[1] <= 0:
raise argparse.ArgumentTypeError("size dimensions must be positive")
return parsed
def aspect_ratio_from_size(size: tuple[int, int]) -> str:
width, height = size
ratio = width / height
common = {
"16:9": 16 / 9,
"9:16": 9 / 16,
"4:3": 4 / 3,
"3:4": 3 / 4,
"1:1": 1,
}
return min(common, key=lambda key: abs(common[key] - ratio))
def image_size_label(size: tuple[int, int]) -> str:
return "2K" if max(size) >= 1500 else "1K"
def utc_now() -> str:
return datetime.now(timezone.utc).isoformat(timespec="seconds")
def prompt_sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def module_path_from_prompt(prompt_file: Path, output_root: Path) -> str:
if prompt_file.stem == "00-login":
return "login"
module_id = prompt_file.stem.split("-", 1)[0]
text = prompt_file.read_text(encoding="utf-8")
match = re.search(r"^对应模块\s*[::]\s*(.+?)\s*$", text, flags=re.MULTILINE)
if match:
raw = match.group(1).strip().strip("`")
path = Path(raw)
if not path.is_absolute():
path = output_root / path
return str(path.resolve())
return str((output_root / "02.modules" / f"{module_id}.md").resolve())
def build_manifest(
prompt_files: list[Path],
output_dir: Path,
batch_file: Path,
size: tuple[int, int],
models: list[str],
max_retries: int,
force: bool,
) -> dict:
output_root = output_dir.parent
previous_manifest: dict = {}
previous: dict[str, dict] = {}
if batch_file.exists() and not force:
try:
data = json.loads(batch_file.read_text(encoding="utf-8"))
previous_manifest = data
previous = {item["id"]: item for item in data.get("items", []) if "id" in item}
except Exception:
previous = {}
items = []
for prompt_file in prompt_files:
output_path = output_dir / f"{prompt_file.stem}.jpg"
previous_item = previous.get(prompt_file.stem, {})
prompt_hash = prompt_sha256(prompt_file)
prompt_changed = previous_item.get("prompt_sha256") not in {None, prompt_hash}
output_exists = output_path.exists() and output_path.stat().st_size > 0
keep_success = (
not force
and not prompt_changed
and previous_item.get("status") == "success"
and output_exists
)
item = {
"id": prompt_file.stem,
"module": module_path_from_prompt(prompt_file, output_root),
"prompt": str(prompt_file.resolve()),
"prompt_sha256": prompt_hash,
"output": str(output_path.resolve()),
"retry": {
"attempts": 0,
"max": max_retries,
},
"status": "success" if keep_success else "pending",
"error": None if keep_success else previous_item.get("error"),
"updated_at": previous_item.get("updated_at") if keep_success else utc_now(),
}
items.append(item)
return {
"version": 1,
"created_at": previous_manifest.get("created_at", utc_now()),
"updated_at": utc_now(),
"size": f"{size[0]}x{size[1]}",
"aspect_ratio": aspect_ratio_from_size(size),
"models": models,
"prompt_dir": str(prompt_files[0].parent.resolve()) if prompt_files else "",
"output_dir": str(output_dir.resolve()),
"items": items,
}
def save_manifest(manifest: dict, batch_file: Path) -> None:
manifest["updated_at"] = utc_now()
batch_file.parent.mkdir(parents=True, exist_ok=True)
batch_file.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
def extract_image_bytes(response: object) -> bytes:
generated_images = getattr(response, "generated_images", None)
if generated_images:
for generated in generated_images:
image = getattr(generated, "image", generated)
for attr in ("image_bytes", "data", "bytes"):
value = getattr(image, attr, None)
if value:
return base64.b64decode(value) if isinstance(value, str) else value
candidates = getattr(response, "candidates", None) or []
for candidate in candidates:
content = getattr(candidate, "content", None)
parts = getattr(content, "parts", None) or []
for part in parts:
inline_data = getattr(part, "inline_data", None)
if inline_data and getattr(inline_data, "data", None):
data = inline_data.data
return base64.b64decode(data) if isinstance(data, str) else data
parts = getattr(response, "parts", None) or []
for part in parts:
inline_data = getattr(part, "inline_data", None)
if inline_data and getattr(inline_data, "data", None):
data = inline_data.data
return base64.b64decode(data) if isinstance(data, str) else data
raise RuntimeError("Google API response did not contain image bytes")
def gemini_generate_content_config(types: object, aspect_ratio: str, size: tuple[int, int], model: str) -> object:
image_options = {"aspect_ratio": aspect_ratio}
if model.startswith("gemini-3"):
image_options["image_size"] = image_size_label(size)
for response_modalities in (["Image"], ["IMAGE"]):
try:
return types.GenerateContentConfig(
response_modalities=response_modalities,
response_format={"image": image_options},
)
except TypeError:
pass
try:
image_config = types.ImageConfig(
aspect_ratio=aspect_ratio,
image_size=image_size_label(size),
)
return types.GenerateContentConfig(response_modalities=["Image"], image_config=image_config)
except Exception:
return types.GenerateContentConfig(response_modalities=["Image"])
def call_google_image_api(client: object, types: object, prompt: str, models: list[str], size: tuple[int, int]) -> bytes:
last_error: Exception | None = None
aspect_ratio = aspect_ratio_from_size(size)
for model in models:
try:
if model.startswith("imagen"):
config = types.GenerateImagesConfig(number_of_images=1, aspect_ratio=aspect_ratio)
response = client.models.generate_images(model=model, prompt=prompt, config=config)
else:
config = gemini_generate_content_config(types, aspect_ratio, size, model)
response = client.models.generate_content(
model=model,
contents=[prompt],
config=config,
)
return extract_image_bytes(response)
except Exception as exc: # Keep trying fallback models when available.
last_error = exc
raise RuntimeError(f"All image models failed. Last error: {last_error}") from last_error
def save_jpeg(image_bytes: bytes, output_path: Path, size: tuple[int, int]) -> None:
try:
from PIL import Image
except ImportError as exc:
raise RuntimeError("Pillow is required to resize and save JPG files: pip install pillow") from exc
target_w, target_h = size
with Image.open(BytesIO(image_bytes)) as image:
image = image.convert("RGB")
scale = max(target_w / image.width, target_h / image.height)
resized_w = max(target_w, round(image.width * scale))
resized_h = max(target_h, round(image.height * scale))
resample = getattr(getattr(Image, "Resampling", Image), "LANCZOS")
image = image.resize((resized_w, resized_h), resample)
left = (resized_w - target_w) // 2
top = (resized_h - target_h) // 2
image = image.crop((left, top, left + target_w, top + target_h))
output_path.parent.mkdir(parents=True, exist_ok=True)
image.save(output_path, "JPEG", quality=92, optimize=True)
def build_prompt(raw_prompt: str, size: tuple[int, int]) -> str:
width, height = size
guardrails = f"""
Render as a polished product UI screenshot, {width}x{height}, 16:9.
Use clear Chinese interface labels, realistic data, readable text, stable spacing,
and no overlapping UI elements. Do not add watermarks or explanatory captions.
"""
return raw_prompt.strip() + guardrails
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--prompt-dir", required=True, type=Path)
parser.add_argument("--output-dir", required=True, type=Path)
parser.add_argument("--module-dir", type=Path)
parser.add_argument("--batch-file", type=Path)
parser.add_argument("--env", default="~/aj-skills/.env", type=Path)
parser.add_argument("--size", default="1920x1080", type=parse_size)
parser.add_argument("--max-retries", default=3, type=int)
parser.add_argument("--force", action="store_true", help="Regenerate all images and reset manifest status")
parser.add_argument("--sleep", default=1.0, type=float, help="Seconds to pause between API calls")
args = parser.parse_args()
if args.max_retries < 1:
print("--max-retries must be >= 1", file=sys.stderr)
return 2
load_env_file(args.env.expanduser())
api_key = os.environ.get("GEMINI_API_KEY")
if not api_key:
print("Missing GEMINI_API_KEY. Add it to ~/aj-skills/.env or export it.", file=sys.stderr)
return 2
try:
from google import genai
from google.genai import types
except ImportError:
print("Missing dependency: pip install google-genai pillow", file=sys.stderr)
return 2
explicit_model = os.environ.get("GEMINI_IMAGE_MODEL") or os.environ.get("GOOGLE_IMAGE_MODEL")
models = [explicit_model] if explicit_model else [DEFAULT_GEMINI_MODEL, *FALLBACK_GEMINI_MODELS, DEFAULT_IMAGEN_MODEL]
client = genai.Client(api_key=api_key)
try:
module_dir = args.module_dir or args.prompt_dir.parent / "02.modules"
prompt_files = validate_prototype_files(args.prompt_dir, ".md", "prototype prompts", module_dir if module_dir.exists() else None)
if module_dir.exists():
validate_numbered_files(module_dir, ".md", "modules")
except ValidationError as exc:
print(f"Validation failed before generation: {exc}", file=sys.stderr)
return 2
batch_file = args.batch_file or args.output_dir / "batch.json"
manifest = build_manifest(
prompt_files=prompt_files,
output_dir=args.output_dir,
batch_file=batch_file,
size=args.size,
models=models,
max_retries=args.max_retries,
force=args.force,
)
save_manifest(manifest, batch_file)
failures: list[str] = []
for item in manifest["items"]:
prompt_file = Path(item["prompt"])
output_path = Path(item["output"])
if item["status"] == "success" and output_path.exists() and not args.force:
print(f"Skipping {output_path}; manifest already marks it successful.")
continue
raw_prompt = prompt_file.read_text(encoding="utf-8")
prompt = build_prompt(raw_prompt, args.size)
print(f"Generating {output_path} from {prompt_file} ...")
while item["retry"]["attempts"] < item["retry"]["max"]:
item["status"] = "running"
item["updated_at"] = utc_now()
save_manifest(manifest, batch_file)
try:
image_bytes = call_google_image_api(client, types, prompt, models, args.size)
save_jpeg(image_bytes, output_path, args.size)
item["status"] = "success"
item["error"] = None
item["updated_at"] = utc_now()
save_manifest(manifest, batch_file)
break
except Exception as exc:
item["retry"]["attempts"] += 1
item["status"] = "failed"
item["error"] = str(exc)
item["updated_at"] = utc_now()
save_manifest(manifest, batch_file)
print(f"FAILED {prompt_file.name} attempt {item['retry']['attempts']}: {exc}", file=sys.stderr)
if item["retry"]["attempts"] < item["retry"]["max"]:
time.sleep(max(args.sleep, 0))
if item["status"] != "success":
failures.append(f"{prompt_file.name}: {item['error']}")
time.sleep(max(args.sleep, 0))
if failures:
print("\nFailures:", file=sys.stderr)
for failure in failures:
print(f"- {failure}", file=sys.stderr)
return 1
try:
validate_prototype_files(args.output_dir, ".jpg", "prototype images", module_dir if module_dir.exists() else None)
except ValidationError as exc:
print(f"Validation failed after generation: {exc}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())
#!/usr/bin/env python3
"""Convert a Markdown operation manual to docx, optionally using a template."""
from __future__ import annotations
import argparse
import re
from pathlib import Path
def import_docx():
try:
from docx import Document
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.shared import Inches, Pt
except ImportError as exc:
raise SystemExit("Missing dependency: pip install python-docx") from exc
return Document, Inches, Pt, WD_ALIGN_PARAGRAPH
def resolve_template(template: str | None, cwd: Path) -> Path | None:
candidates: list[Path] = []
if template:
candidates.append(Path(template).expanduser())
names = ["操作手册模版.docx", "操作手册模板.docx"]
roots = [
cwd / "reference",
cwd / "refence",
cwd / "refrence",
Path.home() / "aj-skills" / "reference",
Path.home() / "aj-skills" / "refence",
Path.home() / "aj-skills" / "refrence",
]
for root in roots:
for name in names:
candidates.append(root / name)
for candidate in candidates:
if candidate.exists():
return candidate
return None
def split_table_row(line: str) -> list[str]:
return [cell.strip() for cell in line.strip().strip("|").split("|")]
def is_separator_row(cells: list[str]) -> bool:
return all(re.fullmatch(r":?-{3,}:?", cell.replace(" ", "")) for cell in cells)
def is_table_line(line: str) -> bool:
stripped = line.strip()
return stripped.startswith("|") and stripped.endswith("|") and stripped.count("|") >= 2
def add_table(document, lines: list[str]) -> None:
rows = [split_table_row(line) for line in lines]
rows = [row for row in rows if not is_separator_row(row)]
if not rows:
return
cols = max(len(row) for row in rows)
table = document.add_table(rows=0, cols=cols)
table.style = "Table Grid"
for row_cells in rows:
row = table.add_row().cells
for idx in range(cols):
if idx < len(row_cells):
add_inline_markdown(row[idx].paragraphs[0], row_cells[idx])
def add_image(document, markdown_image: re.Match[str], base_dir: Path, Inches) -> bool:
alt = markdown_image.group(1).strip()
raw_path = markdown_image.group(2).strip().strip("<>").strip('"').strip("'")
image_path = Path(raw_path)
if not image_path.is_absolute():
image_path = (base_dir / image_path).resolve()
if not image_path.exists():
document.add_paragraph(f"[图片缺失: {alt} - {raw_path}]")
return False
document.add_picture(str(image_path), width=Inches(6.3))
if alt:
caption = document.add_paragraph()
caption.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = caption.add_run(alt)
run.font.size = Pt(10)
return True
def add_code_block(document, code_lines: list[str]) -> None:
for line in code_lines:
paragraph = document.add_paragraph()
run = paragraph.add_run(line if line else " ")
run.font.name = "Consolas"
run.font.size = Pt(8.5)
def add_inline_markdown(paragraph, text: str) -> None:
"""Render a small subset of inline Markdown into docx runs."""
pattern = re.compile(r"(\*\*[^*]+\*\*|`[^`]+`)")
pos = 0
for match in pattern.finditer(text):
if match.start() > pos:
paragraph.add_run(text[pos : match.start()])
token = match.group(0)
if token.startswith("**"):
run = paragraph.add_run(token[2:-2])
run.bold = True
elif token.startswith("`"):
run = paragraph.add_run(token[1:-1])
run.font.name = "Consolas"
pos = match.end()
if pos < len(text):
paragraph.add_run(text[pos:])
def add_paragraph_with_inline(document, text: str, style: str | None = None):
paragraph = document.add_paragraph(style=style) if style else document.add_paragraph()
add_inline_markdown(paragraph, text)
return paragraph
def convert_markdown(markdown_path: Path, output_path: Path, template_path: Path | None, auto_lists: bool = False) -> None:
Document, Inches, imported_pt, imported_align = import_docx()
global Pt, WD_ALIGN_PARAGRAPH
Pt = imported_pt
WD_ALIGN_PARAGRAPH = imported_align
document = Document(str(template_path)) if template_path else Document()
if template_path and any(p.text.strip() for p in document.paragraphs):
document.add_page_break()
lines = markdown_path.read_text(encoding="utf-8").splitlines()
i = 0
in_code = False
code_buffer: list[str] = []
image_re = re.compile(r"!\[([^\]]*)\]\(([^)]+)\)")
while i < len(lines):
line = lines[i]
stripped = line.strip()
if stripped.startswith("```"):
if in_code:
add_code_block(document, code_buffer)
code_buffer = []
in_code = False
else:
in_code = True
i += 1
continue
if in_code:
code_buffer.append(line)
i += 1
continue
if not stripped:
i += 1
continue
if is_table_line(line):
table_lines = []
while i < len(lines) and is_table_line(lines[i]):
table_lines.append(lines[i])
i += 1
add_table(document, table_lines)
continue
image_match = image_re.fullmatch(stripped)
if image_match:
add_image(document, image_match, markdown_path.parent, Inches)
i += 1
continue
heading = re.match(r"^(#{1,6})\s+(.+)$", stripped)
if heading:
level = min(len(heading.group(1)), 4)
document.add_heading(heading.group(2).strip(), level=level)
i += 1
continue
bullet = re.match(r"^[-*]\s+(.+)$", stripped)
if bullet:
if auto_lists:
add_paragraph_with_inline(document, bullet.group(1), style="List Bullet")
else:
add_paragraph_with_inline(document, f"• {bullet.group(1)}")
i += 1
continue
ordered = re.match(r"^\d+[.)]\s+(.+)$", stripped)
if ordered:
if auto_lists:
add_paragraph_with_inline(document, ordered.group(1), style="List Number")
else:
add_paragraph_with_inline(document, stripped)
i += 1
continue
add_paragraph_with_inline(document, stripped)
i += 1
if code_buffer:
add_code_block(document, code_buffer)
output_path.parent.mkdir(parents=True, exist_ok=True)
document.save(str(output_path))
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--input", required=True, type=Path)
parser.add_argument("--output", required=True, type=Path)
parser.add_argument("--template")
parser.add_argument(
"--auto-lists",
action="store_true",
help="Use Word automatic bullet/numbered list styles. Default renders list markers as plain text for cleaner manuals.",
)
args = parser.parse_args()
markdown_path = args.input
template_path = resolve_template(args.template, Path.cwd())
if template_path:
print(f"Using template: {template_path}")
else:
print("Template not found; generating docx without template.")
convert_markdown(markdown_path, args.output, template_path, args.auto_lists)
print(f"Wrote {args.output}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
#!/usr/bin/env python3
"""Screenshot numbered HTML prototypes with a headless browser.
Dependencies:
pip install playwright pillow
python -m playwright install chromium
"""
from __future__ import annotations
import argparse
import hashlib
import json
import re
import sys
from datetime import datetime, timezone
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
if str(SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPT_DIR))
from validate_outputs import ValidationError, validate_html_prototypes, validate_numbered_files, validate_prototype_files
def parse_size(value: str) -> tuple[int, int]:
normalized = value.lower().replace("*", "x")
if normalized in {"1080p", "fullhd", "fhd"}:
return 1920, 1080
if "x" not in normalized:
raise argparse.ArgumentTypeError("viewport must look like 1920x1080 or 1080p")
width, height = normalized.split("x", 1)
parsed = int(width), int(height)
if parsed[0] <= 0 or parsed[1] <= 0:
raise argparse.ArgumentTypeError("viewport dimensions must be positive")
return parsed
def utc_now() -> str:
return datetime.now(timezone.utc).isoformat(timespec="seconds")
def file_sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def module_path_from_html(html_file: Path, output_root: Path) -> str:
if html_file.stem == "00-login":
return "login"
module_id = html_file.stem.split("-", 1)[0]
text = html_file.read_text(encoding="utf-8", errors="ignore")
patterns = [
r"<meta\s+name=[\"']module[\"']\s+content=[\"'](.+?)[\"']\s*/?>",
r"<!--\s*module\s*[::]\s*(.+?)\s*-->",
]
for pattern in patterns:
match = re.search(pattern, text, flags=re.IGNORECASE)
if match:
raw = match.group(1).strip()
path = Path(raw)
if not path.is_absolute():
path = output_root / path
return str(path.resolve())
return str((output_root / "02.modules" / f"{module_id}.md").resolve())
def build_manifest(
html_files: list[Path],
output_dir: Path,
batch_file: Path,
viewport: tuple[int, int],
max_retries: int,
force: bool,
) -> dict:
output_root = output_dir.parent
previous_manifest: dict = {}
previous: dict[str, dict] = {}
if batch_file.exists() and not force:
try:
previous_manifest = json.loads(batch_file.read_text(encoding="utf-8"))
previous = {item["id"]: item for item in previous_manifest.get("items", []) if "id" in item}
except Exception:
previous_manifest = {}
previous = {}
items = []
for html_file in html_files:
output_path = output_dir / f"{html_file.stem}.jpg"
previous_item = previous.get(html_file.stem, {})
html_hash = file_sha256(html_file)
html_changed = previous_item.get("html_sha256") not in {None, html_hash}
output_exists = output_path.exists() and output_path.stat().st_size > 0
keep_success = (
not force
and not html_changed
and previous_item.get("status") == "success"
and output_exists
)
items.append(
{
"id": html_file.stem,
"mode": "html",
"module": module_path_from_html(html_file, output_root),
"html": str(html_file.resolve()),
"html_sha256": html_hash,
"output": str(output_path.resolve()),
"viewport": f"{viewport[0]}x{viewport[1]}",
"retry": {
"attempts": 0,
"max": max_retries,
},
"status": "success" if keep_success else "pending",
"error": None if keep_success else previous_item.get("error"),
"updated_at": previous_item.get("updated_at") if keep_success else utc_now(),
}
)
return {
"version": 1,
"mode": "html",
"created_at": previous_manifest.get("created_at", utc_now()),
"updated_at": utc_now(),
"viewport": f"{viewport[0]}x{viewport[1]}",
"html_dir": str(html_files[0].parent.resolve()) if html_files else "",
"output_dir": str(output_dir.resolve()),
"items": items,
}
def save_manifest(manifest: dict, batch_file: Path) -> None:
manifest["updated_at"] = utc_now()
batch_file.parent.mkdir(parents=True, exist_ok=True)
batch_file.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
def screenshot_items(manifest: dict, batch_file: Path, viewport: tuple[int, int], wait_ms: int, force: bool) -> list[str]:
try:
from playwright.sync_api import sync_playwright
except ImportError:
return ["Missing dependency: pip install playwright && python -m playwright install chromium"]
failures: list[str] = []
with sync_playwright() as playwright:
try:
browser = playwright.chromium.launch(headless=True)
except Exception as exc:
return [f"Failed to launch Chromium: {exc}. Run: python -m playwright install chromium"]
context = browser.new_context(
viewport={"width": viewport[0], "height": viewport[1]},
device_scale_factor=1,
locale="zh-CN",
)
page = context.new_page()
for item in manifest["items"]:
html_path = Path(item["html"])
output_path = Path(item["output"])
if item["status"] == "success" and output_path.exists() and not force:
print(f"Skipping {output_path}; manifest already marks it successful.")
continue
print(f"Screenshot {output_path} from {html_path} ...")
while item["retry"]["attempts"] < item["retry"]["max"]:
item["status"] = "running"
item["updated_at"] = utc_now()
save_manifest(manifest, batch_file)
try:
page.goto(html_path.resolve().as_uri(), wait_until="networkidle")
if wait_ms > 0:
page.wait_for_timeout(wait_ms)
output_path.parent.mkdir(parents=True, exist_ok=True)
page.screenshot(path=str(output_path), type="jpeg", quality=92, full_page=False)
item["status"] = "success"
item["error"] = None
item["updated_at"] = utc_now()
save_manifest(manifest, batch_file)
break
except Exception as exc:
item["retry"]["attempts"] += 1
item["status"] = "failed"
item["error"] = str(exc)
item["updated_at"] = utc_now()
save_manifest(manifest, batch_file)
print(f"FAILED {html_path.name} attempt {item['retry']['attempts']}: {exc}", file=sys.stderr)
if item["status"] != "success":
failures.append(f"{html_path.name}: {item['error']}")
context.close()
browser.close()
return failures
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--html-dir", required=True, type=Path)
parser.add_argument("--output-dir", required=True, type=Path)
parser.add_argument("--module-dir", type=Path)
parser.add_argument("--batch-file", type=Path)
parser.add_argument("--viewport", default="1920x1080", type=parse_size)
parser.add_argument("--wait-ms", default=500, type=int)
parser.add_argument("--max-retries", default=2, type=int)
parser.add_argument("--force", action="store_true")
args = parser.parse_args()
if args.max_retries < 1:
print("--max-retries must be >= 1", file=sys.stderr)
return 2
try:
module_dir = args.module_dir or args.html_dir.parent / "02.modules"
html_files = validate_html_prototypes(args.html_dir, module_dir if module_dir.exists() else None)
if module_dir.exists():
validate_numbered_files(module_dir, ".md", "modules")
except ValidationError as exc:
print(f"Validation failed before screenshot: {exc}", file=sys.stderr)
return 2
batch_file = args.batch_file or args.output_dir / "batch.json"
manifest = build_manifest(
html_files=html_files,
output_dir=args.output_dir,
batch_file=batch_file,
viewport=args.viewport,
max_retries=args.max_retries,
force=args.force,
)
save_manifest(manifest, batch_file)
failures = screenshot_items(manifest, batch_file, args.viewport, args.wait_ms, args.force)
if failures:
print("\nFailures:", file=sys.stderr)
for failure in failures:
print(f"- {failure}", file=sys.stderr)
return 1
try:
validate_prototype_files(args.output_dir, ".jpg", "prototype images", module_dir if module_dir.exists() else None)
except ValidationError as exc:
print(f"Validation failed after screenshot: {exc}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())