Claude Skill

pdlc-tdd

TDD 测试先行(按设计文档生成失败的测试用例)

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download kanfu-panda-pdlc-skills-skills_pdlc-tdd-3cd2f02.zip · 13 KB
Part of kanfu-panda/pdlc-skills — 36 skills

Install

skills CLI npx skills add https://github.com/kanfu-panda/pdlc-skills/tree/main/skills/pdlc-tdd
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install kanfu-panda-pdlc-skills@llmmart
Git git clone https://github.com/kanfu-panda/pdlc-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole kanfu-panda/pdlc-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

TDD 测试先行

IRON LAW · 不可违反的硬门禁

以下规则为不可协商的执行约束:

  1. 文件必须落盘:所有带编号(功能ID / 缺陷ID)的文档,必须作为实际文件写入磁盘,不可仅在对话中输出。
  2. 阶段必须落章:每个阶段完成后必须在状态机 docs/.pdlc-state/<feature-id>.json 追加 history,不可跳过。
  3. 测试必须存在:进入 /pdlc-implement 前,对应测试必须存在且处于红灯状态。违反则中止。
  4. 自检必须执行:段二自检为强制步骤,不得以"已经很好了"为由跳过。
  5. 防循环:段三修复为单次,不递归。无法自动修复的问题记录到报告,继续往下走。
  6. 状态必推进:成功执行某 phase 后 current_stage 必须变更。收尾时若发现 current_stage 未推进,视为失败并报错,不得静默返回(防止外层循环拿滞后的状态空转烧额度)。唯一例外:命中人工点主动 block 时,current_stage 保持不变但必须写 last_phase_result.ok=false + blocked_reason

违反任一条 = 立即中止当前命令,输出违规详情,等待人工介入。

非交互模式(--autonomous

若本命令的参数含 --autonomous,本命令进入无人值守模式,按以下规则处理原本需要人应答的交互点。参数是唯一真源:不带 --autonomous 即为交互模式,一切照旧正常询问用户;绝不回读状态机 run_mode 兜底(「掉出 autonomous」是安全的失败方向)。

  1. 流程性确认(如「测试已绿是否继续」「是否覆盖已有文件」)→ 不询问,按预设默认前进,并把决策追加到状态机 history[].auto_decisions[]
    { "point": "<确认点描述>", "chose": "<所选默认>", "at": "<ISO 8601>" }
    
  2. 真需人判断(PRD 关键取舍、评审「需人工确认」项、真实循环依赖等无法安全默认的点)→ 不猜
    • current_stage 保持不变(不推进)
    • last_phase_result.ok = falseblocked_reason = "<原因>"
    • 末行输出哨兵:<<<PDLC blocked reason="<原因>">>>
    • 立即结束命令,交还人类
  3. 破坏性操作(发布 / 部署 / 打 tag / 触发 CI / DROP / force-push 等不可逆·外发操作)→ --autonomous 无效,仍必须人工显式确认。
  4. 顺手的 sidecar 产物(如缺失时创建 CHANGELOG.md、补全文档 PDLC-TRACE 的创建时间等本阶段职责内、可安全默认的辅助改动)→ 视为流程性默认,直接做并记入 auto_decisions[];这类改动不新增外部副作用,不属破坏性操作。

进入 autonomous 模式时,在状态机顶层写 run_mode: "autonomous" 仅供留痕(复盘区分人工 vs 循环产出)。

根据设计文档,先编写测试用例,再实现代码。严格遵循 TDD 工作流。

PDLC 前置检查(必须执行,不可跳过)

  1. 从用户输入中提取功能名称关键词
  2. docs/02_design/ 的子目录(api/、architecture/、database/、ui-ux/)下搜索包含该关键词的设计文档
    • 匹配新格式:F<日期>-<编号>-*<关键词>*-<类型>.md
    • 匹配旧格式:YYYYMMDD-*<关键词>*-<类型>.md
    • 同时检查文件内容中是否包含该关键词
  3. 未找到任何设计文档 → 输出以下信息后立即停止,不继续执行
    ⛔ PDLC 守卫:未找到与「<功能名>」相关的设计文档(API/架构/数据库/UI 任一)。
    测试用例必须基于已有的设计文档。请先运行:
    👉 /pdlc-design <设计目标>
    
  4. 找到 → 提取功能ID(如 F20260326-090000),读取设计文档内容,继续执行

工作流程

  1. 阅读设计文档: 阅读找到的设计文档,全面理解接口/架构/数据模型
  2. 阅读编码规范: 阅读 docs/00_standards/coding/ 目录了解编码规范(未命中 → 提示 consider /pdlc-standard add coding/<topic>
  3. 编写测试计划: 在 docs/04_testing/unit-tests/ 下创建测试计划文档
    • 使用模板: 本 skill 目录下的 assets/test-plan-template.md
    • 文件名格式: <功能ID>-<功能名>-test-plan.md(如 F20260326-090000-user-auth-test-plan.md
    • 文档顶部必须包含 PDLC 追溯头
      <!-- PDLC-TRACE -->
      <!-- 功能ID: F20260326-090000 -->
      <!-- 功能名称: user-auth -->
      <!-- 阶段: 测试 -->
      <!-- 前置文档: docs/02_design/api/F20260326-090000-user-auth-api.md -->
      
  4. 编写测试代码: 写到项目既有的测试布局里,按下面的规则定位; 不要为迎合某种预设结构新造一套平行的测试目录。

测试代码在哪(布局无关的定位规则)

⚠️ 这条规则的要害:红灯守卫必须区分「项目没有测试」和「测试不在我预期的位置」。 前者才该拦;后者拦了就是误伤——真实项目的测试布局千差万别(单体 backend/tests/、 根级 tests/、Go 同包 *_test.go、Node 与源码同目录的 *.test.tsx…), 按一份写死的路径清单去找、找不到就拦,会让 pdlc 在大量正常项目上直接卡死。

核心原则:优先问 runner,其次翻文件。 测试框架自己最清楚有哪些测试—— 让它报比我们去猜文件位置准得多,也和 pdlc「信退出码、不信目视检查」的哲学一致。 尤其测试写在源文件里的语言(见下),翻文件根本找不到。

按下列顺序定位,命中即停

1. 项目自己的声明 + 向 runner 查询(最高优先级)

若存在 docs/00_standards/test-commands.yml,它的 unit / e2e 命令就是权威—— 项目已经明确告诉你测试怎么跑。用它去问 runner,而不是去翻目录:

runner 列出全部测试 只查某功能相关
cargo (Rust) cargo test -- --list cargo test <关键词> -- --list
pytest pytest --collect-only -q pytest --collect-only -q -k <关键词>
go test go test -list '.*' ./... go test -list '<关键词>' ./...
vitest / jest npx vitest list / --listTests npx vitest list -t <关键词>
gradle / maven --tests '*' 干跑 --tests '*<关键词>*'

查询结果为空 = 该功能没有测试(这是行为证据,比"我没找到文件"可靠得多)。

2. 测试写在源文件里的语言(必须靠内容匹配,文件名扫描无效)⭐

这类语言没有独立测试文件,只能按代码内标记搜:

语言 / 框架 in-source 测试标记
Rust #[cfg(test)]#[test]mod tests
Vitest(in-source testing) import.meta.vitest
Python doctest docstring 里的 >>>
Elixir doctest @doc 里的 iex>
Go(同包但独立文件) *_test.go + func Test

⚠️ Rust 尤其要注意:单元测试几乎总在源文件的 #[cfg(test)] mod tests 里, tests/ 目录按 Cargo 约定只放集成测试。所以「tests/ 目录不存在」在 Rust 项目里 完全不能推出「没有单元测试」——照文件清单判红会稳定误伤所有 Rust 项目。

3. 常见布局约定(按项目实际技术栈挑,不要全试)

生态 常见测试位置
Python tests/backend/tests/test/、与源码同目录的 test_*.py
Node / TS tests/__tests__/src/**/__tests__/、与源码同目录的 *.test.ts(x) / *.spec.ts(x)
Rust 源文件内 #[cfg(test)](单测)+ tests/(集成测试)
Go 与源码同包的 *_test.go
JVM src/test/java/src/test/kotlin/
Ruby spec/test/
微服务 / 单体仓 上述任一可能出现在 backend/backend/services/<名>/frontend/<应用>/ 之下

4. 文件名兜底扫描

前三步都没命中时,按文件名模式全仓搜(*test* / *spec*,排除 node_modules.venvvendordistbuildtarget.git),再按功能关键词筛。

5. 判定

  • 任一步找到相关测试 → 通过,进入下一步(pdlc-implement 还需确认红灯)
  • 四步走完确实找不到任何测试 → 这才是真红灯,按各命令的守卫规则中止
  • 找到测试但与本功能无关 → 按「本功能无测试」处理(同样是真红灯),但报告里要说明 「项目有测试,只是没覆盖本功能」,别让用户以为项目裸奔
  • 无法判定(如 runner 装不上、语言不认识)→ 不要默认放行,也不要假装找到了: 如实报「无法确认本功能是否有测试」并交还人类。「我判断不了」绝不等于「没问题」。

写测试时(pdlc-tdd)同样按本规则决定写到哪:跟随项目既有布局与惯例—— Rust 单测就写进源文件的 #[cfg(test)] mod tests不要为了迎合某种预设结构 新造一套平行的测试目录。

  1. 测试计划自审与自动修复(编写完成后、运行前执行,不可跳过):

    • 重新阅读测试计划和测试代码,对照设计文档和 PRD 逐项检查以下质量门禁:

    验收标准覆盖度

    • PRD 中每条验收标准是否至少有一个对应的测试用例
    • 设计文档中每个接口是否至少有正常流程 + 异常流程的测试

    场景完备性

    • 正常流程:核心业务路径是否全部覆盖
    • 边界条件:空值/null、空字符串、最大值/最小值、零值、超长输入
    • 异常场景:无权限、资源不存在(404)、重复操作(409)、参数校验失败(400)
    • 并发场景:是否考虑了同时操作的冲突(如适用)
    • 幂等性:重复提交同一请求是否有对应测试(如适用)

    测试质量

    • 测试方法命名是否清晰描述场景(如 should_return_404_when_user_not_found
    • 每个测试是否只验证一个行为(单一断言原则)
    • 测试数据是否有意义(非 test1abc123 等无意义数据)

    自动修复

    • 缺失的验收标准测试:自动补充对应的测试用例骨架
    • 缺失的边界条件测试:自动添加空值、超长输入、类型错误等测试
    • 缺失的异常场景测试:根据 API 错误码自动补充 401/403/404/409 等场景测试
    • 命名不规范的测试方法:自动重命名为描述性命名
    • 修复后在测试计划文档末尾追加审查记录:
      ## 自审记录
      - 审查时间:<ISO 8601>
      - 对照 PRD 验收标准:X 条,已覆盖:X 条
      - 对照 API 接口:X 个,已覆盖:X 个
      - 发现问题:X 项
      - 自动修复:X 项
      - 修复明细:
        - [已修复] <问题描述>
      
  2. 确认测试失败: 运行测试确认全部失败(红灯)。运行命令取自 docs/00_standards/test-commands.ymlunit(不存在则回退项目约定)。收尾写 last_phase_result.checks = { "red_verified": true }(红灯已由真跑退出码验证,非模型自评)

  3. 实现代码: 编写最少量的代码使测试通过

  4. 重构: 在测试通过的前提下优化代码

要求

🌐 Output language for generated artifacts

All generated artifacts (PRDs, design docs, code comments, review reports, test plans, deployment manuals, changelog entries, etc.) follow this policy:

  1. Default — match the conversation language exactly:

    • 用户用中文与 Claude 对话 → 产中文文档、中文代码注释、中文报告
    • User talks to Claude in English → produce English artifacts
    • User talks in another language → produce artifacts in that language
    • Never silently default to a fixed language regardless of the user's input.
  2. Explicit override always wins: when the user specifies a language for an artifact (e.g. "write the PRD in English", "用英文写 API 设计文档", "output the deploy doc in Japanese"), use that language for that artifact, regardless of conversation language.

  3. Mixed-language requirements: if the user wants some artifacts in one language and others in a different language (common: Chinese PRD + English API docs for partners), honour each per-artifact instruction.

  4. Uncertain: if you cannot reliably detect the conversation language, ask once before producing the first artifact.

This policy applies to content (prose, comments, headings). It does not override technical conventions like English variable names, English git commit subjects, or English error codes when the project's conventions require them.

  • 测试用例必须覆盖:正常流程、边界条件、异常场景
  • 测试方法命名清晰描述测试场景
  • 单元测试覆盖率:覆盖率达标线以项目配置为准:优先取 docs/00_standards/test-commands.yml 的 coverage 命令阈值参数(那才是强制点,退出码即判定),其次 quality-targets.yml;两者都没有时按 >= 80% 兜底。

目标功能: $ARGUMENTS

跑 check 命令:退出码的三态语义

命令取自 docs/00_standards/test-commands.yml(唯一真源)。逐条真跑,按退出码分三态—— 不是两态。这是 IRON LAW「checks 只认客观事实」在执行层的落法:

观察到的 含义 写进 checks
退出码 0 通过 对应键 = true
退出码非 0(命令跑起来了,只是没过) 未通过 对应键 = false
退出码 127 / command not found / 脚本文件不存在 / 该项为空字符串 无法判定 省略该键,或写 null——绝不能是 false

唯一的红线是不许写 false:那是会误导人的虚报——它说的是"检查失败了", 于是有人去查代码,但真正的问题是配置过期,代码可能完全没毛病。

省略键与 null 等价,两种都可以:对消费方而言无法区分(jq '.checks.lint_clean' 在两种情况下都返回 null)。null 甚至更明确——省略是歧义的("没看"还是"看了判不出"), null 明说"看了,判不出"。别在这上面纠结,力气花在不写 false 上。

这与「没有检查命令可跑的阶段 → checks: {}」同源。

「跑不了」= test-commands.yml 过期信号(顺带检测,零额外成本)

命令跑不起来,几乎总意味着这份 yml 已经跟不上项目了——脚本改名、runner 换了、 工具从依赖里移除、子项目路径调整。真实项目里这类漂移是常态(例如某前端框架升级后 移除了内置 lint 子命令,而 yml 里那条命令还在)。

由于各阶段本来就在跑这些命令,这个信号是白捡的。检测到时:

  1. 在本阶段的报告里单列一条:test-commands.yml 疑似过期」,写明是哪一项、 观察到什么(退出码 / 报错原文)、以及为什么判定为"跑不了"而非"没通过"。
  2. 提示补救:/pdlc-test-setup --refresh(重新探测并给出 diff)。
  3. 不要自作主张改 yml——本阶段的职责是干活,不是改配置;只报告,不动手。

变更方向决定自动化程度(--refresh 时适用)

更新这份 yml 等于改变"通过"的定义,所以按方向区别对待:

方向 例子 处理
让闸门变严 空着的 e2e 现在能跑了、覆盖率阈值上调 可自动应用,报告留痕
平移替换 命令改名但语义相同,且新命令已验证能跑 可自动应用,报告留痕
让闸门变松 删掉某条 check、把命令改成空、下调阈值 必须人确认,绝不自动

⚠️ 这条方向规则是防「自动修复把闸门修没了」:lint 命令坏掉时,把它留空是最省事的 "修法",结果闸门悄悄松了、报告还是绿的——比不更新更危险。 变严可以自动,变松必须由人签字。

本命令的状态机取值:阶段短名 tdd(写进 history[].stagelast_phase_result.stage);下一跳 pdlc-implement(写进 next_step,交接时提示)。

状态机更新(段四必须执行)

本命令完成主产出后,必须更新状态机文件 docs/.pdlc-state/<feature-id>.json

文件格式

{
  "feature_id": "<F/B ID>",
  "feature_name": "<kebab-case>",
  "created_at": "<首次创建时间 ISO 8601>",
  "current_stage": "<当前阶段名>",
  "run_mode": "interactive | autonomous",
  "history": [
    {
      "stage": "<阶段名>",
      "done_at": "<ISO 8601>",
      "produced": ["<相对路径 1>", "<相对路径 2>"],
      "self_audit": { "passed": <N>, "failed": <N>, "manual": <N> },
      "auto_decisions": [
        { "point": "<autonomous 下自动前进的确认点>", "chose": "<所选默认>", "at": "<ISO 8601>" }
      ]
    }
  ],
  "last_phase_result": {
    "stage": "<本次阶段名>",
    "ok": true,
    "advanced_to": "<推进到的下一阶段 | null>",
    "checks": {},
    "self_audit": { "failed": 0 },
    "blocked_reason": null,
    "run_mode": "interactive | autonomous",
    "at": "<ISO 8601>"
  },
  "relations": {
    "extends": [],
    "depends_on": [],
    "supersedes": [],
    "resolves": [],
    "conflicts_with": [],
    "relates_to": [],
    "_updated_at": "<ISO 8601 | 省略>"
  },
  "next_step": "<下一跳命令名,如 pdlc-design;若流程结束则为 null>"
}

⛔ 示例里的 "checks": {} 是「本阶段没有命令可跑」的样子,不是键名示范——键名与取值见下方 §1。

relations 块(RFC#6,Phase 1 可选,Phase 2 推荐):6 个 key 对应 6 种关系类型,各为 ID 数组,存出边。其中 conflicts_with / relates_to 是对称类型,两端都要写;其余四种有向,只写在源 feature 上。拿不准时用 /pdlc-relate set 写入,它会按规则校验。旧状态文件无此块时视为全空,向后兼容。入边由 /pdlc-relate rebuild 派生到 _relations.json,不在此块手维护。

写状态机的四条硬约束——读侧(/pdlc-status/pdlc-retro/pdlc-relate)会逐条体检, 违反的每一处都会出现在它们输出的最前面:

  1. 实例里不写 terminal_state。skill frontmatter 的 terminal_state: 是「这个命令走完后应到达的终态名」, 不是状态字段。判终态只看 current_stage 是否以 _done 结尾。
  2. history[].stage 写本命令的阶段短名(见本命令正文里「本命令的状态机取值」)——pdlc-implementimpl, 不写 implement / implementationpdlc-prdrequirements,不写 prd
  3. 时间戳必须带时刻created_at / done_at / at 一律写完整 ISO 8601(如 2026-07-28T10:40:00+08:00)。 只写日期,同一天内的阶段耗时就全部算成 0——读侧只能记「不可测」。
  4. next_step 只写命令名或 null,不附说明文字(如「pdlc-ship(等评审通过)」)。 要说明原因,阻塞时写进 last_phase_result.blocked_reason

_done 的含义是「已发布」,只由 /pdlc-ship(写 ship_done)与 /pdlc-deploy(写 deploy_done)写入。 其它命令的 current_stage 一律写本命令的阶段短名,走完整条链路的编排命令(/pdlc-feature)也一样—— 它收尾时 current_stage 是最后一个阶段的短名,next_steppdlc-ship

  • 「评审通过、等待发布」就是 current_stagereview(或 e2e 等)且 next_steppdlc-ship。 循环相关文档里说的 review_done 指的就是这个状态,不是要写进 current_stage 的值。
  • 为什么:读侧判「已抵达终态」只看 current_stage 是否以 _done 结尾。评审通过就写 _done/pdlc-ship 就分不清哪些功能已经发布过,发布说明会重复或漏收。
  • 旧版本写入的 feature_done / fix_done / review_done 分不清是否已发布,/pdlc-ship 会列出来请人确认。

更新流程

  1. 文件不存在 → 创建文件,写入初始结构(history 为含当前阶段的数组)
  2. 文件存在 → 读取 JSON,追加当前阶段到 history,更新 current_stagenext_step
  3. 写回文件:用 jq 或等效工具保持格式化

⚠️ 若更新失败(文件损坏/权限问题),必须中止命令并在最终报告中报错。状态机不可跳过。

last_phase_result(机器可读阶段结果,每个 phase 收尾必写)

顶层 last_phase_result 是循环判停的唯一真源,外层只需 jq '.last_phase_result.ok' 即可决定 继续 / 停止 / 交还人类。规则:

  1. checks 必须客观、真跑得来:只放真跑命令的退出码结果(命令取自 docs/00_standards/test-commands.yml,见 test-commands-template.yml),绝不用模型自评、绝不填占位。有测试的阶段用 tests_pass / coverage_pass / lint_clean(退出码 0 → true,非 0 → false);stage 语义不同用对应键(如 tdd 段 { "red_verified": true } 表示红灯已验证)。

    键名与类型都是契约的一部分:键名只能是 tests_pass / coverage_pass / lint_clean / e2e_pass(tdd 段 red_verified),值只能是布尔。 最常见的两种错法:① 照抄 test-commands.ymlunit / coverage / lint / e2e ——那是命令表的字段名,不是状态机的(跑 unit 得到的结论写进 tests_pass); ② 写成 "4 passed, 1 failed" 这类字符串摘要。两种都会让 jq '.checks.tests_pass' 读回 null,消费方(发布闸门、质量报告、自主循环)只看到「无法判定」—— 你诚实跑出来的结果等于没写。三态怎么分见本命令正文里「跑 check 命令:退出码的三态语义」一节;正文里没有这一节的命令不跑 check 命令,checks{}

    ⚠️ 没有检查命令可跑的阶段(如 requirements/design 只产文档,或项目无 test-commands.yml)→ checks: {} 留空。绝不因为「本阶段成功」就把 tests_pass/lint_clean 等填 true——那是虚报,会污染跨工具共用的状态机、误导自主循环判停。 上面 schema 示例里 checks 之所以是空的,正是这个原因——空是"没跑"的意思,不是键名的示范

  2. self_audit 单列:只放自检未通过数,仅供参考,不作循环判停依据

  3. ok 的定义:本阶段全部 checks 通过且未命中 blocked_reasontrue;否则 false

  4. 命名空间advanced_to = 下一阶段的短名不是命令名、也不是本阶段的 current_stage。三者关系:stage=本阶段短名、current_stage=本阶段完成后的当前短名、advanced_to=下一阶段短名、next_step=下一跳命令名。

    短名不是「命令名去掉 pdlc- 前缀」——pdlc-implement 的短名是 impl,不是 implement。别推导,查下表:

next_step(下一跳命令名) advanced_to(下一阶段短名)
pdlc-tdd tdd
pdlc-implement impl
pdlc-review review
pdlc-design design
pdlc-ship ship
pdlc-deploy deploy

next_stepnull(终态或无后续)时 advanced_to 也是 null

📌 本表是唯一真源,且是被断言钉住的:每行的短名必须等于该 skill 自己 frontmatter 里声明的 stage:,且任何 skill 的非 null next_step 都必须在表里有行——两个方向 都由 tests/frontmatter-check.sh 检查,所以表不会和实现各自漂移。

写错短名的后果与键名写错同类:消费方按契约名匹配,认不出就当没这个阶段。

  1. 推进一致ok=true 时本阶段必须真的推进了 current_stage(与第 6 条 IRON LAW 呼应);到达终态或无后续时 advanced_to=nullok=false(含 blocked)时 current_stage 不变、advanced_to=nullblocked_reason 写明原因。
  2. run_mode:镜像本次调用是否带 --autonomous(带了写 autonomous,没带写 interactive)。

段四:交接(Handoff)

命令完成后必须输出以下格式的最终消息:

✅ <阶段名> 完成:<主要产出物路径>
📊 自检:<通过数>/<总数> 通过(若有未通过,附要点)
📦 状态快照:docs/.pdlc-state/<feature-id>.json
👉 下一步:/pdlc-<next_step>
   (如果有分叉)或 /pdlc-<alt>(条件:<选择依据>)

规则:

  • 主流程命令(写状态机的命令;下一跳见正文里「本命令的状态机取值」)必须显式输出"下一步",不可省略
  • 工具型命令(Layer 3)可以没有 next_step,此时输出 👉 下一步:(本次流程结束,无后续)
  • 分叉场景必须说明选择条件,例如"若需补充测试用例 → /pdlc-tdd;若测试已齐 → /pdlc-review"
Files (pdlc-skills)
  • assets
    • test-plan-template.md 1.1 KB
      # 测试计划:[功能/模块名称]
      
      ## 1. 测试范围
      说明测试覆盖的范围和不在范围内的内容。
      
      ## 2. 测试策略
      - [x] 单元测试(TDD - 先写测试)
      - [ ] 集成测试
      - [ ] 端到端测试
      - [ ] 性能测试
      - [ ] 安全测试
      
      ## 3. 测试用例
      
      ### 单元测试
      | 编号 | 测试用例 | 输入 | 预期输出 | 优先级 |
      |------|----------|------|----------|--------|
      | UT-001 | | | | P0 |
      
      ### 集成测试
      | 编号 | 测试用例 | 前置条件 | 操作步骤 | 预期结果 | 优先级 |
      |------|----------|----------|----------|----------|--------|
      | IT-001 | | | | | P0 |
      
      ### 端到端测试
      | 编号 | 场景 | 操作步骤 | 预期结果 | 优先级 |
      |------|------|----------|----------|--------|
      | E2E-001 | | | | P0 |
      
      ## 4. 测试数据
      描述测试数据需求及准备方式。
      
      ## 5. 测试环境
      | 环境 | 地址 | 备注 |
      |------|------|------|
      
      ## 6. 通过标准
      - [ ] 所有 P0 测试用例通过
      - [ ] 代码覆盖率 >= 80%
      - [ ] 无严重/阻塞级缺陷
      - [ ] 性能满足 SLA 要求
      
      ---
      创建日期:
      作者:
      状态:草稿
      
  • SKILL.md 26.7 KB
    ---
    name: pdlc-tdd
    description: TDD 测试先行(按设计文档生成失败的测试用例)
    argument-hint: <功能ID | 功能描述>
    allowed-tools: Read, Write, Edit, Glob, Grep, Bash
    layer: 2
    stage: tdd
    produces:
      # 跟随项目既有测试布局,不限定固定目录(定位规则见 templates/prompts/test-location.md)
      - <测试代码 · 项目既有布局>
      - docs/04_testing/unit-tests/**
    requires:
      - docs/02_design/
    next_step: pdlc-implement
    terminal_state: tdd_done
    recommended_model: sonnet
    recommended_effort: medium
    ---
    
    # TDD 测试先行
    
    <!-- @include templates/prompts/iron-law.md(已内联于下方,无需另读) -->
    ⛔ **IRON LAW · 不可违反的硬门禁**
    
    以下规则为**不可协商**的执行约束:
    
    1. **文件必须落盘**:所有带编号(功能ID / 缺陷ID)的文档,必须作为实际文件写入磁盘,不可仅在对话中输出。
    2. **阶段必须落章**:每个阶段完成后必须在状态机 `docs/.pdlc-state/<feature-id>.json` 追加 history,不可跳过。
    3. **测试必须存在**:进入 `/pdlc-implement` 前,对应测试必须存在且处于红灯状态。违反则中止。
    4. **自检必须执行**:段二自检为强制步骤,不得以"已经很好了"为由跳过。
    5. **防循环**:段三修复为单次,不递归。无法自动修复的问题记录到报告,继续往下走。
    6. **状态必推进**:成功执行某 phase 后 `current_stage` 必须变更。收尾时若发现 `current_stage` 未推进,视为失败并报错,**不得静默返回**(防止外层循环拿滞后的状态空转烧额度)。唯一例外:命中人工点主动 block 时,`current_stage` 保持不变但必须写 `last_phase_result.ok=false` + `blocked_reason`。
    
    **违反任一条 = 立即中止当前命令,输出违规详情,等待人工介入。**
    <!-- @include-end templates/prompts/iron-law.md -->
    <!-- @include templates/prompts/noninteractive.md(已内联于下方,无需另读) -->
    ## 非交互模式(`--autonomous`)
    
    若本命令的参数含 `--autonomous`,本命令进入**无人值守**模式,按以下规则处理原本需要人应答的交互点。**参数是唯一真源**:不带 `--autonomous` 即为交互模式,一切照旧正常询问用户;绝不回读状态机 `run_mode` 兜底(「掉出 autonomous」是安全的失败方向)。
    
    1. **流程性确认**(如「测试已绿是否继续」「是否覆盖已有文件」)→ **不询问**,按预设默认前进,并把决策追加到状态机 `history[].auto_decisions[]`:
       ```json
       { "point": "<确认点描述>", "chose": "<所选默认>", "at": "<ISO 8601>" }
       ```
    2. **真需人判断**(PRD 关键取舍、评审「需人工确认」项、真实循环依赖等无法安全默认的点)→ **不猜**:
       - `current_stage` 保持不变(不推进)
       - 写 `last_phase_result.ok = false` 且 `blocked_reason = "<原因>"`
       - 末行输出哨兵:`<<<PDLC blocked reason="<原因>">>>`
       - 立即结束命令,交还人类
    3. **破坏性操作**(发布 / 部署 / 打 tag / 触发 CI / DROP / force-push 等不可逆·外发操作)→ `--autonomous` **无效**,仍必须人工显式确认。
    4. **顺手的 sidecar 产物**(如缺失时创建 `CHANGELOG.md`、补全文档 PDLC-TRACE 的创建时间等本阶段职责内、可安全默认的辅助改动)→ 视为流程性默认,**直接做并记入 `auto_decisions[]`**;这类改动不新增外部副作用,不属破坏性操作。
    
    > 进入 autonomous 模式时,在状态机顶层写 `run_mode: "autonomous"` 仅供留痕(复盘区分人工 vs 循环产出)。
    <!-- @include-end templates/prompts/noninteractive.md -->
    
    根据设计文档,先编写测试用例,再实现代码。严格遵循 TDD 工作流。
    
    ## PDLC 前置检查(必须执行,不可跳过)
    
    1. 从用户输入中提取功能名称关键词
    2. 在 `docs/02_design/` 的子目录(api/、architecture/、database/、ui-ux/)下搜索包含该关键词的设计文档
       - 匹配新格式:`F<日期>-<编号>-*<关键词>*-<类型>.md`
       - 匹配旧格式:`YYYYMMDD-*<关键词>*-<类型>.md`
       - 同时检查文件内容中是否包含该关键词
    3. **未找到任何设计文档** → 输出以下信息后**立即停止,不继续执行**:
       ```
       ⛔ PDLC 守卫:未找到与「<功能名>」相关的设计文档(API/架构/数据库/UI 任一)。
       测试用例必须基于已有的设计文档。请先运行:
       👉 /pdlc-design <设计目标>
       ```
    4. **找到** → 提取功能ID(如 `F20260326-090000`),读取设计文档内容,继续执行
    
    ## 工作流程
    
    1. **阅读设计文档**: 阅读找到的设计文档,全面理解接口/架构/数据模型
    2. **阅读编码规范**: 阅读 `docs/00_standards/coding/` 目录了解编码规范(未命中 → 提示 `consider /pdlc-standard add coding/<topic>`)
    3. **编写测试计划**: 在 `docs/04_testing/unit-tests/` 下创建测试计划文档
       - **使用模板**: 本 skill 目录下的 `assets/test-plan-template.md`
       - **文件名格式**: `<功能ID>-<功能名>-test-plan.md`(如 `F20260326-090000-user-auth-test-plan.md`)
       - **文档顶部必须包含 PDLC 追溯头**:
         ```
         <!-- PDLC-TRACE -->
         <!-- 功能ID: F20260326-090000 -->
         <!-- 功能名称: user-auth -->
         <!-- 阶段: 测试 -->
         <!-- 前置文档: docs/02_design/api/F20260326-090000-user-auth-api.md -->
         ```
    4. **编写测试代码**: 写到项目**既有的**测试布局里,按下面的规则定位;
       **不要**为迎合某种预设结构新造一套平行的测试目录。
    
    <!-- @include templates/prompts/test-location.md(已内联于下方,无需另读) -->
    ## 测试代码在哪(布局无关的定位规则)
    
    > ⚠️ **这条规则的要害**:红灯守卫必须区分「**项目没有测试**」和「**测试不在我预期的位置**」。
    > 前者才该拦;后者拦了就是误伤——真实项目的测试布局千差万别(单体 `backend/tests/`、
    > 根级 `tests/`、Go 同包 `*_test.go`、Node 与源码同目录的 `*.test.tsx`…),
    > 按一份写死的路径清单去找、找不到就拦,会让 pdlc 在大量正常项目上直接卡死。
    
    **核心原则:优先问 runner,其次翻文件。** 测试框架自己最清楚有哪些测试——
    让它报比我们去猜文件位置准得多,也和 pdlc「信退出码、不信目视检查」的哲学一致。
    尤其**测试写在源文件里**的语言(见下),翻文件根本找不到。
    
    按下列顺序定位,**命中即停**:
    
    ### 1. 项目自己的声明 + 向 runner 查询(最高优先级)
    
    若存在 `docs/00_standards/test-commands.yml`,它的 `unit` / `e2e` 命令**就是权威**——
    项目已经明确告诉你测试怎么跑。**用它去问 runner**,而不是去翻目录:
    
    | runner | 列出全部测试 | 只查某功能相关 |
    |---|---|---|
    | cargo (Rust) | `cargo test -- --list` | `cargo test <关键词> -- --list` |
    | pytest | `pytest --collect-only -q` | `pytest --collect-only -q -k <关键词>` |
    | go test | `go test -list '.*' ./...` | `go test -list '<关键词>' ./...` |
    | vitest / jest | `npx vitest list` / `--listTests` | `npx vitest list -t <关键词>` |
    | gradle / maven | `--tests '*'` 干跑 | `--tests '*<关键词>*'` |
    
    **查询结果为空 = 该功能没有测试**(这是行为证据,比"我没找到文件"可靠得多)。
    
    ### 2. 测试写在源文件里的语言(**必须靠内容匹配,文件名扫描无效**)⭐
    
    这类语言没有独立测试文件,只能按**代码内标记**搜:
    
    | 语言 / 框架 | in-source 测试标记 |
    |---|---|
    | **Rust** | `#[cfg(test)]`、`#[test]`、`mod tests` |
    | **Vitest**(in-source testing) | `import.meta.vitest` |
    | **Python** doctest | docstring 里的 `>>> ` |
    | **Elixir** doctest | `@doc` 里的 `iex>` |
    | **Go**(同包但独立文件) | `*_test.go` + `func Test` |
    
    > ⚠️ **Rust 尤其要注意**:单元测试几乎总在源文件的 `#[cfg(test)] mod tests` 里,
    > `tests/` 目录按 Cargo 约定只放**集成测试**。所以「`tests/` 目录不存在」在 Rust 项目里
    > **完全不能推出「没有单元测试」**——照文件清单判红会稳定误伤所有 Rust 项目。
    
    ### 3. 常见布局约定(按项目实际技术栈挑,不要全试)
    
    | 生态 | 常见测试位置 |
    |---|---|
    | Python | `tests/`、`backend/tests/`、`test/`、与源码同目录的 `test_*.py` |
    | Node / TS | `tests/`、`__tests__/`、`src/**/__tests__/`、与源码同目录的 `*.test.ts(x)` / `*.spec.ts(x)` |
    | Rust | 源文件内 `#[cfg(test)]`(单测)+ `tests/`(集成测试) |
    | Go | 与源码同包的 `*_test.go` |
    | JVM | `src/test/java/`、`src/test/kotlin/` |
    | Ruby | `spec/`、`test/` |
    | 微服务 / 单体仓 | 上述任一可能出现在 `backend/`、`backend/services/<名>/`、`frontend/<应用>/` 之下 |
    
    ### 4. 文件名兜底扫描
    
    前三步都没命中时,按文件名模式全仓搜(`*test*` / `*spec*`,排除 `node_modules`、
    `.venv`、`vendor`、`dist`、`build`、`target`、`.git`),再按功能关键词筛。
    
    ### 5. 判定
    
    - **任一步找到相关测试** → 通过,进入下一步(`pdlc-implement` 还需确认红灯)
    - **四步走完确实找不到任何测试** → 这才是真红灯,按各命令的守卫规则中止
    - **找到测试但与本功能无关** → 按「本功能无测试」处理(同样是真红灯),但报告里要说明
      「项目有测试,只是没覆盖本功能」,别让用户以为项目裸奔
    - **无法判定**(如 runner 装不上、语言不认识)→ **不要默认放行,也不要假装找到了**:
      如实报「无法确认本功能是否有测试」并交还人类。**「我判断不了」绝不等于「没问题」。**
    
    > 写测试时(`pdlc-tdd`)同样按本规则决定**写到哪**:跟随项目既有布局与惯例——
    > Rust 单测就写进源文件的 `#[cfg(test)] mod tests`,**不要**为了迎合某种预设结构
    > 新造一套平行的测试目录。
    <!-- @include-end templates/prompts/test-location.md -->
    
    5. **测试计划自审与自动修复**(编写完成后、运行前执行,不可跳过):
       - 重新阅读测试计划和测试代码,对照设计文档和 PRD 逐项检查以下质量门禁:
    
       **验收标准覆盖度**:
       - [ ] PRD 中每条验收标准是否至少有一个对应的测试用例
       - [ ] 设计文档中每个接口是否至少有正常流程 + 异常流程的测试
    
       **场景完备性**:
       - [ ] 正常流程:核心业务路径是否全部覆盖
       - [ ] 边界条件:空值/null、空字符串、最大值/最小值、零值、超长输入
       - [ ] 异常场景:无权限、资源不存在(404)、重复操作(409)、参数校验失败(400)
       - [ ] 并发场景:是否考虑了同时操作的冲突(如适用)
       - [ ] 幂等性:重复提交同一请求是否有对应测试(如适用)
    
       **测试质量**:
       - [ ] 测试方法命名是否清晰描述场景(如 `should_return_404_when_user_not_found`)
       - [ ] 每个测试是否只验证一个行为(单一断言原则)
       - [ ] 测试数据是否有意义(非 `test1`、`abc123` 等无意义数据)
    
       **自动修复**:
       - 缺失的验收标准测试:自动补充对应的测试用例骨架
       - 缺失的边界条件测试:自动添加空值、超长输入、类型错误等测试
       - 缺失的异常场景测试:根据 API 错误码自动补充 401/403/404/409 等场景测试
       - 命名不规范的测试方法:自动重命名为描述性命名
       - 修复后在测试计划文档末尾追加审查记录:
         ```
         ## 自审记录
         - 审查时间:<ISO 8601>
         - 对照 PRD 验收标准:X 条,已覆盖:X 条
         - 对照 API 接口:X 个,已覆盖:X 个
         - 发现问题:X 项
         - 自动修复:X 项
         - 修复明细:
           - [已修复] <问题描述>
         ```
    
    6. **确认测试失败**: 运行测试确认全部失败(红灯)。运行命令取自 `docs/00_standards/test-commands.yml` 的 `unit`(不存在则回退项目约定)。收尾写 `last_phase_result.checks = { "red_verified": true }`(红灯已由真跑退出码验证,非模型自评)
    7. **实现代码**: 编写最少量的代码使测试通过
    8. **重构**: 在测试通过的前提下优化代码
    
    ## 要求
    
    <!-- @include templates/prompts/output-language.md(已内联于下方,无需另读) -->
    🌐 **Output language for generated artifacts**
    
    All generated artifacts (PRDs, design docs, code comments, review reports,
    test plans, deployment manuals, changelog entries, etc.) follow this policy:
    
    1. **Default — match the conversation language exactly**:
       - 用户用中文与 Claude 对话 → 产中文文档、中文代码注释、中文报告
       - User talks to Claude in English → produce English artifacts
       - User talks in another language → produce artifacts in that language
       - **Never silently default to a fixed language regardless of the user's input.**
    
    2. **Explicit override always wins**: when the user specifies a language for
       an artifact (e.g. "write the PRD in English", "用英文写 API 设计文档",
       "output the deploy doc in Japanese"), use that language for that artifact,
       regardless of conversation language.
    
    3. **Mixed-language requirements**: if the user wants some artifacts in one
       language and others in a different language (common: Chinese PRD + English
       API docs for partners), honour each per-artifact instruction.
    
    4. **Uncertain**: if you cannot reliably detect the conversation language,
       ask once before producing the first artifact.
    
    This policy applies to **content** (prose, comments, headings). It does
    **not** override technical conventions like English variable names, English
    git commit subjects, or English error codes when the project's conventions
    require them.
    <!-- @include-end templates/prompts/output-language.md -->
    - 测试用例必须覆盖:正常流程、边界条件、异常场景
    - 测试方法命名清晰描述测试场景
    - 单元测试覆盖率:覆盖率达标线**以项目配置为准**:优先取 `docs/00_standards/test-commands.yml` 的 coverage 命令阈值参数(那才是强制点,退出码即判定),其次 `quality-targets.yml`;两者都没有时按 >= 80% 兜底。
    
    目标功能: $ARGUMENTS
    
    <!-- @include templates/prompts/check-commands.md(已内联于下方,无需另读) -->
    ## 跑 check 命令:退出码的三态语义
    
    命令取自 `docs/00_standards/test-commands.yml`(唯一真源)。逐条真跑,**按退出码分三态**——
    不是两态。这是 IRON LAW「checks 只认客观事实」在执行层的落法:
    
    | 观察到的 | 含义 | 写进 `checks` |
    |---|---|---|
    | 退出码 `0` | 通过 | 对应键 = `true` |
    | 退出码非 0(命令**跑起来了**,只是没过) | 未通过 | 对应键 = `false` |
    | 退出码 `127` / `command not found` / 脚本文件不存在 / 该项为空字符串 | **无法判定** | **省略该键,或写 `null`**——**绝不能是 `false`** |
    
    > ⛔ **唯一的红线是不许写 `false`**:那是**会误导人的虚报**——它说的是"检查失败了",
    > 于是有人去查代码,但真正的问题是**配置过期**,代码可能完全没毛病。
    >
    > **省略键与 `null` 等价,两种都可以**:对消费方而言无法区分(`jq '.checks.lint_clean'`
    > 在两种情况下都返回 `null`)。`null` 甚至更明确——省略是歧义的("没看"还是"看了判不出"),
    > `null` 明说"看了,判不出"。**别在这上面纠结,力气花在不写 `false` 上。**
    >
    > 这与「没有检查命令可跑的阶段 → `checks: {}`」同源。
    
    ## 「跑不了」= `test-commands.yml` 过期信号(顺带检测,零额外成本)
    
    命令跑不起来,几乎总意味着**这份 yml 已经跟不上项目了**——脚本改名、runner 换了、
    工具从依赖里移除、子项目路径调整。真实项目里这类漂移是常态(例如某前端框架升级后
    移除了内置 lint 子命令,而 yml 里那条命令还在)。
    
    由于**各阶段本来就在跑这些命令**,这个信号是白捡的。检测到时:
    
    1. 在本阶段的报告里单列一条:**「`test-commands.yml` 疑似过期」**,写明是哪一项、
       观察到什么(退出码 / 报错原文)、以及为什么判定为"跑不了"而非"没通过"。
    2. 提示补救:`/pdlc-test-setup --refresh`(重新探测并给出 diff)。
    3. **不要自作主张改 yml**——本阶段的职责是干活,不是改配置;只报告,不动手。
    
    ## 变更方向决定自动化程度(`--refresh` 时适用)
    
    更新这份 yml 等于**改变"通过"的定义**,所以按**方向**区别对待:
    
    | 方向 | 例子 | 处理 |
    |---|---|---|
    | **让闸门变严** | 空着的 `e2e` 现在能跑了、覆盖率阈值上调 | **可自动应用**,报告留痕 |
    | **平移替换** | 命令改名但语义相同,且新命令**已验证能跑** | **可自动应用**,报告留痕 |
    | **让闸门变松** | 删掉某条 check、把命令改成空、下调阈值 | **必须人确认**,绝不自动 |
    
    > ⚠️ 这条方向规则是防「自动修复把闸门修没了」:lint 命令坏掉时,**把它留空**是最省事的
    > "修法",结果闸门悄悄松了、报告还是绿的——比不更新更危险。
    > **变严可以自动,变松必须由人签字。**
    <!-- @include-end templates/prompts/check-commands.md -->
    <!-- pdlc:meta 由 frontmatter 生成(adapters/sync_skills.py),勿手改 -->
    > **本命令的状态机取值**:阶段短名 `tdd`(写进 `history[].stage` 与 `last_phase_result.stage`);下一跳 `pdlc-implement`(写进 `next_step`,交接时提示)。
    <!-- pdlc:meta-end -->
    <!-- @include templates/prompts/state-update.md(已内联于下方,无需另读) -->
    ## 状态机更新(段四必须执行)
    
    本命令完成主产出后,必须更新状态机文件 `docs/.pdlc-state/<feature-id>.json`。
    
    ### 文件格式
    
    ```json
    {
      "feature_id": "<F/B ID>",
      "feature_name": "<kebab-case>",
      "created_at": "<首次创建时间 ISO 8601>",
      "current_stage": "<当前阶段名>",
      "run_mode": "interactive | autonomous",
      "history": [
        {
          "stage": "<阶段名>",
          "done_at": "<ISO 8601>",
          "produced": ["<相对路径 1>", "<相对路径 2>"],
          "self_audit": { "passed": <N>, "failed": <N>, "manual": <N> },
          "auto_decisions": [
            { "point": "<autonomous 下自动前进的确认点>", "chose": "<所选默认>", "at": "<ISO 8601>" }
          ]
        }
      ],
      "last_phase_result": {
        "stage": "<本次阶段名>",
        "ok": true,
        "advanced_to": "<推进到的下一阶段 | null>",
        "checks": {},
        "self_audit": { "failed": 0 },
        "blocked_reason": null,
        "run_mode": "interactive | autonomous",
        "at": "<ISO 8601>"
      },
      "relations": {
        "extends": [],
        "depends_on": [],
        "supersedes": [],
        "resolves": [],
        "conflicts_with": [],
        "relates_to": [],
        "_updated_at": "<ISO 8601 | 省略>"
      },
      "next_step": "<下一跳命令名,如 pdlc-design;若流程结束则为 null>"
    }
    ```
    
    > ⛔ 示例里的 `"checks": {}` 是「本阶段没有命令可跑」的样子,**不是键名示范**——键名与取值见下方 §1。
    
    > **`relations` 块(RFC#6,Phase 1 可选,Phase 2 推荐)**:6 个 key 对应 6 种关系类型,各为 ID 数组,存**出边**。其中 `conflicts_with` / `relates_to` 是对称类型,两端都要写;其余四种有向,只写在源 feature 上。拿不准时用 `/pdlc-relate set` 写入,它会按规则校验。旧状态文件无此块时视为全空,向后兼容。入边由 `/pdlc-relate rebuild` 派生到 `_relations.json`,不在此块手维护。
    
    > ⛔ **写状态机的四条硬约束**——读侧(`/pdlc-status`、`/pdlc-retro`、`/pdlc-relate`)会逐条体检,
    > 违反的每一处都会出现在它们输出的最前面:
    >
    > 1. **实例里不写 `terminal_state`**。skill frontmatter 的 `terminal_state:` 是「这个命令走完后应到达的终态名」,
    >    不是状态字段。判终态只看 `current_stage` 是否以 `_done` 结尾。
    > 2. **`history[].stage` 写本命令的阶段短名**(见本命令正文里「本命令的状态机取值」)——`pdlc-implement` 写 `impl`,
    >    不写 `implement` / `implementation`;`pdlc-prd` 写 `requirements`,不写 `prd`。
    > 3. **时间戳必须带时刻**:`created_at` / `done_at` / `at` 一律写完整 ISO 8601(如 `2026-07-28T10:40:00+08:00`)。
    >    只写日期,同一天内的阶段耗时就全部算成 0——读侧只能记「不可测」。
    > 4. **`next_step` 只写命令名或 `null`**,不附说明文字(如「pdlc-ship(等评审通过)」)。
    >    要说明原因,阻塞时写进 `last_phase_result.blocked_reason`。
    
    > ⛔ **`_done` 的含义是「已发布」,只由 `/pdlc-ship`(写 `ship_done`)与 `/pdlc-deploy`(写 `deploy_done`)写入。**
    > 其它命令的 `current_stage` 一律写本命令的阶段短名,走完整条链路的编排命令(`/pdlc-feature`)也一样——
    > 它收尾时 `current_stage` 是最后一个阶段的短名,`next_step` 是 `pdlc-ship`。
    >
    > - 「评审通过、等待发布」就是 `current_stage` 为 `review`(或 `e2e` 等)且 `next_step` 为 `pdlc-ship`。
    >   循环相关文档里说的 `review_done` 指的就是这个状态,**不是**要写进 `current_stage` 的值。
    > - 为什么:读侧判「已抵达终态」只看 `current_stage` 是否以 `_done` 结尾。评审通过就写 `_done`,
    >   `/pdlc-ship` 就分不清哪些功能已经发布过,发布说明会重复或漏收。
    > - 旧版本写入的 `feature_done` / `fix_done` / `review_done` 分不清是否已发布,`/pdlc-ship` 会列出来请人确认。
    
    ### 更新流程
    
    1. **文件不存在** → 创建文件,写入初始结构(`history` 为含当前阶段的数组)
    2. **文件存在** → 读取 JSON,追加当前阶段到 `history`,更新 `current_stage` 和 `next_step`
    3. **写回文件**:用 `jq` 或等效工具保持格式化
    
    ⚠️ 若更新失败(文件损坏/权限问题),必须中止命令并在最终报告中报错。状态机不可跳过。
    
    ### `last_phase_result`(机器可读阶段结果,每个 phase 收尾必写)
    
    顶层 `last_phase_result` 是循环判停的**唯一真源**,外层只需 `jq '.last_phase_result.ok'` 即可决定 继续 / 停止 / 交还人类。规则:
    
    1. **`checks` 必须客观、真跑得来**:只放**真跑命令的退出码**结果(命令取自 `docs/00_standards/test-commands.yml`,见 `test-commands-template.yml`),**绝不用模型自评、绝不填占位**。有测试的阶段用 `tests_pass` / `coverage_pass` / `lint_clean`(退出码 0 → `true`,非 0 → `false`);stage 语义不同用对应键(如 tdd 段 `{ "red_verified": true }` 表示红灯已验证)。
       > ⛔ **键名与类型都是契约的一部分**:键名只能是 `tests_pass` / `coverage_pass` /
       > `lint_clean` / `e2e_pass`(tdd 段 `red_verified`),值只能是**布尔**。
       > 最常见的两种错法:① 照抄 `test-commands.yml` 的 `unit` / `coverage` / `lint` / `e2e`
       > ——那是**命令表**的字段名,不是状态机的(跑 `unit` 得到的结论写进 `tests_pass`);
       > ② 写成 `"4 passed, 1 failed"` 这类字符串摘要。两种都会让 `jq '.checks.tests_pass'`
       > 读回 `null`,消费方(发布闸门、质量报告、自主循环)只看到「无法判定」——
       > **你诚实跑出来的结果等于没写**。三态怎么分见本命令正文里「跑 check 命令:退出码的三态语义」一节;正文里没有这一节的命令不跑 check 命令,`checks` 写 `{}`。
       >
       > ⚠️ **没有检查命令可跑的阶段(如 requirements/design 只产文档,或项目无 `test-commands.yml`)→ `checks: {}` 留空。绝不因为「本阶段成功」就把 `tests_pass`/`lint_clean` 等填 `true`——那是虚报,会污染跨工具共用的状态机、误导自主循环判停。** 上面 schema 示例里 `checks` 之所以是空的,正是这个原因——**空是"没跑"的意思,不是键名的示范**。
    2. **`self_audit` 单列**:只放自检未通过数,**仅供参考,不作循环判停依据**。
    3. **`ok` 的定义**:本阶段全部 `checks` 通过且未命中 `blocked_reason` → `true`;否则 `false`。
    4. **命名空间**:`advanced_to` = **下一阶段的短名**,**不是命令名、也不是本阶段的 `current_stage`**。三者关系:`stage`=本阶段短名、`current_stage`=本阶段完成后的当前短名、`advanced_to`=下一阶段短名、`next_step`=下一跳命令名。
    
       ⛔ **短名不是「命令名去掉 `pdlc-` 前缀」**——`pdlc-implement` 的短名是 **`impl`**,不是 `implement`。别推导,查下表:
    
    <!-- stage-map:start -->
       | `next_step`(下一跳命令名) | `advanced_to`(下一阶段短名) |
       |---|---|
       | `pdlc-tdd` | `tdd` |
       | `pdlc-implement` | `impl` |
       | `pdlc-review` | `review` |
       | `pdlc-design` | `design` |
       | `pdlc-ship` | `ship` |
       | `pdlc-deploy` | `deploy` |
    <!-- stage-map:end -->
    
       `next_step` 为 `null`(终态或无后续)时 `advanced_to` 也是 `null`。
    
       > 📌 **本表是唯一真源,且是被断言钉住的**:每行的短名必须等于该 skill 自己 frontmatter
       > 里声明的 `stage:`,且任何 skill 的非 `null` `next_step` 都必须在表里有行——两个方向
       > 都由 `tests/frontmatter-check.sh` 检查,所以表不会和实现各自漂移。
       >
       > 写错短名的后果与键名写错同类:消费方按契约名匹配,认不出就当没这个阶段。
    5. **推进一致**:`ok=true` 时本阶段必须真的推进了 `current_stage`(与第 6 条 IRON LAW 呼应);到达终态或无后续时 `advanced_to=null`。`ok=false`(含 blocked)时 `current_stage` 不变、`advanced_to=null`、`blocked_reason` 写明原因。
    6. **`run_mode`**:镜像本次调用是否带 `--autonomous`(带了写 `autonomous`,没带写 `interactive`)。
    <!-- @include-end templates/prompts/state-update.md -->
    <!-- @include templates/prompts/handoff.md(已内联于下方,无需另读) -->
    ## 段四:交接(Handoff)
    
    命令完成后必须输出以下格式的最终消息:
    
    ```
    ✅ <阶段名> 完成:<主要产出物路径>
    📊 自检:<通过数>/<总数> 通过(若有未通过,附要点)
    📦 状态快照:docs/.pdlc-state/<feature-id>.json
    👉 下一步:/pdlc-<next_step>
       (如果有分叉)或 /pdlc-<alt>(条件:<选择依据>)
    ```
    
    **规则:**
    - 主流程命令(写状态机的命令;下一跳见正文里「本命令的状态机取值」)必须显式输出"下一步",不可省略
    - 工具型命令(Layer 3)可以没有 `next_step`,此时输出 `👉 下一步:(本次流程结束,无后续)`
    - 分叉场景必须说明**选择条件**,例如"若需补充测试用例 → `/pdlc-tdd`;若测试已齐 → `/pdlc-review`"
    <!-- @include-end templates/prompts/handoff.md -->
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related