deepseek-harness 学习手册
← 返回 8.3 把工程约定写成 AI 可执行的形式
工程文化 · 8.3›Skill 01 · dsh-pre-push-checks
12 min

dsh-pre-push-checks

push 之前该跑哪些检查——挑最小的、能覆盖这次 diff 的那一组,而不是反射式地跑全套。

最后核对于 v0.1.1-rc.2 · commit b150a551b8 · 2026-08-22

这是 .agents/skills/dsh-pre-push-checks/SKILL.md 的中文精读。它回答的问题是:这次 push 之前,到底该跑哪些检查?答案是「能覆盖这次 diff 的最小集合」,而不是反射式地把全仓套件跑一遍。

什么时候触发

原文 frontmatter 的触发条件:在 push、force-push、标记 ready for review、或声称「检查已通过」之前,以及 gh stack sync 发布完重写过的分支之后立刻使用——目的是为即将发出(或刚发出)的 diff 选出最小的测试与检查组合,而不是条件反射式地跑全仓套件。

分工:本地挑证据,CI 管穷尽

Git hooks 被刻意做得很窄:pre-commit 只修暂存的 lint、查空白符、守 vendored 源码的元数据;pre-push 只跑增量 typecheck。穷尽覆盖和平台矩阵归 CI 管。

唯一的顺序例外是 gh stack sync——它会把层叠 rebase 直接发布出去,本地来不及插进验证。所以 sync 之后立刻补验,证据没齐之前不合并。

一、先看清楚这次要推什么

terminal
$ git status --short --branch
$ git rev-parse --show-toplevel
$ pnpm --silent run change-scope --base <verified-base-ref>

change-scope 从不猜、也不替你 fetch base——base 必须由你从当前远端或栈状态里核实后传入;要看非 HEAD 的提交就加 --head。它输出带版本号的 JSON:已提交路径(相对 merge base)、暂存 / 未暂存 / 未跟踪路径。合进了一个变化过的 base 之后,要重跑一次,重新评估合并后的范围能影响什么行为,只重跑被这次合并作废掉的检查。

二、按 diff 选证据

除了 hooks 之外,不存在「全仓通用基线」。每个行为变化都需要最窄的、回归时能变红的那个测试或检查;只有 diff 真的够到的面,才加更宽的检查:

动了什么跑什么
包或脚本的行为归属它的 Vitest 文件或具体测试名。共享契约变了才加相邻包的测试;全仓覆盖留给 CI
文档、Agent Note、目录、文档关联注释pnpm run doc-sync;文档工作流要求时再跑全量 lint
模型 / 编辑器 / CLI / 终端可见的输出归属该输出的 keyless 快照或可运行示例场景
包清单、公共导出、构建配置、worker/bin 入口、构建产物路径pnpm run build + 相关 hygiene 检查 + 归属的构建产物冒烟
真实 provider 或 agent 行为有凭证时跑对应的 pnpm run test:e2e 目标;绝不打印密钥

不要仅仅因为接下来要 commit 或 push,就手动重跑一个已经通过的检查——尤其不要在 push 前再跑一次 typecheck 去重复 pre-push hook 已经做过的事。

把覆盖率聚焦到受影响的源码上

选测试和选覆盖率是两件事。Vitest 的文件过滤器决定跑哪些测试,而仓库配置默认会统计所有 packages/*/*/src/**/*.ts。需要覆盖率证据时,两个都要指明:

terminal
$ pnpm exec vitest run packages/<group>/<package>/tests/<behavior>.spec.ts \
    --coverage \
    --coverage.include='packages/<group>/<package>/src/**/*.ts'

行为确实只关一个模块时就用精确文件;多个文件或包就重复 --coverage.include。选定范围内,逐文件 100% 的阈值依然生效。

拿不准归属测试时,用 Vitest 的依赖图找候选,但选定后要亲自看过这些测试再把这次运行当证据:

terminal
$ pnpm exec vitest related packages/<group>/<package>/src/<changed>.ts \
    --run --coverage \
    --coverage.include='packages/<group>/<package>/src/<changed>.ts'
vitest related 的盲区

只通过配置、动态加载、子进程、worker、构建产物或外部 provider 才够得到的行为,related 发现不了——这些测试必须显式点名。也不要用 --passWithNoTests、降低覆盖率阈值、或收窄 --coverage.include 来把一个没覆盖到的受影响文件藏起来。

三、什么时候才允许全量本地彩排

只有三种情况:用户明确要求、诊断 CI 失败、或者改动横跨全仓到「任何更窄的组合都不可信」。用当前的工作流和 package scripts 作为清单;不要重建已经被移除的 check:pre-push 聚合。

四、保护改写历史的 push

独立分支和栈式 PR 分支都允许 rebase(review 之后也行)。但改写历史之前,先 fetch 当前远端分支并记下它的精确 OID,发布时用租约:

terminal
$ git push --force-with-lease=<branch>:<observed-oid>

这样远端被别人推进过时 push 会中止。gh stack push / gh stack sync 对它们管理的分支自带租约保护。裸 --force 永远不允许。重写后的 push 之后,重新 fetch 活跃 heads,重新审计未解决的 review 线程、approval、可合并性和检查——重写前的 commit hash 和行内评论锚点都不再是有效证据。

sync 后的验证序列

gh stack sync 把 fetch、级联 rebase、push 合成一步,本地验证插不进去。所以它返回之后:

  1. 重新查询每个分支头和 GitHub 官方栈顺序;
  2. 对照各自的 live PR base,检查每个被重写层的变化范围;
  3. 为每个受影响层跑本 skill 选出的相关证据;
  4. 所有检查通过之前,每个 PR 都保持未合并状态,汇报「验证进行中」。

sync 后验证失败,就把租约保护的已发布 heads 留在原地,修复、验证修复、再发布修正。不要因为命令本身成功就声称栈已就绪。

五、失败怎么办

普通 push 前有相关检查失败:停下,修复或解释阻塞点,不要 push 上去赌 CI 不一样。失败看起来是环境特有的?那就证明它:

六、push 流程

普通 push 和独立分支的 rebase push:

  1. 把选好的检查跑一遍;
  2. 正常 commit,并检查 pre-commit fixer 动过的文件;
  3. 正常 push(授权的重写分支用精确租约),让增量 typecheck hook 跑起来;
  4. 验证远端 ref 与本地 HEAD 一致。
terminal
$ git rev-parse HEAD origin/$(git branch --show-current)
$ gh pr checks   # GitHub PR:push 后看远端 CI

pending 就如实报 pending;失败要先看原因再归因于分支或环境。

「no checks reported」多半不是 GitHub 掉了

当 gh pr checks 说没有检查、/actions/runs?head_sha=<sha> 返回 total_count: 0 时,先读可合并性:

gh pr view <number> --json mergeable,mergeStateStatus

PR 处于 CONFLICTING/DIRTY 时 GitHub 根本不创建 pull_request 工作流运行——缺失的信号就是冲突本身,不是基础设施坏了。唯一修复是解决冲突;空 commit、--allow-empty push、draft/ready 来回切、revert 再恢复,全都只会让 total_count 停在 0 并留下垃圾历史。本地还合不了时,用 git merge-tree --write-tree HEAD origin/<base> 确认冲突路径。

为什么这个 skill 值得抄

它把「push 前跑全套」这个最常见的浪费,换成了一套可执行的判断:证据要配得上 diff,而不是配得上焦虑。而「CI 拥有穷尽覆盖」这条分工,让「本地少跑」从偷懒变成了制度。

← 返回 8.3 把工程约定写成 AI 可执行的形式
目录

本页

Skill 01 · dsh-pre-push-checks