可衍生性是上游的功能,不是下游的负担。
一份写给开源作者的规范:让别人基于你的项目做产品之后, 还能持续跟随你的更新 —— 而不是分裂成一个再也追不上的孤儿版本。
同一套工具、同一份方法,衡量两个衍生版相对上游被改动的文件数。 它们做的事几乎一样:换品牌、改默认值、接自有服务、去掉不要的东西。
| 衍生版 | 上游 | 被改动的上游文件 |
|---|---|---|
| uu-switch | cc-switch | 21 |
| U-Claw | ClawX | 213 |
差了十倍。差异不在下游的能力,在上游的结构。 cc-switch 把产品名放在两个配置文件的具名字段里 —— 改 2 个字段; ClawX 把它散在 67 个源码文件的字符串中 —— 改到哪算哪,每次跟版都冲突。
上游多花一天做结构准备,下游省下的是每次跟版的永久成本。
这不是慈善。fork 你的人跟不上你,你自己也在亏:
| 下游跟不上 | 后果落在你头上 |
|---|---|
| 衍生版停在半年前的版本 | 你修的安全漏洞,他们的用户永远等不到 |
| 跟版成本太高,索性不跟 | 你的新功能传不到那批用户 |
| 衍生版自己打补丁 | bug 修在他们那儿,永远不回流给你 |
| fork 变成孤儿版本 | 他们的用户遇到问题,仍然来你的 issue 区问 |
| 只能整文件删掉你的赞助 | 你的收入没了,而本来只要一个开关 |
SHOW_SPONSORS 开关,下游关掉的只是显示 ——
源数据、默认行为、以及你面向自己用户的收入,一分不少。
产品名、图标、官网、bundle id、包名,集中在配置文件的具名字段里, 不要散进源码字符串。
判据:衍生者应该能靠改配置字段完成大部分品牌替换, 而不需要全局搜索替换。
最疼的一条。某项目的代码里有 290 处产品名字样,散在 67 个文件中, 但它们不是一回事:
| 身份 | 窗口标题、关于页、菜单 —— 应该改 |
| 协议 | autostart 的 bundle 路径、备份导入校验头、配置迁移用的 legacy id、写进第三方配置的 profile 名 —— 改了就坏 |
外表一模一样,后果天差地别。衍生者一次「全局替换品牌名」, 自启动失效、备份互导失败、老用户迁移不了 —— 而且编译通过、测试全绿、界面正常。
默认模型、端点、遥测、更新通道、功能开关,走配置或 feature flag。 衍生版关闭遥测是正当需求(企业内网、隐私承诺、合规要求)。 没有开关,他们只能改你的代码;漏掉一处,用户数据就悄悄流回你的后台 —— 这对双方都是事故。
给 hook / policy / plugin,让衍生版用新增文件表达定制。
✗ 直接删你的预设数据 → 你每次加供应商都冲突 ✓ 在显示层套一个过滤策略 → 你的数据文件一字未动,永远干净合并
一个 hook 换来的是一整个文件永不冲突。 实测中,某衍生版 97% 的定制代码量是纯新增文件 —— 那部分永远不会冲突。
赞助链接、推广位、aff 参数,集中在一处并提供开关。 散着放的下场是衍生版只能整文件删除,你什么都留不下。
这同时是一条诚实条款:标出归因位,衍生者才知道自己在分发什么。 一个以「无广告」为卖点的衍生版,如果不知道你的文档里有几十处返利链接, 就会在不知情下替你打广告 —— 最终双方都要收拾局面。
给关键 UI 元素挂 data-* / automation id / 无障碍 id。
衍生版要跑自己的 UI 自动化,没有稳定标识就只能靠类名和文本定位 ——
而这两样正是品牌定制时必然要改的。于是他们改一次品牌,测试全挂。
这条对你自己也有好处:你的类名重构不会再打断任何人的测试。
前六条写给人;第七条写给机器。在仓库根放一份
.shadowfork/upstream.yaml:
apiVersion: shadowfork.io/v1alpha1 kind: DerivationContract identity: # 衍生版应该改这些 - file: src-tauri/tauri.conf.json path: $.identifier note: 必须改,否则与本项目装在同一台机器会互相覆盖配置与自启动 protected: # 看着像品牌,其实是协议 - glob: "src-tauri/src/**/*.rs" reason: 含 autostart 路径、备份校验头、迁移 legacy id。 改动会破坏兼容性,且不会有任何报错。 extensionPoints: # 从这里扩展,别改核心 - file: src/config/presetPolicy.ts kind: policy-hook attribution: # 归因位在哪、能不能关 - files: ["README*.md"] count: 118 toggle: null license: spdx: MIT preserveCopyright: ["Copyright (c) 2025 Jason Young"] trademark: 项目名与 Logo 为商标,不在 MIT 授权范围内。 commercial: # 商用衍生怎么办 —— 见下节 tier: notify contact: author@example.com
衍生版的配置可以直接继承你的契约。 你移动了扩展点或扩大了保护区,所有衍生版在跟版时立刻知道, 而不是等用户报障才发现。
许可证解决的是版权:MIT 早就允许别人改你的代码、闭源、再分发。 它不解决另外三件事 —— 而那三件才是商用衍生真正卡住的地方: 能不能用你的名字(商标)、你会不会哪天把扩展点重构掉(稳定性)、 以及双方怎么谈钱(没有入口)。
所以本协议不发明新的开源许可证。
新许可证没人做法律审查、企业法务一律拒收,只会加剧许可证泛滥。
commercial.tier 是一份贴在你现有许可证旁边的声明,
不替代它、不改变它,只回答一个问题:有人拿它做商业产品时,你希望怎样。
| tier | 含义 | 适合谁 |
|---|---|---|
open |
随便改、随便闭源发行,不必告知,免费。 | 纯粹想让代码传得越远越好的项目 —— 等同现状。 |
notify |
免费,但商用发行前告知作者一声。 | 默认推荐。见下。 |
revenue-sharelicense |
分成,或一次性商用授权。条款由 terms 指向的页面定义。 |
已经靠项目吃饭、或衍生版明显在挣钱的项目。 |
notify 而不是分成。
绝大多数作者不缺衍生版那点钱,缺的是「知道谁在用我的东西」。
一上来谈分成,作者第一反应是防备,谈判成本高于收益;
而 notify 对双方都近乎零成本:
作者拿到用户去向与真实反馈,衍生者拿到一份「我们尊重上游」的公开证据 ——
在向企业客户交付定制版时,这份证据比任何合规声明都硬。
revenue-share 的项目,
通常两样都拿不到。
tier 不是许可证,不构成授权,也不能收回许可证已经给出的权利。
open 之外的档位表达的是作者的期望与洽谈入口;
真正的约束力来自双方另行签署的协议,或来自上游本就采用的双授权模式
(如 GPL + 商业许可)。工具会把 tier 显示在法务闸门里,
但不会据此阻止任何许可证本就允许的行为 ——
把作者的期望伪装成法律义务,对谁都没好处。
满足七条规则的项目,可以在 README 挂上徽章。分三级,自评即可,无需认证:
| 级别 | 要求 |
|---|---|
| L1 可辨识 | 规则 1、2 —— 身份集中,协议字符串已标注 |
| L2 可覆盖 | + 规则 3、4、5 —— 默认值可配,有扩展点,归因可关 |
| L3 可契约 | + 规则 6、7 —— 有非视觉标识,提供衍生契约 |
[](https://shadowfork.net/protocol)
规则 1、3、5 通常是几小时到一天的重构,且对你自己的代码质量是净收益; 规则 2 是把已有常量摘出来;规则 7 是写一份 YAML。 规则 4、6 视项目而定,可以逐步做 —— 三级制就是为了让你分阶段采用。
抄袭者不需要你的配合 —— 他们本来就会 fork 然后全局替换,改完就跑。 本协议影响的是另一批人:想认真做长期衍生产品、并且愿意持续跟随你的人。 让他们跟得动,你的代码才传得远、bug 才回得来。 而规则 5 恰恰是唯一能让你在衍生版里保住归因的机制。
那些是下游工具,解决「变更能不能重放」。 本协议是上游规范,解决「衍生版为什么一开始就那么难跟」。 方向相反,互补而非竞争。