Communitygithub.com

boyang-hu/website-rebuild-skill

复刻网站的 Agent Skill:抓只读镜像、从压缩代码逐行还原、自动比对验收。An agent skill that mirrors a website, rebuilds it from the minified code, and verifies the result with automated diffs.

Qu'est-ce que website-rebuild-skill ?

website-rebuild-skill is a Claude Code agent skill that 复刻网站的 Agent Skill:抓只读镜像、从压缩代码逐行还原、自动比对验收。An agent skill that mirrors a website, rebuilds it from the minified code, and verifies the result with automated diffs.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add boyang-hu/website-rebuild-skill

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Website Rebuild(获奖创意站 1:1 复刻)

把一个获奖创意网站(WebGL / 滚动叙事 / 作品集站)以取证式方法复刻为可独立运行、可验证还原度的工程。不是"看着像"的仿制——是以源站 bundle 为唯一规格书、以量化验收门收口的逐行为移植。

本方法论提炼自六个连续实践项目(工期从 6.5 周收敛到 1 天),后经 22 个完整复刻 + 5 个死站存档抢救持续回填、43 站边界探测实测校准适用范围(清单见仓库 README「已验证过的网站」)。

使用前提与授权 ⛔ 必读

本 skill 面向学习与研究目的的保真复刻,用于研究获奖创意站的实现手法。适用对象是你自有的、已获授权的,或公开可访问且允许学习临摹的网站。它不是用于未授权地采集受保护内容、规避访问控制、或商业性盗用他人作品的工具。

执行时遵守下列边界:

  • 尊重目标站规则:遵守其 robots.txt、服务条款与版权;抓取保持低频、单会话,不对目标站施加异常负载。⛔ robots.txt 是逐路径的许可声明,不是全站开关——逐 URL 判定(选组 → 最长匹配 → 无匹配即允许),不得因为存在任何 Disallow 行就判"整站禁止"(几乎每个商业站都有 /cart/checkout/adminDisallow);禁令要按行为类别归类,只有针对"抓取"的禁令才影响镜像范围,针对交易的禁令只意味着"别去点结账"。⭐ "读不懂 / 拿不准"不等于"禁止":走呈交,不走停工,更不自行缩小抓取范围。读法见 references/legal-and-deploy.md §0.3。
  • 不触碰受保护边界:不采集需要登录态、付费墙或授权才能访问的内容;本 skill 只处理匿名可公开访问的资源。若目标站明确禁止此类复制,停止并告知用户——何为"明确禁止"见 legal-and-deploy.md §0.3.6 写死的四条门槛,其余一切不确定性走呈交不走停工
  • 产出默认私有:默认 noindex、不公开部署。任何公开前必须完成逐资产版权取证,并显著标注"非官方复刻"与原作者归属(见 references/legal-and-deploy.md)。

法务判断归用户,skill 只取证与呈现(三条,全程有效):

  1. 决定权在用户:skill 收集事实(逐资产归属、许可状态、第三方权利人、源站是否仍在营业、产物内第三方标识符)、列出选项与各自的风险边界、给出建议与理由;凡涉及"能不能公开 / 部署 / 再分发 / 对外展示",必须用下文「User Input Tools」显式交回用户,不许 agent 自行下法律结论后继续往下走。
  2. 未获用户明确决定前按安全默认执行:私有仓库 + noindex + 不公开部署 + 不再分发。写给用户时说明这是默认动作("在你决定之前我不会把它发出去"),不是 agent 已作出的法务结论——两者责任归属完全不同。agent 只能往保守侧执行默认,往公开侧走必须有用户的明确决定。
  3. 法务考量不得削减镜像完整性或门的覆盖面:镜像是证据基座,完整性是技术不变量(四遍法、闭包门、GAP=0 全建立在它之上)。不抓只能有技术性理由(不是文件 / 服务端不提供 / 需授权或登录态 / 源站明令禁止),一律登记;不得以"反正不公开""不该多存一份"这类法务理由留洞。实证:某项目以"产出永不公开"为由对一类资产"登记、不补抓",缺了约 60% 的资产而五道门始终全绿,藏了四个里程碑【objectarchive】。法务决定作用于产出怎么被使用,不是证据基座是否完整。

适用范围 ⛔ 必读

主场(A 类):内容静态托管、签名行为(动画/交互)全部存放在客户端静态资产里的站——命令式 WebGL/Canvas 场景、GSAP 时间轴、烘焙数据文件(GLB/.buf/.riv)、minified 或未混淆的 bundle。绝大多数 Awwwards 风格创意站属于此类。

有条件支持(B 类):管线成立但需要额外场景处理(Shopify 平台层剥离、第三方存储桶资产、运行时 API 快照、SSG payload 展开)。当前版本的指南覆盖大部分 B 类场景,遇到未覆盖的要向用户明示风险。

明确拒绝(C/D 类)

  • C1(v0.3 起可做:重构式逆向):服务端组件源确实不下发,但它的完整输出(flight 流)内联在每页 HTML 里,是可对拍的规格书。路线:flight-decode 建坐标系 → 重构一个可构建的 Next 工程(客户端一方组件按 C2 逐字译,服务端组件从 flight 树反推为显式登记的推断物)→ verify-flight 语义门收口(模块 id 全局双射)。实测 rauchg.com(Next 16/Turbopack):18/18 路由语义一致,盲逆向对答案结构 ≈95%/行为 ≈98%。⚠ C1 的 L2/L3 合并——第一份产物就是「人写的源码 + 门证明的等价」。全流程见 references/rsc-reconstruction.md
  • C2(可做,按 A 类跑):⭐ 写法是声明式但源码下发(R3F / Theatre / Vue SFC 编译产物)。实测一个 R3F 站:useFrame 回调里就是 MathUtils.damp(x, y, 7, t) 这样的普通命令式代码,逐字切片 18 个模块换进页面后 CLEAN、8 个 canvas 齐全、跨侧 99.5%切片器不关心范式——它切的是字节。 渲染器当平台层从镜像伺服。⛔ 判别器不是库名,是「客户端是否持有行为源」(scope-and-fingerprint.md §4.0.1)。
  • D:行为主体在服务端(CMS 内容站、电商 cart/库存、A/B 实验分桶、个性化注水)——客户端没有可移植的目标物,且确定性验收无基准。

X 类(可抢救):原站已消失(域名易主 / 平台回收 / 路径移除 / 原地被替换),但 Internet Archive 往往有捕获——scripts/wayback-mirror.mjs 从 CDX 索引按锚点 + 时间窗选一个连贯时刻、以 id_ 原始字节抓成标准镜像(下游门原样工作),洞按既成事实登记进 wayback-holes.txt(读法与流程见 references/archival-rescue.md)。⭐ 抢救产出是标准镜像——X 类可走完 L3 全程(实测 first-launch:策略 A 外壳 + 数值门 9,856 样本全等 + 像素带宽内 + 自包含门,无一道门需改语义)。⛔ "CDX 无覆盖才是真不可做"按资产层读,不按站读:IA 爬虫不执行 JS,清单/拼接驱动的站可以代码层覆盖 100% 而画面层为零(实测 157/160 资产任何年代零捕获,断网即白屏)——Step 0 先做分层覆盖侦察(推导 + CDX 前缀查询)预判抢救深度,见 archival-rescue.md §1.9。历年获奖站实测消失率约 29%——这也是"第一时间镜像"是本 skill 第一纪律的原因。

判级由 Step 0 指纹侦察决定,完整判定树见 references/scope-and-fingerprint.md拒绝时要解释原因并说明该站属于哪一类,不要硬跑。

User Input Tools

需要向用户提问时(确认范围、法务决定、外部依赖决策):优先使用当前运行时的内置提问工具(如 AskUserQuestion);没有则输出编号问题清单让用户回复编号。支持多问合并时一次问完。法务类提问按 legal-and-deploy.md §0.1 的五段式写:事实 / 查不清的 / 选项 / 每个选项的风险边界 / 建议与当前默认动作。

宪法(六条纪律,全程有效)

以下六条在六个源项目中被称为"宪法级",违反任何一条都会在后续阶段以 bug 形式偿还:

  1. 镜像神圣不可污染mirror/ 磁盘文件永不修改;一切本地化适配(CDN 改写、外链 stub)在服务层响应时动态完成。
  2. 源站代码是唯一裁决,不凭观感修:每个改动先在 bundle/CSS/镜像 HTML 里找到归属行号再落地。Do not tune visuals, motion, or interaction by eye.
  3. 源站有的都要有,源站没有的不做:不自创补偿性 CSS/JS。宁可先不像,也不要发明规则——自创补丁会在机制对齐后反转成 bug。
  4. bug / 死代码 / 怪写法照抄不修:压缩代码里的每个怪写法都可能是行为本身。"好心修正" no-op bug 曾导致转场崩溃(实证见 porting-discipline.md)。
  5. 有意偏差必须登记:写清"源站怎么做 / 我们怎么做 / 为什么 / 什么条件下重新考虑"。没登记的差异一律视为 bug
  6. 代码与文档同一次提交:每个里程碑成对提交(Port xxx + Update rebuild plan: xxx),日志固定含产出 / 验收 / 教训 / 下一步断点(带行号)。

纪律 3 在 M(n+1) 的边界src/ 是显式登记的衍生物,不是对源站的断言,所以src/ 里重命名、拆模块、写注释不算"发明"——纪律 3 约束的是"为了让它看起来像而自创行为",不是"让已证明等价的代码变得可读"。但两条硬边界不动:① 结构性重写默认禁止(合并重复、提取公共函数、改算法——它们让等价不可判定);② 注释里的推测必须标注为推测,不许把逆向笔记里的猜测写成陈述句。port/mirror/ 仍然一个字节都不许动。详见 references/readable-source.md §3.4 与 §5。

Workflow

Progress Checklist

[ ] Step 0  指纹侦察与范围门 ⛔(判级 A/B/C/D/X;C/D/X 拒绝或引导,不进入下一步)
[ ] Step 1  开工评级(架构证否、分项难度打星、工期预估、与用户确认范围 + 终点 L1/L2/L3)
[ ] M0      镜像取证 ⛔(BFS 爬虫 + CDP 补录 + manifest 账本;GAP=0)
[ ] M0.5    镜像断网跑通 ⛔(零 404 / 零控制台错误 / 零外联;serve.mjs 伺服)← L1 镜像存档 终点
[ ] M1      逆向建坐标系 ⛔(_pretty 钉版本展开;engine-notes 先于任何代码;技术栈钉死;REBUILD_PLAN 建立)
[ ] M2+     严格溯源移植(依赖序里程碑推进;先竖切一条端到端链路;每里程碑冷启动实测 + CLEAN 门)
[ ] M(n-1)  对拍验收(按 verification-gates.md 决策树选门型;根因修复,不调参糊平)
[ ] M(n)    收口 ⛔(冷头评审 / 模块清单对账;版权取证 + 呈交用户决定——公开部署前必须完成)← L2 工程化复刻 终点
[ ] M(n+1)  源码化(port/ → src/:拆模块、去混淆重命名、补注释、自包含)← L3 源码化 终点

⛔ = 阻塞门:验收标准未达成不得进入下一阶段。

Flow

Step 0 — 指纹侦察与范围门。加载 references/scope-and-fingerprint.md,对用户给的 URL 执行探测协议(GET 到路径粒度、最终 URL 同一性、双抓 diff、物种/年代校验、bundle 初检),输出判级与依据。A/B 类继续;C/D/X 类向用户解释后停止或引导。

Step 1 — 开工评级。加载 references/recon-and-rating.md。架构假设先证否(依赖表会撒谎),分项难度打星(素材/3D/滚动编排/私有格式/平台层),向用户确认复刻范围(整站或指定页面)与预期。

同一次提问里让用户选终点(三级梯子,带着判级结论与分级成本估计问,不要干巴巴列选项):

终点回答的问题止于典型用途
L1 镜像存档它长什么样M0.5存档、离线欣赏(获奖站年消失率约 29%)
L2 工程化复刻它在做什么M(n)可部署、可验证的 1:1(含版权取证与部署评估)
L3 源码化它怎么做的M(n+1)研究与学习实现手法

梯子单调,选低不亏:每一级都是下一级的前缀,镜像纪律保证时间敏感的部分永远最先完成——今天选 L1,以后想升级随时续跑(向用户说明这一点)。"拿它做自己的项目"(脚手架化)不是本 skill 的阶段——用户问起时指向 references/beyond-the-rebuild.md 交接,那是他的工程,skill 到"人能读懂的真实"为止。

M0 / M0.5 — 镜像取证。加载 references/mirroring.md。用 scripts/mirror-site.mjs BFS 爬取 + scripts/netcapture.mjs 真实浏览器补录,manifest 逐文件登记 sha256,redirect: manual 纪律,外部依赖逐项决策。scripts/verify-mirror.mjs镜像自己的门(五项断言,跑在断网门之前——下游所有门问的都是"渲染得出来吗",错的镜像能让它们全绿;一个 HTTP 200 也不是"你拿到了那个资源"的证据)。scripts/serve.mjs 伺服镜像,断网验收。这一步永远最先做——原站随时可能消失或改版,镜像是全项目唯一证据基准,也是后续一切对拍的参照服。

M1 — 逆向建坐标系。加载 references/reverse-engineering.md。⛔ 第一个动作是判 bundle 形态(扁平拼接 / 模块化打包 / 多 chunk),再选工具——分层表扫顶层声明,而 webpack 打包产物的顶层声明数是 0,边界与依赖边由打包器给定(实测 24,378 行 → 569 个现成模块,用 scripts/module-map.mjs)。认不出容器时 FATAL,禁止回退到分层表(§0.5)。scripts/beautify-bundle.mjs(js-beautify 钉 1.15.1)展开 bundle 到 _pretty/,此后行号是全项目唯一溯源坐标系。先写 docs/engine-notes.md(模板:assets/templates/engine-notes.md)再写任何代码。技术栈从 bundle 取证钉死精确版本。数据驱动动画先 dump 数值账本。建立 REBUILD_PLAN.md(模板:assets/templates/rebuild-plan.md)。

M2+ — 严格溯源移植。加载 references/porting-discipline.md,并按分支路由表加载对应场景指南。每个移植文件头部注明源行号区间;GLSL/魔数/数据逐字提取;数据资产脚本抽取入库不手抄。

M(n-1) — 对拍验收。加载 references/verification-gates.mdreferences/determinism.md。全站渲染广度scripts/sweep-routes.mjs(全路由一个浏览器,逐路由 0 错误/0 失败/0 外联 + 交互钩子与逐路由采集),单路由深度才用 probe.mjs——⛔ 不要手搓逐路由起 Chrome 的循环,成本按浏览器启动次数计,且并发探针会互相收割孤儿。⚠ 归因残差之前先建自比带宽pixelcompare --self,逐侧 ≥4 次、交错跑)——没有带宽的残差一律 UNCLASSIFIED,而 UNCLASSIFIED 是失败不是通过。门型选择:有 SSR/静态 HTML 产物先建字节门 → DOM 静态场景冻结熵源走 byte-equal → 活场景(WebGL/视频/随机相位)降级量化指标 + 噪声归类 → 数据驱动动画补数值探针门 → CLEAN 门全程兜底。判定时序 bug 前先校准探针(references/environment-traps.md)。

M(n) — 收口。冷头评审:对 bundle 顶层类/模块清单逐一核对落点(功能测试测不出整块遗漏,只有清单式核对能)。加载 references/legal-and-deploy.md 完成版权取证并把决定呈交用户——在用户决定之前按安全默认执行(私有 + noindex + 不部署),公开前必须逐资产取证、显著标注非官方复刻。

M(n+1) — 源码化。加载 references/readable-source.md。到 M(n) 为止产物已证明正确但人读不了(实测:14,271 行挤在一个文件里,e 出现 2962 次,注释占 0.2%)。本阶段把 port/ 重写成 src/:拆模块 → 作用域安全地去混淆重命名 → 补分档注释 → 复制资产做到自包含。⛔ 拆分粒度不是自由选择——扁平脚本的声明顺序即求值顺序,粒度由三条硬约束决定(互相引用 / 求值顺序 / import 绑定不可赋值),先出划分方案让人过目,再切;遇到巨型模块时先测「延迟绑定少数末尾单例」的收益曲线再决定(实测 6 个绑定即从 11,246 行降到 1,013 行,而换模块系统要赔上整条工具链才换来同样粒度)。⭐ "这件事做不到"这个判断极不可靠——实测两次判为结构性不可能,两次真凶都是自己工具里的一行 bug;先怀疑测量它的工具,再怀疑对象(readable-source.md §3.1–3.1.3)。⛔ 前置条件不可协商:必须先有全绿的门。 没有裁判的重构是盲改;有了 meanAbsDiff 0.00 的裁判,每一步都能被证死——这是重构能有的最好条件,也是它必须排在最后的原因。现有门全部原样复用(目标换成 src/ 构建产物,容差不许放宽),另加符号映射门与自包含门。⛔ 结构性重写(合并重复、提取公共函数、改算法)默认禁止——它会让门从"证明等价"退化为"没测出不等价"。⭐ 纪律 4 在本阶段依然有效:你现在读得懂了,"这明显是个 bug"的冲动会比任何阶段都强,而它依然可能是行为本身。

无容器 scope-hoisted 产物(Vite/esbuild,逐字分层交付的站)走另一条路:不重写,切——拼接式分解(scripts/census-bundles.mjs 出 chunk 图与坐标 → scripts/slice-esm.mjs 按声明切成语义命名的部件,按序拼接逐字节等于原件 → scripts/verify-reassembly.mjs 一门定案,字节等价成立时全部运行时门的裁决免费转移)。执行侧不变,浏览器继续跑原 chunk。详见 readable-source.md §3.0.6。

分支路由表

Step 1 侦察结果决定加载哪些场景指南(按需,不要全量加载):

侦察发现加载
Next.js App Router / RSC(self.__next_f flight 流)——C1 重构式逆向references/rsc-reconstruction.md
WebGL / Canvas 场景(three.js、自研引擎、GLSL)references/webgl-scenes.md
GSAP / 烘焙动画数据 / CSS 变量动画 / 自研输入状态机references/animation-recovery.md
私有二进制格式(.buf / .sog / VAT / GLB 时间线 / .riv)references/binary-formats.md
Shopify 店铺(指纹见 cdn/shopShopify.themecdn.shopify.comreferences/shopify-platform.md
Sanity CMS(指纹见 cdn.sanity.io/images/<projectId>/*.api.sanity.io、载荷里成片 _key/_type/_ref)——⛔ 判级看内容烘焙时点不看库名,且 auto=format 资产按 Accept 协商返回不同字节references/sanity-platform.md
DOM 层策略选型(所有站必经;Webflow 导出 / 静态单页 / 框架 SSR 分支不同,另有"DOM 被 3D 引擎当坐标源读"的正交约束)references/dom-shell-strategies.md
大体量资产(百 MB 级媒体 / 授权字体)references/asset-management.md
无头探测行为异常 / 疑似环境问题references/environment-traps.md

Step Summary

阶段关键动作阻塞门验收产出物
Step 0指纹探测判级判级明确且已告知用户判级结论与依据
Step 1证否 + 评级 + 确认范围用户确认难度评级表、范围共识
M0/M0.5镜像 + 账本 + 断网跑通verify-mirror 五项全绿;GAP=0;零 404/零错误/零外联mirror/(只读)、manifest、serve.mjs 参照服
M1展开 bundle、逆向笔记、钉栈engine-notes 完成;版本钉死表完成_pretty/docs/engine-notes.mdREBUILD_PLAN.md
M2+溯源移植、里程碑成对提交每里程碑冷启动实测 + CLEAN 门绿带行号注释的源码、三张登记表滚动更新
M(n-1)对拍验收所选门型全绿或差异全部登记验证脚本 + 对拍产物入库(docs/compare/
M(n)冷头评审 + 版权取证 + 呈交用户清单对账零缺口;用户已作出部署决定(未决则维持安全默认)审计记录、DEPLOY.md
M(n+1)拆模块 + 去混淆 + 注释 + 自包含现有门全绿且容差未放宽;符号门双向单射零孤儿;自包含门(复制出去、断网、构建)过src/(可读工程)、docs/rename-map.jsonsrc/README.md

Script Directory

Node 22+,路径相对本 skill 目录。用法与成熟度详见 scripts/README.md

⭐⭐ 依赖纪律是按阶段划的,不是按目录划的:源码化之前,整条流水线零依赖。

Step 0 → M(n) 全程不装任何东西;复刻项目要到 M(n+1) 才获得 devDependencies(作用域安全的重命名需要真正的 parser)。scripts/(零依赖)与 tools/(允许 devDeps)只是这条阶段线在目录上的投影——判据住 scripts/,源码化阶段的重构器住 tools/

任何门不许 import 任何工具verification-gates.md §2.1.2)——检查者不能是生产者。

前面的阶段需要真正的 parser 怎么办?外挂,不要 import。 beautify-bundle.mjs(js-beautify)与 module-map.mjs(acorn)都是 spawn 一个钉死版本的 npx,脚本自身仍然零依赖、仍然可独立审查。⛔ 不要改成手写词法器——本 skill 里试过,一个含引号的正则字面量把它带偏了 16,177 行(F27)。token 流上的括号匹配是精确的,文本上的括号匹配是对字符串/正则/注释的猜测。

⚠ 这条线是被违反之后才被发现的module-map.mjs 依赖 @babel/*,却在 scripts/ 里住了整整八个版本,而同一份纪律的原话就写在它上面三行。一条只写在文档里、没有任何东西去查的规矩,会安静地失效。

脚本用途使用阶段
scripts/fingerprint.mjsStep 0 六步探测协议的跨平台等价实现(GET 存活 + 重定向链与终点域同一性、双抓 diff、物种/年代、HTML 技术指纹、bundle 初检;出现次数计数与 <1KB Referer 重试内置;Sanity CMS 证据采集——projectId/dataset/API 主机/auto=format/_key,三种拼写归一,命中即指路 sanity-platform.md)。只采证据不出判级——判级仍走 scope-and-fingerprint.md §3 判定树Step 0(无 POSIX 工具链时)
scripts/mirror-site.mjsBFS 爬虫镜像(资产白名单 + 迭代到不动点;redirect:manual + 三本账,含逐文件 sha256;⭐ redirects.tsv 与 manifest 一样跨运行累积——--scope 补页曾把它截成只剩表头,载荷门在 /work 撞 404 才发现)。--scope <前缀>页面队列限制在目标路径下(微站挂在企业 CMS 域下时必用;⛔ 只限页面不限资产)。账本累积--seeds 补漏不再截短上一轮的行)+ off-host 普查(不跟的主机逐个计数并告警——静默丢弃曾让 827 条媒体引用消失而报告写着"57 files saved")M0 第一遍
scripts/netcapture.mjs真实浏览器 CDP 抓包对账补录运行时资源(CDN 站必须传 --hosts,否则只观测同源流量、会报假 GAP=0)M0 第二遍
scripts/verify-mirror.mjs镜像自己的门,跑在断网门之前。五项断言:映射单射性 / 账本与磁盘 sha256 / 真实性(挑战页正文 + 声明类型对魔数——一个 200 不是"你拿到了那个资源"的证据) / 闭包 / 可选抽样回源。下游所有门问的都是"渲染得出来吗",错的镜像能让它们全绿M0 关账前,每次重抓镜像后
scripts/gapfill-video.mjsHLS/DASH 流媒体阶梯补录(master → rendition → 分片),静态爬虫的结构性盲区M0(有流媒体时)
scripts/reconcile-gaps.mjs运行时缺口对账器:把 netcapture 记下的 GAP 行与字节推导的全集清单(如 next/image 的 srcset 阶梯穷举——实测 1,078 vs 浏览器碰到 217)逐条补进镜像。⭐ 请求头梯子(标准 profile 4xx → 裸 profile 重试:同一个 403 有两种相反的药,landonorris 要 Referer、video.twimg 恨 Referer)+ 逐 URL 容错 + 分批记账(一次崩溃不留账外文件)。⛔ 图片 URL 的标准 profile 发浏览器同款图片 Acceptlib/negotiate.mjs)——auto=format 类 CDN 按 Accept 协商格式,*/* 拿到的是回退字节(basement D5:391 变体全回退而门全绿);账本记 profile+varyM0(运行时资源多的站)
scripts/flight-decode.mjsC1 的坐标系:把每页 self.__next_f.push 流解成模块引用表(I 行导出名=白送的 tier-1 命名证据)、HL 预载、已解引用的元素树 + JSX 式 outline。T 行按声明字节数走;:HL 空 id 行不许断链M1(C1)
scripts/verify-flight.mjsC1 语义门:构建产物 flight 树 ≟ 镜像 flight 树。自带解析器(⛔ 不 import flight-decode——检查者不能是生产者);规范化只收「证明不携带行为」的构建哈希命名空间(chunk 名/css-module 类/媒体哈希/可提升资源挂载点/编码自由度),模块 id 做全局双射(一对多即红);站点登记项走 --normalize-props(ISR 纪元字段)与 --normalize-class(库渲染子树);其余一切差异照红M(n-1)(C1)
scripts/serve.mjs零依赖静态服务器(MIME/Range/服务层改写/重定向回放),兼任源站参照服。--rewrite FROM::TO登记式字面量替换,为的是一类本地化触及不到的东西——源程序按自己的域名分支location.hostname=="x.com" && (CDN=...),镜像不在那个域名上于是整个子系统走空路径);首次命中打印,因为沉默与生效此前无法区分。--fallback-root 让复刻侧只放产出、资产全部从只读镜像读(asset-management.md 的不复制策略);未知旗标响亮失败——被静默忽略的旗标是一次没人知道的降级M0.5 起全程
scripts/build-site.mjs策略 A 构建层:按 shell-config.mjs 的登记变换表从镜像生成 site/,逐条命中下限 + --check 可复现性 + 目的断言(下限说明变换还活着,目的断言说明它达成了目的)M2+(策略 A)
scripts/verify-shell.mjs外壳字节门:逐文档 patience diff,每个差异块必须能被变换表重放解释。⛔ 不 import build-site.mjs——门不许生产它所审计之物(verification-gates.md §2.1.2)M2+(策略 A)
scripts/verify-payload.mjsSSG payload 门:把内联序列化数据块(Nuxt2 window.__NUXT__ / Nuxt3 __NUXT_DATA__ / React flight self.__next_f求值展开再按结构对拍。字节门在这里不够用——payload 是一段"输出数据的程序"(参数去重、\u002F 转义),两份可以字节不同而语义相同,也可以字节相近而语义不同;且服务层要改写它内部的 URL。实测:它抓到两侧本地化实现不一致(一侧留 href="http://host"、另一侧写出 href=""),而外壳字节门全绿M0.5 起(有 SSG payload 时)
scripts/verify-tokens.mjstoken 流等价门:排版/再发射件 ≟ 源站原件逐 token(类型+值)相等,空白/位置无关(lib/tokens.mjs,acorn 钉死)。凡以 _pretty 字节交付(再发射、切片拼接)必跑——像素/CLEAN/probe 对模板字面量内容改变全部失明(14islands:748,409 vs 748,398)M2+(排版字节交付时每 commit)
scripts/verify-nextdata.mjspages router 载荷门__NEXT_DATA__/_next/data/<buildId>/<route>.json 单侧自洽 + 双侧深比较(键序敏感、值逐字,两侧同 4xx/5xx 视为一致),--a/--b 可给伺服地址或镜像目录;--normalize 只删登记过的纪元字段,⛔ Sanity _key 是化石不 normalize。verify-payload 只认 nuxt/flight/sveltekit,这是它的空白M0.5 起(pages router 站)
scripts/emit-webpack-chunk.mjs多 chunk webpack 站的逐字再发射:按 module-map 边界把 _pretty chunk 切成逐模块部件(含首尾非模块部件),按源站容器形态 push([[ids],{…}],runtime) 原样拼回,解析交给镜像里源站自己的 webpack runtime——Turbopack "再发射进 TURBOPACK" 的 webpack 同构物;--check 拼接门逐字节;--raw 对 token 不等的模块代入压缩原件精确子串M2+(webpack 多 chunk 站)
scripts/verify-lenprefix.mjs自带长度的载荷门:走 React flight 流(Next.js App Router 每页内联的 self.__next_f.push),逐行按声明的 T<十六进制> 字节数前进,确认落点仍是一个行首。⭐ 长度前缀行没有终止符——下一行的行首就贴在声明的末尾,长度本身即分隔符。因此任何改变字节数的文本改写(本地化外链)都会让读取者把下一行的行首吞成正文。实测 eightdesign:115 条路由里 2 条只渲染出 70 字(对侧 2,440),零 404、零请求失败、HTML 字节数一致、其它门全绿;定位它的是拿 python3 -m http.server 伺服同一个目录——两条路由完美渲染,于是错处在服务器而不在字节。⛔ 这道门要先拿源站校准:第一版断言"行末必须是换行",把包括源站自身字节在内的每份文档都判为损坏M0.5 起(有 flight 载荷时)
scripts/verify-refs-served.mjs引用可达门:把产出字节里的每一条资源引用逐条问服务器(一次 GET,不开浏览器)。⭐ 关键在于问服务器,而不是再实现一遍它的解析——一个自己走镜像的离线检查就是第二份 url→path 实现,而第二份实现就是一次等着被报成窟窿的分歧(实测它把 28 张在场的图报成缺失,只因不知道服务器的查询变体回退)。⚠ 它看不见运行时拼出来的 URL,那是资源级探针的活(§1.6 class 4)M2+ 起每 commit
scripts/verify-offline.mjs零外联门的静态一半:枚举产出字节里每个外部绝对 URL 并逐条裁决(§1.6 的四类断言面里,资源级探针看不见的那三类)M0.5 起每 commit
scripts/beautify-bundle.mjs[email protected] 钉死展开 bundle 到 _pretty/ 并生成再生成说明。⛔ 排版后自查 token 流lib/tokens.mjs):js-beautify 会改变嵌套模板字面量内容而所有渲染门照绿(14islands F4)——不等的文件在账本标 DIFFER@n、退出码 1,只能当坐标不能当交付字节;[slug] 类含 glob 字符的文件名喂无括号副本(F5:CLI 对 -f 做 glob,静默零产出);输出 === 压缩输入直接 FAIL。⛔ 撞名响亮告警 + 单射断言——两个不同目录下的 main.built.js 曾静默互相覆盖,而 _pretty/ 是全项目唯一溯源坐标系,覆盖之后每个行号都指向错误的文件M1
scripts/module-map.mjs模块化 bundle 的分层表:spawn 钉死的 acorn --tokenize(零依赖),逐模块给出行区间、requires、导出名。认两种容器:webpack 对象容器(⭐ v0.3.10 起以 webpackChunk*/webpackJsonppush([[ids],{…}]) 正签名定位——three 的 400+ 导出映射曾以"属性更多"赢过真容器,把 256 模块的 chunk 报成 406 个 3 行模块;对象/数组容器模块行数 > 文件行数一律 FATAL)与 Turbopack 扁平列表(后者把导出名直接写在容器里,M(n+1) 命名因此几乎全是 tier-1 证据)。⛔ 认不出容器即 FATAL;⛔⛔ 认出来的还必须解释得了这个文件——行覆盖率 <50%、或依赖边不足 require 调用数的 25%,一律 FATAL:读错容器时工具会「成功」(实测对一个真有 20 个工厂的 chunk 报了 2 个模块)。⛔ 容器可以重复定义同一个 id——实测 597 条属性只有 569 个不同模块,且其中 4 条被遮蔽的定义实现不同;语义是对象字面量后者胜出,工具必须去重并报告,否则每份文档记的模块数都是错的,而下游拿到哪一份取决于迭代顺序M1(模块化打包产物)
scripts/closure.mjs从种子模块算传递依赖闭包,是竖切边界的唯一依据。⛔ 未知种子 ID 一律 FATAL 并给出 did-you-mean——静默丢弃会产出一个“小一号但看似合理”的切片,失败推迟到运行时M2+(模块化打包产物)
scripts/verify-tween.mjs竖切的数值门:同一关键帧规格喂两侧、逐点比补间值与缓动曲线。比像素门早得多判红,且失败会带上产生它的输入。⛔ 用例的字段名与值域必须从源码抄——第一版凭直觉写,六个用例全落在同一条曲线上、全绿、零区分力M2+(有补间/时间轴引擎时)
scripts/harvest-cases.mjs从源站活引擎采用例:驱动源站到 N 个状态,逐状态记录它自己对象里的数值(站点侧写在 harvest.config.mjs,样例见 harvest.config.example.mjs)。手写用例编码的是你相信引擎的参数是什么——六个手写用例曾全落在同一条曲线上、全绿。⛔ 只产出 A 侧,必须配 verify-harvest.mjs。⚠ 采解析完的数值而非源文本M2+(源站引擎可达时)
scripts/verify-harvest.mjs采集基线的 B 侧:把采到的身份逐条喂给移植,要求恰好匹配一个(零个=缺失,两个=移植里有重复行为、映射不成立)。⭐ 按行为匹配还能把名字找回来——源站的缓动函数全匿名,移植侧按可读键导出,指纹一对上就知道源页面在跑哪条曲线M2+(有采集基线时)
scripts/verify-crossside.mjs跨侧门:同一份输入同时喂镜像与移植,逐条比结果(站点侧写在 crossside.config.mjs,样例见 crossside.config.example.mjs)。用在源站公开了接缝时——bundle 没有模块注册表也可能有 window.<Name> = 这类模块顶层副作用,抓住编排层的那一个接缝,覆盖面远大于它的体积。⛔ 两侧串行求值,且每次运行给两侧取指纹、URL 相同直接 FATAL——并发探针会让第二个接到第一个拉起的浏览器,把一侧量两遍并报告完美一致。⛔ 用例分 judged(条件无关,逐字必须相同)与 info(条件相关,只打印但两侧都得解析得出来)M2+(源站有可直接调用的接缝时)
scripts/slice-modules.mjs按模块 id 逐字切片:边界由打包器给定,所以切片表就是一串 id,且工具能自校(--check 重切须字节一致)。转写的 webpack 运行时在文件头登记为偏差M2+(模块化打包产物)
scripts/extract-source.mjs字节切片器:按钉死行号区间切 _pretty/ 拼成生成文件(sha256 守卫 + 切片表 + 别名/桩表,--check 进门),逐字移植首选形式(§2.2;配置样例 scripts/slices.config.example.mjsM2+(逐字移植期)
scripts/probe.mjsCDP 无头探针(console/异常/网络 CLEAN 判定,退出码进 CI;--no-external 断言零外联、--walk 全滚动走查)M0.5 起每 commit
scripts/verify-routes.mjs路由/重定向/状态码契约门M2+
scripts/verify-ssr.mjsSSR/DOM 逐字节门M2+(有 SSR 产物时最先建)
scripts/pixelcompare.mjs量化像素对拍(粗网格相似度 + metric 输出)。视口 ≳ 1500×900 时 PNG 过不了 CDP 载荷硬顶,改 --format jpeg --quality 92产出前先过非空帧前置条件——两张空帧对拍会报 meanAbsDiff 0 / 相似度 100,与完美结果同形(实测:冻结把引擎停在首帧之前,三条路由全报 0,而那是 201 色 99.5% 纯黑);--pump dt,frames 是 probe-shim 的一等驱动入口,且与真实时间交错地泵——冻结页的启动仍在墙钟上等资产,settle 之后一次性泵完会让引擎永远拿不到"资产已到达"的那一帧(determinism.md §2.9.1)。--self自比带宽的合法通道(§1.3.2 要求的那次测量按定义是一侧与自己比,会被跨侧假绿守卫拦下)——产物标 kind:"self-band",且 --max-mean 对它失效:带宽是分类的输入,不是判决M(n-1)
scripts/side-by-side.mjs双侧截图并排合成图(对拍产物留证)M(n-1)
scripts/frame-census.mjs这一帧上有东西吗:数截图的颜色数与主色占比。⛔ 像素门已内置同一判据作前置条件,本脚本用于事后复核任意截图——§4.8「全部的门拍在同一个状态里」的那个状态可能是空的,而此前没有任何一道门会问这句M(n-1)
scripts/probe-shim.js确定性驱动 shim(接管整个熵面:rAF/timer/performance.now/Date.now/定种 Math.random/IntersectionObserver,手动泵到任意 t,双侧同位注入)。⭐ IO 也是一个时钟——浏览器按自己的节奏投递交叉记录,滚动揭示站因此无法冻结;接管后实测带宽 0.31 → 0.04,门的可用阈值从 0.5 收到 0.1M(n-1)
scripts/dump-timelines.mjsGLB 动画曲线 dump 成 JSON 数值账本M1(数据驱动动画时)
tools/name-modules.mjs模块提名:模块化 bundle 的 id 是内容哈希,文件名要从证据里来。按 0–4 级证据提名并把依据的那句话一起记下(人工裁决 / 自注册与全局 / 多消费方字段名 / 常量值与命名前缀 / 报错主语),⛔ 无证据保留 id——错名比哈希更糟。⭐ 最强的证据在模块外面:属性名不被压缩,this._chapterPlayer = new M(…) 能给一个匿名 class {} 命名M(n+1)(模块化打包产物)
scripts/cold-audit-modules.mjsM(n) 冷头清点(模块化产物):逐模块对账,并查有没有计算出来的 require(认 webpack 与 Turbopack 两种工厂签名)。⛔⛔ 必须报出 n/N examined 并把覆盖率当判据——它曾在一个模块都没查的情况下报绿;改成「仅 0 时报错」之后,又在查了 1/20 时报绿。⛔ 实测抓到一处条件 require(require(t ? "a" : "b"))导致闭包少算两个模块,而 9 个检查点逐像素全零毫无察觉。⚠ 零依赖阶段分辨不了作用域遮蔽,所以只判定"有没有未移植模块被已移植模块 require",疑似动态 require 列出来交人读M(n)(模块化打包产物)
scripts/verify-module-map.mjsM(n+1) 等价门(模块化产物):一模块一文件,且每个文件与打包器字节 token 级一致(只允许包装重命名那组一一映射)。⛔ 用文本把重命名 undo 回去比对会失败——module.exports 里的 exports 是属性名;门用捷径就会测到自己的捷径。⛔ 门也不许重跑重命名来比对M(n+1)(模块化打包产物)
scripts/pixelcompare.mjs--freeze-css冻 JS 时钟冻不住 CSS 动画——animation 跑在浏览器动画时间线上,不经过 JS。症状是同侧对照比跨侧还大且最差格相同。该旗标把所有动画 paused + 固定负延迟钉在同一相位(⚠ 它改变被渲染内容,这正是目的:两侧定格在同一位置)。实测带宽 0.31 → 0.20,仍未归零:剩下的是 IntersectionObserver 门控的 canvas,shim 尚未接管M(n-1)(CSS 驱动的站)
scripts/pixel-walk.mjs检查点巡航:在 N 个滚动位置各跑一次像素门。⛔ 滚两次load 时 + 虚拟时间 +1.5s 再一次)——页面在自己的 init 里重置滚动会吃掉 load 时那一次,于是所有检查点都拍页顶而两侧一致地全绿。⛔ 重复帧要逐格报出来:全局 distinct 计数在「9 格里 3 格重复」时照样通过。⛔ 单个 0.00 是这套工具能产出的最误导的数字——它是一帧,通常是页面顶部的头两秒。⚠ 先用 --self 在同样的检查点上测带宽:实测未冻结时自比 4.6–5.0、跨侧 2.6–3.4,差异整个落在噪声里;冻结后两者都归零M(n-1)
scripts/verify-decls.mjs(范式,非本 skill 提供)esbuild 形态的分类门:模块体裹在 var X = VA(() => {…}) 惰性包装里、绑定以逗号链出现,双射式符号门在这里成片假红。正确形状是把每个 port 声明分类进 declarations / collapsed / plumbing / omitted 恰好一个桶,反向要求每个 src 声明有来源或登记理由。⭐ 门的形状要跟着产物的形状走readable-source.md §3.0.5)M(n)/M(n+1)(esbuild 产物)
scripts/verify-symbols.mjs符号映射门port/ 每个顶层声明在 src/ 中有且仅有一个对应符号(双向单射,读 docs/rename-map.json),且 src/ 里没有无来源的孤儿声明。⛔ 必需不是可选——门只跑有限条路由,没被跑到的代码改坏了门是绿的;这是冷启动清点在重构阶段的同构物。⛔ 只读 rename-map.json 与两侧文本,不许 import 重构器的 parser 来"确认"重命名M(n+1)
scripts/verify-fresh.mjs新鲜度门src/dist/site/ 是否同步。⛔ 链条上只要有一步缺 --check,整条链的绿灯就可能过期——过期的产物照样伺服,下游门照样全绿(它们比的是产物对镜像,不是产物对源码)。⛔ 时间戳不是判据:实测一次 mtime 显示过期而重建字节完全相同。⚠ 必须同时查「产物是不是源码现在构建出来的」与「伺服的是不是那一份」——只查前者,缺口只是往下游挪一步M(n+1)(有构建步骤时每次)
scripts/verify-standalone.mjs自包含门:把 src/ 复制到临时目录 → 断网 → 安装 → 构建 → 跑 CLEAN 与零外联。⛔ 必须复制出去跑——原地跑会命中项目根的 node_modules/mirror//根 package.json,而这三样恰好是自包含要证伪的东西M(n+1)
scripts/verify-zerodep.mjs依赖分界门scripts/ 下不许出现 node: 之外的 import,且没有任何门 import tools/。⚠ 存在的理由是这条纪律被违反了八个版本都没人发现——它的原文就写在被违反的文件上方三行。只写在文档里、没有东西去查的规矩会安静失效每次新增脚本
scripts/lib/png.mjs零依赖 PNG 编解码对拍脚本依赖
scripts/lib/tokens.mjstoken 流读法([email protected] 钉死 spawn)+ 首分歧定位;beautify-bundle 自查与 verify-tokens 门共用(门不 import 生产者,只共享读法)beautify-bundle / verify-tokens 依赖
scripts/lib/negotiate.mjs内容协商 Accept 策略(IMG_ACCEPT 逐字照抄 Chrome 图片请求头——标尺只有一把;imageAcceptFor 认 CDP TYPE 提示/扩展名/next/image 代理解码;isNegotiated 读 Vary)+ Sanity 证据提取(sanityEvidence,裸写/\/ 转义/%2F 编码三种拼写归一)。出身 basement D5;合同由 selftest 钉住mirror-site / reconcile-gaps / fingerprint 依赖
scripts/lib/chrome.mjs无头浏览器生命周期(进程组收割 + 全退出路径 + 启动前孤儿自检;漏子进程会抬高参照侧自比带宽,把像素门调松)与 CDP 载荷硬顶常量。node scripts/lib/chrome.mjs --all/--reap 可查/回收残留所有 CDP 脚本依赖

复刻工程目录结构

三个阶段性产物,单向依赖,读作「证据 → 移植 → 源码」

<site>-rebuild/
├── mirror/               # ① 只读证据:源站 URL 空间的字节级还原。永不修改
│   └── _pretty/          #    beautify 展开产物 + 再生成说明 README
├── port/                 # ② 逐字移植:机器读,extract-source --check 守着字节一致。永不手改
│   └── _gen/             #    切片器产物(行号头指回 mirror/_pretty/)
├── src/                  # ③ 人写的工程:可读、可改、自包含(复制到任何地方都能跑)
│   ├── package.json      #    ⛔ 自己的 package.json——自包含门要把它复制出去单独跑
│   ├── assets/           #    资产在这里(③ 阶段必须复制,见 readable-source.md §2)
│   └── README.md         #    怎么跑 / 坐标系怎么读 / 哪些注释是我们写的
├── docs/
│   ├── engine-notes.md   # 逆向笔记(事实/怪癖/复刻结论三段式)
│   ├── rename-map.json   # ③ 阶段符号映射(port 位置 → 旧名 → 新名 → 依据档位)
│   └── compare/          # 对拍产物留证
├── REBUILD_PLAN.md       # §0 纪律 / 阶段计划 / §6 偏差表 / §Q 怪癖表 / §7 里程碑日志
├── mirror-manifest.json  # 镜像账本(sha256 逐文件)
├── scripts/              # 判据与前置工序:零依赖,从本 skill 拷入
└── tools/                # 重构器:③ 阶段专用,允许 devDependencies(见下)

src/ 里发现行为不对,答案在 port/mirror/,不在 src/ 就地"改到对"会把移植 bug 变成无法追溯的本地补丁,而且门会变绿——这是纪律 2 在三段坐标系下的形式。port/src/ 建成后不删除,它是等价性的另一端。

依赖分界按阶段源码化之前零依赖——项目到 M(n+1) 才有 devDependencies。scripts/(判据与前置工序)零依赖,必要时 spawn 钉死版本的 npx;tools/(源码化重构器)允许 devDependencies。任何门不许 import 任何重构器。由 scripts/verify-zerodep.mjs 守。

References

按需加载(Step 0/1 与分支路由表决定),不要开局全量读入:

Notes

  • 版权红线:本 skill 用于学习目的的复刻。产出默认私有 + noindex(安全默认,不是法务结论);公开部署前必须完成逐资产版权取证、把决定交回用户、并显著标注非官方复刻与原作者归属。最大风险是法务不是技术——但法务判断由用户作出,且永不用于削减镜像完整性或门的覆盖面
  • 工期预期:方法论成熟形态下,单页创意站 1-3 天(数十个 commit);多场景 WebGL 作品集站按周计。向用户给预估时参考 Step 1 的难度评级。
  • 对拍失败先怀疑环境:后台节流、HMR 幽灵模块、探针时钟、headless 字体缺失都会伪装成代码 bug。判定源码问题前先过 environment-traps.md 的校准清单。
  • 遇到本 skill 未覆盖的场景(B 类缺口),明确告诉用户"这一段没有既成指南,按通用纪律推进",并把新经验记入项目文档——它们是 skill 下一版的输入。

Skills associés