| name | mcpp-contributing |
| description | Use when contributing to the mcpp project — submitting bug fixes, new features, code optimizations, documentation improvements, or any PR. Covers issue creation, branch conventions, build verification, CI requirements, and PR workflow using gh and git. |
mcpp 项目开发贡献规范
Overview
mcpp 项目的贡献流程:先创建 Issue → 切分支 → 实现改动 → 提交 PR → CI 通过 → Review 合入。
核心原则
禁止直接 push main
所有改动必须通过 PR 合入 main,无论改动大小。这包括:
- 代码改动(feat / fix / refactor)
- 文档改动(docs / skills / .agents/)
- 配置改动(mcpp.toml / .xlings.json / CI workflow)
- 版本号 bump(必须通过 PR,不能直接 push)
唯一例外:紧急 hotfix 需要 --admin 合入,但也必须先创建 PR。
违规示例(不允许):
git commit -m "docs: add skill" && git push origin main
git push origin feature:main
正确做法:
git checkout -b docs/add-release-skill
git commit -m "docs: add release skill"
git push -u origin docs/add-release-skill
gh pr create --title "docs: add release skill" --body "..."
贡献流程
1. 创建 Issue(必须)
所有贡献先创建 Issue,特别是新功能。避免重复工作,留下讨论记录。
Bug 修复
gh issue create \
--title "fix: 简短描述" \
--body "## 复现步骤
1. ...
## 期望行为
...
## 实际行为
...
## 环境
- mcpp 版本:\`mcpp --version\`
- OS:"
新功能
gh issue create \
--title "feat: 简短描述" \
--body "## 动机
...
## 设计思路
...
## 涉及模块
..."
代码优化
gh issue create \
--title "refactor: 简短描述" \
--body "## 当前问题
...
## 优化方案
..."
2. 创建分支
必须从最新 main 创建分支,禁止在 main 上直接开发。
git checkout main && git pull origin main
git checkout -b <type>/<short-description>
分支命名规范:
feat/xxx — 新功能
fix/xxx — Bug 修复
refactor/xxx — 重构
test/xxx — 测试
docs/xxx — 文档
chore/xxx — 版本 bump、配置、依赖更新
3. 实现改动
开发要求
- 遵循现有代码风格(查看相邻代码)
- 模块导入用
import std; 和 import mcpp.xxx;
- 只改需要改的,不顺手重构不相关代码
- 平台相关代码放在
src/platform/ 目录下
构建验证
mcpp build
<fresh-mcpp-binary> --version
测试
<fresh-mcpp-binary> test
MCPP=<absolute-path-to-fresh-mcpp-or-mcpp.exe> bash tests/e2e/01_help_and_version.sh
MCPP=<absolute-path-to-fresh-mcpp-or-mcpp.exe> bash tests/e2e/<relevant-test>.sh
E2E 并不保证完全离线:部分脚本需要工具链、索引或 capability provider。
按 CI 等价方式设置 MCPP_HOME、镜像和其他 capability 后再运行;不要让缓存命中
或空 workspace 选择冒充行为覆盖。
4. 提交 PR
提交信息前缀:feat: / fix: / refactor: / test: / docs: / chore:
git push -u origin <branch>
gh pr create \
--title "<type>: 简短描述" \
--body "## Summary
- 改动点
Closes #<issue>
## Test plan
- [ ] 文档-only:示例与链接已按当前实现复核,无运行时行为变更
- [ ] 涉及行为或测试文档时:`mcpp test`(unit/integration)通过
- [ ] 涉及行为或测试文档时:相关 E2E 脚本使用 fresh `MCPP` 通过"
PR 要求:
- title 用英文,body 中英文均可
- 关联 Issue(
Closes #N)
- 包含 test plan
- 一个 PR 只做一件事,不混入无关改动
5. CI 必须通过
CI 不通过的 PR 不会被合入。
gh pr checks <pr-number>
gh run view <run-id> --log-failed
CI 由分平台的基础构建/单元集成检查与独立 E2E 检查组成:
| Workflow | 平台 | 内容 |
|---|
ci-linux / ci-linux-e2e | Linux x86_64 | 自举构建、unit/integration / 分片 E2E |
ci-macos / ci-macos-e2e | macOS ARM64 | 自举构建、unit/integration / E2E |
ci-windows / ci-windows-e2e | Windows x86_64 | 自举构建、toolchain 回归 / E2E |
cross-build-test | Linux/Windows cross targets | 交叉构建、产物运行与 MinGW/Wine 检查 |
ci-aarch64-fresh-install | Linux ARM64 native | path-filtered fresh install、原生自举与 musl build.mcpp host-helper 回归 |
以 PR 实际 required checks 为准,所有未跳过的 required checks 必须通过。 如果某个平台失败:
- 下载日志分析原因
- 修复后 push 到同一分支,CI 自动重跑
- 如果是 flaky test,在 PR 中说明
6. Review & 合入
维护者 review → 反馈修改 → CI 重跑 → Merge(保留 commit 历史)。
合入方式:
- 默认使用 Merge commit(保留完整历史)
- 单 commit 的 PR 也可用 Squash merge
Agent 开发规范
Agent(Claude Code 等)在执行任务时,同样必须遵守 PR 流程:
Agent 必须做的
- 从最新 main 切新分支
- 所有改动通过 PR 提交
- 等 CI 通过后再请求合入
- 合入前先确认 PR 无冲突
Agent 禁止做的
- 直接 push 到 main(即使有 admin 权限)
- 绕过 CI 检查合入
- 在已合入的分支上继续开发(应切新分支)
- 一个 PR 混入不相关的改动
Agent 的典型工作流
git checkout main && git pull origin main
git checkout -b <type>/<description>
git add <files>
git commit -m "<type>: <description>"
git push -u origin <type>/<description>
gh pr create --title "<type>: <description>" --body "..."
gh run list --branch <branch> --limit 3
gh pr merge <pr-number> --merge
项目结构
modules/ 放会被链进二进制的独立包,src/ 放还没分出去的骨架,缩小 src/
就是方向。 单位是子系统不是文件——一文件一包只会把目录列表写成 N 份清单。
modules/ ← 独立包,各带 mcpp.toml,由 path 引用,依赖显式声明
├── libs/ ← json + toml:文本格式解析器(vendored 与自写)
├── log/ ← 分级日志
├── versioning/ ← mcpp.version + mcpp.version_req
├── source-kind/ ← 源文件角色表
├── dyndep/ ← ninja dyndep 发射
├── platform/ ← 平台抽象层(所有平台相关代码)
├── manifest/ ← manifest 模型、TOML/xpkg 解析 + 它们所用的词汇
├── toolchain-model/ ← 工具链「是什么」:triple / model / dialect /
│ cppfly / fingerprint / linkmodel
└── buildmcpp/ ← build.mcpp 契约:协议、指令表、provision、tool store
src/
├── cli.cppm ← 命令行入口
├── config.cppm ← 全局配置
├── build/ ← 构建系统(ninja 后端、prepare/plan/execute)
├── pm/ ← 包管理子系统
├── toolchain/ ← 工具链「在哪」:探测、registry、gcc/clang/msvc/llvm
├── modgraph/ ← 模块图扫描验证
├── pack/ ← 打包发布
├── runtime/ ← 运行时契约(binding、ELF 事实)
└── xlings/ ← xlings 集成
tests/unit/ ← 跨层单测(`mcpp test`)
modules/<x>/tests/ ← 子系统单测(`mcpp test -p <x>`,CI 逐个跑)
tests/e2e/ ← E2E 测试脚本 (`MCPP=...` + `run_all.sh`)
docs/ ← 用户文档
.agents/docs/ ← 设计文档
.agents/skills/ ← Agent 技能文档
路径窄化不变式(走查得到的 path 不得直接 .string())
Windows 上 std::filesystem::path::string() 会把 native(宽)名经进程 ANSI 代码页
转换,遇到该代码页拼不出的字符就抛 std::system_error。非 Windows 上同一个调用只是
一次拷贝,永不失败——所以这个隐患在 Linux/macOS 上(包括它们的测试里)完全不可见。
它已经付过两次代价,每次戴着不同的面具:#230 抛出后逃到 std::terminate,git-bash
显示为裸 exit 127(看起来像"命令找不到");#516 逃到 main() 的 catch,显示为
internal: unhandled exception(看起来像下载器的解压/编码缺陷)。#231 加固了三处
调用点,漏掉了同一个 walk 循环里早一行执行的第四处。
规则(按用途选,不是三选一的风格问题):
| 用途 | 写法 |
|---|
| 与 ASCII 字面量比较 | 按 path 比,根本不窄化 |
| 需要稳定身份(hash / key / digest) | p.u8string() —— 各平台都是 UTF-8,不碰代码页 |
| 需要交给编译器 / ninja / CDB | mcpp::modgraph::try_narrow(p),并处理 nullopt |
try_narrow 返回 nullopt 表示"这个文件没法出现在任何交给工具链的字符串里"。
跳过它,并且必须报出来——mcpp.diag 的批次不变式对此已有规定:因为前提不满足而
少做事,必须走 diag::degraded() 并给出 impact。静默丢弃是这类缺陷藏身的地方。
src/modgraph/ 与 src/manifest/ 是 leaf 层(全仓没有一条到 mcpp.ui / mcpp.diag
的 import 边),所以它们记录(note_unnarrowable_path),由 CLI 层排空上报。
.github/tools/check_narrow_conversions.sh 是硬门,但它只扫 src/modgraph、
src/scaffold——通过不等于已审计。确有把握的站点用 // NARROW-OK: <理由> 标注,
理由必须写出"为什么这个输入不可能带这种名字"。
测试只有跑在 Windows CI 上才有意义,且必须自己检查 GetACP():runner 镜像哪天默认
UTF-8 ACP(65001),这类用例会静默变成永远绿的装饰品。参见
tests/unit/test_modgraph.cpp 的 Scanner.GlobWalkSurvivesNamesTheCodePageCannotSpell。
注意事项
- C++23 模块项目,修改模块时注意 import 依赖顺序
- 平台相关代码统一放
modules/platform/,不在其他模块中直接使用 #if defined
- 新增
modules/ 包要改四处(包自己的 mcpp.toml、根的
[dependencies.mcpp]、根的 [workspace] members、两份 xmake 源文件清单),
其中三处的遗漏都在很远的地方才失败——最坏的一处只在没有 mcpp 的 macOS
自举机器上。.github/tools/check_modules_wiring.sh 守住它,会在 CI 里跑
- import 要指向类型的提供者,不是它的某个消费者。 一条为拿
Toolchain 而
import mcpp.toolchain.detect 的边(model 才是定义处,detect 只是转发)
把整个 build.mcpp 契约压在了包管理器之上。一行 import 就是「能不能成为独立
模块」的全部距离
- 子系统的单测放
modules/<x>/tests/,跨层的放 tests/unit/。前者构建在
「只有它自己和它声明的依赖」这个配置里——那是根构建从不产生的配置,也是唯一
能抓到「悄悄依赖了未声明之物」的地方。
⚠️ mcpp test -p <x> 对没有测试的成员退 0
- E2E 测试应声明所需 capability,并使用隔离的
MCPP_HOME;需要网络/索引的脚本
不得被描述为完全离线
- 不确定方向时先在 Issue 讨论再动手
- 永远走 PR 流程,不直接 push main