1实现原理 · 为什么它能做到
「每一行都是收据」:片中所有文字不是创作出来的,而是从用户自己的 Claude Code 会话史里抽出的逐字原话,并且在渲染前必须由用户逐行过目批准。
Every line on screen is a receipt. The user approves each one before anything renders.
「普通的一天」不是模型想象的,而是统计出来的:harvest.py 从会话里的工具调用归类活动(读/改/测/构建/跑/装/提交/PR/部署/取数/渲染/上网/委派),统计各活动在会话中的出现率与中位位置,再用「会话最多的那个星期 + 该星期中位首条消息时间」定义这一天。
# the day: the weekday with the most sessions, and its usual start time (mornings preferred)
用户原话按类型分章:CATS 用正则把消息分成 building(同意/放行)、restart、knock(问进度)、notes(纠正)、side-by-side(差一点)、good-part(夸)、frames、watching、kept(记住/下次)等章节类型,并为每类设了「可引用的最大长度」。
CATS = { # chapter type → pattern over the lowercased message (+ max length for short quotable lines)
素材读取面被限定且声明为纯本地:只读 ~/.claude/projects/*/*.jsonl、其下 memory/*.md 与相关项目的 CLAUDE.md,不写不改不上传。
Reads (never writes, never uploads):
隐私闸门是可执行的结构校验,而不是口头约定:new-film.mjs 在生成 story.js 前逐字段正则扫描,命中凭证形状(sk-/ghp_/github_pat_/xox?-/AKIA/BEGIN 私钥)、邮箱、文件路径即报错,命中 URL 只警告;并且 story.approved 未置 true 时会提醒必须先给用户看每一行。
if (SECRET.some(r => r.test(t))) errs.push(`${at}.${k}: looks like a credential; remove it`);
时间轴**锁死**在 archetypes.json:每章有 bars 与若干事件秒数,且全部落在 96 BPM 网格上;画面(scenes.js)与配乐(score.py)都从这一张表取时间,因此换章节顺序也不会跑偏。
Single source of truth for the film clock. scenes.js draws every event at these times and scripts/score.py composes on them, so picture and music cannot drift.
画面是逐帧绘制的(不是 DOM/CSS 动画):kit 在一个隐藏的 p5/WEBGL canvas 上画每一张,再合成到可见的 #out;「boil」= 约 8 张/秒、每 3 帧换一张、任何停顿不超过 3 帧,这几条由 qc.py 直接从渲染出的 MP4 量出来验证。
/* the kit paints in a hidden p5/WEBGL canvas and composites each drawing into #out */
配乐与画面同源且纯本地合成:score.py 读同一份 story.json + archetypes.json,按章写作两小节(96 BPM、D 大调,主题是键入的 claude = D E F# A B A),本地用加法弦乐/Karplus-Strong 拨弦/合成厅渲染并 master,无网络无凭证。
No network, no credentials. Needs numpy, scipy, ffmpeg.
2核心能力
3外部依赖
| Kind | Dependency / Endpoint |
|---|---|
| cli | HyperFrames CLI(钉死 0.8.70,经 npx 从 npm 取;check 与 render) |
| cli | ffmpeg(配乐母带、QC 解码与响度量测、渲染链路) |
| package | p5 2.3.2 (LGPL-2.1) / p5.brush 2.2.1 (MIT) / puppeteer 25.9.0(含其钉死的 Chrome for Testing),由 lockfile 钉住 |
| package | Python3 + numpy + scipy(配乐合成与母带);harvest.py 只用标准库 |
| network | Google Fonts:合成页在预览/渲染时加载手写字体 Gochi Hand(SIL OFL) |
| network | 素材来源(唯一):用户本机的 Claude Code 转录、memory 文件与相关项目的 CLAUDE.md;不上传 |
4管控建议与判定依据
- scripts/: 执行本地脚本或命令: python3 scripts/harvest.py --out <film>/receipts.json(只读本地转录;--root 默认 ~/.c
- 网络端点: 声明外部网络访问: registry.npmjs.org:setup 的 npm ci 安装锁定的 p5/p5.brush/puppeteer;以及每次 npx --ye
- 本地工作目录: 文件读写与工件输出: film 目录内:receipts.json、story.json、story.js、index.html(改写 data-duration)、mus
5第二遍独立确认
- [ok] 脚本是否读凭证(黄/橙分界的关键) — 全目录 grep process.env / os.environ 零命中;harvest.py 走 argparse 参数(--root/--tz/--project),score.py/qc.py 无环境变量入口。new-film.mjs 的 SECRET 常量是用于**拒绝**凭证形状的正则,不是读取。因此橙档第一条不成立。
- [ok] 『Nothing is uploaded』声明是否真的成立 — harvest.py(读转录)import 仅 argparse/collections/datetime/glob/json/os/re/statistics,无任何网络模块;score.py 只 subprocess 调 ffmpeg;shoot.mjs 只 page.goto('file://…');qc.py 只跑 ffmpeg 解码。唯一出网行为是 setup 的 npm ci、每次 npx 取 hyperframes 与页面加载 Google Fonts。
- [ok] Use 门槛(至少 10 个本地会话、跨 5 个活跃日)是否真在代码里 — harvest.py:256 'enough': len(sessions) >= 10 and len(days) >= 5,并在运行摘要里打印 'enough material: {out["enough"]}';与 SKILL.md 的 Use 条件一致,且 SKILL.md 要求 enough 为 false 时停下。
- [ok] 隐私闸(凭证/邮箱/路径/URL)是否真有拦截面 — new-film.mjs 的 validate() 对每章每个字符串字段:SECRET 命中 → errs('looks like a credential; remove it');EMAIL 命中 → errs;PATH 命中 → errs;URL 命中 → warns。errs 非空即 exit 1,故是硬拦截。
- [discrepancy] 『故事必须经用户批准』是否被强制 — SKILL.md 的 Rules 写 'Approval before render.',但代码层面 story.approved 未置 true 时只产生一条 warn('story.approved is not true: show the user every on-screen line …'),并不阻断 --sync 或渲染。也就是说这是一道人工程序纪律,不是技术门禁;SKILL.md 的措辞(`story.approved` is set only by the user's yes)容易被读成强制。该出入对 pin commit 有效。
- [discrepancy] 『8 到 13 章』是否被校验 — SKILL.md Workflow 第 3 步写 '8 to 13 chapters.',但 new-film.mjs 的 validate() 只校验:非空、首章必须是 intro、末章必须是 tag、kept 必须紧邻 tag 之前、各章型必需字段;没有章数上下限检查。故 8–13 是写作纪律而非校验规则。
- [ok] 单一时间轴声明是否成立(画面与配乐同源) — archetypes.json 的 about 明写 scenes.js 与 scripts/score.py『both read it』;score.py 文档行也声明读 story.json + archetypes.json('the same table scenes.js draws from')。两处一致,未见第二处时钟。
- [ok] 没有本地转录时的行为 — harvest.py:137 'if not sessions: raise SystemExit(f\'no sessions under {a.root}: nothing to harvest\')',且没有任何兜底造数路径;与 SKILL.md『this skill never invents receipts』一致。
6结论
94c054dd0a6d242e…