pdlc-test-setup · git:20260801.aec6ed7 · 2026-08-01 · sha256 487bbffcc976223f
pdlc-test-setup git:20260801.aec6ed7A
Immutable. This exact content is served forever at /api/v1/blob/487bbffcc976223f.
---
name: pdlc-test-setup
description: 立测试地基(探测技术栈 → 验证并生成 test-commands.yml → 脚手架测试目录 → 接本地钩子)
argument-hint: [项目目录] [--refresh] [--autonomous]
allowed-tools: Read, Write, Edit, Glob, Grep, Bash
layer: 3
stage: engineering
artifact_type: surface
produces:
- docs/00_standards/test-commands.yml
requires: []
next_step: null
terminal_state: null
recommended_model: sonnet
recommended_effort: medium
---
# 立测试地基
给项目一键立起「客观 check」的地基:**探测技术栈 → 逐条验证命令真能跑 → 写 `docs/00_standards/test-commands.yml` → 脚手架测试目录 → 接本地钩子**。
<!-- @include templates/prompts/iron-law.md -->
<!-- @include templates/prompts/noninteractive.md -->
## 为什么需要它
pdlc 的命门是「`checks` 只认命令退出码,绝不用模型自评」——`pdlc-tdd` / `pdlc-implement` / `pdlc-review`
与外层循环全都从 `docs/00_standards/test-commands.yml` 取命令。但**在此之前没有任何东西帮你把这个文件立起来**,
没有它,整条客观化链路就是空的。本命令把这块地基变成 turnkey。
> ⛔ **本命令最重要的一条纪律**:**写进 `test-commands.yml` 的每条命令,必须先被真跑过一次、亲眼看到退出码。**
> 一条"看起来对但跑不了"的命令**比留空更坏**——它会让下游每个阶段都拿到假的 `checks`,
> 而整个 pdlc 的可信度正建立在这些 checks 是真的之上。**猜出来的命令一律不写。**
## `--refresh`:让这份 yml 跟上项目的演进
项目会漂移——脚本改名、runner 换代、工具从依赖里移除、子项目增删。这份 yml 一旦过期,
下游所有 `checks` 就开始失真。`--refresh` 是**重新探测 + 给出 diff**,而不是从头再来:
1. **逐条复跑现有命令**,按 `check-commands.md` 的三态判定谁还活着(跑不通 ≠ 检查没过)。
2. **重新探测候选**,与现状对比,得出变更清单。
3. **按方向决定自不自动**(这条是安全底线):
<!-- @include templates/prompts/check-commands.md -->
4. **不管自动与否,全部变更都要在报告里列出**:改了什么、为什么、依据是哪次真跑的退出码。
自动应用的也要能一眼看出来,便于事后 `git diff` 复核。
> ⚠️ **最危险的"自动修复"是把坏掉的 check 留空**——闸门瞬间松了,报告还是绿的。
> 所以留空 / 删除 / 降阈值一律走人工确认,`--autonomous` 也不豁免。
**从哪来的过期信号**:不用你盯着——`pdlc-tdd` / `pdlc-implement` / `pdlc-review` 每次跑 check
时遇到"命令跑不了"都会提示,`/pdlc-quality` 的报告里还有专门的「配置健康度」一节。
看到提示再来 `--refresh` 即可。
## 段一:探测与验证
### 1.1 技术栈探测
扫描特征文件,识别语言 / 包管理器 / 测试框架:
| 特征文件 | 栈 | 典型 unit | 典型 coverage | 典型 lint |
|---|---|---|---|---|
| `Cargo.toml` | Rust | `cargo test` | `cargo llvm-cov --fail-under-lines <阈值>` | `cargo clippy -- -D warnings` |
| `package.json` | Node | `pnpm test` / `npm test` | `vitest run --coverage.thresholds.lines=<阈值>` | `npx eslint .` |
| `pyproject.toml` / `requirements.txt` | Python | `pytest` | `pytest --cov --cov-fail-under=<阈值>` | `ruff check .` |
| `go.mod` | Go | `go test ./...` | `go test ./... -cover` | `golangci-lint run` |
| `pom.xml` / `build.gradle` | JVM | `mvn test` / `./gradlew test` | jacoco check | `mvn checkstyle:check` |
| 仅 `*.sh` | Shell | 项目自有测试脚本 | —(通常无) | `shellcheck <文件>` |
**多语言 / monorepo**:逐个子项目探测;`test-commands.yml` 只能有一组命令,所以要么用能覆盖全仓的聚合命令
(如 `pnpm -r test`),要么与用户确认以哪个子项目为准。**探测不到唯一答案时不要自己拍板**(见 §1.3)。
### 1.2 逐条验证(不可跳过)
对每个候选命令**真的跑一次**,按退出码归类:
| 观察到的结果 | 结论 | 动作 |
|---|---|---|
| 退出码 0 | 命令可用且当前通过 | **采纳** |
| 退出码非 0、非 127,且输出像测试/lint 报告 | 命令可用,只是当前有失败项 | **采纳**(地基是"命令能跑",不是"当前全绿") |
| 退出码 127 / `command not found` / 工具未安装 | 命令不可用 | **留空**,在报告里写明缺什么 |
| 无对应配置(如没配覆盖率工具) | 该项本项目暂无 | **留空** |
| 命令挂起 / 需要交互 | 不适合做自动 check | **留空**,报告里说明 |
> ⚠️ **留空是合法且诚实的结果**,与状态机里「没有检查命令可跑的阶段 → `checks: {}` 留空」同一条纪律。
> 宁可空着并在报告里提示怎么补,也不要写一条没验证过的命令。
**覆盖率达标线写死在命令参数里**(如 `--cov-fail-under=85`),不做二次解释——这样"达标"就是退出码本身,
不需要任何一方去解析百分比数字。默认阈值 **85%**;项目已有更高要求则沿用已有。
### 1.3 需要人拍板的点(`--autonomous` 下 block,不猜)
以下属判断题而非流程题,**不得自动选**,须写明原因交还人类:
- 探测到**多个**并列候选(如同时有 `jest` 和 `vitest` 配置),无法判定以哪个为准
- **零候选**(项目还没有任何测试框架)——装哪个框架是技术选型,必须人定
- monorepo 里以哪个子项目 / 哪条聚合命令为准
- 覆盖率阈值定多少(若项目无既有约定)
探测到**唯一**候选且验证通过 → 属流程性确认,`--autonomous` 下自动采纳并在报告里留痕。
## 段二:落地
### 2.1 写 `docs/00_standards/test-commands.yml`
以 `templates/test-commands-template.yml` 为骨架。**这是 surface 型产物**——就地编辑,不做 `-v2` 累积。
- **文件已存在** → **不覆盖**。改为逐条校验现有命令是否仍能跑:
- 仍能跑 → 保持原样(用户的选择优先于探测结果)
- 已跑不通(工具改名 / 脚本删了)→ 报告里列出,**建议**改法,等人确认
- 缺失的项(空字符串)→ 若这次探测到可用命令,提议补上
- **文件不存在** → 用本次验证通过的命令生成;未验证通过的项留空字符串。
### 2.2 脚手架测试目录(已有则不动)
按栈惯例建空目录 + 一个占位说明,**不生成业务测试用例**:
- Rust `tests/`、Node `src/__tests__/` 或 `tests/`、Python `tests/`、Go 同包 `*_test.go`、JVM `src/test/java/`
- **跟随项目既有布局**,不新造平行目录。守卫侧的定位规则是布局无关的
(见 `templates/prompts/test-location.md`),所以这里不必迁就任何预设结构
### 2.3 接本地钩子(不进 CI)
在**本地** git 钩子里跑基础 check(`husky` / `lefthook` / `pre-commit` / 原生 `.git/hooks`,按项目已有的来):
- **pre-commit**:`lint`(快,秒级)
- **pre-push**:`unit`(+ `coverage` 若已配)
> **不新建 CI workflow**:这些 check 本地秒级可得,放 CI 只会让每次迭代都烧配额。
> 已有 CI 的项目也不改它的触发条件——那需要项目所有者单独授权。
### 2.4 老项目:可选的轻量底线回填
仅当用户要求:为**当前覆盖率最低**的若干核心模块补特征化测试(characterization test,锁住现有行为),
把覆盖率抬到阈值线。**这不是补齐测试**,只是让地基能立住。深度用例仍走 `/pdlc-tdd`。
## 段三:自检(强制)
<!-- @include templates/prompts/self-audit.md -->
### 自检清单(必须全部检查)
- [ ] `test-commands.yml` 里**每一条非空命令**,都在本次会话中被真跑过、看到过退出码
- [ ] **收尾复跑一遍**:从写好的 yml 里逐条读命令再跑一次,确认与写入时的结论一致(防止写错路径 / 引号)
- [ ] 留空的项,报告里都写明了「为什么空」和「怎么补」
- [ ] 覆盖率阈值已写死在命令参数里,不依赖任何一方解析百分比
- [ ] 测试目录跟随项目既有布局;若写了 `test-commands.yml`,其 `unit` 命令能定位到这些测试
- [ ] 钩子是**本地**的,没有新建或修改任何 CI workflow
- [ ] 已存在的 `test-commands.yml` 没有被静默覆盖
- [ ] (`--refresh` 时)所有变更已在报告里列出;**没有任何"让闸门变松"的改动被自动应用**
## 段四:修复(单次,不递归)
<!-- @include templates/prompts/loop-prevention.md -->
- 复跑发现某条命令与写入时结论不一致 → 修正或改为留空
- 无法自动修复 → 记入报告,交还人类
## 段五:交接
<!-- @include templates/prompts/handoff.md -->
**本命令的 handoff 输出:**
```
✅ 测试地基已立:docs/00_standards/test-commands.yml
unit : <命令> (退出码 <N>,已验证)
coverage : <命令 | 留空> (<验证结论 | 为什么空>)
lint : <命令> (退出码 <N>,已验证)
e2e : <命令 | 留空> (<验证结论 | 为什么空>)
🪝 本地钩子:pre-commit → lint · pre-push → unit
📁 测试目录:<路径列表>
⚠️ 待人工:<留空项怎么补 / 需要拍板的选型>
👉 下一步:/pdlc-tdd <功能描述> —— 本命令只立地基,深度用例走 TDD
```
## 诚实边界(务必如实说明,不要夸大)
- 本命令**只立地基 + 可选补底线**,**不生成完整测试套件**。AI 生成的测试容易浅、容易只测 happy path,
真正的用例设计仍走 `/pdlc-tdd`(测试先行、红灯门)。
- 留空的项就是**当前没有**,不要为了让输出好看而填一条没验证过的命令。
- 覆盖率阈值只是一条线,**过线不等于测得好**——它挡的是"几乎没测",不保证用例有效。
---
**目标项目**: $ARGUMENTS