/pdlc-retro
迭代复盘——读状态机历史出趋势报告
Claude Code
$ npx -y skills add kanfu-panda/pdlc-skills --skill pdlc-retro --agent claude-code
Codex
$ npx skills add kanfu-panda/pdlc-skills --skill pdlc-retro -a codex -y
Cursor
$ npx skills add kanfu-panda/pdlc-skills --skill pdlc-retro -a cursor -y
Gemini CLI
$ npx skills add kanfu-panda/pdlc-skills --skill pdlc-retro -a gemini-cli -y
GitHub Copilot
$ npx skills add kanfu-panda/pdlc-skills --skill pdlc-retro -a github-copilot -y
OpenCode
$ npx skills add kanfu-panda/pdlc-skills --skill pdlc-retro -a opencode -y
Windsurf
$ npx skills add kanfu-panda/pdlc-skills --skill pdlc-retro -a windsurf -y
SKILL.md
pdlc-retro.SKILL.mdname: pdlc-retro
description: 迭代复盘——读状态机历史出趋势报告
argument-hint: "[--range 7d|30d|all] [--feature <feature-id>]"
allowed-tools: Read, Glob, Bash
layer: 2
stage: retro
produces:
- docs/07_reviews/retro/<YYYY-MM>-retro.md
requires:
- docs/.pdlc-state/
next_step: null
terminal_state: retro_done
PDLC 迭代复盘
读取 `docs/.pdlc-state/` 下的状态机历史,按时间范围聚合,生成趋势复盘报告。
<!-- @include templates/prompts/iron-law-tool.md(已内联于下方,无需另读) --> ⛔ **IRON LAW · 不可违反的硬门禁(工具型命令)**
本命令不是主链路上的阶段:**不追加 `history`,不改 `current_stage` / `next_step` / `last_phase_result`**,也不为此分配功能ID。以下规则不可协商:
1. **文件必须落盘**:正文要求创建的文件必须作为实际文件写入磁盘,不可仅在对话中输出(只要求在对话里输出的,照正文办)。 2. **自检必须执行**:产出后按本命令的自检项逐项核对,不得以"已经很好了"为由跳过。 3. **防循环**:修复只做一次,不递归。修不了的问题记进报告,继续往下走。
**违反任一条 = 立即中止当前命令,输出违规详情,等待人工介入。** <!-- @include-end templates/prompts/iron-law-tool.md -->
<!-- @include templates/prompts/state-read.md(已内联于下方,无需另读) -->
读状态机之前:先做契约体检
`docs/.pdlc-state/*.json` 不一定都是 `/pdlc-*` 命令写出来的——手写的、旧版本写的、照着格式仿写的 都可能混在里面。字段对不上契约时,**不许边猜边算、再把猜出来的东西当结论**。先体检,再计算; 体检发现的每一处偏差,都必须出现在输出的**最前面**。
1. 跑体检
体检脚本随本 skill 一起分发,就在本 skill 目录下:`scripts/pdlc-state-lint.sh`(运行时会告知本 skill 的 所在目录)。它与正在运行的 skill 同一版本,体检规则——合法阶段名、偏差代码——与下方 §2 一致。
在项目根执行 `bash <本 skill 目录>/scripts/pdlc-state-lint.sh .`。stdout 每行一条偏差:`文件<TAB>代码<TAB>说明`。退出码三态:
- `0` 已体检、全部合契约 → 照常出结论,不输出体检块
- `1` 已体检、有偏差 → 照常计算,但输出最前面必须先放「输入契约体检」块(见 §3)
- `2` 无法体检(缺 jq / 无状态目录)→ 体检块写「无法体检:<原因>」,**不得当成合契约**
体检脚本不可用时(找不到,或当前平台没有随 skill 分发它),按 §2 的规则逐条人工核对, 体检块首行注明「体检脚本不可用,以下为人工核对」。
2. 体检规则(偏差代码)
读侧跳过 `_` 前缀的索引文件(如 `_relations.json`)与 `statusline.json`,它们不是功能状态机。
<!-- finding-codes:start --> | 代码 | 判定 | 读侧怎么处理 | |---|---|---| | `json-invalid` | 文件不是合法 JSON | 跳过该文件,列入体检块 | | `missing-field` | 缺 `feature_id` / `current_stage` / `history` / `next_step` / `created_at` 之一 | 用到该字段的指标,对这份文件记「不可判」 | | `field-type-invalid` | 字段在、类型不对(如 `current_stage` 不是字符串、`history` 不是数组、`last_phase_result` 不是对象、history 条目不是对象) | **按缺失处理**:用到它的指标对这份文件记「不可判」。类型不对的时间戳不再另报 `timestamp-no-time` | | `missing-last_phase_result` | 缺 `last_phase_result`(多为旧文件) | 不推断本阶段结果与 checks | | `terminal_state-in-instance` | 实例里出现 `terminal_state` | **忽略该字段**,判终态只看 §4 | | `non-contract-field` | 其它表外顶层字段(如 `title`) | 忽略 | | `stage-alias` | `stage` 用了已知别名 | 可按说明里的 `别名→短名` 归一化计数,**但必须在体检块写明归一化了哪些** | | `stage-unknown` | `stage` 既不是短名也不是已知别名 | 单独成行,不并入任何阶段 | | `current_stage-unknown` | `current_stage` 既不是短名,也不以 `_done` 结尾 | 该功能归入「❓ 异常」 | | `next_step-not-command` | `next_step` 不是纯命令名(如带散文后缀) | 按原文展示,不据此推断下一步 | | `timestamp-no-time` | 时间戳只有日期、没有时刻 | 依赖它的**耗时类指标记「不可测」**,不得算成 0 | | `relations-not-object` | `relations` 不是六键对象(如数组) | 这份文件的关系**不入图**,列入体检块 | | `relations-unknown-type` | 关系键不在六类之内 | 该键下的关系不入图 | | `relations-target-not-id` | 关系目标不是 feature ID(散文、模块路径等) | 该条不入图 | | `relations-dangling` | 目标 ID 没有对应的状态文件 | 标「悬空」,不参与影响半径计算 | | `id-prefix-mismatch` | 走的是修复流程(有 `fix` 阶段),ID 却不以 `B` 开头 | 缺陷计数按 ID 前缀的契约口径算,同时在体检块点名这份 | | `checks-key-unknown` | `last_phase_result.checks` 里有状态机不认的键(如照抄 yml 的 `unit` / `lint`) | 该键当作不存在:对应维度记「无法判定」,**不**按键名猜它对应哪个 check | | `checks-value-invalid` | check 的值不是布尔也不是 `null`(如 `"4 passed, 1 failed"`) | 该项记「无法判定」,不从字符串里解析结论 | | `ok-checks-mismatch` | `ok=true`,但有 check 为 `false` | **以 checks 为准**:本阶段按未通过展示,在体检块点名这份 | | `done-legacy` | `current_stage` 以 `_done` 结尾,但不是 `ship_done` / `deploy_done`(旧版写法) | 按「已走完」展示但标「是否已发布未知」;`/pdlc-ship` 会列出来请人确认 | | `second-state-dir` | 仓库根另有一个 `.pdlc-state/` | 本命令只读 `docs/.pdlc-state/`,提示两处并存 | <!-- finding-codes:end -->
**「不入图」不是丢弃**:关系目标是散文时,把它硬解读成某个 feature,等于把猜测升格成结构—— 影响半径会因此多出一条本不存在的强依赖。宁可在体检块里原样列出,交给人判断。
阶段短名全集
`history[].stage` 与 `last_phase_result.stage` 只能取下表的短名——每个短名由对应命令写入, 等于该命令 frontmatter 的 `stage:`。别名只为**读**旧数据时容忍,归一化必须披露;**写**的时候一律用短名。
<!-- stage-names:start --> | 短名 | 由哪个命令写入 | 已知别名(读侧可归一化,须披露) | |---|---|---| | `requirements` | `pdlc-prd` | `prd` | | `design` | `pdlc-design` | | | `tdd` | `pdlc-tdd` | | | `impl` | `pdlc-implement` | `implement, implementation` | | `review` | `pdlc-review` | | | `e2e` | `pdlc-e2e` | | | `ship` | `pdlc-ship` | | | `deploy` | `pdlc-deploy` | | | `fix` | `pdlc-fix` | `bugfix` | | `refactor` | `pdlc-refactor` | | | `feature` | `pdlc-feature` | | <!-- stage-names:end -->
> 旧版 `/pdlc-task` 曾写入 `task`,现已不写(任务看板不是阶段,写入会覆盖主链路的 `current_stage` / `next_step`)。 > 见到 `current_stage=task` 说明那次任务操作把功能的阶段盖掉了:体检报 `current_stage-unknown`,向用户说明后按 `history` 里最后一个主链路阶段理解进度。
3. 体检块的格式(放在输出最前面)
⚠️ 输入契约体检:<N> 份状态文件,<M> 处偏差——以下结论建立在这些处理之上
· 3/5 份缺 created_at → 时间窗改用 history 末条 done_at
· 阶段别名 prd→requirements ×1、implementation→impl ×2 → 已归一化计数
· 5/5 份含 terminal_state → 已忽略(判终态只看 current_stage)
· 2 份的 done_at 只有日期 → 阶段耗时记「不可测」
- **按「偏差类 × 份数 × 处理方式」汇总**,不要把脚本输出原样倒出来
- **放最前面,不放末尾**——结论会被单独引用,偏差得跟着结论走;写在报告末尾的偏差表,
读者看到的时候已经信了前面的数字
- **计数照抄脚本输出,不要自己重数**:按代码计数用 `cut -f2 | sort | uniq -c`,份数按第一列去重。
真机验证时脚本报 25 处无时刻的时间戳,报告正文却自己数成了 26——同一份报告里两个数字对不上, 读者就不知道该信哪个
- 同一次输出里,前面披露了「已忽略 X」,后面的计算就不得再用 X
4. 判终态的唯一依据 ⛔
**已抵达终态 ⇔ `current_stage` 以 `_done` 结尾**(终态即「已发布」:`/pdlc-ship` 写 `ship_done`、`/pdlc-deploy` 写 `deploy_done`;旧版本留下的 `feature_done` / `fix_done` / `review_done` 也按终态读)。
- **不看 `terminal_state`**。状态机实例本就没有这个字段。skill frontmatter 里的 `terminal_state:`
说的是「这个命令走完后**应当**到达的终态名」——是**目标**,不是**事实**。拿它判终态, 等于把「打算完成」当成「已经完成」。
- **不用封闭列表**(如只认 `[feature_done, fix_done]`)。封闭列表会把合法的终态漏判成「进行中」,
逼着读侧去别的字段里找答案——而最顺手的那个字段恰好就是 `terminal_state`。
- **`next_step` 为 `null` 也不等于终态**:`/pdlc-task` 写的 `next_step` 恒为 `null`,却未必完成;空的或残缺的状态文件也读得出 `null`。
<!-- @include-end templates/prompts/state-read.md -->
段一:聚合统计
1.1 参数解析
- `--range <N>d | all`:时间窗口(默认 `30d`)
- `--feature <feature-id>`:只看单个功能的时间线(输出 history 详情而非聚合)
1.2 读取与过滤
1. 列出 `docs/.pdlc-state/*.json`(跳过 `_` 前缀的索引文件与 `statusline.json`) 2. **跑契约体检**(见上方「读状态机之前:先做契约体检」),结果写进报告开头的「输入契约体检」节 3. 按 `created_at` 过滤到时间窗口内。缺 `created_at` 的文件改用 `history` 末条 `done_at`——**这是口径替换,必须计入体检块**,不得静默 4. 解析每份文件的 `history` 数组
1.3 计算指标
对时间窗口内的所有功能聚合:
**交付量**:
- 完成功能数:`current_stage` 以
Read more
name: pdlc-retro description: 迭代复盘——读状态机历史出趋势报告 argument-hint: "[--range 7d|30d|all] [--feature <feature-id>]" allowed-tools: Read, Glob, Bash layer: 2 stage: retro produces: - docs/07_reviews/retro/<YYYY-MM>-retro.md requires: - docs/.pdlc-state/ next_step: null terminal_state: retro_done
PDLC 迭代复盘
读取 `docs/.pdlc-state/` 下的状态机历史,按时间范围聚合,生成趋势复盘报告。
<!-- @include templates/prompts/iron-law-tool.md(已内联于下方,无需另读) --> ⛔ **IRON LAW · 不可违反的硬门禁(工具型命令)**
本命令不是主链路上的阶段:**不追加 `history`,不改 `current_stage` / `next_step` / `last_phase_result`**,也不为此分配功能ID。以下规则不可协商:
1. **文件必须落盘**:正文要求创建的文件必须作为实际文件写入磁盘,不可仅在对话中输出(只要求在对话里输出的,照正文办)。 2. **自检必须执行**:产出后按本命令的自检项逐项核对,不得以"已经很好了"为由跳过。 3. **防循环**:修复只做一次,不递归。修不了的问题记进报告,继续往下走。
**违反任一条 = 立即中止当前命令,输出违规详情,等待人工介入。** <!-- @include-end templates/prompts/iron-law-tool.md -->
<!-- @include templates/prompts/state-read.md(已内联于下方,无需另读) -->
读状态机之前:先做契约体检
`docs/.pdlc-state/*.json` 不一定都是 `/pdlc-*` 命令写出来的——手写的、旧版本写的、照着格式仿写的 都可能混在里面。字段对不上契约时,**不许边猜边算、再把猜出来的东西当结论**。先体检,再计算; 体检发现的每一处偏差,都必须出现在输出的**最前面**。
1. 跑体检
体检脚本随本 skill 一起分发,就在本 skill 目录下:`scripts/pdlc-state-lint.sh`(运行时会告知本 skill 的 所在目录)。它与正在运行的 skill 同一版本,体检规则——合法阶段名、偏差代码——与下方 §2 一致。
在项目根执行 `bash <本 skill 目录>/scripts/pdlc-state-lint.sh .`。stdout 每行一条偏差:`文件<TAB>代码<TAB>说明`。退出码三态:
- `0` 已体检、全部合契约 → 照常出结论,不输出体检块
- `1` 已体检、有偏差 → 照常计算,但输出最前面必须先放「输入契约体检」块(见 §3)
- `2` 无法体检(缺 jq / 无状态目录)→ 体检块写「无法体检:<原因>」,**不得当成合契约**
体检脚本不可用时(找不到,或当前平台没有随 skill 分发它),按 §2 的规则逐条人工核对, 体检块首行注明「体检脚本不可用,以下为人工核对」。
2. 体检规则(偏差代码)
读侧跳过 `_` 前缀的索引文件(如 `_relations.json`)与 `statusline.json`,它们不是功能状态机。
<!-- finding-codes:start --> | 代码 | 判定 | 读侧怎么处理 | |---|---|---| | `json-invalid` | 文件不是合法 JSON | 跳过该文件,列入体检块 | | `missing-field` | 缺 `feature_id` / `current_stage` / `history` / `next_step` / `created_at` 之一 | 用到该字段的指标,对这份文件记「不可判」 | | `field-type-invalid` | 字段在、类型不对(如 `current_stage` 不是字符串、`history` 不是数组、`last_phase_result` 不是对象、history 条目不是对象) | **按缺失处理**:用到它的指标对这份文件记「不可判」。类型不对的时间戳不再另报 `timestamp-no-time` | | `missing-last_phase_result` | 缺 `last_phase_result`(多为旧文件) | 不推断本阶段结果与 checks | | `terminal_state-in-instance` | 实例里出现 `terminal_state` | **忽略该字段**,判终态只看 §4 | | `non-contract-field` | 其它表外顶层字段(如 `title`) | 忽略 | | `stage-alias` | `stage` 用了已知别名 | 可按说明里的 `别名→短名` 归一化计数,**但必须在体检块写明归一化了哪些** | | `stage-unknown` | `stage` 既不是短名也不是已知别名 | 单独成行,不并入任何阶段 | | `current_stage-unknown` | `current_stage` 既不是短名,也不以 `_done` 结尾 | 该功能归入「❓ 异常」 | | `next_step-not-command` | `next_step` 不是纯命令名(如带散文后缀) | 按原文展示,不据此推断下一步 | | `timestamp-no-time` | 时间戳只有日期、没有时刻 | 依赖它的**耗时类指标记「不可测」**,不得算成 0 | | `relations-not-object` | `relations` 不是六键对象(如数组) | 这份文件的关系**不入图**,列入体检块 | | `relations-unknown-type` | 关系键不在六类之内 | 该键下的关系不入图 | | `relations-target-not-id` | 关系目标不是 feature ID(散文、模块路径等) | 该条不入图 | | `relations-dangling` | 目标 ID 没有对应的状态文件 | 标「悬空」,不参与影响半径计算 | | `id-prefix-mismatch` | 走的是修复流程(有 `fix` 阶段),ID 却不以 `B` 开头 | 缺陷计数按 ID 前缀的契约口径算,同时在体检块点名这份 | | `checks-key-unknown` | `last_phase_result.checks` 里有状态机不认的键(如照抄 yml 的 `unit` / `lint`) | 该键当作不存在:对应维度记「无法判定」,**不**按键名猜它对应哪个 check | | `checks-value-invalid` | check 的值不是布尔也不是 `null`(如 `"4 passed, 1 failed"`) | 该项记「无法判定」,不从字符串里解析结论 | | `ok-checks-mismatch` | `ok=true`,但有 check 为 `false` | **以 checks 为准**:本阶段按未通过展示,在体检块点名这份 | | `done-legacy` | `current_stage` 以 `_done` 结尾,但不是 `ship_done` / `deploy_done`(旧版写法) | 按「已走完」展示但标「是否已发布未知」;`/pdlc-ship` 会列出来请人确认 | | `second-state-dir` | 仓库根另有一个 `.pdlc-state/` | 本命令只读 `docs/.pdlc-state/`,提示两处并存 | <!-- finding-codes:end -->
**「不入图」不是丢弃**:关系目标是散文时,把它硬解读成某个 feature,等于把猜测升格成结构—— 影响半径会因此多出一条本不存在的强依赖。宁可在体检块里原样列出,交给人判断。
阶段短名全集
`history[].stage` 与 `last_phase_result.stage` 只能取下表的短名——每个短名由对应命令写入, 等于该命令 frontmatter 的 `stage:`。别名只为**读**旧数据时容忍,归一化必须披露;**写**的时候一律用短名。
<!-- stage-names:start --> | 短名 | 由哪个命令写入 | 已知别名(读侧可归一化,须披露) | |---|---|---| | `requirements` | `pdlc-prd` | `prd` | | `design` | `pdlc-design` | | | `tdd` | `pdlc-tdd` | | | `impl` | `pdlc-implement` | `implement, implementation` | | `review` | `pdlc-review` | | | `e2e` | `pdlc-e2e` | | | `ship` | `pdlc-ship` | | | `deploy` | `pdlc-deploy` | | | `fix` | `pdlc-fix` | `bugfix` | | `refactor` | `pdlc-refactor` | | | `feature` | `pdlc-feature` | | <!-- stage-names:end -->
> 旧版 `/pdlc-task` 曾写入 `task`,现已不写(任务看板不是阶段,写入会覆盖主链路的 `current_stage` / `next_step`)。 > 见到 `current_stage=task` 说明那次任务操作把功能的阶段盖掉了:体检报 `current_stage-unknown`,向用户说明后按 `history` 里最后一个主链路阶段理解进度。
3. 体检块的格式(放在输出最前面)
⚠️ 输入契约体检:<N> 份状态文件,<M> 处偏差——以下结论建立在这些处理之上 · 3/5 份缺 created_at → 时间窗改用 history 末条 done_at · 阶段别名 prd→requirements ×1、implementation→impl ×2 → 已归一化计数 · 5/5 份含 terminal_state → 已忽略(判终态只看 current_stage) · 2 份的 done_at 只有日期 → 阶段耗时记「不可测」
- **按「偏差类 × 份数 × 处理方式」汇总**,不要把脚本输出原样倒出来
- **放最前面,不放末尾**——结论会被单独引用,偏差得跟着结论走;写在报告末尾的偏差表,
读者看到的时候已经信了前面的数字
- **计数照抄脚本输出,不要自己重数**:按代码计数用 `cut -f2 | sort | uniq -c`,份数按第一列去重。
真机验证时脚本报 25 处无时刻的时间戳,报告正文却自己数成了 26——同一份报告里两个数字对不上, 读者就不知道该信哪个
- 同一次输出里,前面披露了「已忽略 X」,后面的计算就不得再用 X
4. 判终态的唯一依据 ⛔
**已抵达终态 ⇔ `current_stage` 以 `_done` 结尾**(终态即「已发布」:`/pdlc-ship` 写 `ship_done`、`/pdlc-deploy` 写 `deploy_done`;旧版本留下的 `feature_done` / `fix_done` / `review_done` 也按终态读)。
- **不看 `terminal_state`**。状态机实例本就没有这个字段。skill frontmatter 里的 `terminal_state:`
说的是「这个命令走完后**应当**到达的终态名」——是**目标**,不是**事实**。拿它判终态, 等于把「打算完成」当成「已经完成」。
- **不用封闭列表**(如只认 `[feature_done, fix_done]`)。封闭列表会把合法的终态漏判成「进行中」,
逼着读侧去别的字段里找答案——而最顺手的那个字段恰好就是 `terminal_state`。
- **`next_step` 为 `null` 也不等于终态**:`/pdlc-task` 写的 `next_step` 恒为 `null`,却未必完成;空的或残缺的状态文件也读得出 `null`。
<!-- @include-end templates/prompts/state-read.md -->
段一:聚合统计
1.1 参数解析
- `--range <N>d | all`:时间窗口(默认 `30d`)
- `--feature <feature-id>`:只看单个功能的时间线(输出 history 详情而非聚合)
1.2 读取与过滤
1. 列出 `docs/.pdlc-state/*.json`(跳过 `_` 前缀的索引文件与 `statusline.json`) 2. **跑契约体检**(见上方「读状态机之前:先做契约体检」),结果写进报告开头的「输入契约体检」节 3. 按 `created_at` 过滤到时间窗口内。缺 `created_at` 的文件改用 `history` 末条 `done_at`——**这是口径替换,必须计入体检块**,不得静默 4. 解析每份文件的 `history` 数组
1.3 计算指标
对时间窗口内的所有功能聚合:
**交付量**:
- 完成功能数:`current_stage` 以
Author: kanfu-panda Repo: github.com/kanfu-panda/pdlc-skills License: MIT pdlc-skills is built on one rule: an AI saying "done" doesn't count — exit codes do.
Other skills on pdlc.
pdlc-adopt
已有代码、还没有 PDLC 文档的老项目接入——只读扫描后逆向生成基线文档并做健康检查,不改代码;空目录新项目用 /pdlc-bootstrap
pdlc-bootstrap
空目录从一句话描述初始化新项目(服务 / 应用拆分、技术栈、目录骨架与 PDLC 文档目录);已有代码的项目用 /pdlc-adopt
More skills from other plugins.
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
ask-matt
Ask which skill or flow fits your situation. A router over the skills in this repo.
Questions about pdlc-retro.
- Which coding agents can use pdlc-retro?
- pdlc-retro installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

