covgate 0.0.0-stage → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +504 -2
  3. package/bin/covgate-postinstall.js +13 -0
  4. package/bin/covgate.js +5 -0
  5. package/docs/ai-workflow.md +197 -0
  6. package/docs/design.md +327 -0
  7. package/docs/setup.md +165 -0
  8. package/package.json +48 -4
  9. package/schema/covgate.schema.json +295 -0
  10. package/src/annotations.js +224 -0
  11. package/src/args.js +189 -0
  12. package/src/cli.js +486 -0
  13. package/src/config.js +442 -0
  14. package/src/coverage/index.js +85 -0
  15. package/src/coverage/istanbul.js +215 -0
  16. package/src/coverage/lcov.js +234 -0
  17. package/src/coverage/model.js +316 -0
  18. package/src/coverage/paths.js +40 -0
  19. package/src/errors.js +34 -0
  20. package/src/gate.js +203 -0
  21. package/src/glob.js +242 -0
  22. package/src/index.js +28 -0
  23. package/src/process.js +170 -0
  24. package/src/reason.js +77 -0
  25. package/src/report/build.js +135 -0
  26. package/src/report/index.js +29 -0
  27. package/src/report/markdown.js +343 -0
  28. package/src/report/text.js +267 -0
  29. package/src/reports.js +113 -0
  30. package/src/runner.js +95 -0
  31. package/src/scope.js +440 -0
  32. package/src/setup/agent.js +329 -0
  33. package/src/setup/config.js +129 -0
  34. package/src/setup/index.js +270 -0
  35. package/src/setup/project.js +391 -0
  36. package/src/setup/prompt.js +43 -0
  37. package/src/util.js +163 -0
  38. package/src/version.js +8 -0
  39. package/templates/AGENTS.md.snippet.md +10 -0
  40. package/templates/agent-stop-hook.sh +7 -0
  41. package/templates/covgate.config.json +98 -0
  42. package/templates/github-actions-coverage.yml +41 -0
  43. package/templates/gitignore +5 -0
  44. package/templates/pre-commit +10 -0
@@ -0,0 +1,197 @@
1
+ # 让 AI 写完代码后自动跑覆盖率
2
+
3
+ CovGate 是一个命令,不是文件监听器。它不知道“AI 已经写完了”。自动化的关键是:**在代理完成回合、提交或合并时触发它,并把非零退出码和 JSON 反馈给代理。**
4
+
5
+ 推荐三层防线:
6
+
7
+ ```text
8
+ 编辑阶段:AGENTS.md / 系统提示(告诉代理必须跑)
9
+ ↓
10
+ 结束阶段:Stop hook / pre-commit(真正执行,失败阻断)
11
+ ↓
12
+ 合并阶段:CI required check(最终兜底)
13
+ ```
14
+
15
+ ## 0. 先运行 setup
16
+
17
+ ```bash
18
+ npx --no-install covgate setup
19
+ ```
20
+
21
+ 它会检测/安装测试框架、生成 `covgate.config.json`、`AGENTS.md`、Agent 门禁脚本和 CI。详见 [setup](setup.md)。
22
+
23
+ ## 1. 在 AGENTS.md 中写“完成定义”
24
+
25
+ 把下面片段放进项目根目录的 `AGENTS.md`(或等价的代理指令文件):
26
+
27
+ ```md
28
+ ## 覆盖率门禁(完成定义)
29
+
30
+ - 任何代码修改完成后,必须运行 `npx --no-install covgate run --json`;退出码 0 才能说完成。
31
+ - 退出码 1:补测试或修实现;2:修配置;3:修测试命令/报告。不要绕过。
32
+ - 禁止降低 covgate.config.json 中的门槛、扩大 exclude、删除豁免检查来让门禁通过;这些改动必须由人类评审。
33
+ - **行为变更必须新增或更新覆盖该行为的真实测试**;门禁失败不能作为完成理由,也不能用 `--passWithNoTests`、空报告或 ignore 绕过。
34
+ - 禁止 istanbul ignore / c8 ignore / v8 ignore / node:coverage ignore。
35
+ - 确实不可达的代码使用 covgate-ignore-next-line / start/end / file,写具体原因、owner 和 expires。
36
+ - 新增或修改的源码必须出现在覆盖率报告中;sources.include 是范围事实来源。
37
+ - 交付时附上 covgate 的结果摘要。
38
+ ```
39
+
40
+ 完整片段见 [../templates/AGENTS.md.snippet.md](../templates/AGENTS.md.snippet.md)。
41
+
42
+ ## 2. Stop hook:代理结束前执行
43
+
44
+ 支持 Stop hook 的代理(例如在代理配置中注册“停止前执行命令”)可以这样接:
45
+
46
+ 命令:
47
+
48
+ ```bash
49
+ npx --no-install covgate run --json
50
+ ```
51
+
52
+ 退出码语义:
53
+
54
+ - `0`:允许代理结束;
55
+ - 非零:阻断结束,把 stderr/报告反馈给代理,让它继续修复。
56
+
57
+ 如果代理只读取 stderr,可以这样保留可读摘要:
58
+
59
+ ```bash
60
+ npx --no-install covgate run
61
+ ```
62
+
63
+ 如果代理读取 JSON:
64
+
65
+ ```bash
66
+ npx --no-install covgate run --json
67
+ ```
68
+
69
+ JSON 在 stdout,过程日志在 stderr,互不污染。
70
+
71
+ 现成脚本见 [../templates/agent-stop-hook.sh](../templates/agent-stop-hook.sh)。
72
+
73
+ ### 通用 Stop hook 脚本
74
+
75
+ ```sh
76
+ #!/bin/sh
77
+ # 放到 scripts/covgate-stop-hook.sh,并 chmod +x
78
+ set -eu
79
+ cd "$(dirname "$0")/.."
80
+ exec npx --no-install covgate run --json
81
+ ```
82
+
83
+ 具体 hook 配置语法因代理产品而异,但原则相同:**退出码非 0 时阻止“完成”**。
84
+
85
+ ### 不建议每个文件保存都跑
86
+
87
+ 在 PostToolUse / 每次写入后执行完整测试套件很慢,也会制造大量中间失败。更好的时机:
88
+
89
+ - 代理准备结束回合时(Stop hook);
90
+ - 提交前(pre-commit);
91
+ - 推送后(CI)。
92
+
93
+ 如果确实要频繁运行,用 `covgate check` 只检查已有报告,但不要用它代替完整测试。
94
+
95
+ ## 3. pre-commit
96
+
97
+ 把 [../templates/pre-commit](../templates/pre-commit) 复制到 `.git/hooks/pre-commit` 并加执行权限。
98
+
99
+ 它执行完整门禁,可能较慢;适合客户端 100% 的服务端仓库。若团队更依赖 CI,可以把 pre-commit 换成只跑单元测试,覆盖率交给 CI。
100
+
101
+ ## 4. CI required check
102
+
103
+ 把 [../templates/github-actions-coverage.yml](../templates/github-actions-coverage.yml) 复制到 `.github/workflows/coverage-gate.yml`,按项目修改安装命令。
104
+
105
+ 要求:
106
+
107
+ - `covgate` 已在 devDependencies(通过 `file:` 或私有 registry 安装);
108
+ - 配置中的测试命令在 CI 环境可执行;
109
+ - 把 `covgate run` 设为 required check;
110
+ - 上传 `.covgate/` 作为失败诊断 artifact。
111
+
112
+ ## 5. 代理如何读失败结果
113
+
114
+ 运行 `--json` 时,stdout 示例(节选):
115
+
116
+ ```json
117
+ {
118
+ "status": "fail",
119
+ "exitCode": 1,
120
+ "summary": {
121
+ "failedScopes": ["client"],
122
+ "missingSources": 0
123
+ },
124
+ "scopes": [
125
+ {
126
+ "name": "client",
127
+ "passed": false,
128
+ "metrics": {
129
+ "lines": { "covered": 39, "total": 40, "percent": 97.5, "threshold": 100, "passed": false }
130
+ },
131
+ "failingFiles": [
132
+ {
133
+ "path": "web/src/legacy.ts",
134
+ "uncovered": { "lines": [42, 43], "linesTotal": 2 }
135
+ }
136
+ ],
137
+ "issues": [],
138
+ "waivers": []
139
+ }
140
+ ]
141
+ }
142
+ ```
143
+
144
+ 代理应该:
145
+
146
+ 1. 先修 `commands[]` 中失败的测试(如果有);
147
+ 2. 对 `failingFiles[].uncovered` 补测试;
148
+ 3. 对 `missingSources` 补测试或写文件级说明;
149
+ 4. 对 `issues` 修原生 ignore、stale、过期、重叠、无效原因;
150
+ 5. 重跑,直到退出码 0;
151
+ 6. 报告结果,不要只说“已写测试”。
152
+
153
+ ## 6. 什么时候允许写豁免
154
+
155
+ 按优先级:
156
+
157
+ 1. 能写测试就写测试;
158
+ 2. 平台/运行时保证不可达 => 行级或块级豁免,写原因 + owner + 到期日;
159
+ 3. 整个生成文件、类型声明文件、测试文件 => 配置 exclude 或文件级豁免,写原因;
160
+ 4. 不要为了让 100% 通过而排除业务代码。
161
+
162
+ 建议团队开启 `requireOwner: true`,并给所有豁免设置到期日。豁免会出现在 `report.md` 中,评审时逐条看。
163
+
164
+ ## 7. 防止代理“作弊”
165
+
166
+ AI 可能:
167
+
168
+ - 改 `covgate.config.json` 把 client 改成 99:被产品底线拒绝;
169
+ - 删除 `sources.include` 中的文件:会导致文件不在门禁范围,需评审配置 diff;
170
+ - 用 `exclude` 排除整个 src:需要原因,且全排除会触发 no-gated-sources;但仍需 CODEOWNERS;
171
+ - 写原生 ignore:直接失败;
172
+ - 写“TODO”原因:被占位词/长度检查拒绝;
173
+ - 写假原因:工具无法验证真实性,必须人工评审;
174
+ - 复制旧报告:mtime/size 检查拒绝;
175
+ - 写空测试提高覆盖率:工具无法检测语义,需要测试评审。
176
+
177
+ 因此:
178
+
179
+ - 用 CODEOWNERS 保护 `covgate.config.json`、`AGENTS.md`、`.github/workflows`;
180
+ - CI 只从主分支读取 workflow;
181
+ - 门禁失败时看报告中的豁免和排除审计,不只看百分比;
182
+ - 重要模块的 100% 覆盖率仍要配合 mutation testing 或人工测试评审(可选)。
183
+
184
+ ## 8. 一个可复制的代理循环提示词
185
+
186
+ ```text
187
+ 你在修改代码后必须运行:
188
+ npx --no-install covgate run --json
189
+
190
+ 只有退出码为 0 才能结束。
191
+ 若失败:
192
+ - 读 JSON 的 scopes[].failingFiles / missingSources / issues;
193
+ - 先补测试,再考虑带原因的豁免;
194
+ - 禁止降低阈值、删除测试、使用 istanbul/c8/v8 ignore;
195
+ - 任何新增豁免必须写具体原因、owner 和 expires,并在最终回复中列出;
196
+ - 重跑,直到通过。
197
+ ```
package/docs/design.md ADDED
@@ -0,0 +1,327 @@
1
+ # CovGate 设计文档
2
+
3
+ ## 1. 问题
4
+
5
+ AI 编码代理通常能快速写完实现,但“完成”缺少可执行定义:
6
+
7
+ - 只跑了部分测试;
8
+ - 跑了测试但没跑覆盖率;
9
+ - 覆盖率差一点也能过;
10
+ - 未 import 的新文件根本不在报告里;
11
+ - 用 `istanbul ignore next` 把困难代码藏起来;
12
+ - 加一个“TODO”就当作不可达说明;
13
+ - 读取上次留下的覆盖率报告;
14
+ - 客户端和服务端用同一套宽松标准。
15
+
16
+ CovGate 把这些都变成一道命令:测试必须真实执行,报告必须新鲜,源码必须被枚举,豁免必须有可审计说明,阈值必须精确达标。
17
+
18
+ ## 2. 目标与非目标
19
+
20
+ ### 目标
21
+
22
+ 1. 客户端 100%、服务端 ≥95%,任何项目配置都不能降低。
23
+ 2. 无法覆盖的代码必须用带原因的指令豁免;没有说明就不能从分母中消失。
24
+ 3. 支持主流覆盖率报告,不绑定测试框架。
25
+ 4. 输出适合 CI 和 AI 代理读取的结构化结果。
26
+ 5. 零运行时依赖,不需要构建,跨平台。
27
+ 6. 默认严格,同时保留明确的、可评审的例外机制。
28
+
29
+ ### 非目标
30
+
31
+ - 不替代测试框架,不生成覆盖率。
32
+ - 不判断测试断言是否有意义。
33
+ - 不提供“自动修改代码直到通过”的逻辑。
34
+ - 不实现基于 git diff 的增量覆盖率(当前是 scope 全量;可作为后续扩展)。
35
+ - 不内置 Cobertura/JaCoCo XML;先通过 LCOV 接入。
36
+
37
+ ## 3. 核心流程
38
+
39
+ ```text
40
+ covgate.config.json
41
+ │
42
+ ▼
43
+ [配置校验 + 产品底线]
44
+ │
45
+ ▼
46
+ [执行测试命令] ──失败──▶ 退出 3
47
+ │
48
+ ▼
49
+ [发现报告 + 新鲜度检查] ──缺失/陈旧──▶ 退出 3
50
+ │
51
+ ▼
52
+ [载入 Istanbul / LCOV] ──格式错误──▶ 退出 3
53
+ │
54
+ ▼
55
+ [枚举 sources.include]
56
+ │
57
+ ├─ 配置 exclude(必须带原因,必须命中文件)
58
+ ├─ covgate-ignore-file(必须带原因)
59
+ ├─ 原生 ignore(一律失败)
60
+ └─ 漏报文件(失败)
61
+ │
62
+ ▼
63
+ [应用行/块豁免 + stale/过期/重叠检查]
64
+ │
65
+ ▼
66
+ [精确阈值比较]
67
+ │
68
+ ├─ 通过 ──▶ 0
69
+ └─ 不通过 ─▶ 1
70
+ │
71
+ ▼
72
+ [写 report.json / report.md]
73
+ ```
74
+
75
+ ## 4. 配置与信任边界
76
+
77
+ `covgate.config.json` 是可信策略文件。它会执行 shell 命令,因此等价于代码。推荐:
78
+
79
+ - 用 CODEOWNERS 保护 `covgate.config.json`;
80
+ - CI 中的门禁使用仓库内固定配置;
81
+ - 不让 AI 在没有人类复核的情况下修改 thresholds、exclude、sources.include;
82
+ - 把 `.covgate/` 作为构建产物,不提交。
83
+
84
+ CovGate 在代码层面限制配置能做什么:
85
+
86
+ - 客户端门槛不能低于 100;
87
+ - 服务端门槛不能低于 95;
88
+ - 未知字段直接报错,避免拼写错误静默生效;
89
+ - exclude 不能写成裸字符串,必须提供原因;
90
+ - 文件级豁免和行级豁免互斥;
91
+ - 原生 ignore 永远失败。
92
+
93
+ 它不能阻止有权限的人把整个工具删掉。这是所有客户端门禁的共同边界;CI required check 和 CODEOWNERS 是最后的防线。
94
+
95
+ ## 5. 阈值模型
96
+
97
+ 每个指标保存整数 `covered` / `total`。门槛先转成 basis points:
98
+
99
+ ```text
100
+ threshold 95 -> 9500
101
+ threshold 95.5 -> 9550
102
+ ```
103
+
104
+ 判断:
105
+
106
+ ```text
107
+ covered * 10000 >= thresholdBp * total
108
+ ```
109
+
110
+ 这样不会出现 `94.999999%` 被四舍五入显示成 95% 而误放行。显示值四舍五入到两位小数,只用于展示。
111
+
112
+ 客户端底线基点是 10000,等价于 `covered === total`(total > 0)。
113
+
114
+ ### 零分母
115
+
116
+ - 某个 scope 的所有指标都没有可统计单元:失败,避免空 scope 虚假 100%。
117
+ - 某一条指标 total = 0(例如没有分支):视为满足,显示 N/A;不影响其他指标。
118
+ - LCOV 不提供语句覆盖率:该指标标记为不适用,并在报告中 warning;行、函数、分支仍严格执行。
119
+
120
+ ## 6. 覆盖率数据模型
121
+
122
+ ```text
123
+ FileCoverage
124
+ absPath / relPath
125
+ format: istanbul | lcov
126
+ statements: Map<key, { line, endLine, count }> | null
127
+ functions: Map<key, { name, line, endLine, count }>
128
+ branches: Map<key, { line, type, index, count }>
129
+ lines: Map<lineNumber, count>
130
+ ```
131
+
132
+ ### Istanbul JSON
133
+
134
+ - 语句、函数、分支来自 `statementMap/s`、`fnMap/f`、`branchMap/b`。
135
+ - 行覆盖遵循 Istanbul 语义:同一行起始的语句中取最大命中数。
136
+ - 多个 shard 的同一文件按位置 key 合并计数。
137
+ - 关键点:未覆盖语句也会生成行记录(count=0),不能因为“值为 0”而漏掉该行。
138
+
139
+ ### LCOV
140
+
141
+ - `DA` 生成行覆盖。
142
+ - `FN/FNDA` 生成函数覆盖;只有 FN 没有 FNDA 表示未覆盖。
143
+ - `BRDA` 生成分支覆盖;`-` 视为 0。
144
+ - `SF` 路径相对配置文件目录解析;支持绝对路径、`file://` 和 Windows 盘符。
145
+ - 多个报告按文件合并。
146
+
147
+ ### 格式选择
148
+
149
+ - 同一 scope 只允许一种格式,避免语句/行语义混用。
150
+ - `auto` 根据扩展名和文件头推断。
151
+ - 文件级路径使用规范化 key 匹配;Windows 下不区分盘符大小写。
152
+
153
+ ## 7. 源码范围与漏报检测
154
+
155
+ `sources.include` 是门禁的“宇宙”。处理顺序:
156
+
157
+ 1. 展开 include 中的全部文件;
158
+ 2. 应用带原因的 `exclude`;
159
+ 3. 扫描源码中的文件级 `covgate-ignore-file`;
160
+ 4. 对剩余文件检查覆盖率报告中是否存在;
161
+ 5. 不存在 => `missing-coverage`,失败。
162
+
163
+ 为什么必须这样做:覆盖率工具通常只报告“被加载过的文件”。如果 AI 新写了一个模块但没有任何测试 import 它,报告里不会出现,整体覆盖率仍然可能是 100%。源码枚举是防止这种漏报的关键。
164
+
165
+ 代价:类型声明文件、纯配置文件、生成文件等无运行时代码的文件也会被视为“应有覆盖率”。解决办法不是关掉检查,而是写一条带原因的 exclude 或 file-level waiver。
166
+
167
+ ### 覆盖率工具排除必须同步
168
+
169
+ 如果 Jest/Vitest/c8 自己把 `*.test.ts` 或 `generated/**` 排除,CovGate 仍会按 `sources.include` 找它们。正确处理:
170
+
171
+ - 把同样的模式写进 CovGate 的 `exclude`,并提供原因;
172
+ - 或不要把这些文件放进 `sources.include`。
173
+
174
+ 两者不一致时,CovGate 会报 missing-coverage,这是预期行为。
175
+
176
+ ## 8. 豁免设计
177
+
178
+ ### 语法
179
+
180
+ - `covgate-ignore-line: reason`
181
+ - `covgate-ignore-next-line: reason`
182
+ - `covgate-ignore-start: reason` ... `covgate-ignore-end`
183
+ - `covgate-ignore-file: reason`
184
+
185
+ 元数据:`| owner=... | expires=YYYY-MM-DD | ticket=...`。
186
+
187
+ ### 为什么用 CovGate 指令而不是原生 ignore
188
+
189
+ 原生 `istanbul ignore` 会在覆盖率工具阶段直接删除代码,CovGate 看不到它。如果允许它存在,AI 只需写一行原生注释就能绕过所有说明检查。因此:
190
+
191
+ 1. 扫描并拒绝原生 ignore;
192
+ 2. CovGate 指令不改变测试工具的插桩,只在我们自己的统计模型中移除对应单元;
193
+ 3. 因此 CovGate 能看到原始覆盖率、判断豁免是否真的移除了未覆盖代码。
194
+
195
+ ### 移除粒度
196
+
197
+ 豁免区间会移除:
198
+
199
+ - 行:起始行在区间内的行;
200
+ - 语句:起始行在区间内的语句;
201
+ - 函数:声明起始行在区间内的函数;
202
+ - 分支:分支路径起始行在区间内的分支。
203
+
204
+ 所以:
205
+
206
+ - 只豁免函数声明行不会自动豁免函数体;需要覆盖整个函数时用 start/end;
207
+ - 只豁免 `if` 行不一定覆盖分支体;以报告中的未覆盖行/分支为准;
208
+ - 多行代码块使用 start/end。
209
+
210
+ ### stale 与过期
211
+
212
+ - stale:区间内没有移除任何未覆盖单元。说明该豁免已不再需要,或写错了位置。
213
+ - expired:`expires` 结束日期已过。
214
+ - overlap:区间重叠,无法归因。
215
+ - 文件级豁免:如果文件已有覆盖率且没有任何未覆盖单元,也视为 stale;如果文件完全没有报告,则可用文件级豁免来记录“这个文件不参与统计”。
216
+
217
+ 默认都失败。`waivers.stale` / `waivers.expiry` 可设为 `warn` 做迁移,但生产环境建议保持 `fail`。
218
+
219
+ ### 原因质量
220
+
221
+ 工具会拒绝:
222
+
223
+ - 空原因;
224
+ - 少于 `minReasonLength` 的原因;
225
+ - 占位词:`todo`、`unreachable`、`无法覆盖`、`忽略` 等;
226
+ - 缺少 owner(当 `requireOwner=true`);
227
+ - 非法或过期日期。
228
+
229
+ 工具不能拒绝一个编造但看似详细的原因。因此报告必须进入评审,推荐要求 owner 和到期日。
230
+
231
+ ## 9. 原生 ignore 检测
232
+
233
+ 扫描规则(不区分大小写):
234
+
235
+ ```text
236
+ istanbul ignore / istanbul disable
237
+ c8 ignore / c8 disable
238
+ nyc ignore / nyc disable
239
+ v8 ignore / v8 disable
240
+ node:coverage ignore / node:coverage disable
241
+ ```
242
+
243
+ 检测命中即 error。修复方式:
244
+
245
+ 1. 删除原生注释;
246
+ 2. 如果代码确实不可达,写 `covgate-ignore-*` 说明;
247
+ 3. 如果整个文件不参与统计,写文件级豁免或配置 exclude。
248
+
249
+ 注意:扫描是行级文本匹配,不包含完整语法树;如果字符串常量里出现这些词,也可能误报。这类情况应改写字符串,或把文件排除。
250
+
251
+ ## 10. 报告陈旧检测
252
+
253
+ 运行前记录覆盖率文件的 `mtimeMs + size`,运行后重新发现报告:
254
+
255
+ - 新文件 => 新鲜;
256
+ - mtime 或 size 改变 => 新鲜;
257
+ - 仍然相同 => 拒绝使用,退出 3。
258
+
259
+ 这避免了“测试命令忘记生成覆盖率,CI 读取上一次报告”的假通过。
260
+
261
+ 这只是工程级防护,不是密码学证明。测试命令本身仍然是可信配置。
262
+
263
+ ## 11. 测试命令执行
264
+
265
+ - 顺序执行每个 scope 的 commands。
266
+ - 默认 900 秒超时,可配置到 86400 秒。
267
+ - 子进程通过 shell 执行,支持 `npm`、`pnpm`、`&&` 等。
268
+ - stdout/stderr 有 4MB 捕获上限,防止内存爆炸;失败时把尾部输出写入报告。
269
+ - 任一命令非零退出 => 停止并退出 3,不评估覆盖率。
270
+ - 成功命令的输出不进入报告,保持 JSON 简洁;`--verbose` 可实时转发到 stderr。
271
+
272
+ ## 12. 报告与面向 AI 的输出
273
+
274
+ - 文本:给人看,包含失败文件、行号、函数/分支、豁免说明。
275
+ - JSON:给 CI/代理看,schemaVersion=1,字段稳定。
276
+ - Markdown:给代码评审看,包含总览、未覆盖明细、豁免/排除审计表。
277
+ - stdout 在 `--json` 下只输出 JSON;日志全部走 stderr。
278
+
279
+ 报告 JSON 的关键字段:
280
+
281
+ ```text
282
+ status pass | fail | error
283
+ exitCode 0 | 1 | 3
284
+ summary scope、失败 scope、豁免、排除、缺失文件计数
285
+ scopes[].metrics covered/total/percent/threshold/passed
286
+ scopes[].failingFiles[].uncovered
287
+ scopes[].waivers file/range/reason/owner/expires/status
288
+ scopes[].exclusions pattern/reason/matched files/status
289
+ commands[] scope/name/command/exitCode/duration
290
+ ```
291
+
292
+ ## 13. 扩展新覆盖率格式
293
+
294
+ 在 `src/coverage/` 新增 parser,输出统一的 `FileCoverage`:
295
+
296
+ 1. 解析报告;
297
+ 2. 规范化源码路径;
298
+ 3. 填充 lines/statements/functions/branches;
299
+ 4. 多报告合并;
300
+ 5. 在 `loadCoverage` 注册 format 和自动推断。
301
+
302
+ 必须保留“未覆盖单元 count=0”的记录。这是本工具能审计豁免的基础。
303
+
304
+ ## 14. 测试策略
305
+
306
+ - 单元:配置底线、glob、路径、LCOV/Istanbul 解析与合并、原因校验、注解解析、阈值边界。
307
+ - 集成:CLI 执行命令、陈旧报告、失败命令、JSON 纯度。
308
+ - 端到端:双 scope、100%/95% 边界、豁免、stale、过期、原生 ignore、漏报文件、配置排除、文件级豁免、报告产物。
309
+ - 当前覆盖 56 个测试(随代码变化)。
310
+
311
+ ## 15. 已知取舍
312
+
313
+ 1. **全量而非增量**:客户端 100% 对遗留项目可能很难。先通过带原因的排除缩小范围,再逐步扩大;不要用假豁免。
314
+ 2. **行级文本扫描**:实现简单、跨语言,但有字符串误报可能;换来零依赖和可移植性。
315
+ 3. **配置可信**:无法在工具内部防止有权限的人删掉门禁;CI + CODEOWNERS 负责。
316
+ 4. **覆盖率不等于正确性**:100% 行覆盖仍可能没有有效断言;需要测试评审。
317
+ 5. **LCOV 缺语句指标**:保持诚实,标记 N/A,而不是用行数冒充语句数。
318
+ 6. **只支持两种格式**:覆盖 JS/TS 主流栈和所有能产出 LCOV 的栈;XML 生态通过转换或 adapter 接入。
319
+
320
+ ## 16. 后续可做
321
+
322
+ - 基于 git diff 的“新增/修改行必须覆盖”模式;
323
+ - Cobertura / JaCoCo XML adapter;
324
+ - 并行执行 scope(当前顺序执行,便于日志和资源控制);
325
+ - 豁免审批记录(PR 链接、签名策略);
326
+ - 更完整的配置 JSON Schema 校验(当前是手写严格校验,不依赖 ajv);
327
+ - 覆盖率趋势历史(当前只保留最近一次报告)。
package/docs/setup.md ADDED
@@ -0,0 +1,165 @@
1
+ # 安装与自动配置
2
+
3
+ ## 在线安装
4
+
5
+ ### npm registry(推荐)
6
+
7
+ ```bash
8
+ npm install --save-dev covgate
9
+ npx --no-install covgate setup
10
+ ```
11
+
12
+ 包页面:[covgate](https://www.npmjs.com/package/covgate)。当前 latest 是 **0.2.1**。
13
+
14
+ > `0.0.0-stage` 是 npm 暂存发布产生的占位版本,不会被 `npm install covgate` 选中,可以忽略。
15
+
16
+ ### Gitee(源码 / 镜像)
17
+
18
+ ```bash
19
+ # HTTPS(公开仓库)
20
+ npm install --save-dev git+https://gitee.com/wxzzhj/covgate.git
21
+
22
+ # 或 SSH
23
+ npm install --save-dev git+ssh://git@gitee.com/wxzzhj/covgate.git
24
+
25
+ npx --no-install covgate setup
26
+ ```
27
+
28
+ ### 从本地目录安装
29
+
30
+ ```bash
31
+ npm install --save-dev file:D:/Projects/DSH默认工作区/covgate
32
+ npx --no-install covgate setup
33
+ ```
34
+
35
+ ## npm 11.19+ 的安装脚本策略
36
+
37
+ npm 11.19+ 默认不执行依赖的 `postinstall`,会显示类似:
38
+
39
+ ```text
40
+ npm warn install-scripts 1 package has install scripts not yet covered by allowScripts
41
+ npm warn install-scripts covgate@0.2.1 (postinstall: node bin/covgate-postinstall.js)
42
+ ```
43
+
44
+ 因此:
45
+
46
+ - **最可靠的方式**:安装后运行 `npx covgate setup`。它会检测测试框架;如果项目没有框架,会询问是否安装 Vitest;配置 AI 接入。
47
+ - **想使用安装时自动提示**:在 npm 允许脚本时,`postinstall` 会调用同一套 setup 流程。可以在安装后执行:
48
+
49
+ ```bash
50
+ npm approve-scripts covgate
51
+ npm rebuild covgate
52
+ ```
53
+
54
+ 或在项目 `package.json` 中按 npm 文档配置 `allowScripts`,然后重新安装。不同 npm 版本/安装来源的 allowScripts 键可能不同;`npx covgate setup` 不受此策略影响。
55
+ - CI 中设置 `CI=1` 或 `COVGATE_SKIP_SETUP=1` 会跳过自动配置,避免阻塞流水线。
56
+
57
+ ## setup 做什么
58
+
59
+ 1. 读取 `package.json`,检测包管理器(npm/pnpm/yarn/bun)和测试框架(Vitest/Jest/Playwright/Mocha/AVA/tap/Node test)。
60
+ 2. 没有测试框架时,交互询问是否安装 **Vitest + @vitest/coverage-v8**;选择是则执行对应包管理器安装命令。
61
+ 3. 添加 `npm scripts`:
62
+ - `coverage:gate`:`covgate run`
63
+ - `test:coverage`:Vitest/Jest 的 JSON + LCOV 覆盖率命令
64
+ 4. 生成 `covgate.config.json`(普通项目单 scope;Electron 自动生成 client + server;已存在则不覆盖):
65
+
66
+ ```json
67
+ {
68
+ "scopes": {
69
+ "server": {
70
+ "profile": "server",
71
+ "commands": [{ "run": "npm run test:coverage" }],
72
+ "coverage": { "paths": ["coverage/coverage-final.json"] },
73
+ "sources": { "include": ["src/**/*.{js,jsx,ts,tsx,mjs,cjs}"] }
74
+ }
75
+ }
76
+ }
77
+ ```
78
+
79
+ 5. 配置 AI 编码代理:
80
+ - `AGENTS.md` 幂等区块
81
+ - `scripts/covgate-agent-gate.sh` / `.ps1`(可接 Stop hook)
82
+ - `.github/workflows/covgate.yml`(如果存在 `.github/`)
83
+ - `.gitignore` 加入 `.covgate/`、`coverage/`
84
+ - 若已有 `CLAUDE.md`、`.cursor`、`.github/copilot-instructions.md`,同步写入对应说明。
85
+ 6. 输出下一步命令。
86
+
87
+ > **CovGate 不生成测试,也不补测试。** setup 只配置测试框架和门禁;本次行为变更的测试必须由项目自己补。没有测试时 `test:coverage` 会直接失败(已移除 `--passWithNoTests`),不会产生可蒙混过关的空覆盖率。
88
+
89
+ ## setup 选项
90
+
91
+ | 选项 | 作用 |
92
+ | --- | --- |
93
+ | `--yes, -y` | 无框架时直接安装 Vitest,使用默认配置 |
94
+ | `--no-install` | 不安装框架 |
95
+ | `--no-agent` | 跳过 AGENTS/Agent 脚本/CI |
96
+ | `--no-config` | 不生成 CovGate 配置 |
97
+ | `--profile <client\|server>` | 强制 scope 类型 |
98
+ | `--source <glob>` | 指定源码 glob |
99
+ | `--coverage <path>` | 指定报告路径 |
100
+ | `--test-command <cmd>` | 指定覆盖率测试命令 |
101
+ | `--force` | 覆盖已有配置/生成文件 |
102
+ | `--dry-run` | 只打印计划,不写文件 |
103
+ | `--json` | stdout 输出机器可读摘要 |
104
+
105
+ 示例:
106
+
107
+ ```bash
108
+ # 非交互(适合脚本/CI 初始化)
109
+ npx covgate setup --yes --json
110
+
111
+ # 只生成 AI 接入,不动测试
112
+ npx covgate setup --no-install --no-config
113
+
114
+ # 全栈项目先配置客户端,再手工添加 server scope
115
+ npx covgate setup --profile client --source "web/src/**/*.{ts,tsx}"
116
+ ```
117
+
118
+ ## 生成的 scope
119
+
120
+ 普通项目只生成一个 scope:
121
+
122
+ - 有源码目录时自动选择源码(优先 `src/`,其次 `app.js`、`app/`、`lib/`);
123
+ - 有根目录或 `src/index.html`、React/Vue/Svelte 等依赖、Electron 时选 `client`;
124
+ - 否则选 `server`。
125
+
126
+ Electron 项目会生成两个 scope:
127
+
128
+ - `client`(profile=client):`src/**` 等渲染进程源码,目标 100%;
129
+ - `server`(profile=server):`desktop/**` 主进程/预加载源码,目标 95%。
130
+
131
+ 两个 scope 共用同一个测试命令和覆盖率报告;CovGate 会按各自源码范围计算覆盖率。若使用 `--profile` 或 `--source` 手工覆盖,则只生成一个 scope。
132
+
133
+ 生成配置后请先运行:
134
+
135
+ ```bash
136
+ npx --no-install covgate validate
137
+ npx --no-install covgate run
138
+ ```
139
+
140
+ 门禁失败时,正确做法是让项目补覆盖该行为的真实测试;CovGate 不会替项目补测试,也不会接受空报告、`--passWithNoTests` 或降低门槛。
141
+
142
+ ## AI 代理如何真正做到“自动跑”
143
+
144
+ 生成的文件不等于自动触发。需要把它接到代理生命周期:
145
+
146
+ 1. `AGENTS.md` / `CLAUDE.md` 写完成条件;
147
+ 2. 支持 Stop hook 的代理调用 `scripts/covgate-agent-gate.ps1`(或 `.sh`);
148
+ 3. CI 里执行 `npx --no-install covgate run --json` 作为 required check。
149
+
150
+ 具体接法见 [AI 工作流](ai-workflow.md)。
151
+
152
+ ## 环境变量
153
+
154
+ | 变量 | 作用 |
155
+ | --- | --- |
156
+ | `COVGATE_SKIP_SETUP=1` | 安装时完全跳过 setup |
157
+ | `COVGATE_SETUP_ASSUME_YES=1` | 无人值守时自动接受 Vitest 安装(测试/CI 用) |
158
+ | `CI=1` | postinstall 跳过自动配置 |
159
+
160
+ ## 故障排查
161
+
162
+ - **没有弹出测试框架询问**:npm 11 默认阻止 install scripts。运行 `npx covgate setup`。
163
+ - **没有生成 CovGate 配置**:可能没有识别到源码目录或覆盖率命令。用 `--source`、`--test-command` 指定,或先安装框架。
164
+ - **Vitest 安装超时**:setup 对安装命令有 180 秒限制。检查网络/registry,手动安装 `vitest @vitest/coverage-v8` 后重跑 setup。
165
+ - **多 scope 项目**:setup 只生成一个;手工编辑或分目录 setup。