Files
newgrok/CONTRIBUTING.md
T
iceBear67 62bd461356
CI / test (push) Canceled after 0s
CI / grok-linux-amd64 (push) Canceled after 0s
CI / grok-windows-amd64 (push) Canceled after 0s
CI / grok-macos-arm64 (push) Canceled after 0s
CI / release (push) Canceled after 0s
update readme
2026-08-16 03:26:51 +00:00

205 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 协作与开发工作流
本仓库是 [`xai-org/grok-build`](https://github.com/xai-org/grok-build) 的 patch 化 fork。fork 多了哪些能力见 [README.md](README.md);这里只讲怎么改、怎么同步、怎么不把自己的活弄丢。
以 CraftBukkit / Spigot 的模型,在**不接受外部 PR 的上游**之上维护本地改动。
## 为什么是 patch,而不是 fork 后直接改
上游有两个决定性特征:
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/`
一字不差地重建。
两条规则,违反任意一条都会默默丢掉工作成果:
1. **Rust 源码改动写在 `work/`,不要手改 `patches/`。** 补丁文件是生成物,手改之后下次就合不上。
2. **`make rebuild` 是离开 `work/` 的唯一出口。** `make apply``git reset --hard` + `git clean -fdx`。没 commit 并且没 export 的改动,下次 apply 就没了。
## 日常循环
改代码永远在 `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 仓库,`base` 标签是上游和我们的分界:
```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``make help` 列出全部目标)。脚本自己把 `~/.cargo/bin` 加进 `PATH`,交互 shell 里不必先有 `cargo`
| 命令 | 作用 |
|------|------|
| `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`
**跑单测。** `make test` 是薄封装;要带 filter,直接走 `build.sh`,多出来的参数会原样转给 cargo:
```sh
CARGO_CMD=test PKG=xai-grok-version ./scripts/build.sh test_fork_tag
CARGO_CMD=test PKG=xai-grok-pager-bin ./scripts/build.sh version_output_writer -- --nocapture
```
## 安全网
脚本刻意不会静默吞掉工作成果:
- `work/` 有**未提交改动**时,`make apply` 拒绝执行(提示先 commit + rebuild,或 `FORCE=1`
- `work/` 的提交数和 `patches/` 的文件数**对不上**时同样拒绝——这基本意味着你忘了 `make rebuild`。故意增删补丁文件时也会触发,检查分不清这两种情况
- `git am` 进行到一半时,`make rebuild` / `make build` 会拒绝执行,而不是产出半成品
- `FORCE=1`(即 `make apply FORCE=1`)是两条 apply 护栏的逃生口,意思是「丢掉 `work/`,按现在的 `patches/` 重放」
- `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`)都是公开可访问的。
在这台机器上(4 核 / 7 GB),`xai-grok-pager-bin` 冷编译 debug 大约 11 分钟,`work/target` 会长到约 24 GB,二进制大约 612 MB。`cargo check``cargo build` 各有一份缓存,所以 `make build` 之后再 `make check` 并不快(冷跑大约 6 分钟)。不到真的缺盘,别跑 `make clean` / `make distclean`。迭代时尽量用单 crate`build.sh` 总会带 `-p`)。
## 上游树里该改哪
`work/` 里大约 91 个 workspace member。常动的几个:
| Crate | 角色 |
|-------|------|
| `xai-grok-pager-bin` | 组合根,产出 `xai-grok-pager` 二进制(发行名 `grok` |
| `xai-grok-pager` | TUIscrollback、prompt、modal、渲染;用户手册也在这里 |
| `xai-grok-shell` | Agent runtime + leader / stdio / headless 入口 |
| `xai-grok-tools` | 工具实现(terminal、文件编辑、搜索……) |
| `xai-grok-workspace` | 宿主文件系统、VCS、执行、checkpoint |
从上游继承的约束:
- **根目录 `Cargo.toml` 是生成的,当只读。** 改各 crate 自己的 `Cargo.toml`。它还带着 `[patch.crates-io]` 里对 `our-forks/async-openai` 的 git pin。
- **永远指定 crate`-p <crate>`)。** 全 workspace 编译慢到不现实;`build.sh` 会强制带 `-p`
- 动上游代码时,先看它已有的测试还成不成立。
## 示例补丁
`patches/0001``0002` 的提交信息标了 `[EXAMPLE PATCH]`,演示「改上游 Rust」和「新增文件」两种补丁,不需要就可以删。`0001` 同时也是 fork 品牌标记(`--version` 打出 `[newgrok]`),删之前想清楚。
删补丁文件要带 `FORCE=1`——`work/` 里还留着对应提交,删完之后 `patches/``work/` 少,安全检查会拦下来(它无法区分「你故意删的」和「你忘了 rebuild」):
```sh
rm patches/0001-*.patch patches/0002-*.patch
make apply FORCE=1
```
## 许可
上游代码为 Apache-2.0(见 `work/LICENSE`)。`patches/` 里的改动同样按 Apache-2.0 分发。
本仓库是非官方 fork,与 xAI 无关。