Skip to content

Commit 13310f4

Browse files
CodeCasterXcodexclaude
committed
fix(templates): preserve local test policy
- Register testing discipline as merged during updates - Keep project test commands in the local test skill - Cover upgrade behavior and the watch self-heal contract Co-Authored-By: Codex <noreply@openai.com> Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 94cb168 commit 13310f4

12 files changed

Lines changed: 137 additions & 13 deletions

File tree

.agents/.airc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"**/test-integration.*",
8080
"**/test.*",
8181
"**/upgrade-dependency.*",
82+
".agents/rules/testing-discipline.*",
8283
".agents/QUICKSTART.md",
8384
".agents/README.md",
8485
".agents/skills/post-release/SKILL.*",

.agents/rules/testing-discipline.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,32 +39,40 @@ assert.match(content, /^name: code-task$/m); // 正向断言已足够
3939
- **测试实现细节**:优先断言公开接口、产物、状态变化或错误结果;避免断言私有函数、内部调用顺序、临时数据结构。
4040
- **断言不充分**:断言必须锁定具体期望值;不要用"只要不抛异常""结果存在即可"替代对关键字段、数量和边界的验证。
4141

42-
## 覆盖率定位(信息层)
42+
## 项目级测试策略
43+
44+
仓库特定的命令、目录约定、覆盖率阈值、CI 集成和报告服务应记录在项目自己的测试文档中。新增测试前先读取该策略,并按测试的可观察范围与运行成本选择对应层级。
45+
46+
如果项目没有定义分层测试套件,RED 与 GREEN 验证都使用项目的完整测试命令。不要在无关改动中自行引入测试层级或覆盖率门禁。
47+
48+
### agent-infra 项目策略
49+
50+
#### 覆盖率定位(信息层)
4351

4452
> CI 中通过 `node --test --experimental-test-coverage` 输出覆盖率,仅作为"哪些文件被测试薄弱"的提示,**不作为 merge gate**
4553
46-
### 本地运行
54+
##### 本地运行
4755

4856
```bash
4957
npm run test:coverage
5058
```
5159

5260
stdout 末尾会打印按文件粒度的行 / 分支 / 函数覆盖率以及未覆盖行号。
5361

54-
### CI 展示
62+
##### CI 展示
5563

5664
`.github/workflows/unit-tests.yml` 在 ubuntu-latest 分片上把覆盖率块写入 GitHub Actions 的 step summary(PR Checks 页可见)。Windows / macOS 分片不重复输出。
5765

5866
README 顶部的 Codecov 徽章由 `.github/workflows/unit-tests.yml` 在 ubuntu-latest 分片上传 `coverage.lcov` 后由 Codecov 生成。
5967

60-
### 边界
68+
##### 边界
6169

6270
- **不设置百分比阈值**`--test-coverage-lines/branches/functions` 等阈值参数禁止加入;Goodhart's law 提醒我们一旦把覆盖率作为指标,开发者会写"覆盖率友好但行为弱"的测试。
6371
- **第三方服务仅用于徽章**:已接入 Codecov 托管 README 覆盖率徽章,但通过根 `codecov.yml` 显式关闭其 project/patch status check 与 PR 评论——Codecov 在本项目只展示数字,不参与 merge 决策。不接入 coveralls 等其他服务。
6472
- **不区分 tier**:当前只对 full `test` tier 输出覆盖率;smoke / core tier 的覆盖率没有独立价值。
6573
- **不阻塞 PR**:CI 步骤 `continue-on-error: true`,即便覆盖率采集失败也不影响 merge。
6674

67-
### 新测试该放哪一层
75+
#### 新测试该放哪一层
6876

6977
测试文件放入哪一层决定它会被哪些 npm script 自动执行:
7078

@@ -74,7 +82,7 @@ README 顶部的 Codecov 徽章由 `.github/workflows/unit-tests.yml` 在 ubuntu
7482

7583
模块继续作为第二级目录(如 `cli``core``scripts``templates`)。共享 helper 和 fixtures 保持在 `tests/helpers/``tests/helpers.ts``tests/fixtures/`,不要放入任一 tier。
7684

77-
### 与"测试 tier 覆盖"的关系
85+
#### 与"测试 tier 覆盖"的关系
7886

7987
注意区分两个概念:
8088

.agents/skills/test/SKILL.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@ description: >
1111

1212
## 1. 编译 / 类型检查
1313

14-
本项目由 Node.js CLI 和模板文件组成,无需编译。跳过此步骤。
14+
```bash
15+
npm run typecheck
16+
```
17+
18+
项目测试脚本会先运行 `npm run build`,因此单独执行类型检查后无需在本步骤重复构建。
1519

1620
## 2. 运行单元测试(按层级选择)
1721

18-
本项目把测试分为三层,按场景选择运行命令;新增测试文件默认归入 **full**,确认足够快且足够核心后,再上调到 core 或 smoke。
22+
三层测试是反馈速度优化;本项目按测试的可观察范围与运行成本选择对应层级。新增测试文件默认归入 **full**,确认足够快且足够核心后,再上调到 core 或 smoke。
1923

2024
### smoke(目标 <5s)
2125

.agents/skills/update-agent-infra/scripts/sync-templates.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ const DEFAULTS = {
7373
"**/test-integration.*",
7474
"**/test.*",
7575
"**/upgrade-dependency.*",
76+
".agents/rules/testing-discipline.*",
7677
".agents/skills/post-release/SKILL.*",
7778
".agents/skills/release/SKILL.*",
7879
".agents/skills/test-integration/SKILL.*",

.agents/skills/watch-pr/reference/monitor-and-heal.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212

1313
## 自愈决策树
1414

15+
```text
16+
# self-heal-test-command-contract
17+
primary: failing-job-command
18+
fallback-source: project-test-skill
19+
unknown: help
20+
```
21+
1522
对每个失败 check,按下列顺序判定「自愈」还是「求助」:
1623

1724
1. **能否定位到对应的 CI run**(按规则的「解析失败 run id」)?否 → 求助。
@@ -22,7 +29,7 @@
2229
4. 满足「可定位 + 代码层 + 未达上限」时执行一次自愈:
2330
- 自愈前先 `git status -s` 记录当前工作树,确保后续只纳入与本次失败相关的改动。
2431
- 在本地按日志定位并最小化修复(只动与该失败相关的代码 / 测试 / 配置)。
25-
- 运行对应测试:优先失败 job 对应的本地命令;仓库统一回退 `npm run test:core`**测试通过前不得提交或推送。**
32+
- 运行对应测试:优先失败 job 对应的本地命令;否则读取项目 `test` skill,选择其中声明的 core 或 full 验证命令。两者均未知时进入求助出口**测试通过前不得提交或推送。**
2633
- 测试通过后发布修复:按 `.agents/rules/commit-and-pr.md` 仅暂存本次相关文件(`git add <相关路径>`,避免 `git add -A` 卷入无关改动)→ 创建修复 commit(`git commit`)→ `git push` 到当前 PR 分支。
2734
- 记录本次修复的 commit SHA,修复计数 +1,回到 SKILL 步骤 2 重新监控。
2835
- 绝不执行与失败无关的「顺手优化」;不放宽 / 跳过失败的断言来「修绿」。

lib/defaults.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"**/test-integration.*",
5353
"**/test.*",
5454
"**/upgrade-dependency.*",
55+
".agents/rules/testing-discipline.*",
5556
".agents/skills/post-release/SKILL.*",
5657
".agents/skills/release/SKILL.*",
5758
".agents/skills/test-integration/SKILL.*",

templates/.agents/skills/update-agent-infra/scripts/sync-templates.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ const DEFAULTS = {
7373
"**/test-integration.*",
7474
"**/test.*",
7575
"**/upgrade-dependency.*",
76+
".agents/rules/testing-discipline.*",
7677
".agents/skills/post-release/SKILL.*",
7778
".agents/skills/release/SKILL.*",
7879
".agents/skills/test-integration/SKILL.*",

templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ After running the watch command from `.agents/rules/pr-checks-commands.md`, clas
1212

1313
## Self-Heal Decision Tree
1414

15+
```text
16+
# self-heal-test-command-contract
17+
primary: failing-job-command
18+
fallback-source: project-test-skill
19+
unknown: help
20+
```
21+
1522
For each failing check, decide "self-heal" vs "ask for help" in this order:
1623

1724
1. **Can the corresponding CI run be located** (per the rule's "Resolve a failing run id")? No → ask for help.
@@ -22,7 +29,7 @@ For each failing check, decide "self-heal" vs "ask for help" in this order:
2229
4. When "locatable + code layer + under cap" holds, perform one self-heal:
2330
- Before fixing, run `git status -s` to record the working tree and ensure only changes related to this failure are included.
2431
- Locate and make a minimal fix per the logs (touch only code / tests / config related to that failure).
25-
- Run the relevant tests: prefer the local command for the failing job; otherwise use the project's declared full test command. If neither command is known, ask for help. **Do not commit or push before tests pass.**
32+
- Run the relevant tests: prefer the local command for the failing job; otherwise read the project `test` skill and select its declared core or full validation command. If neither command is known, ask for help. **Do not commit or push before tests pass.**
2633
- After tests pass, publish the fix: per `.agents/rules/commit-and-pr.md` stage only the related files (`git add <paths>`, avoid `git add -A` pulling in unrelated changes) → create the fix commit (`git commit`) → `git push` to the current PR branch.
2734
- Record the fix commit SHA, increment the fix count, and return to SKILL step 2 to re-watch.
2835
- Never make unrelated "drive-by" optimizations; never loosen / skip the failing assertion to "make it green".

templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212

1313
## 自愈决策树
1414

15+
```text
16+
# self-heal-test-command-contract
17+
primary: failing-job-command
18+
fallback-source: project-test-skill
19+
unknown: help
20+
```
21+
1522
对每个失败 check,按下列顺序判定「自愈」还是「求助」:
1623

1724
1. **能否定位到对应的 CI run**(按规则的「解析失败 run id」)?否 → 求助。
@@ -22,7 +29,7 @@
2229
4. 满足「可定位 + 代码层 + 未达上限」时执行一次自愈:
2330
- 自愈前先 `git status -s` 记录当前工作树,确保后续只纳入与本次失败相关的改动。
2431
- 在本地按日志定位并最小化修复(只动与该失败相关的代码 / 测试 / 配置)。
25-
- 运行对应测试:优先失败 job 对应的本地命令;否则使用项目已声明的完整测试命令。两者均未知时进入求助出口。**测试通过前不得提交或推送。**
32+
- 运行对应测试:优先失败 job 对应的本地命令;否则读取项目 `test` skill,选择其中声明的 core 或 full 验证命令。两者均未知时进入求助出口。**测试通过前不得提交或推送。**
2633
- 测试通过后发布修复:按 `.agents/rules/commit-and-pr.md` 仅暂存本次相关文件(`git add <相关路径>`,避免 `git add -A` 卷入无关改动)→ 创建修复 commit(`git commit`)→ `git push` 到当前 PR 分支。
2734
- 记录本次修复的 commit SHA,修复计数 +1,回到 SKILL 步骤 2 重新监控。
2835
- 绝不执行与失败无关的「顺手优化」;不放宽 / 跳过失败的断言来「修绿」。

tests/unit/cli/sync-templates.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,3 +1099,60 @@ test("syncTemplates reports shared pre-commit as a merged pending file", async (
10991099
fs.rmSync(tmpDir, { recursive: true, force: true });
11001100
}
11011101
});
1102+
1103+
test("syncTemplates preserves project testing discipline as a merged file", async () => {
1104+
const originalExecSync = childProcess.execSync;
1105+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "ai-collab-sync-testing-discipline-"));
1106+
1107+
try {
1108+
const projectRoot = path.join(tmpDir, "project");
1109+
const { templateRoot } = createTemplateInstall(tmpDir);
1110+
const target = ".agents/rules/testing-discipline.md";
1111+
const localPolicy = "Project-specific testing policy\n";
1112+
1113+
fs.mkdirSync(projectRoot, { recursive: true });
1114+
writeFile(templateRoot, ".agents/rules/testing-discipline.en.md", "Common testing discipline\n");
1115+
writeFile(templateRoot, ".agents/rules/testing-discipline.zh-CN.md", "通用测试纪律\n");
1116+
writeFile(projectRoot, target, localPolicy);
1117+
writeJson(projectRoot, ".agents/.airc.json", {
1118+
project: "demo",
1119+
org: "acme",
1120+
language: "zh-CN",
1121+
platform: { type: "github" },
1122+
files: {
1123+
managed: [".agents/rules/"],
1124+
merged: [],
1125+
ejected: []
1126+
}
1127+
});
1128+
1129+
childProcess.execSync = (command) => {
1130+
if (command === "git remote get-url origin") {
1131+
throw new Error("not a git repo");
1132+
}
1133+
throw new Error(`Unexpected command: ${command}`);
1134+
};
1135+
1136+
const { syncTemplates } = await loadFreshEsm<SyncTemplatesModule>(".agents/skills/update-agent-infra/scripts/sync-templates.js");
1137+
const firstReport = syncTemplates(projectRoot, templateRoot);
1138+
const secondReport = syncTemplates(projectRoot, templateRoot);
1139+
1140+
assert.ok(
1141+
firstReport.registryAdded.some(
1142+
(entry) => entry.entry === ".agents/rules/testing-discipline.*" && entry.list === "merged"
1143+
)
1144+
);
1145+
assert.equal(fs.readFileSync(path.join(projectRoot, target), "utf8"), localPolicy);
1146+
assert.ok(firstReport.managed.skippedMerged.includes(target));
1147+
assert.ok(secondReport.managed.skippedMerged.includes(target));
1148+
assert.deepEqual(
1149+
firstReport.merged.pending.filter((entry) => entry.target === target),
1150+
[{ target, template: ".agents/rules/testing-discipline.zh-CN.md" }]
1151+
);
1152+
assert.deepEqual(firstReport.managed.written, []);
1153+
assert.deepEqual(secondReport.managed.written, []);
1154+
} finally {
1155+
childProcess.execSync = originalExecSync;
1156+
fs.rmSync(tmpDir, { recursive: true, force: true });
1157+
}
1158+
});

0 commit comments

Comments
 (0)