Repository navigation
chore(dco): 落地 DCO 签名基础设施(App 门禁 + 本地 hook + 自查脚本) - #396
Conversation
仓库公开前把 DCO 从 CONTRIBUTING 里的一句要求补成可执行的门禁。PR 侧用已装到 org 的 DCO GitHub App,本仓提供它的配置、本地自查与自动签名。历史提交不受影响:只看 PR 内的 commit。 - DCO:Developer Certificate of Origin 1.1 原文,逐字未改 - .github/dco.yml:开启 individual + thirdParty remediation commit,让漏签的 PR 可以 不改写历史、保住已有 review 讨论 - .githooks/prepare-commit-msg:本地自动补签。git 没有让 commit 自动签名的配置项 (format.signOff 只作用于 format-patch / am),只能用 hook;输出排版对齐原生 git commit -s,并显式钉住 --if-exists / --if-missing,避免开发者的 trailer.ifExists 配置导致静默漏签 - scripts/install-dco-hook.mjs:把上面的 hook 复制进 hooks 目录,不覆盖他人同名 hook - scripts/check-dco.mjs:提交前自查(pnpm check:dco)。判定对齐 App——名字与邮箱都要 匹配 author 或 committer,并校验邮箱形状;刻意不识别 remediation commit,保证本地 通过则 App 必过,不会出现本地绿而 PR 红 - scripts/__tests__/check-dco.test.mjs:13 个用例,含真 git 仓库端到端 - CONTRIBUTING / README(中英)、PR 模板、AGENTS.md、development-workflow.md 写明门禁、 两种补签方式,以及 agent 自动提交同样适用 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com>
|
| Filename | Overview |
|---|---|
| .githooks/prepare-commit-msg | hook 已改用 GIT_COMMITTER_IDENT,避免在覆盖 author、cherry-pick 或 rebase 时替原作者生成 DCO 声明。 |
| scripts/check-dco.mjs | 校验器接受与完整 author 或 committer 身份匹配的签名,与 hook 修复后的行为一致。 |
| scripts/tests/check-dco.test.mjs | 新增真实 Git 仓库回归测试,确认覆盖 author 时 hook 只签署 committer,并与原生 git commit -s 行为一致。 |
| scripts/install-dco-hook.mjs | 安装器复制并管理 DCO hook,同时避免默认覆盖用户已有或修改过的 hook。 |
Reviews (10): Last reviewed commit: "fix(dco): 本地不再按邮箱豁免 bot,改为只豁免 merge comm..." | Re-trigger Greptile
There was a problem hiding this comment.
Pull request overview
This PR formalizes DCO (Developer Certificate of Origin) compliance for the repository by adding the GitHub DCO App configuration, local tooling (pre-check + auto sign-off hook installer), and contributor-facing documentation so sign-off requirements are enforceable and discoverable before the repo is opened publicly.
Changes:
- Add a local DCO verifier (
pnpm check:dco) plus unit/e2e tests that validate commit ranges, parsing, exemptions, and hook behavior. - Add an installable
prepare-commit-msghook (and installer script) to automatically appendSigned-off-byfor local commits without taking over the entire hooks directory. - Document the DCO requirement and remediation options across README/CONTRIBUTING/PR template/dev rules, and configure the DCO GitHub App via
.github/dco.yml.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/install-dco-hook.mjs | Hook installer + status probe for local auto sign-off. |
| scripts/check-dco.mjs | Local DCO gate: validates sign-off trailers across a PR-like commit range. |
| scripts/tests/check-dco.test.mjs | Contract + e2e tests covering validator behavior and the hook. |
| .githooks/prepare-commit-msg | POSIX sh hook that appends Signed-off-by for non-merge local commits. |
| .github/dco.yml | DCO GitHub App configuration enabling remediation commits. |
| package.json | Adds check:dco and dco:install-hook scripts; wires new tests into test:runner. |
| CONTRIBUTING.md | Explains DCO requirement, how to fix missing sign-offs, and remediation commit format (CN). |
| CONTRIBUTING.en.md | Same as above in English. |
| README.md | Adds contributor-facing note about DCO sign-off and no CLA (EN). |
| README.zh-CN.md | Adds contributor-facing note about DCO sign-off and no CLA (ZH). |
| docs/dev-rules/development-workflow.md | Adds DCO as a hard pre-submit requirement and points to local tooling. |
| AGENTS.md | Adds DCO requirement for agent-assisted commits and points to tooling. |
| .github/PULL_REQUEST_TEMPLATE.md | Adds a pre-submit checklist item for DCO sign-off. |
| DCO | Adds the DCO 1.1 text to the repository root. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 22a71da37c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@dashhuang 👋 这个 PR 还有 9 条 review conversation 没 resolve(.githooks/prepare-commit-msg / scripts/check-dco.mjs / AGENTS.md / scripts/install-dco-hook.mjs),auto-review 因此暂时跳过、没法继续审查 / 合并。 如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。 |
处理 review 反馈里核实成立的部分。两个真 bug: - hook 原来取 GIT_AUTHOR_IDENT,在 git commit --author=、cherry-pick 或 rebase 他人 patch 时会替原作者伪造 DCO 声明。改取 GIT_COMMITTER_IDENT——原生 git commit -s 就是 这个语义(签名是执行提交者的声明);门禁接受 author 或 committer 的签名,不影响通过。 - install-dco-hook 的所有权判定原来只看「文件含 marker」。开发者按提示把本仓逻辑合并进 自己的 hook 后,那份复合 hook 会被判成本仓的旧版本并被整份覆盖,抹掉他自己的逻辑。 改为比对开头两行(shebang + marker 注释),复合 hook 一律算 foreign 不覆盖。 三处收紧本地自查,都是为了不出现「本地绿、PR 红」: - 邮箱形状原来的单行正则会放行 validator.isEmail 拒绝的地址(连续点、下划线域名等), 现在 local part 与域名分开校验并要求字母 TLD - 签名改为 name+email 成对匹配某一个身份。原来拆成两个集合,author=Alice<a@x>、 committer=Bob<b@x> 时 `Alice <b@x>` 也能过,尽管这个身份不存在。这让本脚本比 App 更严, 偏严只会本地红、PR 绿,是安全方向 - 修复提示里的 rebase base 用完整 sha,短 sha 在大仓可能歧义 另外删掉了 check-dco.mjs 头部对 .github/workflows/dco.yml 的引用(该 workflow 已改用 DCO App,文件不存在)。 新增回归用例覆盖:--author 场景下签 committer 且与原生 -s 一致、复合 hook 判 foreign、 validator 会拒的地址逐个断言拒绝、交叉身份签名不通过。反向验证过前两项——还原任一处 修复会分别红 2 个用例。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b1920cab68
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@dashhuang 👋 这个 PR 还有 4 条 review conversation 没 resolve(scripts/install-dco-hook.mjs / AGENTS.md / scripts/check-dco.mjs),auto-review 因此暂时跳过、没法继续审查 / 合并。 如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。 |
第二轮 review 反馈,三处成立: - hook 所有权判定上一版按「开头两行」判断,只挡住了「把本仓逻辑合并进自己的 hook」, 挡不住「在本仓装好的 hook 后面追加自己的逻辑」——后者开头两行仍是我们的,会被判成 旧版本并整份覆盖。这两种情况在磁盘上分辨不出来,所以不再猜:内容不等于源文件就是 modified,一律要显式 --force 才覆盖,并提示先 diff。 - 邮箱形状仍比 validator.isEmail 宽松:local part 收紧到 RFC 5322 的 atext 字符集 (拒 a(b)@x.com 这类),域名 label 不再允许以连字符开头或结尾(拒 a@-foo.com)。 含方括号的 bot 地址因此也不合格,但 bot 提交在 exemptReason 就豁免了,走不到这里—— 补了交叉用例把这条钉住。 - resolveHooksDir 依赖的 --path-format 要 git 2.31+,旧版直接失败会让安装器不可用, 回落成 --git-path + show-toplevel 自己拼绝对路径。 另有一条 P1 是误报:说 commit 651e353f 没有签名,但该 sha 在远端与本地都不存在 (gh api 返回 422、git cat-file 失败),PR 的两个 commit 都带 Signed-off-by。上一轮 同一 bot 报的 ac842de1 也是同样情况,都是它在自己环境重建 commit 后检查了自己的 sha。 新增用例:追加型复合 hook 判 modified、无 --force 时拒绝覆盖且不改动文件、--force 才 覆盖、resolveHooksDir 返回绝对路径、bot 地址不合格但仍豁免、validator 会拒的新反例。 反向验证过邮箱那处——放宽 atext 会红 2 个用例。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 23c73e4bc4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@dashhuang 👋 这个 PR 还有 4 条 review conversation 没 resolve(AGENTS.md / scripts/check-dco.mjs / scripts/install-dco-hook.mjs),auto-review 因此暂时跳过、没法继续审查 / 合并。 如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。 |
第三轮 review 反馈,三处成立: - resolveHooksDir 的旧版 git 回落分支用仓库根去解析 `git rev-parse --git-path hooks` 的返回值,但那个相对路径是相对于**执行 git 时的 cwd**。从子目录调用时它形如 ../../.git/hooks,拿仓库根解析会向上多走几级,把 hook 装进隔壁或上层仓库的 .git/hooks——既漏装本仓,又动了别的仓库。改用同一个 cwd 解析,并把这一步抽成 resolveHooksPathFrom 以便直接断言(走到该分支需要 git < 2.31,CI 上跑不到)。 - 邮箱校验只做了字符集与连字符规则,没有长度上限:65 字符的 local part 本地放行、 validator.isEmail 拒绝。补上 local part ≤ 64、域名 ≤ 254、单个 label ≤ 63,并断言 恰好在上限的地址仍放行。 - 本地默认基准候选加 upstream/main 且排在 origin/main 之前。从 fork 干活时 origin/main 可能领先于上游,拿它当基准会把 fork 自己那些未签名的提交排除在范围外,而 PR 的真实 base 是上游。这只是本地自查的启发式,真实结论仍以 PR 上的 DCO check 为准。 另有一条 P1 是同类误报(第三次):说 commit 426347872c 没有签名,但该 sha 在远端与本地 都不存在,PR 的三个 commit 都带 Signed-off-by,DCO check 为 success。 反向验证过前两处:把 hooks 路径基准改回仓库根、或去掉长度检查,各会红 1 个用例。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 084235ca80
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@dashhuang 👋 这个 PR 还有 5 条 review conversation 没 resolve(scripts/install-dco-hook.mjs / .githooks/prepare-commit-msg / scripts/check-dco.mjs / AGENTS.md),auto-review 因此暂时跳过、没法继续审查 / 合并。 如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。 |
两处都是前几轮改动留下的文档漂移,reviewer 指出的都对: - install-dco-hook.mjs 顶部仍写「追加与 commit author 一致的 Signed-off-by」,但 hook 在 第一轮修复后已改成签 committer(与原生 git commit -s 一致)。 - 两个文件都把「PR 上的门禁」说成 scripts/check-dco.mjs。实际权威门禁是 DCO GitHub App (配置见 .github/dco.yml),check-dco.mjs 只是本地提交前自查。 只改注释,无逻辑改动。顺带全仓 grep 了同类措辞,没有其它残留。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e83fca651a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@dashhuang 👋 这个 PR 还有 6 条 review conversation 没 resolve(AGENTS.md / scripts/check-dco.mjs / scripts/install-dco-hook.mjs / .githooks/prepare-commit-msg),auto-review 因此暂时跳过、没法继续审查 / 合并。 如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。 |
第六轮 review 反馈,三处成立: - .githooks/ 下的 hook 没有扩展名,命不中 .gitattributes 已有的 `*.sh` / `*.mjs` 规则。Windows autocrlf 检出成 CRLF 后 `#!/bin/sh\r` 无法执行,git commit 会直接 `fatal: cannot exec`——装了 hook 的人从此提交不了。补 `.githooks/** text eol=lf`, git check-attr 已确认生效。仓库先前为 *.sh 与 *.mjs 加过同样保护并写了注释,这次是漏了。 - 签名解析比 App 宽松三处,都会造成「本地绿、PR 红」:行首空白(App 的 `^Signed-off-by:` 锚在行首,缩进行在它眼里不是签名)、冒号后多个空格(多出的空格被 App 算进 name, 与 author 比不上)、捕获值 trim(App 拿原样值比对)。正则改为与 dcoapp/app 的 `/^Signed-off-by: (.*) <(.*)>\s*$/gim` 逐字对齐,比对时签名侧只 lowercase 不 trim。 - 安装器提示的 diff 命令换成 `git diff --no-index`:git 本来就是本仓前置依赖,而 diff(1) 在部分 Windows 环境没有。 另有两条 P1 是同类误报(第五、六次,sha 7c571f80 与 be844285 在本仓远端与本地都不存在), 一条是过时反馈(bot name 豁免已在 e83fca6 去掉)。 CI 的 verify 之前红过一次,核实是 main 基线问题:#329 合入后有 3 条源码文本断言过时, main 上的 5c1a81c 已修好,但它比本 PR 那轮 CI 晚合入。已 update-branch 同步最新 main。 反向验证过两处正则修复:放宽任一处各会红 1 个用例。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 35c0471f44
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@dashhuang 👋 这个 PR 还有 3 条 review conversation 没 resolve(scripts/install-dco-hook.mjs / AGENTS.md / scripts/check-dco.mjs),auto-review 因此暂时跳过、没法继续审查 / 合并。 如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。 |
默认参数 `expectedContent = readHookSource()` 会在调用时就求值,于是 `classifyHook(null)`(没装 hook 这条最常见的路径)也要读一次磁盘,并且把这个判断 绑到了源文件可读上。改成先判 missing、再按需 `?? readHookSource()`。 行为不变,纯效率与依赖面收敛。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com>
|
@dashhuang 👋 这个 PR 还有 2 条 review conversation 没 resolve(AGENTS.md / scripts/check-dco.mjs),auto-review 因此暂时跳过、没法继续审查 / 合并。 如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。 |
reviewer 指出上一轮的邮箱豁免仍可伪造:author 邮箱是自由填写的, `999999+not-a-real-account[bot]@users.noreply.github.com` 谁都能设,而 App 认的是 GitHub 账号类型(author.type === "Bot")。按邮箱豁免等于留一个可伪造的后门,方向还是 最坏的「本地绿、PR 红」。 离线能可靠判定的豁免依据只有父提交数量,所以 exemptReason 现在只豁免 merge commit。 bot 提交在本地照常检查——方向安全(本地红、PR 绿),失败输出里解释清楚:这类地址在 PR 上由 App 按账号类型豁免,而账号类型离线取不到,所以本地不敢信一个可伪造的地址。 原 isBotIdentity 改名为 looksLikeBotAddress,语义收窄为「只用于失败输出的提示」, 并去掉了已在上一轮移除的 name 后缀判断残留说明。 同步了三处因语义变更而过时的表述:check-dco.mjs 里「bot 提交在 exemptReason 就被豁免, 走不到这里」、测试文件头的「merge/bot 豁免」、以及 CONTRIBUTING 中英两版里对 `pnpm check:dco` 保守程度的说明(现在明确列出 remediation 与 bot 两项差异)。 提交前门禁:根 pnpm test:unit 有 1 项 apps/mobile 的 startupSplashOverlay 超时失败, 与本 PR 无关——该测试单跑 3.95s、默认上限 5s,本机负载峰值 150+ 时必然 flake;定向重跑 6/6 通过。不在本 PR 混入无关修复。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com>
MagicLizi
left a comment
There was a problem hiding this comment.
审查通过:DCO 签名基础设施落地干净,无 P0/P1。安全软命中全部为测试夹具(example.com 邮箱、bot noreply 地址);21 条单元测试通过;shell hook POSIX 语法验证通过;所有 commit 签名正确。
review 反馈 5 条: 1. 邮箱 local part 允许 CJK,会把紧贴在地址前的中文正文一路吞到上一个空白 `返回worker联系a@x.com` 整条被剥成一个空格,里面小写的 worker 违规随之消失。 中文文案里地址常与正文无空格相接。已限定为邮箱合法字符集。 2. 省略号规则漏掉 en DESIGN.md §11 Voice & Content 明文要求英文也用「…」而非三个半角点, 漏掉 en 等于让门禁替既有违规背书。已纳入,清理 54 处(含 `~/.ssh/...` 进度提示与 `-2...` 的「以此类推」,逐条确认都是真省略号; `loading ...` 另去掉了省略号前的空格,符合「现在进行时 + …」)。 3-4. Agent 运行实例被并成「对话」,语义说反了 英文原文区分 agent session 与 conversation:开关对「同一对话内重启的 Agent 会话」也生效,写成「只对新建对话生效」等于告诉用户要新开对话。 4 个 toggleHint 恢复「Agent 会话」;forkErrors.unsupportedHistory 失效的是 历史引擎 session、产品对话仍在,恢复「历史引擎会话」。均加进 exempt。 5. help step3 的 `把对话区当作一个持续的工作对话` 同义反复 英文是 persistent work thread,改为「把对话区当作持续推进工作的地方」。 合并 origin/main:package.json 的 test:runner 冲突——main 加了 check-dco.test.mjs (DCO 基建 #396),本 PR 加了 glossary-rules.test.mjs,两者都保留。 门禁又从 main 新 key 里捞出 1 处半角分号,已修。 新增 2 个单测(共 51 个)。 验证:pnpm check:dco 通过(18 签名 + 5 merge 豁免)、desktop 13294 测试全过、 mobile 2355 测试全过、双端 typecheck 干净、test:runner 226 passed、 四项 i18n guard 与 check:dev-docs 通过。 Signed-off-by: Dash <dashhuang@gmail.com>
* chore(dco): 落地 DCO 签名基础设施 仓库公开前把 DCO 从 CONTRIBUTING 里的一句要求补成可执行的门禁。PR 侧用已装到 org 的 DCO GitHub App,本仓提供它的配置、本地自查与自动签名。历史提交不受影响:只看 PR 内的 commit。 - DCO:Developer Certificate of Origin 1.1 原文,逐字未改 - .github/dco.yml:开启 individual + thirdParty remediation commit,让漏签的 PR 可以 不改写历史、保住已有 review 讨论 - .githooks/prepare-commit-msg:本地自动补签。git 没有让 commit 自动签名的配置项 (format.signOff 只作用于 format-patch / am),只能用 hook;输出排版对齐原生 git commit -s,并显式钉住 --if-exists / --if-missing,避免开发者的 trailer.ifExists 配置导致静默漏签 - scripts/install-dco-hook.mjs:把上面的 hook 复制进 hooks 目录,不覆盖他人同名 hook - scripts/check-dco.mjs:提交前自查(pnpm check:dco)。判定对齐 App——名字与邮箱都要 匹配 author 或 committer,并校验邮箱形状;刻意不识别 remediation commit,保证本地 通过则 App 必过,不会出现本地绿而 PR 红 - scripts/__tests__/check-dco.test.mjs:13 个用例,含真 git 仓库端到端 - CONTRIBUTING / README(中英)、PR 模板、AGENTS.md、development-workflow.md 写明门禁、 两种补签方式,以及 agent 自动提交同样适用 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com> * fix(dco): hook 改签 committer,并收紧本地自查与 hook 所有权判定 处理 review 反馈里核实成立的部分。两个真 bug: - hook 原来取 GIT_AUTHOR_IDENT,在 git commit --author=、cherry-pick 或 rebase 他人 patch 时会替原作者伪造 DCO 声明。改取 GIT_COMMITTER_IDENT——原生 git commit -s 就是 这个语义(签名是执行提交者的声明);门禁接受 author 或 committer 的签名,不影响通过。 - install-dco-hook 的所有权判定原来只看「文件含 marker」。开发者按提示把本仓逻辑合并进 自己的 hook 后,那份复合 hook 会被判成本仓的旧版本并被整份覆盖,抹掉他自己的逻辑。 改为比对开头两行(shebang + marker 注释),复合 hook 一律算 foreign 不覆盖。 三处收紧本地自查,都是为了不出现「本地绿、PR 红」: - 邮箱形状原来的单行正则会放行 validator.isEmail 拒绝的地址(连续点、下划线域名等), 现在 local part 与域名分开校验并要求字母 TLD - 签名改为 name+email 成对匹配某一个身份。原来拆成两个集合,author=Alice<a@x>、 committer=Bob<b@x> 时 `Alice <b@x>` 也能过,尽管这个身份不存在。这让本脚本比 App 更严, 偏严只会本地红、PR 绿,是安全方向 - 修复提示里的 rebase base 用完整 sha,短 sha 在大仓可能歧义 另外删掉了 check-dco.mjs 头部对 .github/workflows/dco.yml 的引用(该 workflow 已改用 DCO App,文件不存在)。 新增回归用例覆盖:--author 场景下签 committer 且与原生 -s 一致、复合 hook 判 foreign、 validator 会拒的地址逐个断言拒绝、交叉身份签名不通过。反向验证过前两项——还原任一处 修复会分别红 2 个用例。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com> * fix(dco): hook 安装器不再猜所有权,邮箱校验对齐 validator 字符集 第二轮 review 反馈,三处成立: - hook 所有权判定上一版按「开头两行」判断,只挡住了「把本仓逻辑合并进自己的 hook」, 挡不住「在本仓装好的 hook 后面追加自己的逻辑」——后者开头两行仍是我们的,会被判成 旧版本并整份覆盖。这两种情况在磁盘上分辨不出来,所以不再猜:内容不等于源文件就是 modified,一律要显式 --force 才覆盖,并提示先 diff。 - 邮箱形状仍比 validator.isEmail 宽松:local part 收紧到 RFC 5322 的 atext 字符集 (拒 a(b)@x.com 这类),域名 label 不再允许以连字符开头或结尾(拒 a@-foo.com)。 含方括号的 bot 地址因此也不合格,但 bot 提交在 exemptReason 就豁免了,走不到这里—— 补了交叉用例把这条钉住。 - resolveHooksDir 依赖的 --path-format 要 git 2.31+,旧版直接失败会让安装器不可用, 回落成 --git-path + show-toplevel 自己拼绝对路径。 另有一条 P1 是误报:说 commit 651e353f 没有签名,但该 sha 在远端与本地都不存在 (gh api 返回 422、git cat-file 失败),PR 的两个 commit 都带 Signed-off-by。上一轮 同一 bot 报的 ac842de1 也是同样情况,都是它在自己环境重建 commit 后检查了自己的 sha。 新增用例:追加型复合 hook 判 modified、无 --force 时拒绝覆盖且不改动文件、--force 才 覆盖、resolveHooksDir 返回绝对路径、bot 地址不合格但仍豁免、validator 会拒的新反例。 反向验证过邮箱那处——放宽 atext 会红 2 个用例。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com> * fix(dco): 修正旧版 git 的 hooks 路径基准,补齐邮箱长度上限与 fork 默认基准 第三轮 review 反馈,三处成立: - resolveHooksDir 的旧版 git 回落分支用仓库根去解析 `git rev-parse --git-path hooks` 的返回值,但那个相对路径是相对于**执行 git 时的 cwd**。从子目录调用时它形如 ../../.git/hooks,拿仓库根解析会向上多走几级,把 hook 装进隔壁或上层仓库的 .git/hooks——既漏装本仓,又动了别的仓库。改用同一个 cwd 解析,并把这一步抽成 resolveHooksPathFrom 以便直接断言(走到该分支需要 git < 2.31,CI 上跑不到)。 - 邮箱校验只做了字符集与连字符规则,没有长度上限:65 字符的 local part 本地放行、 validator.isEmail 拒绝。补上 local part ≤ 64、域名 ≤ 254、单个 label ≤ 63,并断言 恰好在上限的地址仍放行。 - 本地默认基准候选加 upstream/main 且排在 origin/main 之前。从 fork 干活时 origin/main 可能领先于上游,拿它当基准会把 fork 自己那些未签名的提交排除在范围外,而 PR 的真实 base 是上游。这只是本地自查的启发式,真实结论仍以 PR 上的 DCO check 为准。 另有一条 P1 是同类误报(第三次):说 commit 426347872c 没有签名,但该 sha 在远端与本地 都不存在,PR 的三个 commit 都带 Signed-off-by,DCO check 为 success。 反向验证过前两处:把 hooks 路径基准改回仓库根、或去掉长度检查,各会红 1 个用例。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com> * docs(dco): 修正 hook 与安装器注释里过时的 author/门禁归属表述 两处都是前几轮改动留下的文档漂移,reviewer 指出的都对: - install-dco-hook.mjs 顶部仍写「追加与 commit author 一致的 Signed-off-by」,但 hook 在 第一轮修复后已改成签 committer(与原生 git commit -s 一致)。 - 两个文件都把「PR 上的门禁」说成 scripts/check-dco.mjs。实际权威门禁是 DCO GitHub App (配置见 .github/dco.yml),check-dco.mjs 只是本地提交前自查。 只改注释,无逻辑改动。顺带全仓 grep 了同类措辞,没有其它残留。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com> * fix(dco): 补邮箱整体长度上限、bot 判定只认邮箱、修复丢失可执行位的 hook 第五轮 review 反馈,三处成立: - 邮箱长度上限漏了整体那一条。local part ≤ 64、域名 ≤ 254、label ≤ 63 各自满足时, 整个地址仍可能超过 validator.isEmail 的 254 上限(reviewer 给的例子是 258 字符)。 补上整体检查,并加了长度恰好 254 放行、255 拒绝的边界断言。 - isBotIdentity 不再认 author name 的 `[bot]` 后缀。`git config user.name` 谁都能设成 `Foo[bot]`,按 name 豁免等于给本地检查留一个一步就能绕过的后门,且方向是「本地绿、 PR 红」——App 判定的是不可伪造的 GitHub 账号类型。现在只认 bot 邮箱 (web-flow 的 noreply@github.com 与 `*[bot]@users.noreply.github.com`)。 - 安装器遇到「内容逐字一致但丢了可执行位」时会报「已是最新」直接返回,而 git 会静默 忽略不可执行的 hook——于是安装器说装好了,提交却一直没签名。现在这种情况会补回 执行位并说明原因,--check 也会点出「NOT executable」而不是笼统报 installed。 另有一条 P1 是同类误报(第四次):说 commit 8d8ec210 没有签名且「resolves locally」,但 该 sha 在本仓远端与本地都不存在(gh api 422、git cat-file 失败),PR 的 5 个 commit 全部 带 Signed-off-by。 顺带清理了上一轮抽函数时留在 resolveHooksPathFrom 前面的悬空注释。 反向验证过三处:去掉整体长度检查、恢复 name 后缀豁免、去掉可执行位修复,各会红 1 个用例。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com> * fix(dco): hook 固定 LF 检出,签名解析对齐 App 的行格式 第六轮 review 反馈,三处成立: - .githooks/ 下的 hook 没有扩展名,命不中 .gitattributes 已有的 `*.sh` / `*.mjs` 规则。Windows autocrlf 检出成 CRLF 后 `#!/bin/sh\r` 无法执行,git commit 会直接 `fatal: cannot exec`——装了 hook 的人从此提交不了。补 `.githooks/** text eol=lf`, git check-attr 已确认生效。仓库先前为 *.sh 与 *.mjs 加过同样保护并写了注释,这次是漏了。 - 签名解析比 App 宽松三处,都会造成「本地绿、PR 红」:行首空白(App 的 `^Signed-off-by:` 锚在行首,缩进行在它眼里不是签名)、冒号后多个空格(多出的空格被 App 算进 name, 与 author 比不上)、捕获值 trim(App 拿原样值比对)。正则改为与 dcoapp/app 的 `/^Signed-off-by: (.*) <(.*)>\s*$/gim` 逐字对齐,比对时签名侧只 lowercase 不 trim。 - 安装器提示的 diff 命令换成 `git diff --no-index`:git 本来就是本仓前置依赖,而 diff(1) 在部分 Windows 环境没有。 另有两条 P1 是同类误报(第五、六次,sha 7c571f80 与 be844285 在本仓远端与本地都不存在), 一条是过时反馈(bot name 豁免已在 e83fca6 去掉)。 CI 的 verify 之前红过一次,核实是 main 基线问题:makecindy#329 合入后有 3 条源码文本断言过时, main 上的 5c1a81c 已修好,但它比本 PR 那轮 CI 晚合入。已 update-branch 同步最新 main。 反向验证过两处正则修复:放宽任一处各会红 1 个用例。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com> * refactor(dco): classifyHook 的源文件读取改为懒求值 默认参数 `expectedContent = readHookSource()` 会在调用时就求值,于是 `classifyHook(null)`(没装 hook 这条最常见的路径)也要读一次磁盘,并且把这个判断 绑到了源文件可读上。改成先判 missing、再按需 `?? readHookSource()`。 行为不变,纯效率与依赖面收敛。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com> * fix(dco): 本地不再按邮箱豁免 bot,改为只豁免 merge commit reviewer 指出上一轮的邮箱豁免仍可伪造:author 邮箱是自由填写的, `999999+not-a-real-account[bot]@users.noreply.github.com` 谁都能设,而 App 认的是 GitHub 账号类型(author.type === "Bot")。按邮箱豁免等于留一个可伪造的后门,方向还是 最坏的「本地绿、PR 红」。 离线能可靠判定的豁免依据只有父提交数量,所以 exemptReason 现在只豁免 merge commit。 bot 提交在本地照常检查——方向安全(本地红、PR 绿),失败输出里解释清楚:这类地址在 PR 上由 App 按账号类型豁免,而账号类型离线取不到,所以本地不敢信一个可伪造的地址。 原 isBotIdentity 改名为 looksLikeBotAddress,语义收窄为「只用于失败输出的提示」, 并去掉了已在上一轮移除的 name 后缀判断残留说明。 同步了三处因语义变更而过时的表述:check-dco.mjs 里「bot 提交在 exemptReason 就被豁免, 走不到这里」、测试文件头的「merge/bot 豁免」、以及 CONTRIBUTING 中英两版里对 `pnpm check:dco` 保守程度的说明(现在明确列出 remediation 与 bot 两项差异)。 提交前门禁:根 pnpm test:unit 有 1 项 apps/mobile 的 startupSplashOverlay 超时失败, 与本 PR 无关——该测试单跑 3.95s、默认上限 5s,本机负载峰值 150+ 时必然 flake;定向重跑 6/6 通过。不在本 PR 混入无关修复。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com> --------- Signed-off-by: Dash <dashhuang@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
review 反馈 5 条: 1. 邮箱 local part 允许 CJK,会把紧贴在地址前的中文正文一路吞到上一个空白 `返回worker联系a@x.com` 整条被剥成一个空格,里面小写的 worker 违规随之消失。 中文文案里地址常与正文无空格相接。已限定为邮箱合法字符集。 2. 省略号规则漏掉 en DESIGN.md §11 Voice & Content 明文要求英文也用「…」而非三个半角点, 漏掉 en 等于让门禁替既有违规背书。已纳入,清理 54 处(含 `~/.ssh/...` 进度提示与 `-2...` 的「以此类推」,逐条确认都是真省略号; `loading ...` 另去掉了省略号前的空格,符合「现在进行时 + …」)。 3-4. Agent 运行实例被并成「对话」,语义说反了 英文原文区分 agent session 与 conversation:开关对「同一对话内重启的 Agent 会话」也生效,写成「只对新建对话生效」等于告诉用户要新开对话。 4 个 toggleHint 恢复「Agent 会话」;forkErrors.unsupportedHistory 失效的是 历史引擎 session、产品对话仍在,恢复「历史引擎会话」。均加进 exempt。 5. help step3 的 `把对话区当作一个持续的工作对话` 同义反复 英文是 persistent work thread,改为「把对话区当作持续推进工作的地方」。 合并 origin/main:package.json 的 test:runner 冲突——main 加了 check-dco.test.mjs (DCO 基建 makecindy#396),本 PR 加了 glossary-rules.test.mjs,两者都保留。 门禁又从 main 新 key 里捞出 1 处半角分号,已修。 新增 2 个单测(共 51 个)。 验证:pnpm check:dco 通过(18 签名 + 5 merge 豁免)、desktop 13294 测试全过、 mobile 2355 测试全过、双端 typecheck 干净、test:runner 226 passed、 四项 i18n guard 与 check:dev-docs 通过。 Signed-off-by: Dash <dashhuang@gmail.com>
* chore(dco): 落地 DCO 签名基础设施 仓库公开前把 DCO 从 CONTRIBUTING 里的一句要求补成可执行的门禁。PR 侧用已装到 org 的 DCO GitHub App,本仓提供它的配置、本地自查与自动签名。历史提交不受影响:只看 PR 内的 commit。 - DCO:Developer Certificate of Origin 1.1 原文,逐字未改 - .github/dco.yml:开启 individual + thirdParty remediation commit,让漏签的 PR 可以 不改写历史、保住已有 review 讨论 - .githooks/prepare-commit-msg:本地自动补签。git 没有让 commit 自动签名的配置项 (format.signOff 只作用于 format-patch / am),只能用 hook;输出排版对齐原生 git commit -s,并显式钉住 --if-exists / --if-missing,避免开发者的 trailer.ifExists 配置导致静默漏签 - scripts/install-dco-hook.mjs:把上面的 hook 复制进 hooks 目录,不覆盖他人同名 hook - scripts/check-dco.mjs:提交前自查(pnpm check:dco)。判定对齐 App——名字与邮箱都要 匹配 author 或 committer,并校验邮箱形状;刻意不识别 remediation commit,保证本地 通过则 App 必过,不会出现本地绿而 PR 红 - scripts/__tests__/check-dco.test.mjs:13 个用例,含真 git 仓库端到端 - CONTRIBUTING / README(中英)、PR 模板、AGENTS.md、development-workflow.md 写明门禁、 两种补签方式,以及 agent 自动提交同样适用 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com> * fix(dco): hook 改签 committer,并收紧本地自查与 hook 所有权判定 处理 review 反馈里核实成立的部分。两个真 bug: - hook 原来取 GIT_AUTHOR_IDENT,在 git commit --author=、cherry-pick 或 rebase 他人 patch 时会替原作者伪造 DCO 声明。改取 GIT_COMMITTER_IDENT——原生 git commit -s 就是 这个语义(签名是执行提交者的声明);门禁接受 author 或 committer 的签名,不影响通过。 - install-dco-hook 的所有权判定原来只看「文件含 marker」。开发者按提示把本仓逻辑合并进 自己的 hook 后,那份复合 hook 会被判成本仓的旧版本并被整份覆盖,抹掉他自己的逻辑。 改为比对开头两行(shebang + marker 注释),复合 hook 一律算 foreign 不覆盖。 三处收紧本地自查,都是为了不出现「本地绿、PR 红」: - 邮箱形状原来的单行正则会放行 validator.isEmail 拒绝的地址(连续点、下划线域名等), 现在 local part 与域名分开校验并要求字母 TLD - 签名改为 name+email 成对匹配某一个身份。原来拆成两个集合,author=Alice<a@x>、 committer=Bob<b@x> 时 `Alice <b@x>` 也能过,尽管这个身份不存在。这让本脚本比 App 更严, 偏严只会本地红、PR 绿,是安全方向 - 修复提示里的 rebase base 用完整 sha,短 sha 在大仓可能歧义 另外删掉了 check-dco.mjs 头部对 .github/workflows/dco.yml 的引用(该 workflow 已改用 DCO App,文件不存在)。 新增回归用例覆盖:--author 场景下签 committer 且与原生 -s 一致、复合 hook 判 foreign、 validator 会拒的地址逐个断言拒绝、交叉身份签名不通过。反向验证过前两项——还原任一处 修复会分别红 2 个用例。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com> * fix(dco): hook 安装器不再猜所有权,邮箱校验对齐 validator 字符集 第二轮 review 反馈,三处成立: - hook 所有权判定上一版按「开头两行」判断,只挡住了「把本仓逻辑合并进自己的 hook」, 挡不住「在本仓装好的 hook 后面追加自己的逻辑」——后者开头两行仍是我们的,会被判成 旧版本并整份覆盖。这两种情况在磁盘上分辨不出来,所以不再猜:内容不等于源文件就是 modified,一律要显式 --force 才覆盖,并提示先 diff。 - 邮箱形状仍比 validator.isEmail 宽松:local part 收紧到 RFC 5322 的 atext 字符集 (拒 a(b)@x.com 这类),域名 label 不再允许以连字符开头或结尾(拒 a@-foo.com)。 含方括号的 bot 地址因此也不合格,但 bot 提交在 exemptReason 就豁免了,走不到这里—— 补了交叉用例把这条钉住。 - resolveHooksDir 依赖的 --path-format 要 git 2.31+,旧版直接失败会让安装器不可用, 回落成 --git-path + show-toplevel 自己拼绝对路径。 另有一条 P1 是误报:说 commit 651e353f 没有签名,但该 sha 在远端与本地都不存在 (gh api 返回 422、git cat-file 失败),PR 的两个 commit 都带 Signed-off-by。上一轮 同一 bot 报的 ac842de1 也是同样情况,都是它在自己环境重建 commit 后检查了自己的 sha。 新增用例:追加型复合 hook 判 modified、无 --force 时拒绝覆盖且不改动文件、--force 才 覆盖、resolveHooksDir 返回绝对路径、bot 地址不合格但仍豁免、validator 会拒的新反例。 反向验证过邮箱那处——放宽 atext 会红 2 个用例。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com> * fix(dco): 修正旧版 git 的 hooks 路径基准,补齐邮箱长度上限与 fork 默认基准 第三轮 review 反馈,三处成立: - resolveHooksDir 的旧版 git 回落分支用仓库根去解析 `git rev-parse --git-path hooks` 的返回值,但那个相对路径是相对于**执行 git 时的 cwd**。从子目录调用时它形如 ../../.git/hooks,拿仓库根解析会向上多走几级,把 hook 装进隔壁或上层仓库的 .git/hooks——既漏装本仓,又动了别的仓库。改用同一个 cwd 解析,并把这一步抽成 resolveHooksPathFrom 以便直接断言(走到该分支需要 git < 2.31,CI 上跑不到)。 - 邮箱校验只做了字符集与连字符规则,没有长度上限:65 字符的 local part 本地放行、 validator.isEmail 拒绝。补上 local part ≤ 64、域名 ≤ 254、单个 label ≤ 63,并断言 恰好在上限的地址仍放行。 - 本地默认基准候选加 upstream/main 且排在 origin/main 之前。从 fork 干活时 origin/main 可能领先于上游,拿它当基准会把 fork 自己那些未签名的提交排除在范围外,而 PR 的真实 base 是上游。这只是本地自查的启发式,真实结论仍以 PR 上的 DCO check 为准。 另有一条 P1 是同类误报(第三次):说 commit 426347872c 没有签名,但该 sha 在远端与本地 都不存在,PR 的三个 commit 都带 Signed-off-by,DCO check 为 success。 反向验证过前两处:把 hooks 路径基准改回仓库根、或去掉长度检查,各会红 1 个用例。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com> * docs(dco): 修正 hook 与安装器注释里过时的 author/门禁归属表述 两处都是前几轮改动留下的文档漂移,reviewer 指出的都对: - install-dco-hook.mjs 顶部仍写「追加与 commit author 一致的 Signed-off-by」,但 hook 在 第一轮修复后已改成签 committer(与原生 git commit -s 一致)。 - 两个文件都把「PR 上的门禁」说成 scripts/check-dco.mjs。实际权威门禁是 DCO GitHub App (配置见 .github/dco.yml),check-dco.mjs 只是本地提交前自查。 只改注释,无逻辑改动。顺带全仓 grep 了同类措辞,没有其它残留。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com> * fix(dco): 补邮箱整体长度上限、bot 判定只认邮箱、修复丢失可执行位的 hook 第五轮 review 反馈,三处成立: - 邮箱长度上限漏了整体那一条。local part ≤ 64、域名 ≤ 254、label ≤ 63 各自满足时, 整个地址仍可能超过 validator.isEmail 的 254 上限(reviewer 给的例子是 258 字符)。 补上整体检查,并加了长度恰好 254 放行、255 拒绝的边界断言。 - isBotIdentity 不再认 author name 的 `[bot]` 后缀。`git config user.name` 谁都能设成 `Foo[bot]`,按 name 豁免等于给本地检查留一个一步就能绕过的后门,且方向是「本地绿、 PR 红」——App 判定的是不可伪造的 GitHub 账号类型。现在只认 bot 邮箱 (web-flow 的 noreply@github.com 与 `*[bot]@users.noreply.github.com`)。 - 安装器遇到「内容逐字一致但丢了可执行位」时会报「已是最新」直接返回,而 git 会静默 忽略不可执行的 hook——于是安装器说装好了,提交却一直没签名。现在这种情况会补回 执行位并说明原因,--check 也会点出「NOT executable」而不是笼统报 installed。 另有一条 P1 是同类误报(第四次):说 commit 8d8ec210 没有签名且「resolves locally」,但 该 sha 在本仓远端与本地都不存在(gh api 422、git cat-file 失败),PR 的 5 个 commit 全部 带 Signed-off-by。 顺带清理了上一轮抽函数时留在 resolveHooksPathFrom 前面的悬空注释。 反向验证过三处:去掉整体长度检查、恢复 name 后缀豁免、去掉可执行位修复,各会红 1 个用例。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com> * fix(dco): hook 固定 LF 检出,签名解析对齐 App 的行格式 第六轮 review 反馈,三处成立: - .githooks/ 下的 hook 没有扩展名,命不中 .gitattributes 已有的 `*.sh` / `*.mjs` 规则。Windows autocrlf 检出成 CRLF 后 `#!/bin/sh\r` 无法执行,git commit 会直接 `fatal: cannot exec`——装了 hook 的人从此提交不了。补 `.githooks/** text eol=lf`, git check-attr 已确认生效。仓库先前为 *.sh 与 *.mjs 加过同样保护并写了注释,这次是漏了。 - 签名解析比 App 宽松三处,都会造成「本地绿、PR 红」:行首空白(App 的 `^Signed-off-by:` 锚在行首,缩进行在它眼里不是签名)、冒号后多个空格(多出的空格被 App 算进 name, 与 author 比不上)、捕获值 trim(App 拿原样值比对)。正则改为与 dcoapp/app 的 `/^Signed-off-by: (.*) <(.*)>\s*$/gim` 逐字对齐,比对时签名侧只 lowercase 不 trim。 - 安装器提示的 diff 命令换成 `git diff --no-index`:git 本来就是本仓前置依赖,而 diff(1) 在部分 Windows 环境没有。 另有两条 P1 是同类误报(第五、六次,sha 7c571f80 与 be844285 在本仓远端与本地都不存在), 一条是过时反馈(bot name 豁免已在 e83fca6 去掉)。 CI 的 verify 之前红过一次,核实是 main 基线问题:makecindy#329 合入后有 3 条源码文本断言过时, main 上的 5c1a81c 已修好,但它比本 PR 那轮 CI 晚合入。已 update-branch 同步最新 main。 反向验证过两处正则修复:放宽任一处各会红 1 个用例。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com> * refactor(dco): classifyHook 的源文件读取改为懒求值 默认参数 `expectedContent = readHookSource()` 会在调用时就求值,于是 `classifyHook(null)`(没装 hook 这条最常见的路径)也要读一次磁盘,并且把这个判断 绑到了源文件可读上。改成先判 missing、再按需 `?? readHookSource()`。 行为不变,纯效率与依赖面收敛。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com> * fix(dco): 本地不再按邮箱豁免 bot,改为只豁免 merge commit reviewer 指出上一轮的邮箱豁免仍可伪造:author 邮箱是自由填写的, `999999+not-a-real-account[bot]@users.noreply.github.com` 谁都能设,而 App 认的是 GitHub 账号类型(author.type === "Bot")。按邮箱豁免等于留一个可伪造的后门,方向还是 最坏的「本地绿、PR 红」。 离线能可靠判定的豁免依据只有父提交数量,所以 exemptReason 现在只豁免 merge commit。 bot 提交在本地照常检查——方向安全(本地红、PR 绿),失败输出里解释清楚:这类地址在 PR 上由 App 按账号类型豁免,而账号类型离线取不到,所以本地不敢信一个可伪造的地址。 原 isBotIdentity 改名为 looksLikeBotAddress,语义收窄为「只用于失败输出的提示」, 并去掉了已在上一轮移除的 name 后缀判断残留说明。 同步了三处因语义变更而过时的表述:check-dco.mjs 里「bot 提交在 exemptReason 就被豁免, 走不到这里」、测试文件头的「merge/bot 豁免」、以及 CONTRIBUTING 中英两版里对 `pnpm check:dco` 保守程度的说明(现在明确列出 remediation 与 bot 两项差异)。 提交前门禁:根 pnpm test:unit 有 1 项 apps/mobile 的 startupSplashOverlay 超时失败, 与本 PR 无关——该测试单跑 3.95s、默认上限 5s,本机负载峰值 150+ 时必然 flake;定向重跑 6/6 通过。不在本 PR 混入无关修复。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Dash <dashhuang@gmail.com> --------- Signed-off-by: Dash <dashhuang@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
review 反馈 5 条: 1. 邮箱 local part 允许 CJK,会把紧贴在地址前的中文正文一路吞到上一个空白 `返回worker联系a@x.com` 整条被剥成一个空格,里面小写的 worker 违规随之消失。 中文文案里地址常与正文无空格相接。已限定为邮箱合法字符集。 2. 省略号规则漏掉 en DESIGN.md §11 Voice & Content 明文要求英文也用「…」而非三个半角点, 漏掉 en 等于让门禁替既有违规背书。已纳入,清理 54 处(含 `~/.ssh/...` 进度提示与 `-2...` 的「以此类推」,逐条确认都是真省略号; `loading ...` 另去掉了省略号前的空格,符合「现在进行时 + …」)。 3-4. Agent 运行实例被并成「对话」,语义说反了 英文原文区分 agent session 与 conversation:开关对「同一对话内重启的 Agent 会话」也生效,写成「只对新建对话生效」等于告诉用户要新开对话。 4 个 toggleHint 恢复「Agent 会话」;forkErrors.unsupportedHistory 失效的是 历史引擎 session、产品对话仍在,恢复「历史引擎会话」。均加进 exempt。 5. help step3 的 `把对话区当作一个持续的工作对话` 同义反复 英文是 persistent work thread,改为「把对话区当作持续推进工作的地方」。 合并 origin/main:package.json 的 test:runner 冲突——main 加了 check-dco.test.mjs (DCO 基建 makecindy#396),本 PR 加了 glossary-rules.test.mjs,两者都保留。 门禁又从 main 新 key 里捞出 1 处半角分号,已修。 新增 2 个单测(共 51 个)。 验证:pnpm check:dco 通过(18 签名 + 5 merge 豁免)、desktop 13294 测试全过、 mobile 2355 测试全过、双端 typecheck 干净、test:runner 226 passed、 四项 i18n guard 与 check:dev-docs 通过。 Signed-off-by: Dash <dashhuang@gmail.com>
这次改了什么
摘要
仓库公开前把 DCO 从「CONTRIBUTING 里的一句要求」补成真正可执行的基础设施:PR 上有门禁、
本地能自查、提交时自动签名、贡献者知道漏签后怎么补。
PR 门禁用已安装到本 org 的 DCO GitHub App,本仓这边提供它的
配置、本地自查脚本、自动签名 hook 与全套说明。历史提交不受影响:App 只看 PR 里的 commit。
变更类型
feat新功能fix缺陷修复refactor/perf重构或性能优化docs/test/chore文档、测试或工程维护范围
DCO—— Developer Certificate of Origin 1.1 原文(Linux Foundation 版,逐字未改).github/dco.yml—— DCO App 配置,开启 individual + thirdParty remediation commit.githooks/prepare-commit-msg—— 本地自动补签的 hook(普通 sh 脚本,可自行审阅)scripts/install-dco-hook.mjs—— 把上面的 hook 装进 hooks 目录,不覆盖他人同名 hookscripts/check-dco.mjs—— 提交前本地自查(pnpm check:dco)scripts/__tests__/check-dco.test.mjs—— 13 个用例,含真 git 仓库端到端CONTRIBUTING(中英)、README(中英)、PR 模板、AGENTS.md、docs/dev-rules/development-workflow.md的说明UI 变化
不涉及。
怎么验证的
自动验证
新增用例覆盖:签名解析、name/邮箱与 author/committer 的比对、邮箱形状校验、merge 与 bot
豁免、
git log解析、hook 源文件契约、CLI 在漏签时失败并在补签后通过、hook 自动补签、hook 在交互式提交下保持原生
git commit -s的排版、GITHUB_EVENT_PATH事件路径、base 分支前进后不误报。
本 PR 自己的 commit 带
Signed-off-by,因此这个 PR 同时是 App 是否装对的验证。手工验证
在
os.tmpdir的临时仓库里跑过一遍真实贡献者流程:漏签 → 看门禁输出 → 按提示git rebase --signoff→ 通过。确认过反向用例:把 hook 里的--if-exists addIfDifferent去掉后有 2 个用例变红,还原后恢复全绿——即新增的回归断言不是空断言。
没有在本机安装 hook(
core.hooksPath指向主 checkout,装了会影响所有 worktree 的提交行为),只跑了
node scripts/install-dco-hook.mjs --check确认状态探测正确。未执行的验证
pnpm test:all:本 PR 不含任何 TS / 运行时代码改动,只有.mjs脚本、文档、CI 配置与 shell hook;受影响范围已由
test:runner全量覆盖。typecheck:没有 package 的 TS 源码被改动。win32上按平台跳过(Git Bash下应可用,但没有环境实测)。CI 只跑 ubuntu。
风险
风险分类
影响与回滚
PR 事件触发,所以它们现在还是绿的;一旦有新 push,DCO check 会变红,需要补签
(
git rebase --signoff后 force-push,或用本 PR 开启的 remediation commit 保住 review讨论)。feat(plugins): 对话内通用 Setup Runtime(集成 #329 至最新 main) #393 含他人作者的 commit,补签需要由对应作者本人来做,或走 third-party
remediation。
AGENTS.md与development-workflow.md已写明,并提供
pnpm dco:install-hook让漏签在提交时就被避免。出现「本地绿、PR 红」。
.github/dco.yml(App 退回默认行为:仍要求签名但不允许remediation);要完全停用则在 org 的 App 设置里移除本仓,并去掉分支保护里的 required
check。仓库内其余文件(hook、脚本、文档)无副作用,删除即可,不影响构建与运行时。
提交前检查
git commit -s,见 DCO)