1实现原理 · 为什么它能做到
编排者模式:把 PR→视频拆成 7 步(Step 0 setup → 1 ingest → 2 design system → 3 storyboard/script → 3.1 audio → 4 visual design → 5 frames → 6 finalize),除 Step 5 派帧工外全部由编排者自己按序执行,且每步都有明确 Gate。
User-gated steps are Step 0, Step 3, and Step 6.
输入被定义为「代码变更」,不是网站:靠 gh 读 PR,没有 capture 步骤,也没有除贡献者头像之外的真实素材。
The input is a **code change** (read via `gh`), not a website — there is **no capture step and no real assets** beyond the contributors' avatars.
Step 1 是确定性摄取:fetch-pr.mjs 跑 gh 取 PR 核心对象,并用分页 gh api 补齐 files 列表(gh pr view 只到约 100 个文件),只写 capture/pr.json 与 capture/diff.patch,不建临时目录;MERGED 的 PR 还会尽力解析出一个真实的 shipped_version。
// gh pr view --json files caps at ~100 files; the REST endpoint paginates with no
项目落在解析出的外部目录,绝不在调用方仓库里建 videos/:先取 PR 引用 → 由 project-dir.mjs 解析(可由 PR_TO_VIDEO_PROJECT_DIR 覆盖)→ preflight 校验 CLI 能力 → 缺 hyperframes.json 才 init。
Never create `videos/` in the caller repository:
风格固定为 code-editorial,不询问用户:由 build-frame.mjs 把 preset 的 FRAME.md 复制并混入品牌令牌(PR 没有品牌 → colors:[]/fonts:[]),再复制 caption skin 并自校验。
The style is fixed — **code-editorial** (warm editorial; a navy code surface built for diffs).
Step 3 的叙事纪律:顺序来自叙事设计而不是 diff 的文件顺序,且用「字数预算」量化(TTS ≈2.2 词/秒,单帧软目标 ≤19 词/≤9s,硬上限 >26 词/>12s,整片甜点区 ~30–90s)。
Scene order comes from narrative design, not from the diff's file order or the commit list.
代码节拍交给 registry 的 code-* 积木(code-diff / code-morph / code-typing / code-highlight 等),并且要求先查 live catalog 再手写;非代码的「机制节拍」则要求发明动画/流程图/数据图。
**Reach for one of these first** for any code beat; fall back to hand-authored composition only when none fits.
Step 5 用有界并行:帧包由 frame-packets.mjs 生成(缺 ### Source excerpt 的代码帧直接硬失败、并硬限字节数),派发**至多 3 个** worker,每个 worker 只读自己的 packet 与 frame.md;某帧失败只重派该帧一次,且必须带上具体 finding。
Dispatch **at most three workers total**, balanced across the packet paths
验证段很克制:lint + check + snapshot 接触表,且文档预先点名一个已知误报(caption 高亮的 text_box_overflow 1–4px)要求不要追;渲染必须等用户批准。
**Known false-positive — do not chase it.**
2核心能力
3外部依赖
| 类型 | 依赖 |
|---|---|
| cli | GitHub CLI(gh):pr view / pr diff / api 分页 / users/<login> 取显示名——摄取 PR 的唯一通道 |
| api | GitHub 用户 API(解析 null 显示名,供 credits 帧旁白念姓名) |
| network | 贡献者头像下载(白名单仅 GitHub 头像主机;github.com/<login>.png 302 到 avatars.githubusercontent.com) |
| cli | 官方 hyperframes CLI:init / add / catalog / lint / check / snapshot / preview / render(--skill=pr-to-video) |
| api | HeyGen 音频后端(BGM 检索;TTS 由同一个共享音频引擎承担,signed in 时走 HeyGen、离线走本地引擎) |
| package | 兄弟 skill 依赖(被指名调用):/media-use(BGM/SFX/图像/logo 来源与音频引擎)、/hyperframes、/hyperframes-core、/hyperframes-animation(rules/blueprints)、/hyperframes-creative(帧 preset) |
| network | CDN 资源(产物期):组装的 index.html/captions.html 引用 GSAP,带 SRI 与 crossorigin |
| package | 共享音频引擎路径可用环境变量覆盖(HF_MEDIA_ENGINE),默认指向 /media-use 的 audio.mjs |
4风险提醒 风险提醒:橙色 · 评估后使用
- PR 内容是不可信输入 — 标题/正文/评论/diff 会进入模型上下文并影响脚本与画面,理论上含指令式文本可干扰生成;同时产物是「关于一个真实变更的公开视频」,错误描述会损害项目形象——建议人对 STORYBOARD 与脚本做一次事实核对。
- 以用户 GitHub 身份读取,且读的是可能私有的仓 — gh 凭证决定能读到什么;在多账号机器上应确认 gh 的当前身份,避免把私有内容做成视频(导出物会长期留在 PROJECT_DIR)。
- 音频把内容送往外部后端并消耗额度 — signed in 时 narration/BGM 走 HeyGen(API 检索 + TTS),意味着脚本内容与音乐选择会经过 HeyGen;未登录则降级本地引擎。使用者应知道这一点再决定是否登录。
- 残留的过期注释(stage-assets.mjs) — scripts/lib/assets.mjs 与 assemble-index.mjs 仍引用本 skill 未分发的 stage-assets.mjs;虽不影响运行(仅注释),但会误导维护者以为 Step 4 有独立暂存步骤。
- 流程依赖多个姊妹 skill 与 CLI 版本 — 样式、叙事、动效 rules/blueprints、音频与资产解析都指向 /hyperframes-core、/hyperframes-creative、/hyperframes-animation、/media-use;缺其中一个或 CLI 过旧,preflight 会阻断(这是设计,但意味着安装面较大)。
5第二遍独立确认
- [ok] gh 凭证是否真被使用(橙档证据之一) — fetch-pr.mjs 顶部注释写明「gh runs HERE so auth / not-found / private-repo errors surface with gh's own stderr and exit 1」,实现里第一步就是 `if (!ghTry(["auth", "status"]).ok) { die("gh is not authenticated — run: gh auth login"); }`,随后 pr view / api 分页 / pr diff 三处都用同一 execFileSync 包装。调用点真实存在。
- [ok] HeyGen 凭证与 TTS/BGM 实现到底在谁身上(避免把别家行为算进档位) — 本 skill 的 scripts/audio.mjs 头注释自述「The TTS / BGM / SFX implementation no longer lives here: it is the shared engine at ../../media-use/audio/scripts/audio.mjs」,实现只做 SCRIPT/STORYBOARD → audio_request.json 的映射与回写;凭证读取发生在被指名的姊妹 skill 内。按范式「他人 skill 被指名调用只写进 reason、不作档位依据」,橙档依据只用本目录自身可取证的两条:SKILL.md 明写音频共用 ~/.heygen 凭证,以及 fetch-pr.mjs 的 gh 凭证依赖。
- [ok] 头像下载的防护是否真实(是否只是口号) — fetch-people-avatars.mjs 里有 AVATAR_HOSTS 白名单 + isAllowedAvatarUrl(拒绝非 GitHub 主机)与 isUnderProject(写入路径必须在项目目录内,防 ../../ 逃逸),并以 softExit 保证任何失败都 exit 0;注释解释了 github.com/<login>.png 302 到 avatars.githubusercontent.com 属同主控重定向。防护与注释一致,非表面文章。
- [discrepancy] 「无 asset-staging 步骤」的声明与随包代码是否一致 — SKILL.md Step 4 明写 `There is **no asset-staging step** — the only real assets are the credits avatars, already in `assets/`。`,但 scripts/lib/assets.mjs 的头注释仍写着「Shared by stage-assets.mjs (Step 4 close, BEFORE the frame workers run) and assemble-index.mjs (Step 5, idempotent backstop)」,而 **pr-to-video 目录内并不存在 stage-assets.mjs**(该脚本只存在于同仓 product-launch-video 与 music-to-video),assemble-index.mjs 第 521 行也仍在注释里引用它。结论:这是复用姊妹 skill 时残留的过期注释(audio.mjs 自述该适配层在复用者之间「intentionally identical」,可解释来源);实际行为与 SKILL.md 一致(只有 assemble-index 作为幂等兜底暂存头像),但读者若去找 Step 4 的 stage-assets 会找不到——对 pin commit 有效的真实出入。
- [ok] 是否漏掉未声明的网络调用 — 全目录 URL 命中仅 5 处:两处 gsap CDN(assemble-index.mjs 与 captions.mjs,产物期)、一处 ingest.mjs 的 `https://github.com/${login}.png?size=200` 头像 URL、两处测试文件里的 PR fixture URL;fetch( 仅出现在 fetch-people-avatars.mjs(带白名单);无其它隐藏端点。
- [ok] 「Step 1 无 capture、只读 gh」与脚本能力是否相符 — fetch-pr.mjs 只写 capture/pr.json 与 capture/diff.patch(头注释明确 no scratch dir),ingest.mjs 是纯离线变换,两者都不抓网页;Quick Reference 亦写明「no Step 1 capture」。与 description「the input is a code change, not a website」一致,无夸大。
6结论
8f260d47a576b3e2…d2f0bc7f34