Skip to content

chore(dco): 落地 DCO 签名基础设施(App 门禁 + 本地 hook + 自查脚本) - #396

Merged
MagicLizi merged 10 commits into
mainfrom
dash/dco-infrastructure
Jul 25, 2026
Merged

MagicLizi merged 10 commits into
mainfrom
dash/dco-infrastructure

Conversation

@dashhuang

Copy link
Copy Markdown
Member

这次改了什么

摘要

仓库公开前把 DCO 从「CONTRIBUTING 里的一句要求」补成真正可执行的基础设施:PR 上有门禁、
本地能自查、提交时自动签名、贡献者知道漏签后怎么补。

PR 门禁用已安装到本 org 的 DCO GitHub App,本仓这边提供它的
配置、本地自查脚本、自动签名 hook 与全套说明。历史提交不受影响:App 只看 PR 里的 commit。

变更类型

  • feat 新功能
  • fix 缺陷修复
  • refactor / perf 重构或性能优化
  • docs / test / chore 文档、测试或工程维护
  • 其他:

范围

  • 关联 Issue / 需求:开源发布前的合规准备项(DCO 基础设施,窗口是仓库公开前)
  • 本 PR 包含:
    • 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 目录,不覆盖他人同名 hook
    • scripts/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 的说明
  • 明确不包含:
  • 用户可见变化:无。仅影响贡献流程。
  • 是否存在 breaking change:无(对贡献流程是新增要求,不影响产物与运行时)

UI 变化

不涉及。

  • 引用的设计规范:不涉及:纯工程与文档改动,无界面、交互或 UI 文案变化。

怎么验证的

自动验证

pnpm test:unit
结果:通过(GATE_EXIT=0)

pnpm test:runner
结果:158 tests / 158 pass / 0 fail(其中 13 个是本 PR 新增的 DCO 用例)

node --test scripts/__tests__/check-dco.test.mjs
结果:13 pass / 0 fail

node scripts/check-pr-template-rules.mjs
结果:PR 模板结构符合公开贡献契约

node --test scripts/__tests__/dev-docs-contract.test.mjs
结果:5 pass / 0 fail

sh -n .githooks/prepare-commit-msg && dash -n .githooks/prepare-commit-msg
结果:语法检查通过(POSIX sh 兼容)

新增用例覆盖:签名解析、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 全量覆盖。
  • 未跑任何 package 的 typecheck:没有 package 的 TS 源码被改动。
  • 未在 Windows 上验证 hook:hook 与真 git 端到端用例在 win32 上按平台跳过(Git Bash
    下应可用,但没有环境实测)。CI 只跑 ubuntu。

风险

风险分类

  • 无已知风险
  • SQLite / migration
  • system prompt
  • 协议兼容
  • 权限 / 安全 / 用户数据
  • 原生层 / fingerprint / OTA
  • 跨平台差异
  • 其他:改变所有人的提交流程(此后每个 commit 都需要 DCO 签名)

影响与回滚

提交前检查

  • 已 review 完整 diff
  • 每个 commit 都带 DCO 签名(git commit -s,见 DCO)
  • UI 改动已在「UI 变化」注明引用的设计规范章节(不涉及 UI 则跳过)
  • 未提交凭证、令牌或授权文件
  • 已补充必要文档
  • 已确认测试结果或说明未执行原因

仓库公开前把 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>
Copilot AI review requested due to automatic review settings July 25, 2026 05:29
@greptile-apps

greptile-apps Bot commented Jul 25, 2026 •

Copy link
Copy Markdown

Greptile Summary

本 PR 落地 DCO 签名基础设施:

  • 配置 DCO GitHub App 门禁并补充中英文贡献指南。
  • 新增自动使用 committer 身份补签的本地 hook 及安全安装脚本。
  • 新增 DCO 自查脚本与端到端测试,覆盖自定义 author、签名校验及 hook 安装场景。

Confidence Score: 5/5

此前线程指出的原作者签名冒用问题已完整修复,本 PR 看起来可以安全合并。

hook、校验器、文档及端到端测试均一致采用 committer 签名语义,没有遗留阻塞性故障。

Important Files Changed

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

Comment thread .githooks/prepare-commit-msg Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-msg hook (and installer script) to automatically append Signed-off-by for 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.

Comment thread scripts/check-dco.mjs Outdated
Comment thread scripts/check-dco.mjs
Comment thread scripts/check-dco.mjs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread AGENTS.md
Comment thread .githooks/prepare-commit-msg Outdated
Comment thread scripts/install-dco-hook.mjs Outdated
Comment thread scripts/check-dco.mjs Outdated
Comment thread scripts/check-dco.mjs Outdated
@MagicLizi

Copy link
Copy Markdown
Contributor

@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>
Copilot AI review requested due to automatic review settings July 25, 2026 05:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Comment thread scripts/install-dco-hook.mjs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread AGENTS.md
Comment thread scripts/install-dco-hook.mjs Outdated
Comment thread scripts/check-dco.mjs Outdated
@MagicLizi

Copy link
Copy Markdown
Contributor

@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>
Copilot AI review requested due to automatic review settings July 25, 2026 06:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread AGENTS.md
Comment thread scripts/check-dco.mjs Outdated
Comment thread scripts/install-dco-hook.mjs Outdated
Comment thread scripts/check-dco.mjs Outdated
@MagicLizi

Copy link
Copy Markdown
Contributor

@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>
Copilot AI review requested due to automatic review settings July 25, 2026 07:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Comment thread scripts/install-dco-hook.mjs Outdated
Comment thread .githooks/prepare-commit-msg Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread scripts/check-dco.mjs
Comment thread scripts/install-dco-hook.mjs
Comment thread AGENTS.md
@MagicLizi

Copy link
Copy Markdown
Contributor

@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>
Copilot AI review requested due to automatic review settings July 25, 2026 07:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Comment thread scripts/check-dco.mjs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread AGENTS.md
Comment thread scripts/check-dco.mjs Outdated
Comment thread .githooks/prepare-commit-msg
@MagicLizi

Copy link
Copy Markdown
Contributor

@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。

Copilot AI review requested due to automatic review settings July 25, 2026 08:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.

第六轮 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>
Copilot AI review requested due to automatic review settings July 25, 2026 08:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.

Comment thread scripts/install-dco-hook.mjs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread AGENTS.md
Comment thread scripts/check-dco.mjs Outdated
@MagicLizi

Copy link
Copy Markdown
Contributor

@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>
Copilot AI review requested due to automatic review settings July 25, 2026 08:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated no new comments.

@MagicLizi

Copy link
Copy Markdown
Contributor

@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>
Copilot AI review requested due to automatic review settings July 25, 2026 11:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated no new comments.

@MagicLizi MagicLizi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审查通过:DCO 签名基础设施落地干净,无 P0/P1。安全软命中全部为测试夹具(example.com 邮箱、bot noreply 地址);21 条单元测试通过;shell hook POSIX 语法验证通过;所有 commit 签名正确。

@MagicLizi
MagicLizi merged commit 2dd1600 into main Jul 25, 2026
13 checks passed
dashhuang added a commit that referenced this pull request Jul 25, 2026
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>
gardenZzz pushed a commit to gardenZzz/cindy that referenced this pull request Jul 31, 2026
* 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>
gardenZzz pushed a commit to gardenZzz/cindy that referenced this pull request Jul 31, 2026
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>
gardenZzz pushed a commit to gardenZzz/cindy that referenced this pull request Aug 5, 2026
* 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>
gardenZzz pushed a commit to gardenZzz/cindy that referenced this pull request Aug 5, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants