diff --git a/README.md b/README.md index 1366af5..d6db923 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # MAOMOMO 文章生成器 -这是一个面向 Codex 的中文写作技能,用来生成 MAOMOMO / 猫MOMO 风格的出海金融实操文章、文章大纲、发布用 Markdown、SEO 优化稿、独立配图 assets 和 ZIP 发布包。 +这是一个面向 Codex 的中文写作技能,用来生成 MAOMOMO / 猫MOMO 风格的出海金融实操文章、文章大纲、发布用 Markdown、SEO 优化稿、独立配图 assets、ZIP 发布包,以及小红书卡片 / 多图 / 3:4 / 9:16 竖版 PNG 交付。 -新版流程参考了 `codex-ppt-skill` 的阶段化设计:先确认来源和文章口径,再确认大纲与配图规划,最后写作、生成配图、QA 和打包交付。这样可以降低金融活动文章里最常见的翻车点:旧规则混入新稿、来源没读全、图片残留旧口径、只给正文不交付文件。 +新版流程参考了 `codex-ppt-skill` 的阶段化设计:先确认来源和文章口径,再确认大纲 / 卡片结构与配图规划,最后写作或整理小红书文案、生成配图、QA、写入 `final_manifest.json`,并按渠道打包或 GitHub 分批交付。这样可以降低金融活动文章里最常见的翻车点:旧规则混入新稿、来源没读全、图片残留旧口径、只给正文不交付文件。 ## 适用场景 @@ -13,7 +13,7 @@ - 开户教程、保号教程、转账 / 入金 / 出金教程 - 美股券商、数字货币、欧元通道、汇款路径 - 避坑经验、实测记录、活动规则整理 -- MAOMOMO 风格封面图、截图槽位和信息图提示词 +- MAOMOMO 风格封面图、截图槽位、信息图和小红书卡片 不适合直接生成投资建议、法律意见、税务结论或任何“保证获批 / 稳赚 / 一定到账”的承诺型内容。 @@ -33,8 +33,9 @@ maomomo-article-writer/ ├── visual-assets.md # 配图清单、视觉规则和图片 QA ├── recommended-styles.md # 推荐配图风格和提示词片段 ├── image-styles/ # 参考 codex-ppt 风格库拆分的出图风格 brief - ├── image-generation.md # 图片生成脚本、manifest 和后端选择 - └── delivery-rules.md # 完整交付、打包和最终检查 + ├── image-generation.md # 图片生成脚本、manifest、final_manifest 和后端选择 + ├── github-delivery.md # GitHub 交付仓库、专用 KEY 和推送门禁 + └── delivery-rules.md # 完整交付、GitHub 分批、打包和最终检查 ``` ## 安装方式 @@ -69,6 +70,10 @@ maomomo-article-writer 把这个银行活动整理成猫MOMO风格文章,重点写怎么吃满和常见翻车点。 ``` +```text +把这个活动做成小红书 3:4 多图卡片,先给我确认 2D / 3D 视觉方向。 +``` + ## 推荐提示词 生成完整文章: @@ -91,6 +96,18 @@ maomomo-article-writer 使用 $maomomo-article-writer,为【主题】生成 MAOMOMO 风格封面图提示词、正文截图槽位和一张信息图提示词。不要写完整正文。 ``` +小红书卡片: + +```text +使用 $maomomo-article-writer,做一组小红书 9:16 多图卡片。默认 2D 扁平风,要求每张独立 PNG,最后走 GitHub 分批交付。 +``` + +GitHub 交付配置: + +```text +使用 $maomomo-article-writer,先引导我配置 GitHub 交付仓库和专用 KEY,之后小红书卡片都走 GitHub 分批交付。 +``` + 改写已有文章: ```text @@ -117,18 +134,35 @@ maomomo-article-writer - `assets/` 独立 PNG 配图目录 - `article_package.zip` +小红书卡片通常包含: + +- 20 字内标题 +- 小红书正文和 3-6 个话题标签 +- 多张 3:4 或 9:16 独立 PNG +- `prompts/`、`slide_jobs.json`、`slide_run_state.json` +- `final_manifest.json` +- GitHub 分批链接 + +GitHub 分批交付前,技能会先引导配置: + +- 专用私有交付仓库,例如 `OWNER/maomomo-delivery` +- `MAOMOMO_GITHUB_REPO`、`MAOMOMO_GITHUB_BRANCH`、`MAOMOMO_GITHUB_BASE_PATH` +- GitHub CLI 登录、fine-grained PAT,或 SSH deploy key +- 最小权限原则:只给交付仓库写入权限 +- 密钥不进入聊天、prompt、manifest、状态文件、README、日志或 commit + 如果事实不完整,技能会使用 `【待确认:...】` 标记缺口,而不是编造数字、日期、奖励或链接。 ## 默认流程 完整交付任务通常按以下步骤执行: -1. 识别任务模式:完整文章、大纲、SEO 优化、改稿、配图或小红书版。 +1. 识别任务模式:完整文章、大纲、SEO 优化、改稿、配图或小红书卡片。 2. 读取来源并列出已读 / 未读材料,整理事实表和待确认项。 -3. 确认文章范围、大纲、标题方向、核心口径和图片规划。 +3. 确认文章范围、大纲 / 卡片结构、标题方向、核心口径和图片规划。 4. 写作或优化发布用 Markdown。 -5. 确认配图风格,生成或准备独立 PNG 配图,插入 Markdown。 -6. 检查来源、图片路径、风险提示、编辑痕迹和 ZIP 内容,最后交付文件路径。 +5. 确认配图风格;小红书卡片需先确认 2D / 3D 和 3:4 / 9:16,再生成或准备独立 PNG。 +6. 检查来源、图片路径、风险提示、编辑痕迹、ZIP 或 `final_manifest.json`;需要 GitHub 交付时先确认仓库和专用 KEY 配置,最后交付文件路径或 GitHub 分批链接。 用户明确说“只要文字”“只要大纲”“不要图片”“不要打包”时,会按用户限制执行,不强行创建完整发布包。 @@ -153,6 +187,7 @@ maomomo-article-writer - 涉及金融、券商、银行、数字货币和合规内容时,保留谨慎措辞 - 配图统一使用白橘猫、暖橙色、干净金融科技教程风 - 如果用户没有指定配图风格,先在 `recommended-styles.md` 和 `image-styles/` 中按文章类型推荐 2-3 个方向,例如 `clean-professional`、`data-card-dashboard`、`handdrawn-note`、`e-ink-editorial` 或 `xiaohongshu-vertical`。 +- 小红书卡片出图前必须确认 `2D 扁平风` / `3D 轻拟物风` 和 `3:4` / `9:16`;用户没指定时默认推荐 2D,并按原生竖版重新构图,不能把横版图压进竖图。 文章不应当: @@ -169,7 +204,8 @@ maomomo-article-writer - 配图类型、猫咪视觉、文件命名和图片 QA 写在 `references/visual-assets.md`。 - 推荐配图风格、适用场景和提示词片段写在 `references/recommended-styles.md`。 - 具体出图风格 brief 放在 `references/image-styles/`,结构参考 `codex-ppt-skill` 的风格库。 -- 图片 API/CLI fallback、批量 manifest 和脚本用法写在 `references/image-generation.md`,脚本为 `scripts/maomomo_image_gen.py`。 -- 完整交付、ZIP、附件和最终检查写在 `references/delivery-rules.md`。 +- 图片 API/CLI fallback、批量 manifest、final manifest、GitHub 批次记录和脚本用法写在 `references/image-generation.md`,脚本为 `scripts/maomomo_image_gen.py` 和 `scripts/maomomo_job_state.py`。 +- GitHub 交付仓库、专用 KEY / token / deploy key、环境变量、隐私边界和推送前检查写在 `references/github-delivery.md`。 +- 完整交付、ZIP、GitHub 分批、附件和最终检查写在 `references/delivery-rules.md`。 - 修改技能展示名称或默认提示语时,同步更新 `agents/openai.yaml`。 - 如果新增具体行业资料、平台规则或长期复用模板,优先放入 `references/`,避免让 `SKILL.md` 过长。 diff --git a/SKILL.md b/SKILL.md index 917cf19..63fcaab 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,6 +1,6 @@ --- name: maomomo-article-writer -description: 生成或优化符合 maomomo / 猫MOMO 风格的中文出海金融实操文章、发布用 Markdown、SEO 稿、配图规划、独立高清配图 assets 和 ZIP 发布包。适用于 maomomo.com、港卡、香港银行、香港信用卡、美股券商、数字货币、汇款、支付优化、返现活动、开户教程、保号教程、避坑经验、官方条款整理和实测攻略。用户要求使用 skill、按猫MOMO风格、完整文章、发布用 Markdown、配图、打包、一键下载或最终交付时触发。 +description: 生成或优化符合 maomomo / 猫MOMO 风格的中文出海金融实操文章、发布用 Markdown、SEO 稿、配图规划、独立高清配图 assets、ZIP 发布包,以及小红书卡片 / 多图 / 3:4 / 9:16 竖版 PNG 交付;包含 GitHub 交付仓库和专用 KEY / token / deploy key 配置引导。适用于 maomomo.com、港卡、香港银行、香港信用卡、美股券商、数字货币、汇款、支付优化、返现活动、开户教程、保号教程、避坑经验、官方条款整理和实测攻略。用户要求使用 skill、按猫MOMO风格、完整文章、发布用 Markdown、配图、打包、一键下载、最终交付、小红书、卡片、多图、3:4、9:16、GitHub 交付、仓库配置或专用 KEY 配置时触发。 --- # MAOMOMO 文章生成器 @@ -16,6 +16,7 @@ description: 生成或优化符合 maomomo / 猫MOMO 风格的中文出海金融 - 银行、港卡、香港信用卡、券商、数字货币、汇款、支付、账户维护、返现和羊毛活动。 - 活动规则整理、开户教程、转账 / 入金 / 出金教程、保号方案、用卡姿势、对比总结、避坑经验。 - 已有文章 SEO 优化、发布用 Markdown、配图规划、独立配图、ZIP 打包和后续改稿。 +- 小红书卡片、多图信息卡、3:4 或 9:16 竖版 PNG 系列图、封面 / 内容卡 / 结尾卡分批交付。 不适合: @@ -27,6 +28,7 @@ description: 生成或优化符合 maomomo / 猫MOMO 风格的中文出海金融 - 如果本技能任何旧规则与“确认优先、单步推进”的流程冲突,以本节和“默认工作流”中的阶段门禁为准。 - 确认优先,单步推进;不要一上来就完整生成文章、全部配图或 ZIP。 +- 用户提到“小红书”“卡片”“多图”“3:4”“9:16”任一触发词时,默认进入“小红书卡片模式”:不默认走完整文章 + Markdown + ZIP 流程,输出重点改为多张独立 PNG、统一视觉风格、适合小红书发布。只有用户明确要求文章、Markdown 或 ZIP 时,才叠加对应交付物。 - 读写 Markdown、JSON、CSV、HTML、代码或文本文件时显式使用 UTF-8。 - 处理用户上传或提供的 Markdown / PDF / 官方链接 / 参考文章时,必须先完整读取或说明无法读取的部分,再写最终稿。 - 费用、活动日期、奖励门槛、账户规则、返现、券商迎新、转账路径、数字货币出入金和合规事项,必须优先核查最新信息。优先级:用户提供的最新官方材料 > 官方页面 / 条款 PDF / App 显示 > 用户实测 > MAOMOMO 旧文 > 其他参考。 @@ -35,6 +37,8 @@ description: 生成或优化符合 maomomo / 猫MOMO 风格的中文出海金融 - 写作前必须先给用户确认标题或标题方向、文章核心口径、`outline.md` 大纲、来源缺口、必选素材映射和配图风格。用户明确要求跳过确认时,未确认事实仍必须标 `【待确认:...】`。 - 大纲未确认前,不得创建最终 `deck_spec.json`、`speech.md`、图片 prompt jobs、文章配图图片、ZIP 或用户指定的最终文章包。 - 出图前必须先把全部图片 prompt 写入文件,包含文件名、用途、放置位置、核心文案、完整 prompt 和依赖素材;不要在聊天里输出超长 prompt。 +- 小红书卡片模式出图前必须先让用户确认视觉方向:`2D 扁平风` 或 `3D 轻拟物风`。用户没指定时,默认推荐 `2D 扁平风`,因为小红书信息图更稳、更不容易文字翻车;一旦确认,整组图片必须保持同一种视觉方向,不能中途混用。 +- 小红书竖图必须按原生竖版重新构图,禁止把横版设计硬塞进 3:4 或 9:16。每个小红书图片 prompt 必须包含英文约束:`native portrait composition`、`no squeezed elements`、`no stretched card / logo / text`、`vertical layout redesigned for Xiaohongshu`。 - 图片 prompt 中的视觉风格只能作为后台画面描述,不得成为画面文字;prompt 必须限制可见文字只包含指定业务文案、`MAOMOMO` 和已核对允许出现的品牌 / 支付标识,不得出现风格标签、prompt 描述、额外 slogan 或无关文字。 - 发布稿、图片文案、prompt、manifest 和 `deck_spec.json` 中金额统一写 `HKD`,不要使用港币符号;来源原文抽取文件可保留原文写法。 - 金融活动配图如出现信用卡、支付卡、卡组织、支付网络或机构 Logo,必须按官方材料或用户素材核对并记录来源;用户没有提供 Logo / 卡面素材但画面需要品牌或支付标识时,应主动尽力从官方活动页、品牌资源页、条款 PDF、App / 官网截图等来源查找准确标识,保存到 `sources/` 并记录来源。不得把银联 / UnionPay 画成 Mastercard、Visa 等其他卡组织。已核对正确的 Mastercard / Visa / UnionPay / 银行 / 支付宝等标识可以辅助出现,但不能抢占或替代 `MAOMOMO` 主标识位置。 @@ -43,6 +47,10 @@ description: 生成或优化符合 maomomo / 猫MOMO 风格的中文出海金融 - 每张最终图片必须来自确认后的图片生成后端:当前 agent 的内置图片生成工具,或用户确认后的 `scripts/maomomo_image_gen.py` API/CLI fallback。不得用 Playwright、HTML/CSS、SVG、Pillow、canvas、本地拼贴或手工文字覆盖冒充最终生成图。例外:用户提供真实银行 / 券商 / 钱包截图且需要上传图片 API 做编辑时,必须先向用户确认实际 API 地址 / baseURL、接口归属和是否允许外发;该 API 可能是用户自有兼容服务,不能默认按公共第三方处理。若未确认、确认不允许或安全策略不允许上传,则先用图片后端生成不含真实截图的空白模板,再在本地把原截图完整嵌入模板;本地步骤只能做排版嵌入、圆角、阴影和尺寸适配,不得改动截图内金额、日期、姓名、账号、交易描述等关键内容,并必须在状态文件和最终回复说明这是“图片后端模板 + 本地嵌入真实截图”的混合流程。 - 生成图片按“一张图一个 job / 一次请求 / 一次 QA”推进;通过 QA 后才用状态脚本记录 result 并同步到 `assets/`,不能只生成到 `origin_image/` 后声称完成。运行环境支持子 agent 时,样张通过后可以一图一个子 agent 并发处理;每个 dispatch、结果和 blocker 都必须写入状态文件,不能只靠聊天说明。 - 完整交付模式下,不能只在聊天里输出正文;必须生成 Markdown 文件、`assets/` 目录和 ZIP 包,除非用户明确说只要文字、只要大纲、不要图片、不要打包或先不生成文件。 +- 图片交付完成后必须生成或更新 `final_manifest.json`,最终推送、打包或链接交付只能读取 `final_manifest.json` 或状态文件里明确标记为 final 的 assets;不得把 `draft`、`v2`、旧比例、失败图或未通过 QA 的图一起交付。 +- 面向 Telegram / 聊天渠道的大文件交付默认改为 GitHub 分批推送:例如 `batch-01-cover`、`batch-02-content-cards`、`batch-03-final-assets`。Telegram 只负责通知进度和给 GitHub 文件路径、commit、release 或下载链接,不承载大 ZIP 或大图片包。 +- 需要 GitHub 分批交付,或用户提到 GitHub 仓库、专用 KEY、token、PAT、deploy key 时,必须先读 `references/github-delivery.md`,引导配置专用私有交付仓库和最小权限凭据;不得要求用户在聊天里粘贴密钥,不得把 token / 私钥写入 prompt、manifest、状态文件、README、日志或提交记录。 +- 每张图都必须在状态文件记录:prompt 文件、生成结果、QA 状态、是否进入 `assets/`、是否已推送 GitHub。中断恢复时先读状态文件和 `final_manifest.json`,不要依赖聊天记忆。 - 配图文件必须是独立 PNG;不要用一张大拼图代替多张图,不要只给提示词冒充图片文件。 - 后续改稿必须更新文件本体;影响图片、来源区、标题、摘要或 ZIP 时同步更新。 @@ -56,19 +64,19 @@ description: 生成或优化符合 maomomo / 猫MOMO 风格的中文出海金融 4. 输出全部图片 prompt 文件和图片任务状态文件。 5. 生成并确认一张样张。 6. 逐张生成剩余图片并记录 dispatch / result / blocker。 -7. QA、返修、生成 `speech.md` 或备注稿。 -8. 打包并交付最终文件。 +7. QA、返修、生成 `speech.md`、小红书发布文案或备注稿。 +8. 写入 `final_manifest.json`,按渠道打包或 GitHub 分批推送最终文件。 -不要因为聊天里说“已完成”就标记完成;用实际文件、已读取来源、已生成图片和 ZIP 作为完成证据。 +不要因为聊天里说“已完成”就标记完成;用实际文件、已读取来源、已生成图片、`final_manifest.json`、ZIP 或 GitHub 批次记录作为完成证据。 ## 默认工作流 -一句话版:读资料 -> 给标题 / 大纲 / 风格让用户确认 -> 写文章 -> 输出图片 prompts 文件让用户改 -> 先出一张图确认 -> 逐张生成剩余图 -> QA -> 打包交付。 +一句话版:判断模式 -> 读资料 -> 给标题 / 大纲 / 卡片结构 / 风格让用户确认 -> 写文章或小红书文案 -> 输出图片 prompts 文件让用户改 -> 先出一张图确认 -> 逐张生成剩余图 -> QA -> 写 `final_manifest.json` -> 打包或 GitHub 分批交付。 1. 判断任务模式。 - - 完整交付、只要大纲、只要正文、只要配图、SEO 优化、改稿更新、小红书发布版。 + - 完整交付、只要大纲、只要正文、只要配图、SEO 优化、改稿更新、小红书卡片模式。 - 判断是否涉及金融 / 活动 / 合规事实核查。 - - 完整交付或打包任务先读 `references/delivery-rules.md`;需要配图时先读 `references/visual-assets.md`。 + - 完整交付、打包、GitHub 分批推送或小红书卡片任务先读 `references/delivery-rules.md`;需要配图时先读 `references/visual-assets.md`;需要 GitHub 推送或仓库 / KEY 配置时先读 `references/github-delivery.md`。 2. 读取源材料并建立事实表。 - 先读用户提供的官方页面、PDF、旧文、参考链接、截图或素材,不先动手写最终稿。 @@ -83,8 +91,12 @@ description: 生成或优化符合 maomomo / 猫MOMO 风格的中文出海金融 - 有必选素材时,必须先确认“素材到章节 / 图位”的映射,再进入风格选择或图片生成。 - 若存在多个活动分支、旧版活动、小众资格或来源冲突,先建议主文范围,等用户确认后再写最终稿。 - 用户明确要求“直接生成”时,可以继续执行,但所有未确认事实必须标 `【待确认:...】`。 + - 小红书卡片模式的 `outline.md` 可以简化为卡片结构:图片数量、比例、每张卡片角色、核心文案、话题标签、必选素材映射和分批交付计划。 4. 确认视觉风格。 + - 小红书卡片模式必须先确认比例 `3:4` 或 `9:16`,以及视觉方向 `2D 扁平风` 或 `3D 轻拟物风`。 + - 小红书卡片模式用户没指定视觉方向时,推荐 `2D 扁平风`:清爽、信息卡片、适合教程 / 规则说明,文字更稳;`3D 轻拟物风` 更有质感,适合封面 / 活动感 / 金融产品展示。 + - 小红书卡片模式一旦确认 2D / 3D,所有 prompt、样张、剩余图片和 final manifest 都必须记录同一 `visual_direction`,不得混用。 - 若用户没有指定视觉方向,读 `references/recommended-styles.md`,给 2-3 个 MAOMOMO 配图风格选项并推荐一个。 - 用户提供参考图、PPT、PDF 或旧文章风格时,先总结可复用的配色、版式、字体气质、猫咪元素、信息密度和禁用项。 - 确认后,整篇文章保持同一视觉系统:稳定配色、猫咪造型、标题区、图标语言、品牌识别和信息密度;不同图位可以变化构图。 @@ -123,6 +135,8 @@ description: 生成或优化符合 maomomo / 猫MOMO 风格的中文出海金融 ├── deck_spec.json ├── slide_jobs.json ├── slide_run_state.json +├── final_manifest.json +├── github_batches.json ├── outline.md ├── article.md ├── speech.md @@ -134,11 +148,14 @@ description: 生成或优化符合 maomomo / 猫MOMO 风格的中文出海金融 - `deck_spec.json` 记录文章级上下文、确认后的风格、生图后端、样张生成方法 `sample_generation_method`、参考素材映射和图片任务列表。 - `prompts/{article_slug}_XX.json` 记录每张图的文件名、用途、放置位置、核心文案、完整 prompt、依赖素材和输出路径。 - `slide_jobs.json` 和 `slide_run_state.json` 记录每个图片 job 的 pending / dispatched / recorded / blocked 状态;使用 `scripts/maomomo_job_state.py` 初始化和记录状态。 + - `final_manifest.json` 只记录通过 QA 且进入 `assets/` 的最终交付文件;`github_batches.json` 记录每批 GitHub 推送的 batch id、文件路径、commit、release 或下载链接。 + - 小红书卡片项目没有文章正文时,可以省略 `article.md` 和 ZIP,但仍必须保留 `origin_image/`、`assets/`、`prompts/`、`slide_jobs.json`、`slide_run_state.json`、`final_manifest.json` 和 `github_batches.json`。 8. 出图前整理全部 prompt。 - 每张图的文件名、用途、放置位置、核心文案、完整 prompt、alt text、风格、依赖素材和输出路径先写入 `prompts/` 或 manifest 文件。 - prompt 中不要写“暖橙金融教程风”“数据仪表盘风”“手绘便签风”等容易被模型照抄的中文风格标签;改写为白底、暖橙强调、信息卡片、猫咪向导、清晰标题等视觉描述。 - 每张图的 prompt 必须明确:只允许出现指定业务文案、`MAOMOMO` 和已核对允许出现的品牌 / 支付标识;不得添加副标题、风格标签、prompt 描述、说明文字、额外 slogan 或无关文字。 + - 小红书 3:4 / 9:16 prompt 必须明确原生竖版重构,并包含 `native portrait composition`、`no squeezed elements`、`no stretched card / logo / text`、`vertical layout redesigned for Xiaohongshu`。 - 封面只放最高优先级结论和少量核心数字;复杂计算、全年理论上限、长公式和详细规则放到计算图或正文。 - 不要在聊天里输出超长 prompt;告诉用户可直接编辑 prompt 文件。 - 如果用户修改 prompt,先同步文件,再生成图片。 @@ -160,16 +177,21 @@ description: 生成或优化符合 maomomo / 猫MOMO 风格的中文出海金融 11. 记录状态、QA 和返修。 - 每个图片 job 的 dispatch、结果和 blocker 都用 `scripts/maomomo_job_state.py` 写入 `slide_jobs.json` / `slide_run_state.json`,不能只靠聊天说明完成。 + - 每张图的状态必须记录 prompt 文件、生成结果、QA 状态、是否进入 `assets/`、是否已推送 GitHub;缺失记录时不得进入最终交付。 - 每张图通过前必须检查日期、金额、公式、卡组织 / 支付网络、禁用分支、风格说明文字、伪造截图、乱码和多余文案。 - 检查文字清晰度、中文乱码、金额 / 日期 / 规则是否残留旧口径、标题截断、风格一致性、必选素材是否正确、元素是否重叠、是否伪造截图或 Logo。 + - 小红书 3:4 / 9:16 图额外检查:比例正确、竖版原生构图、元素没有被压扁、卡片 / Logo / 文字没有拉伸、不是横图硬塞进竖版。 - 严重问题重新生成该单张;局部问题优先使用已确认后端的编辑能力修复,不得本地手工覆盖关键文字冒充模型输出。 -12. 生成 `speech.md`、组装文章包并最终 QA。 +12. 生成 `speech.md`、`final_manifest.json`、组装文章包或 GitHub 批次并最终 QA。 - `speech.md` 用作每张图的备注 / 讲解稿,标题格式使用 `## Slide N: 标题`,便于和图片顺序映射。 - 组装最终文章包时,把 `origin_image/{article_slug}_XX.png` 按顺序同步到 Markdown 引用的 `assets/`,并确保 `article.md`、图片路径、`speech.md`、来源区和 ZIP 都是最新版本。 + - 最终交付只能读取 `final_manifest.json` 或状态文件中 `qa_status=passed`、`entered_assets=true`、`final=true` 的文件,不扫描整个目录交付。 + - 需要 GitHub 分批交付时,先按 `references/github-delivery.md` 确认交付仓库、认证方式、目标路径和仓库隐私级别;缺配置时先给用户本机配置步骤并暂停 push。配置就绪后按 `batch-01-cover`、`batch-02-content-cards`、`batch-03-final-assets` 等批次推送;每批推送后记录 GitHub 文件路径、commit、release 或下载链接,并写入 `github_batches.json` / 状态文件。 - 完整交付、文件交付或改稿打包前读 `references/delivery-rules.md`。 - QA 必须用脚本检查 Markdown 中所有图片路径真实存在,并检查 assets 是否存在、来源区只保留实际使用来源、正文无编辑痕迹、图片无错误日期 / 金额 / 未确认结论、用户要求删除内容不残留、ZIP 内容正确。 - - 最终回复保持简短:给出项目目录、文章路径、图片目录、ZIP 路径、图片数量、生图后端、状态记录情况、备注写入情况和已知限制。 + - 小红书卡片模式最终回复固定包含:GitHub 链接、图片数量、风格:2D / 3D、比例:3:4 / 9:16、批次数量、QA 是否完成、已知限制。 + - 完整文章模式最终回复保持简短:给出项目目录、文章路径、图片目录、ZIP 或 GitHub 链接、图片数量、生图后端、状态记录情况、备注写入情况和已知限制。 ## 模式选择 @@ -177,7 +199,7 @@ description: 生成或优化符合 maomomo / 猫MOMO 风格的中文出海金融 - “只要大纲”“先列结构”:只输出标题备选、大纲、事实缺口、来源清单和图片规划。 - “只要文字”“不要图片”“不要打包”“先不要生成文件”:按用户限制输出,不强制创建文件包。 - “SEO 优化”“配图”“打包”且用户提供已有 Markdown:读取原文后输出优化版 Markdown、assets 和 ZIP。 -- “小红书”“4 图看完”“发小红书”:读 `references/style-guide.md` 的小红书规则,并按 9:16 竖图规划。 +- “小红书”“卡片”“多图”“4 图看完”“发小红书”“3:4”“9:16”:小红书卡片模式;读 `references/style-guide.md` 和 `references/visual-assets.md` 的小红书规则,按 3:4 或 9:16 原生竖版规划,不默认生成完整文章、Markdown 或 ZIP。 - 后续说“删掉 X”“合并 3、4”“不要写 Y”“改成 Z”:视为编辑指令,不要把指令原样写进正文。 ## 改稿协作规则 @@ -203,6 +225,16 @@ description: 生成或优化符合 maomomo / 猫MOMO 风格的中文出海金融 - 金融、银行、券商、数字货币、汇款、税务或合规内容包含保守风险提示。 - 用户要求删除或收敛的分支,不残留在标题、摘要、正文、图片 alt、配图清单、来源区或 ZIP。 +小红书卡片模式必须满足: + +- 每张最终图是独立 PNG,比例为用户确认的 `3:4` 或 `9:16`。 +- 已确认 `2D 扁平风` 或 `3D 轻拟物风`,且全组一致。 +- prompt、状态文件和 QA 记录包含原生竖版构图、防压扁和防拉伸约束。 +- `final_manifest.json` 只包含通过 QA 且进入 `assets/` 的最终图片。 +- GitHub 分批推送记录已写入状态文件或 `github_batches.json`;Telegram 只发送进度和链接。 +- GitHub 交付仓库和专用 KEY / token / deploy key 已按 `references/github-delivery.md` 配置或明确记录为阻塞项。 +- 最终回复包含 GitHub 链接、图片数量、风格、比例、批次数量、QA 是否完成和已知限制。 + 如果无法完成,最终回复必须说明:阻塞阶段、缺失材料或失败工具、已完成文件路径、未完成项和下一步需要什么。 ## Reference Map @@ -212,5 +244,6 @@ description: 生成或优化符合 maomomo / 猫MOMO 风格的中文出海金融 - `references/visual-assets.md`:配图类型、猫咪视觉、文件命名、Markdown 插图、金融卡面核对、可见文字限制、禁止伪造截图和图片 QA。 - `references/recommended-styles.md`:推荐配图风格、适用场景、推荐话术和提示词片段。 - `references/image-styles/*.md`:具体出图风格 brief,借鉴 `codex-ppt-skill` 风格库结构并适配 MAOMOMO 文章配图。 -- `references/image-generation.md`:图片生成后端选择、`scripts/maomomo_image_gen.py` 用法、`scripts/maomomo_job_state.py` 状态记录、manifest 和故障处理。 -- `references/delivery-rules.md`:完整交付、ZIP、更新打包、附件策略和最终检查清单。 +- `references/image-generation.md`:图片生成后端选择、`scripts/maomomo_image_gen.py` 用法、`scripts/maomomo_job_state.py` 状态记录、final manifest、GitHub 批次记录和故障处理。 +- `references/github-delivery.md`:GitHub 交付仓库、专用 KEY / token / deploy key、环境变量、隐私边界和分批推送前置检查。 +- `references/delivery-rules.md`:完整交付、ZIP、GitHub 分批推送、更新打包、附件策略和最终检查清单。 diff --git a/agents/openai.yaml b/agents/openai.yaml index 7c0a3e3..457ddc9 100644 --- a/agents/openai.yaml +++ b/agents/openai.yaml @@ -1,7 +1,7 @@ interface: display_name: "MAOMOMO 文章生成器" - short_description: "分阶段确认后生成 MAOMOMO 文章、配图和 ZIP 发布包" - default_prompt: "使用 $maomomo-article-writer 生成或优化一篇 MAOMOMO 风格的中文出海金融实操文章。请先核查来源和事实表,再确认标题、口径、大纲、素材映射、配图风格和生图后端;写文章后先输出图片 prompts 文件,只生成一张样张确认,再逐张生成剩余图片,最后交付 Markdown、assets 独立配图目录和 ZIP 包。" + short_description: "分阶段确认后生成 MAOMOMO 文章、配图、小红书卡片和 GitHub 交付包" + default_prompt: "使用 $maomomo-article-writer 生成或优化一篇 MAOMOMO 风格的中文出海金融实操文章或小红书卡片。请先核查来源和事实表,再确认标题、口径、大纲或卡片结构、素材映射、2D/3D 视觉方向、比例和生图后端;如需 GitHub 交付,先引导配置专用私有仓库和最小权限 KEY / token / deploy key;先输出图片 prompts 文件,只生成一张样张确认,再逐张生成剩余图片,最后用 final_manifest.json 控制最终 assets,并按需要交付 Markdown、ZIP 或 GitHub 分批链接。" policy: allow_implicit_invocation: true diff --git a/references/delivery-rules.md b/references/delivery-rules.md index 9dc60da..da180d9 100644 --- a/references/delivery-rules.md +++ b/references/delivery-rules.md @@ -1,6 +1,6 @@ # MAOMOMO 完整交付规则 -本文件用于完整文章、配图、发布包、打包、一键下载和后续更新任务。只要用户没有明确说“只要文字”“不要图片”“不要打包”“先不要生成文件”“先只要大纲”,默认按完整交付模式执行。 +本文件用于完整文章、配图、发布包、打包、一键下载、GitHub 分批推送和后续更新任务。只要用户没有明确说“只要文字”“不要图片”“不要打包”“先不要生成文件”“先只要大纲”,默认按完整交付模式执行。用户提到“小红书”“卡片”“多图”“3:4”“9:16”时,默认进入小红书卡片模式,不默认生成完整文章、Markdown 或 ZIP。 完整交付模式指“最终交付物必须完整”,不代表可以跳过确认一次性全量生成。执行时必须遵守 `SKILL.md` 的阶段门禁:先读资料和事实表,再确认标题 / 口径 / 大纲 / 素材映射 / 配图风格 / 生图后端,随后写文章、整理 prompts、先出一张样张,样张确认后再逐张生成剩余图片。 @@ -47,16 +47,48 @@ article_package.zip ## 多渠道交付策略 -完整交付默认生成 Markdown、assets 和 ZIP,但最终发送方式按渠道和用户要求调整。 +完整文章交付默认生成 Markdown、assets 和 ZIP;小红书卡片和大图片交付默认生成 `assets/`、`final_manifest.json` 和 GitHub 分批链接。最终发送方式按渠道和用户要求调整。 -- 本地 / Web:优先给 Markdown + ZIP;如用户要图片,也附独立图片。 -- Telegram / 聊天:ZIP 可能失败,优先发送 Markdown + 多张图片独立附件;ZIP 作为可选补充。 +- 本地 / Web:完整文章可给 Markdown + ZIP;小红书卡片给 `assets/`、`final_manifest.json` 和 GitHub 链接。 +- GitHub:图片较多、文件较大、Telegram 可能失败或用户要求最终交付时,先读 `references/github-delivery.md`,确认交付仓库、专用 KEY / token / deploy key、仓库隐私级别和目标路径;配置就绪后按批次推送,例如 `batch-01-cover`、`batch-02-content-cards`、`batch-03-final-assets`。每批推送后记录 GitHub 文件路径、commit、release 或下载链接。 +- Telegram / 聊天:只负责通知进度和给 GitHub 链接;不要依赖 Telegram 直接承载 ZIP 或大图片包。少量小文件附件只能作为补充,不作为唯一交付证据。 - Email:按用户要求选择 ZIP 或独立附件。 - 用户说“打包发我 email”:发送 ZIP。 - 用户说“不打包”“分多个附件”:发送 Markdown 和图片作为多个独立附件,不再附 ZIP。 - 若附件发送失败,不要反复重复发送同一批附件;改用更稳定的渠道或分批发送,并说明只保留最后版本。 - 发送前确认附件对应的是最后确认版,不要把旧图、旧 Markdown 或临时文件发出。 +## 小红书卡片交付模式 + +触发词包括“小红书”“卡片”“多图”“4 图看完”“发小红书”“3:4”“9:16”。 + +执行规则: + +1. 不默认走完整文章 + Markdown + ZIP;除非用户明确要求,只交付小红书标题、正文、话题标签和多张独立 PNG。 +2. 出图前确认比例 `3:4` 或 `9:16`,确认视觉方向 `2D 扁平风` 或 `3D 轻拟物风`。用户没指定时推荐 `2D 扁平风`。 +3. 每张图 prompt 必须包含原生竖版防压扁规则:`native portrait composition`、`no squeezed elements`、`no stretched card / logo / text`、`vertical layout redesigned for Xiaohongshu`。 +4. 每张图按一个 job / 一次请求 / 一次 QA 推进,状态文件记录 prompt 文件、生成结果、QA 状态、是否进入 `assets/`、是否已推送 GitHub。 +5. QA 通过后生成 `final_manifest.json`,只包含最终图;推送或打包只能读取该 manifest 或状态文件里标记为 final 的 assets。 +6. GitHub 分批推送前必须按 `references/github-delivery.md` 完成或确认仓库和专用凭据配置。缺少配置时先输出本机配置步骤并暂停 push,不生成假链接。 +7. GitHub 分批推送建议: + - `batch-01-cover`:封面图和封面 prompt。 + - `batch-02-content-cards`:内容卡、流程卡、避坑卡。 + - `batch-03-final-assets`:`final_manifest.json`、全部 final assets、状态文件。 +8. 每批推送后立即回报 GitHub 文件路径、commit、release 或下载链接,并写入 `github_batches.json` 或状态文件。 +9. 最终回复固定包含 GitHub 链接、图片数量、风格:2D / 3D、比例:3:4 / 9:16、批次数量、QA 是否完成、已知限制。 + +## GitHub 交付配置门禁 + +GitHub 交付前必须先完成以下检查: + +- 已读取 `references/github-delivery.md`。 +- 已确认目标仓库,例如 `OWNER/maomomo-delivery`。 +- 仓库默认是私有;公开仓库必须经用户明确确认。 +- 已确认认证方式:`gh auth login`、`GH_TOKEN` fine-grained PAT,或 SSH deploy key。 +- 凭据必须是专用且最小权限:fine-grained PAT 只给交付仓库 `Contents: Read and write`;deploy key 只授权单仓库 write access。 +- 不要求用户在聊天里粘贴 token 或私钥;不把密钥写入项目文件、manifest、状态文件、日志或 commit。 +- `github_batches.json` 只记录 batch、文件路径、commit、release / url 和备注。 + ## 配图执行规则 具体视觉风格、图片清单、文件命名、Markdown 插图和图片 QA 规则见 `references/visual-assets.md`。本节只规定完整交付时的执行边界。 @@ -74,6 +106,7 @@ article_package.zip - 第三方 Logo 必须尺寸克制、位置辅助,不能抢占、替代或压过 `MAOMOMO` 主标识。 - 不得伪造真实 App 截图。 - prompt 中的视觉风格只能写成后台画面描述,不要写容易被模型照抄的中文风格标签;可见文字只允许出现指定业务文案、`MAOMOMO` 和已核对允许出现的品牌 / 支付标识。 +- 小红书 3:4 / 9:16 图片必须原生竖版重新构图,不能把横版设计压进竖图;prompt 必须包含防压扁和防拉伸英文约束。 - 生成图应明确是 MAOMOMO 示意图 / 信息图,不伪装成真实截图或官方页面。 - 生成后的原始最终图先保存到 `origin_image/`,通过 QA 后同步到 `assets/`,并插入 Markdown。 - 港币金额在发布稿、图片文案、prompt、manifest 和 `deck_spec.json` 中统一写 `HKD`,来源原文抽取文件除外。 @@ -97,7 +130,9 @@ article_package.zip - 只输出配图提示词,不生成图片文件。 - 只生成图片,不插入 Markdown。 - 只生成一张总图,不生成多张独立配图。 -- 只给 assets,不打 ZIP。 +- 完整文章模式只给 assets,不打 ZIP。 +- 小红书卡片只扫描目录交付,未生成 `final_manifest.json`,导致草稿、失败图、旧比例图混入最终包。 +- Telegram 发送失败后仍反复传大 ZIP,而不是改用 GitHub 分批链接。 - 只给一段新增内容,不输出更新后的完整文章。 - 只给旧文件链接,没有确认文件内容已更新。 - 修改了正文,但没有同步更新标题、摘要、配图清单、来源区和 ZIP 包。 @@ -145,6 +180,9 @@ article_package.zip - `prompts/` 中每张图的 prompt 文件已保存,且与最终图片数量和顺序一致。 - 一张样张已确认,后续图片继承同一生图后端和视觉系统。 - `slide_jobs.json` / `slide_run_state.json` 已记录图片任务状态;如缺少记录,最终回复必须说明原因。 +- 每张图的状态记录包含 prompt 文件、生成结果、QA 状态、是否进入 `assets/`、是否已推送 GitHub。 +- `final_manifest.json` 已生成,并且最终交付只读取 manifest 中列出的 final assets。 +- 需要 GitHub 交付时,已确认 `references/github-delivery.md` 中的仓库、专用凭据、隐私级别和目标路径;缺失时最终回复说明配置阻塞项。 - `origin_image/` 中最终原始图片存在。 - `assets/` 目录已生成。 - 已运行 `uv run python scripts/maomomo_check_markdown_assets.py article/article.md` 或等效脚本,确认所有 Markdown 图片路径都能对应到真实图片文件。 @@ -158,7 +196,7 @@ article_package.zip - 文末来源区只包含实际使用的来源。 - 正文没有 `contentReference`、`oaicite`、调试标记或工具残留。 - 用户要求删除的分支,标题、正文、配图清单和来源区都已删除干净。 -- ZIP 包已创建。 +- 完整文章模式下 ZIP 包已创建;小红书卡片模式下 `final_manifest.json` 和 GitHub 批次记录已创建。 - 用户最后确认的标题是否已同步到 frontmatter 和 H1。 - 用户要求删除的词是否只删除指定词,未误删整条信息。 - 正文是否残留编辑指令或模型解释。 @@ -166,8 +204,9 @@ article_package.zip - 用户提供的明确实测结论是否写成确定语气,未被改软成“可能 / 疑似 / 大概”。 - 用户提供的“以往惯例 / 过往尿性 / 过往经验”是否写成“大概率 / 按过往案例判断”的强概率语气,并保留合理边界。 - TLDR 是否过长,是否适合当前平台。 -- 小红书图是否为 9:16 竖版,是否每张都有 MAOMOMO 标识。 +- 小红书图是否为用户确认的 3:4 或 9:16,是否每张都有 MAOMOMO 标识,是否为原生竖版重构,是否没有压扁或拉伸。 +- 小红书图是否全组保持同一 2D / 3D 视觉方向。 - 图片是否匹配最终文章口径,未残留旧规则、旧金额、旧标题。 -- 邮件/聊天附件是否按用户要求选择 ZIP 或多个独立附件。 +- 邮件/聊天附件是否按用户要求选择 ZIP、多个独立附件或 GitHub 分批链接;Telegram 是否只承载通知和链接。 - 若已发送过旧版本,最终回复要明确这是最后版本,避免重复发同一批图片。 -- 最终回复包含 Markdown 下载链接和 ZIP 下载链接。 +- 完整文章最终回复包含 Markdown 下载链接和 ZIP 或 GitHub 链接;小红书卡片最终回复包含 GitHub 链接、图片数量、风格、比例、批次数量、QA 是否完成和已知限制。 diff --git a/references/github-delivery.md b/references/github-delivery.md new file mode 100644 index 0000000..40baf87 --- /dev/null +++ b/references/github-delivery.md @@ -0,0 +1,138 @@ +# GitHub 交付配置 + +本文件用于 GitHub 分批交付前的仓库和专用凭据配置。只要任务需要 GitHub push、release、下载链接,或用户提到 GitHub 仓库、专用 KEY、token、PAT、deploy key,先读本文件。 + +## 目标 + +- 使用专门的交付仓库,不和源码仓库混放。 +- 默认使用私有仓库,除非用户明确要求公开交付。 +- 使用专用、最小权限凭据;不要复用高权限个人 token。 +- 不在聊天、prompt、manifest、状态文件、README、日志或提交记录里暴露完整 token / 私钥。 + +## 推荐配置 + +推荐环境变量: + +```bash +export MAOMOMO_GITHUB_REPO="OWNER/maomomo-delivery" +export MAOMOMO_GITHUB_BRANCH="main" +export MAOMOMO_GITHUB_BASE_PATH="deliveries" +``` + +认证方式任选其一: + +1. GitHub CLI 登录,适合本机交互使用: + +```bash +gh auth login --web --git-protocol https +gh auth status +``` + +2. Fine-grained PAT,适合脚本或无头环境: + +- Repository access:只选交付仓库。 +- Repository permissions:只给 `Contents: Read and write`。 +- 过期时间:建议 30-90 天。 +- 本机设置为环境变量,不要发到聊天里: + +```bash +export GH_TOKEN="只在本机 shell 中设置,不写入项目文件" +gh auth status +``` + +3. SSH deploy key,适合只给单仓库写权限: + +```bash +ssh-keygen -t ed25519 -f "$HOME/.ssh/maomomo_delivery_ed25519" -C "maomomo-delivery" +``` + +把 `.pub` 公钥加入 GitHub 仓库 Settings -> Deploy keys,并勾选 write access。私钥只留本机,推送时使用: + +```bash +export GIT_SSH_COMMAND="ssh -i $HOME/.ssh/maomomo_delivery_ed25519 -o IdentitiesOnly=yes" +``` + +## 首次仓库准备 + +如果用户还没有交付仓库,先引导其创建或确认仓库名。不要擅自创建公开仓库。 + +```bash +gh repo create "$MAOMOMO_GITHUB_REPO" --private --clone=false +``` + +检查仓库: + +```bash +gh repo view "$MAOMOMO_GITHUB_REPO" --json nameWithOwner,visibility,defaultBranchRef +``` + +如果 `visibility` 不是 `PRIVATE`,先提醒用户可能公开图片、prompt、状态文件和活动信息;除非用户明确确认,否则不要继续推送。 + +## 本地工作目录 + +GitHub 交付工作目录默认放在 `$HOME/dev` 下,避免散落到桌面、下载目录或系统目录: + +```bash +mkdir -p "$HOME/dev/maomomo-delivery-work" +cd "$HOME/dev/maomomo-delivery-work" +``` + +如果仓库尚未 clone: + +```bash +gh repo clone "$MAOMOMO_GITHUB_REPO" repo +``` + +目标路径建议: + +```text +deliveries/{project_slug}/ +├── batch-01-cover/ +├── batch-02-content-cards/ +├── batch-03-final-assets/ +├── final_manifest.json +├── github_batches.json +└── slide_run_state.json +``` + +## 推送前检查 + +推送前必须确认: + +- `MAOMOMO_GITHUB_REPO` 已设置,或用户已明确给出目标仓库。 +- 已通过 `gh auth status`,或 SSH deploy key 已能写入目标仓库。 +- 仓库默认为私有;公开仓库必须经用户明确确认。 +- `final_manifest.json` 已生成,最终文件只从 manifest 或状态文件里的 final assets 读取。 +- 本批文件不包含 token、私钥、账号、真实个人信息或未确认敏感素材。 +- `github_batches.json` 只记录 batch、路径、commit、release / url,不记录任何密钥。 + +## 分批推送流程 + +每批只复制本批需要交付的文件,不扫描整个项目目录。提交信息优先中文: + +```bash +git add deliveries/{project_slug}/batch-01-cover deliveries/{project_slug}/github_batches.json +git commit -m "交付:{project_slug} batch-01-cover" +git push origin "$MAOMOMO_GITHUB_BRANCH" +``` + +推送后用状态脚本记录: + +```bash +uv run python scripts/maomomo_job_state.py github-batch article \ + --batch batch-01-cover \ + --commit "" \ + --url "https://github.com/OWNER/REPO/commit/" \ + --paths assets/example-01-cover.png prompts/example_01.json final_manifest.json +``` + +每批推送后回复用户:GitHub 文件路径、commit、release 或下载链接,并说明下一批状态。 + +## 阻塞处理 + +如果缺少仓库或凭据,不要继续生成假链接,也不要要求用户把 token 粘贴到聊天里。回复应包含: + +- 当前阻塞:缺少交付仓库、未登录 GitHub CLI、token 权限不足、deploy key 未授权等。 +- 本机配置命令。 +- 需要用户确认的仓库名或认证方式。 +- 已完成的本地文件路径和下一步。 diff --git a/references/image-generation.md b/references/image-generation.md index 0725a54..5b1b98f 100644 --- a/references/image-generation.md +++ b/references/image-generation.md @@ -102,6 +102,8 @@ manifest 示例: "core_text": "先报名,再看门槛和封顶", "alt_text": "MAOMOMO 汇丰 Mastercard 支付宝返现活动封面图", "style": "warm-fintech-guide", + "visual_direction": "2D 扁平风", + "delivery_batch": "batch-01-cover", "allowed_brand_marks": ["HSBC", "Mastercard", "支付宝"], "brand_mark_sources": [ { @@ -143,6 +145,8 @@ uv run python scripts/maomomo_image_gen.py batch \ - 发布用 Markdown 默认引用 `assets/`,因此如果 manifest 输出到 `origin_image/`,必须在 QA 后复制到对应 `assets/` 路径。 - 金融活动 manifest 中涉及信用卡、支付卡、支付网络或机构 Logo 时,必须记录 `reference_assets`、`payment_network`、`allowed_brand_marks`、`brand_mark_sources` 和 `card_art_rule`,并在 QA 中核对卡组织和第三方 Logo 没有画错,也没有抢 MAOMOMO 主标识位置。用户未提供 Logo / 卡面素材时,先尽力查找官方或可信来源并保存到 `sources/`;无法核对时使用通用符号,不要凭记忆画真实 Logo。 - manifest、prompt、`deck_spec.json` 和发布稿中的港币金额统一使用 `HKD`,来源原文抽取文件除外。 +- 小红书卡片 manifest 必须记录 `aspect_ratio`、`visual_direction`、`delivery_batch` 和 `portrait_composition_rules`;prompt 必须包含 `native portrait composition`、`no squeezed elements`、`no stretched card / logo / text`、`vertical layout redesigned for Xiaohongshu`。 +- 小红书卡片默认不生成完整文章、Markdown 或 ZIP;最终文件清单以 `final_manifest.json` 为准。 ## 内置风格名 @@ -194,6 +198,8 @@ uv run python scripts/maomomo_image_gen.py styles - 发布图,例如 `assets/hsbc-mastercard-alipay-campaign-01-cover.png` - 状态:`pending`、`dispatched`、`recorded`、`blocked` - 生图后端、agent id、QA note、失败原因 +- 小红书卡片字段:`aspect_ratio`、`visual_direction`、`delivery_batch` +- 最终交付字段:`qa_status`、`entered_assets`、`final`、`github_pushed`、`github_batch` 使用 `scripts/maomomo_job_state.py` 更新状态,不要手工改 JSON 后声称完成。 @@ -240,3 +246,25 @@ uv run python scripts/maomomo_job_state.py blocker article \ ``` 如果状态脚本失败,最终报告必须说明原因,并附上状态文件当前路径和未完成 job。 + +生成最终 manifest: + +```bash +uv run python scripts/maomomo_job_state.py final-manifest article +``` + +`final_manifest.json` 只写入状态为 `recorded`、QA 已通过、且 `assets/` 文件真实存在的图片。最终推送、打包或给链接时只能读取该文件,不能扫描整个目录。 + +记录 GitHub 批次: + +运行前必须先按 `references/github-delivery.md` 完成交付仓库和专用凭据配置;`github-batch` 只记录已经真实推送后的批次结果,不负责创建仓库、保存密钥或生成假链接。 + +```bash +uv run python scripts/maomomo_job_state.py github-batch article \ + --batch batch-01-cover \ + --commit "" \ + --url "https://github.com/org/repo/commit/" \ + --paths assets/example-01-cover.png prompts/example_01.json +``` + +每批推送后都要写入 batch 记录。状态文件里的单图记录会同步标记 `github_pushed=true`、`github_batch=batch-xx`;最终回复从 `github_batches.json` 或状态文件读取批次数量和链接。 diff --git a/references/image-styles/小红书竖版攻略风.md b/references/image-styles/小红书竖版攻略风.md index 6976dc8..b8a354a 100644 --- a/references/image-styles/小红书竖版攻略风.md +++ b/references/image-styles/小红书竖版攻略风.md @@ -4,6 +4,7 @@ - 小红书发布版 - 4 图看完 +- 多图卡片 / 3:4 / 9:16 - 手机端快速理解的活动 / 避坑 / 教程内容 ## MAOMOMO 出图 Brief @@ -11,11 +12,11 @@ ```json { "style_name": "xiaohongshu-vertical", - "visual_direction": "vertical mobile-first Chinese finance guide card, big readable text, series layout", + "visual_direction": "2D flat infographic card by default, optional 3D soft neumorphic direction only after user confirmation, vertical mobile-first Chinese finance guide card, big readable text, series layout", "canvas": { - "aspect_ratio": "9:16", + "aspect_ratio": "3:4 or 9:16, confirm before generation", "background": "warm white or light orange", - "composition": "large hook title, 2-4 key blocks, cat watermark or small mascot", + "composition": "native portrait composition, large hook title, 2-4 key blocks, cat watermark or small mascot, vertical layout redesigned for Xiaohongshu", "density": "low, mobile readable" }, "color_palette": { @@ -36,6 +37,9 @@ ], "avoid": [ "reuse of horizontal article image", + "squeezed elements", + "stretched card / logo / text", + "mixing 2D and 3D visual directions in one image set", "long source paragraph", "tiny screenshot-like UI", "exaggerated earning promise" @@ -46,5 +50,5 @@ ## Prompt 片段 ```text -9:16 竖版,大字少字,手机端可读,暖橙白底,白橘猫水印,适合 4 图系列,包含 MAOMOMO 标识。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。 +3:4 或 9:16 原生竖版,native portrait composition,vertical layout redesigned for Xiaohongshu,大字少字,手机端可读,暖橙白底,白橘猫水印,适合多图系列,包含 MAOMOMO 标识。no squeezed elements,no stretched card / logo / text。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。 ``` diff --git a/references/recommended-styles.md b/references/recommended-styles.md index 60787ed..6f1c105 100644 --- a/references/recommended-styles.md +++ b/references/recommended-styles.md @@ -187,20 +187,23 @@ 推荐语: ```text -我建议用 xiaohongshu-vertical,因为用户要发小红书,应该重新做 9:16 竖图,而不是复用站内横版图。 +我建议用 xiaohongshu-vertical,并默认选 2D 扁平风,因为用户要发小红书,应该重新做 3:4 或 9:16 原生竖图,而不是复用站内横版图;2D 信息卡片更清爽,中文文字也更稳。 ``` 视觉要点: -- 9:16 竖版,手机端可读。 +- 3:4 或 9:16 竖版,手机端可读。 +- 出图前先确认 `2D 扁平风` 或 `3D 轻拟物风`;默认推荐 2D,3D 适合封面、活动感和金融产品展示。 +- 全组保持同一 `visual_direction`,不得中途混用。 - 大字少字,标题钩子强。 - 4 图系列统一:封面、核心拆解、操作流程、避坑总结。 - 每张图都有 MAOMOMO 标识或猫咪水印。 +- 原生竖版重新构图,禁止横版硬塞;元素、卡片、Logo 和文字不能压扁或拉伸。 提示词片段: ```text -9:16 竖版,手机端可读,大标题,少量关键数字,暖橙白底,白橘猫水印,适合 4 图看完的中文金融攻略,包含 MAOMOMO 标识。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。 +3:4 或 9:16 原生竖版,native portrait composition,vertical layout redesigned for Xiaohongshu,手机端可读,大标题,少量关键数字,暖橙白底,白橘猫水印,适合多图系列的中文金融攻略,包含 MAOMOMO 标识。no squeezed elements,no stretched card / logo / text。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。 ``` ## 风格选择输出格式 @@ -214,7 +217,7 @@ A. warm-fintech-guide(推荐):【适合原因】 B. data-card-dashboard:【适合原因】 C. handdrawn-note:【适合原因】 -你选哪个?也可以指定更偏简洁、数据感、手绘感或小红书感。 +你选哪个?如果是小红书卡片,也请确认用 2D 扁平风还是 3D 轻拟物风;没指定我会默认用 2D。 ``` 用户选择后,把风格名写入图片清单的 `风格` 字段或 manifest 的 `style` 字段。 diff --git a/references/style-guide.md b/references/style-guide.md index d03fb36..ae19017 100644 --- a/references/style-guide.md +++ b/references/style-guide.md @@ -176,18 +176,21 @@ MAOMOMO 是偏实操的出海经验平台。公开站点标语是:“打破信 - 核心结论可以放在引用块里。 - 结尾提醒政策变化风险,例如“直至另行通知”“随时可能取消”。 -### 6. 小红书发布版 / 4 图看完 +### 6. 小红书卡片模式 / 多图发布 -用户说“小红书”“发小红书”“4图看完”“超短文字”“20字内标题”时,进入小红书模式。 +用户说“小红书”“卡片”“多图”“发小红书”“4图看完”“3:4”“9:16”“超短文字”“20字内标题”时,进入小红书卡片模式。 + +小红书卡片模式是独立交付模式,不默认走完整文章 + Markdown + ZIP 流程。除非用户明确要求文章或打包,否则重点交付多张统一风格的独立 PNG,以及可复制的小红书标题、正文和话题标签。 交付物: - 20 字内标题。 - 超短正文,适合直接复制发布。 - 3-6 个话题标签。 -- 4 张 9:16 竖版 PNG 图,不复用文章横版图。 +- 多张 3:4 或 9:16 竖版 PNG 图,默认 4 张;不复用文章横版图。 - 每张图加入 MAOMOMO logo / 猫咪水印。 -- 如果用户要求 email,按用户要求打包或多附件发送。 +- `final_manifest.json`,只列通过 QA 且进入 `assets/` 的最终图片。 +- GitHub 分批交付链接;Telegram / 聊天只通知进度和链接,不承载大 ZIP 或大图包。 文字规则: @@ -205,12 +208,27 @@ MAOMOMO 是偏实操的出海经验平台。公开站点标语是:“打破信 视觉规则: -- 纵向 9:16,手机端可读,大字少字。 +- 出图前必须先确认比例:`3:4` 或 `9:16`。 +- 出图前必须先确认视觉方向:`2D 扁平风` 或 `3D 轻拟物风`。 +- 用户没指定时,默认推荐 `2D 扁平风`:清爽、信息卡片、适合教程 / 规则说明,中文文字更稳。 +- `3D 轻拟物风` 更适合封面、活动感、金融产品展示;一旦确认,整组不能和 2D 混用。 +- 纵向原生构图,手机端可读,大字少字。 - 每张图独立,不要拼图。 - 每张图有 MAOMOMO 标识。 - 文章横图不能直接当小红书图;需要重新设计。 +- 禁止把横版设计硬塞进 3:4 或 9:16;prompt 必须包含 `native portrait composition`、`no squeezed elements`、`no stretched card / logo / text`、`vertical layout redesigned for Xiaohongshu`。 - 如果用户提供卡片、截图或 Logo 参考,只做原创示意,不复制敏感信息,不伪造真实 App 页面。 +最终回复固定包含: + +- GitHub 链接。 +- 图片数量。 +- 风格:2D / 3D。 +- 比例:3:4 / 9:16。 +- 批次数量。 +- QA 是否完成。 +- 已知限制。 + ### 5. 避坑经验 用于“不要浪费时间试了”、DCC、支付失败、转账姓名顺序问题、线下网点提醒等。 diff --git a/references/visual-assets.md b/references/visual-assets.md index 46d09f0..de38bac 100644 --- a/references/visual-assets.md +++ b/references/visual-assets.md @@ -92,13 +92,20 @@ MAOMOMO 暖橙金融教程风,画出一张手绘便签风仪表盘图。 如果用户没有指定视觉方向,生成图片前先读 `references/recommended-styles.md`,给 2-3 个可选风格,并推荐一个。确认后,整篇文章的配图保持同一套视觉身份:稳定的配色、猫咪造型、标题区、图标语言和信息密度;不同图片可以根据用途变化构图。 +小红书卡片模式先确认两件事: + +- 比例:`3:4` 或 `9:16`。 +- 视觉方向:`2D 扁平风` 或 `3D 轻拟物风`。用户没指定时,推荐 `2D 扁平风`,因为信息卡片更清爽、教程 / 规则说明更稳,也更不容易出现中文文字翻车;`3D 轻拟物风` 更适合封面、活动感和金融产品展示。 + +小红书卡片一旦确认 `2D` 或 `3D`,整组图片必须继承同一个 `visual_direction`,不得中途混用。 + 默认推荐逻辑: - 活动 / 返现 / 港卡攻略:优先 `warm-fintech-guide`。 - 规则、门槛、费用、返现计算和产品对比:优先 `data-card-dashboard`。 - SEO 长文、经验整理、品牌感更强的文章:优先 `clean-editorial`。 - 保姆式教程、避坑经验和轻量操作步骤:优先 `handdrawn-note`。 -- 小红书 / 4 图看完:必须用 `xiaohongshu-vertical`,并生成 9:16 竖图。 +- 小红书 / 4 图看完 / 多图卡片:必须用 `xiaohongshu-vertical`,并按用户确认生成 3:4 或 9:16 竖图。 可选风格: @@ -114,7 +121,7 @@ MAOMOMO 暖橙金融教程风,画出一张手绘便签风仪表盘图。 | `e-ink-editorial` | 深度解释、观点长文 | 纸感、黑白灰、少量强调色、元信息条 | | `scientific-defense` | 条款核对、规则证据链 | 严谨浅色版式、来源卡片、条件树 | | `mckinsey-brief` | 多方案对比、路径选择 | 结论先行、矩阵、取舍图、商业简报感 | -| `xiaohongshu-vertical` | 小红书发布版、4 图看完 | 9:16 竖版、大字少字、强钩子、系列化版式 | +| `xiaohongshu-vertical` | 小红书发布版、4 图看完、多图卡片 | 3:4 或 9:16 竖版、大字少字、强钩子、系列化版式 | 示例确认话术: @@ -143,7 +150,7 @@ C. handdrawn-note:便签和手绘箭头更明显,适合避坑经验和保姆 ## 图片清单格式 -生成图片前先规划清单,并同步写入 `prompts/{article_slug}_XX.json` 或图片 manifest。每个 prompt 文件至少包含:文件名、类型、放置位置、核心文案、可见文字白名单、alt text、风格、依赖素材、输出路径和完整 prompt。涉及信用卡、支付卡、支付网络或机构 Logo 时,还要包含参考素材、卡组织 / 支付网络、`allowed_brand_marks`、`brand_mark_sources` 和核对依据。 +生成图片前先规划清单,并同步写入 `prompts/{article_slug}_XX.json` 或图片 manifest。每个 prompt 文件至少包含:文件名、类型、放置位置、核心文案、可见文字白名单、alt text、风格、依赖素材、输出路径和完整 prompt。小红书卡片还必须包含 `aspect_ratio`、`visual_direction`、`portrait_composition_rules` 和分批交付批次。涉及信用卡、支付卡、支付网络或机构 Logo 时,还要包含参考素材、卡组织 / 支付网络、`allowed_brand_marks`、`brand_mark_sources` 和核对依据。 ```markdown | 文件名 | 类型 | 放置位置 | 核心文案 | alt text | 风格 | 依赖素材 | @@ -168,6 +175,10 @@ C. handdrawn-note:便签和手绘箭头更明显,适合避坑经验和保姆 "visible_text_allowed": ["先报名,再看门槛和封顶", "MAOMOMO"], "alt_text": "MAOMOMO 汇丰 Mastercard 支付宝返现活动封面图", "style": "warm-fintech-guide", + "aspect_ratio": "16:9", + "visual_direction": "2D 扁平风", + "delivery_batch": "batch-01-cover", + "portrait_composition_rules": [], "required_assets": [], "reference_assets": [], "brand_mark_sources": [], @@ -179,6 +190,17 @@ C. handdrawn-note:便签和手绘箭头更明显,适合避坑经验和保姆 } ``` +小红书卡片 prompt JSON 中,`portrait_composition_rules` 必须包含: + +```json +[ + "native portrait composition", + "no squeezed elements", + "no stretched card / logo / text", + "vertical layout redesigned for Xiaohongshu" +] +``` + ## 文件命名 图片文件名必须 SEO 友好,并包含主题前缀: @@ -259,13 +281,17 @@ assets/ ## 小红书图 -用户说“小红书”“4 图看完”“发小红书”时: +用户说“小红书”“卡片”“多图”“4 图看完”“发小红书”“3:4”“9:16”时: -- 生成 4 张 9:16 竖版 PNG,不复用文章横版图。 +- 进入小红书卡片模式,不默认生成完整文章、Markdown 或 ZIP。 +- 生成多张 3:4 或 9:16 竖版 PNG,默认 4 张;不复用文章横版图。 +- 出图前先确认 `2D 扁平风` / `3D 轻拟物风`;默认推荐 `2D 扁平风`。 - 每张图有 MAOMOMO 标识或猫咪水印。 - 手机端可读,大字少字。 - 默认结构:封面、核心拆解、操作流程、避坑总结。 -- 风格固定为 `xiaohongshu-vertical`,不直接复用横版文章图。 +- 风格固定为 `xiaohongshu-vertical`,同组保持同一 `visual_direction`,不直接复用横版文章图。 +- prompt 必须包含 `native portrait composition`、`no squeezed elements`、`no stretched card / logo / text`、`vertical layout redesigned for Xiaohongshu`。 +- 通过 QA 后写入 `final_manifest.json`;最终交付只读取 final manifest 或状态文件中的 final assets。 ## 图片 QA @@ -273,6 +299,8 @@ assets/ - 样张已被用户确认,后续图片没有切换生图后端或视觉风格。 - 文件真实存在,格式为 PNG。 +- 小红书卡片比例为已确认的 `3:4` 或 `9:16`,不是横版图压缩或拉伸后的结果。 +- 小红书卡片是原生竖版重构,元素没有压扁,卡片、Logo 和文字没有拉伸。 - `origin_image/` 原始最终图和 `assets/` 发布图对应。 - Markdown 路径能打开对应文件。 - 图片文字清楚,无乱码、错字、截断和多余文案。 @@ -287,6 +315,8 @@ assets/ - 没有把示意图伪装成真实截图、账单、订单、App 页面或官方页面。 - 每张图独立主题明确,不是同一画面的机械变体。 - 单张 QA 通过后,才用 `scripts/maomomo_job_state.py result` 记录结果并同步到 `assets/`;不得只生成到 `origin_image/` 后声称完成。 +- 单张 QA 通过后,状态文件必须显示 `qa_status=passed`、`entered_assets=true`;GitHub 推送后必须记录 `github_pushed=true` 或对应 batch 记录。 +- 最终交付前必须生成 `final_manifest.json`,且只包含通过 QA 并进入 `assets/` 的最终文件。 - `article.md` 已插入对应章节,且用脚本检查 Markdown 中所有图片路径真实存在。 - `slide_jobs.json` / `slide_run_state.json` 中没有未处理的 `pending`、`dispatched` 或 `blocked` job;如有,不能声称完整交付完成。 diff --git a/scripts/maomomo_image_gen.py b/scripts/maomomo_image_gen.py index a522d08..849bf25 100644 --- a/scripts/maomomo_image_gen.py +++ b/scripts/maomomo_image_gen.py @@ -42,7 +42,8 @@ STYLE_PRESETS: Dict[str, str] = { "白底纸感、手绘箭头、便利贴、重点圈注和轻量猫咪贴纸,适合避坑经验和保姆式步骤。" ), "xiaohongshu-vertical": ( - "9:16 手机端可读,大字少字,强标题钩子,4 图系列一致视觉,带 MAOMOMO 标识。" + "3:4 或 9:16 原生竖版,手机端可读,大字少字,强标题钩子,多图系列一致视觉," + "默认 2D 扁平信息卡片,用户确认后可用 3D 轻拟物风,带 MAOMOMO 标识。" ), "clean-professional": ( "浅色背景、蓝绿或暖橙强调、结构化信息卡片和清晰层级,适合正式银行规则、开户教程和综合攻略。" @@ -267,6 +268,8 @@ def build_prompt( core_text: str, style: str, aspect_ratio: str, + visual_direction: str = "", + portrait_rules: str = "", extra: str = "", ) -> str: allowed_visible = [value for value in (title, core_text, "MAOMOMO") if value.strip()] @@ -281,6 +284,15 @@ def build_prompt( f"可见文字限制:画面只允许出现这些指定业务文案:{allowed_visible_text};如补充要求明确列出已核对的品牌或支付标识,可作为小型辅助标识出现;不要添加副标题、风格标签、prompt 描述、说明文字、额外 slogan 或无关文字。", "硬性要求:包含清晰可见的 MAOMOMO 主标识;中文文字清楚可读;信息层级明确;港币金额统一使用 HKD 写法;不要伪造真实 App 截图;Mastercard、Visa、UnionPay、银行、支付宝等 Logo 或支付网络标识只有在官方材料、用户素材或补充要求明确核对正确时才可出现,并且只能作为辅助识别,不能抢占或替代 MAOMOMO 主标识位置;未确认时使用通用卡片或通用支付符号,不要凭空加入或替换卡组织;不要出现真实个人信息;猫咪不穿戴第三方 Logo。", ] + if visual_direction.strip(): + parts.append(f"确认视觉方向:{visual_direction.strip()}。整组图片必须保持同一视觉方向,不得混用 2D 和 3D。") + if style == "xiaohongshu-vertical" or aspect_ratio in {"3:4", "9:16"}: + rules = portrait_rules.strip() or ( + "native portrait composition;no squeezed elements;" + "no stretched card / logo / text;vertical layout redesigned for Xiaohongshu;" + "禁止把横版设计硬塞进竖版画幅。" + ) + parts.append(f"小红书竖图要求:{rules}") if extra.strip(): parts.append(f"补充要求:{extra.strip()}") return "\n".join(parts) @@ -352,6 +364,8 @@ def generate_one(args: argparse.Namespace) -> Dict[str, str]: core_text=args.core_text, style=args.style, aspect_ratio=args.aspect_ratio, + visual_direction=args.visual_direction, + portrait_rules=args.portrait_rules, extra=args.extra, ) @@ -393,6 +407,12 @@ def batch(args: argparse.Namespace) -> List[Dict[str, str]]: die(f"manifest 第 {index} 项缺少 file_name。") style = str(item.get("style") or args.style or DEFAULT_STYLE) aspect_ratio = str(item.get("aspect_ratio") or args.aspect_ratio) + visual_direction = str(item.get("visual_direction") or args.visual_direction or "") + raw_portrait_rules = item.get("portrait_composition_rules") or args.portrait_rules + if isinstance(raw_portrait_rules, list): + portrait_rules = ";".join(str(value) for value in raw_portrait_rules) + else: + portrait_rules = str(raw_portrait_rules or "") prompt = item.get("prompt") if not isinstance(prompt, str) or not prompt.strip(): prompt = build_prompt( @@ -401,6 +421,8 @@ def batch(args: argparse.Namespace) -> List[Dict[str, str]]: core_text=str(item.get("core_text") or item.get("caption") or item.get("alt_text") or ""), style=style, aspect_ratio=aspect_ratio, + visual_direction=visual_direction, + portrait_rules=portrait_rules, extra=str(item.get("extra") or item.get("requirements") or ""), ) @@ -439,6 +461,8 @@ def build_parser() -> argparse.ArgumentParser: common.add_argument("--quality", default=DEFAULT_QUALITY) common.add_argument("--output-format", default="png") common.add_argument("--aspect-ratio", default="16:9") + common.add_argument("--visual-direction", default="") + common.add_argument("--portrait-rules", default="") common.add_argument("--dry-run", action="store_true", help="Only write prompt files; do not call the image API.") generate = subparsers.add_parser("generate", parents=[common], help="Generate one image.") diff --git a/scripts/maomomo_job_state.py b/scripts/maomomo_job_state.py index 186014d..416961d 100644 --- a/scripts/maomomo_job_state.py +++ b/scripts/maomomo_job_state.py @@ -17,6 +17,8 @@ from typing import Any, Dict, Iterable, List, Optional, Tuple JOBS_FILE = "slide_jobs.json" STATE_FILE = "slide_run_state.json" +FINAL_MANIFEST_FILE = "final_manifest.json" +GITHUB_BATCHES_FILE = "github_batches.json" def now_iso() -> str: @@ -64,6 +66,13 @@ def resolve_in_project(project: Path, value: str) -> Path: return path.resolve() +def relative_path_string(project: Path, path: Path) -> str: + try: + return str(path.resolve().relative_to(project)) + except ValueError: + return str(path.resolve()) + + def load_job_doc(project: Path) -> Dict[str, Any]: raw = read_json(project / JOBS_FILE) if isinstance(raw, list): @@ -88,6 +97,18 @@ def load_state_doc(project: Path) -> Dict[str, Any]: return raw +def load_batches_doc(project: Path) -> Dict[str, Any]: + path = project / GITHUB_BATCHES_FILE + if not path.exists(): + return {"version": 1, "batches": []} + raw = read_json(path) + if not isinstance(raw, dict): + die(f"{GITHUB_BATCHES_FILE} 必须是对象。") + if not isinstance(raw.get("batches"), list): + raw["batches"] = [] + return raw + + def save_docs(project: Path, job_doc: Dict[str, Any], state_doc: Dict[str, Any]) -> None: write_json(project / JOBS_FILE, job_doc) write_json(project / STATE_FILE, state_doc) @@ -159,6 +180,14 @@ def cmd_init(args: argparse.Namespace) -> int: "asset_out": infer_asset_out(data, origin_out), "status": "pending", "selected_backend": args.selected_backend, + "aspect_ratio": data.get("aspect_ratio", ""), + "visual_direction": data.get("visual_direction", ""), + "delivery_batch": data.get("delivery_batch", ""), + "qa_status": "pending", + "entered_assets": False, + "final": False, + "github_pushed": False, + "github_batch": "", "created_at": now_iso(), } ) @@ -186,7 +215,9 @@ def cmd_status(args: argparse.Namespace) -> int: print(json.dumps({"project": str(project), "counts": counts}, ensure_ascii=False, indent=2)) for job in job_doc["jobs"]: if isinstance(job, dict): - print(f"{job_key(job) or ''}\t{job.get('status', 'unknown')}\t{job.get('origin_out', '')}") + github = "github" if job.get("github_pushed") else "local" + qa = job.get("qa_status", "") + print(f"{job_key(job) or ''}\t{job.get('status', 'unknown')}\t{qa}\t{github}\t{job.get('origin_out', '')}") return 0 @@ -238,19 +269,25 @@ def cmd_result(args: argparse.Namespace) -> int: copy_if_needed(source, origin_out) asset_value = args.asset_out or job.get("asset_out") + entered_assets = False if isinstance(asset_value, str) and asset_value: copy_if_needed(origin_out, resolve_in_project(project, asset_value)) + entered_assets = True result = { "agent_id": args.agent_id, "backend_used": args.backend_used, "selected_source": str(source), - "origin_out": str(origin_out.relative_to(project) if origin_out.is_relative_to(project) else origin_out), + "origin_out": relative_path_string(project, origin_out), "asset_out": asset_value, + "qa_status": "passed", + "entered_assets": entered_assets, "qa_note": args.qa_note, "time": now_iso(), } job["status"] = "recorded" + job["qa_status"] = "passed" + job["entered_assets"] = entered_assets job["result"] = result event(state_doc, "result", args.job, result) save_docs(project, job_doc, state_doc) @@ -277,6 +314,177 @@ def cmd_blocker(args: argparse.Namespace) -> int: return 0 +def cmd_final_manifest(args: argparse.Namespace) -> int: + project = project_path(args.project) + job_doc = load_job_doc(project) + state_doc = load_state_doc(project) + assets: List[Dict[str, Any]] = [] + incomplete: List[str] = [] + + for job in job_doc["jobs"]: + if not isinstance(job, dict): + continue + key = job_key(job) or "" + if job.get("status") != "recorded": + incomplete.append(f"{key}: status={job.get('status', 'unknown')}") + continue + if job.get("qa_status") != "passed": + incomplete.append(f"{key}: qa_status={job.get('qa_status', 'unknown')}") + continue + if job.get("entered_assets") is not True: + incomplete.append(f"{key}: entered_assets=false") + continue + + result = job.get("result") if isinstance(job.get("result"), dict) else {} + asset_value = result.get("asset_out") or job.get("asset_out") + if not isinstance(asset_value, str) or not asset_value: + incomplete.append(f"{key}: 缺少 asset_out") + continue + + asset_path = resolve_in_project(project, asset_value) + if not asset_path.exists(): + incomplete.append(f"{key}: assets 文件不存在:{asset_value}") + continue + + job["final"] = True + assets.append( + { + "id": key, + "prompt_file": job.get("prompt_file", ""), + "origin_out": result.get("origin_out") or job.get("origin_out", ""), + "asset_out": relative_path_string(project, asset_path), + "qa_status": job.get("qa_status", ""), + "qa_note": result.get("qa_note", ""), + "aspect_ratio": job.get("aspect_ratio", ""), + "visual_direction": job.get("visual_direction", ""), + "delivery_batch": job.get("delivery_batch", ""), + "github_pushed": bool(job.get("github_pushed")), + "github_batch": job.get("github_batch", ""), + } + ) + + if incomplete and not args.allow_incomplete: + die("仍有未进入最终 manifest 的图片 job:\n" + "\n".join(incomplete)) + if not assets: + die("没有可写入 final_manifest.json 的最终图片。") + + manifest_path = resolve_in_project(project, args.out) + manifest = { + "version": 1, + "generated_at": now_iso(), + "project": project.name, + "asset_count": len(assets), + "assets": assets, + } + write_json(manifest_path, manifest) + event( + state_doc, + "final_manifest", + "*", + { + "path": relative_path_string(project, manifest_path), + "asset_count": len(assets), + "allow_incomplete": args.allow_incomplete, + }, + ) + save_docs(project, job_doc, state_doc) + print( + json.dumps( + { + "status": "final_manifest_written", + "path": relative_path_string(project, manifest_path), + "assets": len(assets), + }, + ensure_ascii=False, + indent=2, + ) + ) + return 0 + + +def cmd_github_batch(args: argparse.Namespace) -> int: + project = project_path(args.project) + job_doc = load_job_doc(project) + state_doc = load_state_doc(project) + batches_doc = load_batches_doc(project) + + rel_paths: List[str] = [] + for value in args.paths: + path = resolve_in_project(project, value) + if not path.exists(): + die(f"批次文件不存在:{value}") + rel_paths.append(relative_path_string(project, path)) + + path_set = set(rel_paths) + pushed_jobs: List[str] = [] + for job in job_doc["jobs"]: + if not isinstance(job, dict): + continue + result = job.get("result") if isinstance(job.get("result"), dict) else {} + candidate_values = [job.get("asset_out"), result.get("asset_out")] + normalized = { + relative_path_string(project, resolve_in_project(project, value)) + for value in candidate_values + if isinstance(value, str) and value + } + if normalized.intersection(path_set): + job["github_pushed"] = True + job["github_batch"] = args.batch + if isinstance(job.get("result"), dict): + job["result"]["github_pushed"] = True + job["result"]["github_batch"] = args.batch + pushed_jobs.append(job_key(job) or "") + + batch_record = { + "batch": args.batch, + "commit": args.commit, + "url": args.url, + "release": args.release, + "paths": rel_paths, + "pushed_jobs": pushed_jobs, + "note": args.note, + "time": now_iso(), + } + batches = [item for item in batches_doc["batches"] if not (isinstance(item, dict) and item.get("batch") == args.batch)] + batches.append(batch_record) + batches_doc["batches"] = batches + + manifest_path = project / FINAL_MANIFEST_FILE + if manifest_path.exists(): + manifest = read_json(manifest_path) + if isinstance(manifest, dict) and isinstance(manifest.get("assets"), list): + for item in manifest["assets"]: + if not isinstance(item, dict): + continue + asset_value = item.get("asset_out") + if not isinstance(asset_value, str) or not asset_value: + continue + normalized = relative_path_string(project, resolve_in_project(project, asset_value)) + if normalized in path_set: + item["github_pushed"] = True + item["github_batch"] = args.batch + item["github_commit"] = args.commit + item["github_url"] = args.url + write_json(manifest_path, manifest) + + write_json(project / GITHUB_BATCHES_FILE, batches_doc) + event(state_doc, "github_batch", "*", batch_record) + save_docs(project, job_doc, state_doc) + print( + json.dumps( + { + "status": "github_batch_recorded", + "batch": args.batch, + "paths": len(rel_paths), + "pushed_jobs": pushed_jobs, + }, + ensure_ascii=False, + indent=2, + ) + ) + return 0 + + def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description="Record MAOMOMO article image job state.") subparsers = parser.add_subparsers(dest="command", required=True) @@ -317,6 +525,22 @@ def build_parser() -> argparse.ArgumentParser: blocker.add_argument("--reason", required=True) blocker.add_argument("--evidence", default="") blocker.set_defaults(func=cmd_blocker) + + final_manifest = subparsers.add_parser("final-manifest", help="Write final_manifest.json from recorded assets.") + final_manifest.add_argument("project") + final_manifest.add_argument("--out", default=FINAL_MANIFEST_FILE) + final_manifest.add_argument("--allow-incomplete", action="store_true") + final_manifest.set_defaults(func=cmd_final_manifest) + + github_batch = subparsers.add_parser("github-batch", help="Record a GitHub delivery batch.") + github_batch.add_argument("project") + github_batch.add_argument("--batch", required=True) + github_batch.add_argument("--commit", required=True) + github_batch.add_argument("--url", required=True) + github_batch.add_argument("--release", default="") + github_batch.add_argument("--paths", nargs="+", required=True) + github_batch.add_argument("--note", default="") + github_batch.set_defaults(func=cmd_github_batch) return parser