Set up CraftBukkit-style patch workflow for xai-org/grok-build

Upstream is a periodic one-way export of xAI's monorepo (linear
"Synced from monorepo" commits, SOURCE_REV pinning the internal SHA)
and explicitly refuses external contributions. So local changes can
never be upstreamed, and upstream re-drops the whole tree on every
sync -- structurally the same problem CraftBukkit has with Mojang.

Adopt the Spigot/BuildTools model: patches/ is the source of truth,
work/ is a disposable build artifact regenerated from upstream.rev
plus patches/.

  scripts/apply-patches.sh    ~ applyPatches.sh
  scripts/rebuild-patches.sh  ~ rebuildPatches.sh
  scripts/update-upstream.sh  forward-ports patches onto a new sync
  scripts/setup.sh            toolchain: rust 1.94.0, dotslash/protoc
  upstream.rev                ~ BuildTools versions/*.json pin

format-patch uses --zero-commit so rebases do not rewrite the From
line of every patch, and apply's git clean preserves work/target so
replaying patches does not cost a cold Rust rebuild.

Ships two [EXAMPLE PATCH] commits demonstrating a source edit and a
new-file addition; both are safe to delete.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iceBear67
2026-08-14 04:50:07 +00:00
co-authored by Claude Opus 5
commit 618c8e31ee
12 changed files with 841 additions and 0 deletions
+188
View File
@@ -0,0 +1,188 @@
# newgrok — `xai-org/grok-build` 的 patch 化开发工作流
以 CraftBukkit / Spigot 的模型,在**不接受外部 PR 的上游**之上维护本地改动。
## 为什么是 patch,而不是 fork 后直接改
上游 [`xai-org/grok-build`](https://github.com/xai-org/grok-build) 有两个决定性特征:
1. 它是 xAI 内部 monorepo 的**周期性单向导出**。历史是一串线性的 `Synced from monorepo`
压扁提交,根目录 `SOURCE_REV` 记录对应的内部 commit。上游随时会把整棵树重新投放一次。
2. `CONTRIBUTING.md` 明确写着**不接受任何外部 PR 或补丁**。
也就是说:改动推不回去,而上游会不断整体覆盖。直接在 fork 上改,每次同步都要打一场
merge 混战,而且没人说得清「我们到底改了什么」。
这正是 CraftBukkit 面对 Mojang 的处境。解法也一样——**把改动本身作为版本库里的唯一真相**:
> `patches/` 是源码,`work/` 是编译产物。
## 目录结构
```
newgrok/ ← 你提交的仓库
├── patches/ ★ 唯一真相:0001-*.patch,一个补丁一件事
├── scripts/ 工作流脚本
├── upstream.rev 钉住的上游 commit
├── Makefile
├── upstream/ 派生:上游的纯净 clone .gitignore
└── work/ 派生:upstream@rev + patches.gitignore
```
对照 Spigot
| 这里 | Spigot / BuildTools |
|------|---------------------|
| `upstream/` | CraftBukkit 的 clone(纯净上游) |
| `work/` | 打完补丁的工作目录 |
| `patches/` | `CraftBukkit-Patches/` |
| `scripts/apply-patches.sh` | `applyPatches.sh` |
| `scripts/rebuild-patches.sh` | `rebuildPatches.sh` |
| `upstream.rev` | `versions/*.json` 里的版本钉 |
`upstream/``work/` 都**不进 git**——它们任何时候都能从 `upstream.rev` + `patches/`
一字不差地重建。
## 快速开始
```sh
make setup # 装 Rust 工具链(1.94.0,由 rust-toolchain.toml 钉住)+ dotslash/protoc
make apply # 用 upstream.rev + patches/ 生成 work/
make build # 编译 work/target/debug/xai-grok-pager
make run # 直接启动 TUI
```
`make setup` 是幂等的,可以随时重跑。
> Rust 装在 `~/.cargo`,脚本会自己把它加进 `PATH`。若要在交互 shell 里直接用 `cargo`
> fish 执行 `source ~/.cargo/env.fish`bash 执行 `source ~/.cargo/env`。
## 日常循环
改代码永远在 `work/` 里,**一个提交 = 一个补丁**:
```sh
make apply # 拿到干净的、打好补丁的树
$EDITOR work/crates/codegen/.../foo.rs # 改
cd work && git add -A && git commit -m '...' # 提交(提交信息就是补丁标题)
cd .. && make rebuild # 写回 patches/
git add patches/ && git commit -m '...' # 提交到 fork 仓库
```
**`make rebuild` 是唯一的出口。** 没跑过它的改动,下次 `make apply` 就没了。
修改已有的补丁,用普通的 git 手段就行——`work/` 是个正常的 git 仓库:
```sh
cd work
git rebase -i base # 改写、合并、重排、删除任意补丁
cd .. && make rebuild # patches/ 会整体重新生成
```
随时看当前状态:
```sh
make status
```
## 跟进上游同步
```sh
make update-dry # 只看会拉进来什么,不动任何东西
make update # 重新钉 upstream.rev,并把补丁前移到新基线
```
`make update` 会:
1. `git fetch` 上游,列出 `upstream.rev..origin/main` 之间的新提交和 `SOURCE_REV` 变化
2.`upstream.rev` 更新为最新
3. 在新基线上重放 `patches/`
4. 干净通过则自动 `make rebuild`,让补丁上下文对齐新代码
冲突时脚本会停下并给出解决步骤——和处理 rebase 冲突完全一样:
```sh
cd work
git status # 看冲突文件
$EDITOR <冲突文件> # 消掉冲突标记
git add -A
git am --continue # 或 git am --skip / git am --abort
cd .. && make rebuild # 把前移后的结果写回去
```
补丁数量多的时候,冲突是这个模型的固有成本,也是它的价值所在:冲突精确地指出上游改动
撞上了你的哪一处改动,而不是让它悄悄消失。
## 全部命令
| 命令 | 作用 |
|------|------|
| `make setup` | 安装工具链(rustup / dotslash / protoc |
| `make apply` | `upstream.rev + patches/ → work/`;加 `FORCE=1` 丢弃 `work/` 里的未提交改动 |
| `make rebuild` | `work/``base` 之上的提交 → `patches/` |
| `make update` | 跟进最新上游同步并前移补丁 |
| `make update-dry` | 预览上游差异,不做修改 |
| `make build` / `make release` | 编译 debug / release 二进制 |
| `make check` / `make clippy` / `make fmt` | 快速类型检查 / lint / 格式化 |
| `make test` | 跑测试(`PKG=<crate>` 指定 crate |
| `make run` | 编译并启动 TUI |
| `make status` | 显示当前钉住的 rev、补丁数、`work/` 状态 |
| `make clean` | 删 `work/target` |
| `make distclean` | 删 `work/``upstream/`(不动 `patches/` |
`make build` 之后的额外参数会透传给 cargo,例如 `./scripts/build.sh --release --locked`
## 安全网
脚本刻意不会静默吞掉工作成果:
- `work/` 有**未提交改动**时,`make apply` 拒绝执行(提示先 commit + rebuild,或 `FORCE=1`
- `work/` 的提交数和 `patches/` 的文件数**对不上**时同样拒绝——这基本意味着你忘了 `make rebuild`
- `git am` 进行到一半时,`make rebuild` / `make build` 会拒绝执行,而不是产出半成品
- `make apply``git clean` 显式保留 `work/target`,否则每次打补丁都要付一次冷编译的代价
## 补丁格式
`rebuild` 用的是:
```
git format-patch --no-stat -N --zero-commit --full-index --no-signature
```
- `--zero-commit` —— 否则每次 rebase 后每个补丁的 `From <sha>` 行都会变,`git diff` 里全是噪音
- `--full-index` —— 给 `git am --3way` 留下按 blob 哈希回退的余地,上游漂移时更容易自动合上
## 构建依赖
`make setup` 会处理,这里说明它在做什么:
- **Rust 1.94.0** —— 由上游 `rust-toolchain.toml` 钉住,rustup 自动装
- **protoc** —— 只有 `xai-grok-tools-api` 需要(编译 `proto/grok-tools.proto`)。上游的解析顺序是
`$PROTOC` → 向上查找 `bin/protoc``PATH``bin/protoc` 是个
[DotSlash](https://dotslash-cli.com) wrapper,会按需下载 protoc 29.3;所以 `setup.sh`
装 dotslash,失败则回退到系统 `protobuf-compiler`
两个 git 依赖(`our-forks/async-openai``helix-editor/nucleo`)都是公开可访问的。
## 示例补丁
`patches/` 里预置了两个补丁,提交信息都标了 `[EXAMPLE PATCH]`
1. **`0001`** 改上游 Rust 源码——给 `xai-grok-version``FORK_NAME` / `fork_tag()`
并接进 pager 的版本输出,于是 `--version` 显示 `grok [newgrok] 1.0.3 (…)`
标记插在 `grok ` 之后而不是追加到末尾,因为 `main.rs`
`version_output_writer_preserves_channel_aware_contract` 断言该行仍以 channel label
(或 `)`)结尾——**改上游代码时顺手确认它的测试仍然成立**,这个补丁本身就是个例子。
2. **`0002`** 新增文件——根目录 `FORK.md`
不需要就直接删:
```sh
rm patches/0001-*.patch patches/0002-*.patch
make apply
```
## 许可
上游代码为 Apache-2.0(见 `work/LICENSE`)。`patches/` 里的改动同样按 Apache-2.0 分发。
本仓库是非官方 fork,与 xAI 无关。