sillyspec 3.23.5 → 3.23.7

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.
@@ -1,86 +1,86 @@
1
- ---
2
- name: sillyspec:brainstorm
3
- description: 用于正式开始开发前的需求澄清和技术方案设计。适合用户提出新功能、新模块、架构调整、复杂改造,或说"先做需求分析、输出技术方案、创建变更前先梳理、帮我设计下"。产出结构化方案(design/proposal/requirements/tasks 四件套),但不直接写代码。
4
- ---
5
-
6
- ## 交互规范
7
-
8
- **当需要用户从多个选项中做出选择时,必须使用 Claude Code 内置的 AskUserQuestion 工具,将选项以参数传入。** 不要用编号列表让用户手动输入数字。
9
-
10
- ## 何时使用
11
-
12
- - 用户提出新功能、新模块、架构调整、复杂改造
13
- - 用户说"先做需求分析、输出技术方案、创建变更前先梳理、帮我设计下"
14
- - 产出:`design.md` + `proposal.md` + `requirements.md` + `tasks.md`(四件套),不写代码
15
-
16
- ## 多变更说明
17
-
18
- 项目有多个活跃变更(`.sillyspec/changes/` 下有多个目录)时,所有 `sillyspec run` 命令需加 `--change <变更名>` 指定操作目标;只有一个变更时可省略(CLI 自动检测)。建议变更名格式:`YYYY-MM-DD-<简短描述>`。
19
-
20
- ## 步骤生命周期(所有阶段通用)
21
-
22
- > `sillyspec brainstorm` 是 `sillyspec run brainstorm` 的顶层别名,两者等价。
23
-
24
- ```bash
25
- sillyspec run brainstorm # 输出当前步骤 prompt
26
- sillyspec run brainstorm --done --output "摘要" # 完成当前步骤(--input "用户原话" 记录输入)
27
- sillyspec run brainstorm --status # 查看阶段进度
28
- sillyspec run brainstorm --skip # 跳过可选步骤
29
- sillyspec run brainstorm --reset # 重置阶段(从头开始)
30
- sillyspec run brainstorm --reopen --from-step N # 重新打开已完成阶段修订(N=序号或名称)
31
- sillyspec run brainstorm --wait --reason "..." --options "A,B" # 暂停等用户决策
32
- sillyspec run brainstorm --continue --answer "..." # 恢复等待中的步骤
33
- sillyspec run brainstorm --done --answer "..." --output "..." # 一步完成 wait+done
34
- ```
35
-
36
- ## 通用参数(所有阶段适用)
37
-
38
- | 参数 | 说明 |
39
- |---|---|
40
- | `--change <名>` | 指定变更名(多活跃变更必填,单变更可省略自动检测) |
41
- | `--spec-dir <path>` | 指定规范目录(默认 `<项目>/.sillyspec`) |
42
- | `--non-interactive` | CI/脚本下禁用交互式 prompt |
43
- | `--interactive` | 强制交互(即便 stdin 非 TTY) |
44
- | `--skip-approval` | 跳过审批/校验门控(需明确意图) |
45
- | `--json` | 输出 JSON(程序化读取) |
46
-
47
- ## brainstorm 特有:requiresWait 步骤
48
-
49
- 某些步骤(如"对话式探索")需要用户输入。两种方式:
50
-
51
- > 自动去重:若前置 step 已对同一问题(waitReason 归一化后相同,如"确认设计方案" vs "最终确认设计方案")确认过,后续重复 wait 会自动跳过,无需再 `--wait`。
52
-
53
- - **方式一(推荐)**:AI 自行与用户交互后,一步完成:
54
- ```bash
55
- sillyspec run brainstorm --done --change <名> --answer "用户回答" --output "需求已澄清"
56
- ```
57
- - **方式二**:分步——先 `--wait` 记录等待,再 `--continue --answer`,最后 `--done`:
58
- ```bash
59
- sillyspec run brainstorm --wait --change <名> --reason "等待用户回答" --output "探索问题"
60
- sillyspec run brainstorm --continue --answer "用户回答" --change <名>
61
- sillyspec run brainstorm --done --change <名> --output "需求已澄清"
62
- ```
63
-
64
- ## 阶段流转
65
-
66
- ```
67
- ┌─ scale=large → plan(四件套齐)
68
- scan → brainstorm ┤
69
- └─ scale=small → quick --linked-changes(仅 design.md)
70
- ```
71
-
72
- brainstorm 完成时按 design.md frontmatter 的 `scale` 分叉:
73
- - **large**(多文件/跨模块/有状态机或 schema 变更):四件套齐 + 自审通过 `sillyspec run plan --change <变更名>`
74
- - **small**(≤2 文件、单模块、无跨模块依赖):仅生成 design.md → `sillyspec run quick --linked-changes <变更名>`
75
-
76
- > 规模由 AI 在 brainstorm 最后一步评估并写入 design.md frontmatter。判错可手动改 `scale` 后再跑相应阶段。
77
-
78
- ## 铁律
79
-
80
- - **必须用 exec 工具(shell)执行 CLI,不要自己编造流程**
81
- - 只做当前步骤 prompt 描述的操作,不跳过、不自行扩展
82
- - 产物写入 CLI 输出的 `changeDir` 目录(如 `<changeDir>/design.md`),不要自己拼路径
83
- - 完成后立即 `--done`,不跳过
84
-
85
- ## 用户指令
86
- $ARGUMENTS
1
+ ---
2
+ name: sillyspec:brainstorm
3
+ description: 用于正式开始开发前的需求澄清和技术方案设计。适合用户提出新功能、新模块、架构调整、复杂改造,或说"先做需求分析、输出技术方案、创建变更前先梳理、帮我设计下"。产出结构化方案(design/proposal/requirements/tasks 四件套),但不直接写代码。
4
+ ---
5
+
6
+ ## 交互规范
7
+
8
+ **当需要用户从多个选项中做出选择时,必须使用 Claude Code 内置的 AskUserQuestion 工具,将选项以参数传入。** 不要用编号列表让用户手动输入数字。
9
+
10
+ ## 何时使用
11
+
12
+ - 用户提出新功能、新模块、架构调整、复杂改造
13
+ - 用户说"先做需求分析、输出技术方案、创建变更前先梳理、帮我设计下"
14
+ - 产出:`design.md` + `proposal.md` + `requirements.md` + `tasks.md`(四件套),不写代码
15
+
16
+ ## 多变更说明
17
+
18
+ 项目有多个活跃变更(`.sillyspec/changes/` 下有多个目录)时,所有 `sillyspec run` 命令需加 `--change <变更名>` 指定操作目标;只有一个变更时可省略(CLI 自动检测)。建议变更名格式:`YYYY-MM-DD-<简短描述>`。
19
+
20
+ ## 步骤生命周期(所有阶段通用)
21
+
22
+ > `sillyspec brainstorm` 是 `sillyspec run brainstorm` 的顶层别名,两者等价。
23
+
24
+ ```bash
25
+ sillyspec run brainstorm # 输出当前步骤 prompt
26
+ sillyspec run brainstorm --done --output "摘要" # 完成当前步骤(--input "用户原话" 记录输入)
27
+ sillyspec run brainstorm --status # 查看阶段进度
28
+ sillyspec run brainstorm --skip # 跳过可选步骤
29
+ sillyspec run brainstorm --reset # 重置阶段(从头开始)
30
+ sillyspec run brainstorm --reopen --from-step N # 重新打开已完成阶段修订(N=序号或名称)
31
+ sillyspec run brainstorm --wait --reason "..." --options "A,B" # 暂停等用户决策
32
+ sillyspec run brainstorm --continue --answer "..." # 恢复等待中的步骤
33
+ sillyspec run brainstorm --done --answer "..." --output "..." # 一步完成 wait+done
34
+ ```
35
+
36
+ ## 通用参数(所有阶段适用)
37
+
38
+ | 参数 | 说明 |
39
+ |---|---|
40
+ | `--change <名>` | 指定变更名(多活跃变更必填,单变更可省略自动检测) |
41
+ | `--spec-dir <path>` | 指定规范目录(默认 `<项目>/.sillyspec`) |
42
+ | `--non-interactive` | CI/脚本下禁用交互式 prompt |
43
+ | `--interactive` | 强制交互(即便 stdin 非 TTY) |
44
+ | `--skip-approval` | 跳过审批/校验门控(需明确意图) |
45
+ | `--json` | 输出 JSON(程序化读取) |
46
+
47
+ ## brainstorm 特有:requiresWait 步骤
48
+
49
+ 某些步骤(如"对话式探索")需要用户输入。两种方式:
50
+
51
+ > 自动去重:若前置 step 已对同一问题(waitReason 归一化后相同,如"确认设计方案" vs "最终确认设计方案")确认过,后续重复 wait 会自动跳过,无需再 `--wait`。
52
+
53
+ - **方式一(推荐)**:AI 自行与用户交互后,一步完成:
54
+ ```bash
55
+ sillyspec run brainstorm --done --change <名> --answer "用户回答" --output "需求已澄清"
56
+ ```
57
+ - **方式二**:分步——先 `--wait` 记录等待,再 `--continue --answer`,最后 `--done`:
58
+ ```bash
59
+ sillyspec run brainstorm --wait --change <名> --reason "等待用户回答" --output "探索问题"
60
+ sillyspec run brainstorm --continue --answer "用户回答" --change <名>
61
+ sillyspec run brainstorm --done --change <名> --output "需求已澄清"
62
+ ```
63
+
64
+ ## 阶段流转
65
+
66
+ ```
67
+ ┌─ scale=large → plan(四件套齐)
68
+ scan → brainstorm ┤
69
+ └─ scale=small → quick --linked-changes(仅 design.md)
70
+ ```
71
+
72
+ brainstorm 完成时按 design.md frontmatter 的 `scale` 分叉:
73
+ - **large**(多文件/跨模块/有状态机或 schema 变更):四件套齐 + Design Grill 审查通过(tier=independent 时由独立审查子代理产出 stage review.json)→ `sillyspec run plan --change <变更名>`
74
+ - **small**(≤2 文件、单模块、无跨模块依赖):仅生成 design.md → `sillyspec run quick --linked-changes <变更名>`
75
+
76
+ > 规模由 AI 在 brainstorm 最后一步评估并写入 design.md frontmatter。判错可手动改 `scale` 后再跑相应阶段。
77
+
78
+ ## 铁律
79
+
80
+ - **必须用 exec 工具(shell)执行 CLI,不要自己编造流程**
81
+ - 只做当前步骤 prompt 描述的操作,不跳过、不自行扩展
82
+ - 产物写入 CLI 输出的 `changeDir` 目录(如 `<changeDir>/design.md`),不要自己拼路径
83
+ - 完成后立即 `--done`,不跳过
84
+
85
+ ## 用户指令
86
+ $ARGUMENTS
@@ -1,74 +1,74 @@
1
- ---
2
- name: sillyspec:plan
3
- description: 用于把 design 拆解为可执行的实现计划。适合用户说"拆任务、做计划、排 wave、规划实现步骤"。产出 plan.md(Wave 分组 + Task 列表 + 依赖关系)。
4
- ---
5
-
6
- ## 何时使用
7
-
8
- - 用户说"拆任务、做计划、排 wave、规划实现步骤"
9
- - 把 brainstorm 的 design.md 拆成可执行的 Wave + Task
10
- - 产出:`plan.md`(Wave 分组 + Task 列表 + 依赖关系),可能含 `tasks/task-NN.md` 任务蓝图
11
-
12
- ## 多变更说明
13
-
14
- 项目有多个活跃变更(`.sillyspec/changes/` 下有多个目录)时,所有 `sillyspec run` 命令需加 `--change <变更名>` 指定操作目标;只有一个变更时可省略(CLI 自动检测)。
15
-
16
- ## 步骤生命周期(所有阶段通用)
17
-
18
- > `sillyspec plan` 是 `sillyspec run plan` 的顶层别名,两者等价。
19
-
20
- ```bash
21
- sillyspec run plan # 输出当前步骤 prompt
22
- sillyspec run plan --done --output "摘要" # 完成当前步骤(--input "用户原话" 记录输入)
23
- sillyspec run plan --status # 查看阶段进度
24
- sillyspec run plan --skip # 跳过可选步骤
25
- sillyspec run plan --reset # 重置阶段(从头开始)
26
- sillyspec run plan --reopen --from-step N # 重新打开已完成阶段修订(N=序号或名称)
27
- sillyspec run plan --wait --reason "..." --options "A,B" # 暂停等用户决策
28
- sillyspec run plan --continue --answer "..." # 恢复等待中的步骤
29
- sillyspec run plan --done --answer "..." --output "..." # 一步完成 wait+done
30
- ```
31
-
32
- ## 通用参数(所有阶段适用)
33
-
34
- | 参数 | 说明 |
35
- |---|---|
36
- | `--change <名>` | 指定变更名(多活跃变更必填,单变更可省略自动检测) |
37
- | `--spec-dir <path>` | 指定规范目录(默认 `<项目>/.sillyspec`) |
38
- | `--non-interactive` | CI/脚本下禁用交互式 prompt |
39
- | `--interactive` | 强制交互(即便 stdin 非 TTY) |
40
- | `--skip-approval` | 跳过审批/校验门控(需明确意图) |
41
- | `--json` | 输出 JSON(程序化读取) |
42
-
43
- ## plan 特有
44
-
45
- ### 动态步骤
46
-
47
- plan 的步骤是动态的:`generate_plan` 步骤完成后,CLI 会从刚生成的 `plan.md` 解析出 task,自动插入"任务蓝图协调器"步骤(per-task)。这是正常行为,不要手动添加。
48
-
49
- ### 契约门控(阻断完成)
50
-
51
- - **plan 启动前**:CLI 校验 `design.md` 是否满足 plan 契约(缺文件变更清单/风险登记/自审章节会阻断)。若失败需先 `sillyspec run brainstorm --reopen --from-step N` 修订设计。
52
- - **plan 完成时**:CLI 校验 `plan.md` 是否满足 execute 契约(Wave 结构、task 引用等)。失败会阻断完成,提示修复后重新 `--done`。
53
-
54
- ### 生产接线路径检查
55
-
56
- plan 完成校验会检查:design 提到入口文件(cli.ts/main.ts/server.ts 等)但 task 的 allowed_paths 不含该文件 → 报 error。若确实不需要改入口,在 design.md 明示理由。
57
-
58
- ## 阶段流转
59
-
60
- ```
61
- brainstorm → plan → execute
62
- ```
63
-
64
- plan 完成后(plan.md 通过 execute 契约校验),运行 `sillyspec run execute --change <变更名>` 开始实现。
65
-
66
- ## 铁律
67
-
68
- - **必须用 exec 工具(shell)执行 CLI,不要自己编造流程**
69
- - 只做当前步骤 prompt 描述的操作,不跳过、不自行扩展
70
- - plan.md 是任务完成的唯一真相源,task 拆解粒度要均匀、依赖要明确
71
- - 完成后立即 `--done`,不跳过
72
-
73
- ## 用户指令
74
- $ARGUMENTS
1
+ ---
2
+ name: sillyspec:plan
3
+ description: 用于把 design 拆解为可执行的实现计划。适合用户说"拆任务、做计划、排 wave、规划实现步骤"。产出 plan.md(Wave 分组 + Task 列表 + 依赖关系)。
4
+ ---
5
+
6
+ ## 何时使用
7
+
8
+ - 用户说"拆任务、做计划、排 wave、规划实现步骤"
9
+ - 把 brainstorm 的 design.md 拆成可执行的 Wave + Task
10
+ - 产出:`plan.md`(Wave 分组 + Task 列表 + 依赖关系),可能含 `tasks/task-NN.md` 任务蓝图
11
+
12
+ ## 多变更说明
13
+
14
+ 项目有多个活跃变更(`.sillyspec/changes/` 下有多个目录)时,所有 `sillyspec run` 命令需加 `--change <变更名>` 指定操作目标;只有一个变更时可省略(CLI 自动检测)。
15
+
16
+ ## 步骤生命周期(所有阶段通用)
17
+
18
+ > `sillyspec plan` 是 `sillyspec run plan` 的顶层别名,两者等价。
19
+
20
+ ```bash
21
+ sillyspec run plan # 输出当前步骤 prompt
22
+ sillyspec run plan --done --output "摘要" # 完成当前步骤(--input "用户原话" 记录输入)
23
+ sillyspec run plan --status # 查看阶段进度
24
+ sillyspec run plan --skip # 跳过可选步骤
25
+ sillyspec run plan --reset # 重置阶段(从头开始)
26
+ sillyspec run plan --reopen --from-step N # 重新打开已完成阶段修订(N=序号或名称)
27
+ sillyspec run plan --wait --reason "..." --options "A,B" # 暂停等用户决策
28
+ sillyspec run plan --continue --answer "..." # 恢复等待中的步骤
29
+ sillyspec run plan --done --answer "..." --output "..." # 一步完成 wait+done
30
+ ```
31
+
32
+ ## 通用参数(所有阶段适用)
33
+
34
+ | 参数 | 说明 |
35
+ |---|---|
36
+ | `--change <名>` | 指定变更名(多活跃变更必填,单变更可省略自动检测) |
37
+ | `--spec-dir <path>` | 指定规范目录(默认 `<项目>/.sillyspec`) |
38
+ | `--non-interactive` | CI/脚本下禁用交互式 prompt |
39
+ | `--interactive` | 强制交互(即便 stdin 非 TTY) |
40
+ | `--skip-approval` | 跳过审批/校验门控(需明确意图) |
41
+ | `--json` | 输出 JSON(程序化读取) |
42
+
43
+ ## plan 特有
44
+
45
+ ### 动态步骤
46
+
47
+ plan 的步骤是动态的:`generate_plan`(生成分级计划)→ `review_plan`(审查计划,按规模分级:tier=self 当前 agent 自审 / tier=independent 启动独立审查子代理产出 stage review.json,避免生成+自审同一次输出的偏差)→ CLI `plan.md` 解析出 task 自动插入"任务蓝图协调器"步骤(per-task)。这是正常行为,不要手动添加。
48
+
49
+ ### 契约门控(阻断完成)
50
+
51
+ - **plan 启动前**:CLI 校验 `design.md` 是否满足 plan 契约(缺文件变更清单/风险登记/自审章节会阻断)。若失败需先 `sillyspec run brainstorm --reopen --from-step N` 修订设计。
52
+ - **plan 完成时**:CLI 校验 `plan.md` 是否满足 execute 契约(Wave 结构、task 引用等)。失败会阻断完成,提示修复后重新 `--done`。
53
+
54
+ ### 生产接线路径检查
55
+
56
+ plan 完成校验会检查:design 提到入口文件(cli.ts/main.ts/server.ts 等)但 task 的 allowed_paths 不含该文件 → 报 error。若确实不需要改入口,在 design.md 明示理由。
57
+
58
+ ## 阶段流转
59
+
60
+ ```
61
+ brainstorm → plan → execute
62
+ ```
63
+
64
+ plan 完成后(plan.md 通过 execute 契约校验),运行 `sillyspec run execute --change <变更名>` 开始实现。
65
+
66
+ ## 铁律
67
+
68
+ - **必须用 exec 工具(shell)执行 CLI,不要自己编造流程**
69
+ - 只做当前步骤 prompt 描述的操作,不跳过、不自行扩展
70
+ - plan.md 是任务完成的唯一真相源,task 拆解粒度要均匀、依赖要明确
71
+ - 完成后立即 `--done`,不跳过
72
+
73
+ ## 用户指令
74
+ $ARGUMENTS
@@ -1,61 +1,61 @@
1
- ---
2
- name: sillyspec:propose
3
- description: 生成结构化规范 — proposal + design + tasks。适合用户说"生成规范、补全四件套、propose"。产出 proposal.md + design.md + tasks.md(+ 可能的 requirements.md)。
4
- ---
5
-
6
- ## 何时使用
7
-
8
- - 用户说"生成规范、补全四件套、propose"
9
- - 已有零散设计/需求,需补全结构化规范四件套
10
- - 产出:`proposal.md` + `design.md` + `tasks.md`(+ 可选 `requirements.md`)
11
-
12
- > 注:propose 与 brainstorm 都产出四件套。brainstorm 是从需求开始的完整设计流程;propose 更偏"补全/生成规范"。新需求一般走 brainstorm。
13
-
14
- ## 多变更说明
15
-
16
- 项目有多个活跃变更(`.sillyspec/changes/` 下有多个目录)时,所有 `sillyspec run` 命令需加 `--change <变更名>` 指定操作目标;只有一个变更时可省略(CLI 自动检测)。
17
-
18
- ## 步骤生命周期(所有阶段通用)
19
-
20
- > `sillyspec run propose` 需用 `run` 形式(propose 无顶层别名,与 brainstorm/plan 等不同)。
21
-
22
- ```bash
23
- sillyspec run propose # 输出当前步骤 prompt
24
- sillyspec run propose --done --output "摘要" # 完成当前步骤
25
- sillyspec run propose --status # 查看阶段进度
26
- sillyspec run propose --skip # 跳过可选步骤
27
- sillyspec run propose --reset # 重置阶段(从头开始)
28
- sillyspec run propose --reopen --from-step N # 重新打开已完成阶段修订
29
- ```
30
-
31
- ## 通用参数(所有阶段适用)
32
-
33
- | 参数 | 说明 |
34
- |---|---|
35
- | `--change <名>` | 指定变更名(多活跃变更必填,单变更可省略自动检测) |
36
- | `--spec-dir <path>` | 指定规范目录(默认 `<项目>/.sillyspec`) |
37
- | `--non-interactive` | CI/脚本下禁用交互式 prompt |
38
- | `--skip-approval` | 跳过审批/校验门控 |
39
- | `--json` | 输出 JSON(程序化读取) |
40
-
41
- ## propose 特有:自检门控
42
-
43
- propose 含自检步骤,完成时校验四件套文档齐全 + 内容章节(如 proposal 的 Non-Goals、design 的文件变更清单/风险登记/自审)。缺失会阻断完成。
44
-
45
- ## 阶段流转
46
-
47
- ```
48
- (零散设计/需求) → propose → plan
49
- ```
50
-
51
- propose 完成后(四件套齐 + 自检通过),运行 `sillyspec run plan --change <变更名>` 进入实现计划。
52
-
53
- ## 铁律
54
-
55
- - **必须用 exec 工具(shell)执行 CLI,不要自己编造流程**
56
- - 只做当前步骤 prompt 描述的操作,不跳过
57
- - 产物写入 CLI 输出的 `changeDir` 目录
58
- - 完成后立即 `--done`,不跳过
59
-
60
- ## 用户指令
61
- $ARGUMENTS
1
+ ---
2
+ name: sillyspec:propose
3
+ description: 生成结构化规范 — proposal + design + tasks。适合用户说"生成规范、补全四件套、propose"。产出 proposal.md + design.md + tasks.md(+ 可能的 requirements.md)。
4
+ ---
5
+
6
+ ## 何时使用
7
+
8
+ - 用户说"生成规范、补全四件套、propose"
9
+ - 已有零散设计/需求,需补全结构化规范四件套
10
+ - 产出:`proposal.md` + `design.md` + `tasks.md`(+ 可选 `requirements.md`)
11
+
12
+ > 注:propose 与 brainstorm 都产出四件套。brainstorm 是从需求开始的完整设计流程;propose 更偏"补全/生成规范"。新需求一般走 brainstorm。
13
+
14
+ ## 多变更说明
15
+
16
+ 项目有多个活跃变更(`.sillyspec/changes/` 下有多个目录)时,所有 `sillyspec run` 命令需加 `--change <变更名>` 指定操作目标;只有一个变更时可省略(CLI 自动检测)。
17
+
18
+ ## 步骤生命周期(所有阶段通用)
19
+
20
+ > `sillyspec run propose` 需用 `run` 形式(propose 无顶层别名,与 brainstorm/plan 等不同)。
21
+
22
+ ```bash
23
+ sillyspec run propose # 输出当前步骤 prompt
24
+ sillyspec run propose --done --output "摘要" # 完成当前步骤
25
+ sillyspec run propose --status # 查看阶段进度
26
+ sillyspec run propose --skip # 跳过可选步骤
27
+ sillyspec run propose --reset # 重置阶段(从头开始)
28
+ sillyspec run propose --reopen --from-step N # 重新打开已完成阶段修订
29
+ ```
30
+
31
+ ## 通用参数(所有阶段适用)
32
+
33
+ | 参数 | 说明 |
34
+ |---|---|
35
+ | `--change <名>` | 指定变更名(多活跃变更必填,单变更可省略自动检测) |
36
+ | `--spec-dir <path>` | 指定规范目录(默认 `<项目>/.sillyspec`) |
37
+ | `--non-interactive` | CI/脚本下禁用交互式 prompt |
38
+ | `--skip-approval` | 跳过审批/校验门控 |
39
+ | `--json` | 输出 JSON(程序化读取) |
40
+
41
+ ## propose 特有:自检门控
42
+
43
+ propose 含自检步骤(按规模分级:tier=self 当前 agent 自审 / tier=independent 启动独立审查子代理产出 stage review.json),完成时校验四件套文档齐全 + 内容章节(如 proposal 的 Non-Goals、design 的文件变更清单/风险登记/自审)。缺失会阻断完成。
44
+
45
+ ## 阶段流转
46
+
47
+ ```
48
+ (零散设计/需求) → propose → plan
49
+ ```
50
+
51
+ propose 完成后(四件套齐 + 自检通过),运行 `sillyspec run plan --change <变更名>` 进入实现计划。
52
+
53
+ ## 铁律
54
+
55
+ - **必须用 exec 工具(shell)执行 CLI,不要自己编造流程**
56
+ - 只做当前步骤 prompt 描述的操作,不跳过
57
+ - 产物写入 CLI 输出的 `changeDir` 目录
58
+ - 完成后立即 `--done`,不跳过
59
+
60
+ ## 用户指令
61
+ $ARGUMENTS
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sillyspec",
3
- "version": "3.23.5",
3
+ "version": "3.23.7",
4
4
  "description": "SillySpec CLI — 流程状态机,让 AI 严格按步骤来",
5
5
  "icon": "logo.jpg",
6
6
  "homepage": "https://sillyspec.ppdmq.top/",
@@ -134,8 +134,10 @@ function parseTaskDependencies(planContent) {
134
134
  * @param {string} taskName - task-04
135
135
  * @returns {{ ok: boolean, endpoints: Array, artifactPath: string|null }}
136
136
  */
137
- export function extractProviderArtifact(changeDir, worktreePath, specBase, taskName) {
138
- const artifactDir = join(specBase, '.runtime', 'contract-artifacts', taskName)
137
+ export function extractProviderArtifact(changeDir, worktreePath, specBase, taskName, runtimeRoot) {
138
+ // 平台模式 contract-artifacts 落 runtimeRoot;否则落 specBase/.runtime
139
+ const artifactRoot = runtimeRoot || join(specBase, '.runtime')
140
+ const artifactDir = join(artifactRoot, 'contract-artifacts', taskName)
139
141
  const artifactPath = join(artifactDir, 'endpoints.json')
140
142
 
141
143
  if (!worktreePath || !existsSync(worktreePath)) {
@@ -179,13 +181,15 @@ export function extractProviderArtifact(changeDir, worktreePath, specBase, taskN
179
181
  * @param {Array<{ provider: string, consumer: string, type: string }>} contracts
180
182
  * @returns {string|null} 注入到 prompt 的契约文本,无契约时返回 null
181
183
  */
182
- export function buildConsumerInjection(changeDir, specBase, taskName, contracts) {
184
+ export function buildConsumerInjection(changeDir, specBase, taskName, contracts, runtimeRoot) {
185
+ // 平台模式 contract-artifacts 落 runtimeRoot;否则落 specBase/.runtime
186
+ const artifactRoot = runtimeRoot || join(specBase, '.runtime')
183
187
  const myContracts = contracts.filter(c => c.consumer === taskName)
184
188
  if (myContracts.length === 0) return null
185
189
 
186
190
  const parts = []
187
191
  for (const contract of myContracts) {
188
- const artifactDir = join(specBase, '.runtime', 'contract-artifacts', contract.provider)
192
+ const artifactDir = join(artifactRoot, 'contract-artifacts', contract.provider)
189
193
  const artifactFile = join(artifactDir, 'endpoints.json')
190
194
 
191
195
  let endpoints = []
@@ -293,11 +297,13 @@ export function buildContractFieldInjection(changeDir, taskName) {
293
297
  * @param {string} worktreePath - worktree 路径
294
298
  * @returns {{ ok: boolean, missingBackend: Array, unusedBackend: Array, summary: string }}
295
299
  */
296
- export function verifyApiParity(specBase, worktreePath) {
300
+ export function verifyApiParity(specBase, worktreePath, runtimeRoot) {
297
301
  const { diffApiParity } = require('./endpoint-extractor.js')
298
302
 
303
+ // 平台模式 contract-artifacts 落 runtimeRoot;否则落 specBase/.runtime
304
+ const artifactRoot = runtimeRoot || join(specBase, '.runtime')
299
305
  // 读取所有 provider artifacts
300
- const artifactBase = join(specBase, '.runtime', 'contract-artifacts')
306
+ const artifactBase = join(artifactRoot, 'contract-artifacts')
301
307
  const allProviderEndpoints = []
302
308
 
303
309
  if (existsSync(artifactBase)) {
package/src/db.js CHANGED
@@ -83,7 +83,7 @@ export class DB {
83
83
  CREATE TABLE IF NOT EXISTS project (
84
84
  id INTEGER PRIMARY KEY DEFAULT 1,
85
85
  name TEXT NOT NULL,
86
- schema_version INTEGER DEFAULT 4,
86
+ schema_version INTEGER DEFAULT 3,
87
87
  created_at TEXT NOT NULL,
88
88
  updated_at TEXT NOT NULL
89
89
  )
package/src/progress.js CHANGED
@@ -4,8 +4,7 @@
4
4
  * 纯 Node.js,无外部依赖。支持多变更并行。
5
5
  *
6
6
  * 存储结构:
7
- * .sillyspec/.runtime/sillyspec.db — SQLite 数据库(权威状态源)
8
- * .sillyspec/.runtime/global.json — 全局状态缓存(项目名、活跃变更列表)
7
+ * .sillyspec/.runtime/sillyspec.db — SQLite 数据库(权威状态源,含全局状态/项目名/活跃变更)
9
8
  * .sillyspec/.runtime/gate-status.json — worktree-guard 门禁状态缓存
10
9
  *
11
10
  * 历史迁移:v1/v2 使用 progress.json 文件,v3 已全部迁移至 SQLite。
@@ -103,7 +102,6 @@ export function resolvePlatformSpecDir(cwd, explicitSpecDir = null) {
103
102
  }
104
103
 
105
104
  const CHANGES_SUBDIR = 'changes';
106
- const GLOBAL_FILE = 'global.json';
107
105
  const CURRENT_VERSION = 3;
108
106
  const VALID_STAGES = ['scan', 'brainstorm', 'plan', 'execute', 'verify', 'archive', 'quick', 'explore'];
109
107
  const VALID_STATUSES = ['pending', 'in-progress', 'completed', 'failed', 'blocked', 'waiting', 'stale'];
@@ -731,7 +729,11 @@ export class ProgressManager {
731
729
  console.warn('⚠️ initChange: changeName 为空,跳过');
732
730
  return null;
733
731
  }
734
- this._ensureChangeDir(cwd, changeName);
732
+ // quick 会话 id(quick-<uuid8>,见 run.js QUICK_SID_RE)只作 progress 的跨进程 session key,
733
+ // 进度存 SQL 不需要实体 change 目录——跳过避免 changes/quick-<uuid>/ 空目录残留。
734
+ if (!/^quick-[0-9a-f]{8}$/.test(changeName)) {
735
+ this._ensureChangeDir(cwd, changeName);
736
+ }
735
737
 
736
738
  const db = await this._ensureDB(cwd);
737
739
  db.transaction((sqlDb) => {
@@ -0,0 +1,65 @@
1
+ /**
2
+ * SillySpec Review Tier — 审查分级
3
+ *
4
+ * 决定某阶段的"审查/自检"由「当前 agent 自审」还是「独立审查子代理」执行。
5
+ *
6
+ * 为什么需要分级:
7
+ * scanProfile(决定 maxAgentCalls / 是否禁子代理)只在 scan 阶段生效(run.js:810
8
+ * 门控 stageName==='scan');change-risk-profile 的 P0/P1/P2 只管 apply 阻塞与
9
+ * verify 证据。两者都不约束 brainstorm/plan/execute/propose 的审查方式。
10
+ * 故新选「plan_level / 变更文件数」作为审查分级维度。
11
+ *
12
+ * 分级结果:
13
+ * - tier=self → 当前 agent 自审,CLI 放行(不要求 review.json)
14
+ * - tier=independent → 强制独立审查子代理 + review.json,CLI 硬校验
15
+ *
16
+ * CLI 侧(run.js done gate)与 prompt 注入侧用同一个函数,保证两边判定一致——
17
+ * 不信 agent 自报 tier。
18
+ */
19
+
20
+ import { parseFileChangeList } from './change-list.js'
21
+
22
+ /**
23
+ * 审查分级阈值:变更文件数 ≤ 此值 → tier=self。
24
+ * 依据:≤3 文件的变更通常是单点修复/文案调整,独立审查子代理的仪式成本 > 收益。
25
+ */
26
+ export const SELF_REVIEW_FILE_THRESHOLD = 3
27
+
28
+ /**
29
+ * 审查分级
30
+ *
31
+ * 判定顺序(短路):
32
+ * 1. plan_level === 'none' → self(极小变更)
33
+ * 2. design.md 变更文件数 ≤ SELF_REVIEW_FILE_THRESHOLD → self
34
+ * 3. 否则 → independent
35
+ *
36
+ * fail-safe:designPath 传了但解析不到文件清单(fileCount=0)按字面 0 处理(真无文件
37
+ * 变更 = 极小);designPath 完全没传(fileCount=null,无法证明规模)→ independent
38
+ * (宁严勿松)。
39
+ *
40
+ * @param {object} [opts]
41
+ * @param {string} [opts.planLevel] - plan_level 分类结果(none/light/full)。brainstorm/propose 在 plan 之前,可能拿不到。
42
+ * @param {string} [opts.designPath] - design.md 绝对路径,用于算变更文件数
43
+ * @returns {{ tier: 'self'|'independent', reason: string, fileCount: number|null }}
44
+ */
45
+ export function classifyReviewTier({ planLevel, designPath } = {}) {
46
+ if (planLevel === 'none') {
47
+ return { tier: 'self', reason: `plan_level=none(极小变更,≤${SELF_REVIEW_FILE_THRESHOLD} 文件等价)`, fileCount: null }
48
+ }
49
+
50
+ let fileCount = null
51
+ if (designPath) {
52
+ fileCount = parseFileChangeList(designPath).size
53
+ }
54
+
55
+ if (fileCount !== null && fileCount <= SELF_REVIEW_FILE_THRESHOLD) {
56
+ return { tier: 'self', reason: `变更文件 ${fileCount} ≤ ${SELF_REVIEW_FILE_THRESHOLD}`, fileCount }
57
+ }
58
+
59
+ const reason = fileCount === null
60
+ ? (planLevel
61
+ ? `plan_level=${planLevel},无法解析变更文件数,按正常规模处理`
62
+ : '无法解析 design.md 变更文件清单,按正常规模处理')
63
+ : `变更文件 ${fileCount} > ${SELF_REVIEW_FILE_THRESHOLD}`
64
+ return { tier: 'independent', reason, fileCount }
65
+ }
package/src/run.js CHANGED
@@ -893,6 +893,39 @@ async function outputStep(stageName, stepIndex, steps, cwd, changeName, dbProjec
893
893
  promptText = promptText.replace(/\{EXECUTE_RUN_ID\}/g, runId)
894
894
  }
895
895
 
896
+ // Stage Review Tier:brainstorm/plan/propose/execute 阶段注入审查分级占位符
897
+ // (scanProfile 只在 scan 生效、change-risk-profile 只管 apply/verify,都不约束这些阶段的审查方式,
898
+ // 故按 plan_level / 变更文件数分级:self 当前 agent 自审,independent 强制独立子代理 + review.json)
899
+ if (['brainstorm', 'plan', 'propose', 'execute'].includes(stageName) && promptText.includes('{REVIEW_TIER}')) {
900
+ try {
901
+ const { classifyReviewTier } = await import('./review-tier.js')
902
+ const { generateStageReviewRunId } = await import('./stage-review.js')
903
+ const tierSpecBase = platformOpts?.specRoot || join(cwd, '.sillyspec')
904
+ const tierChangeDir = changeName ? join(tierSpecBase, 'changes', changeName) : null
905
+ const designPath = tierChangeDir ? join(tierChangeDir, 'design.md') : null
906
+ let planLevel = null
907
+ if (tierChangeDir) {
908
+ const planPath = join(tierChangeDir, 'plan.md')
909
+ if (existsSync(planPath)) {
910
+ const fmLine = readFileSync(planPath, 'utf8').split('\n').find(l => l.trim().startsWith('plan_level:'))
911
+ if (fmLine) planLevel = fmLine.split(':')[1].trim()
912
+ }
913
+ }
914
+ const tier = classifyReviewTier({ planLevel, designPath })
915
+ const reviewRunId = generateStageReviewRunId()
916
+ promptText = promptText
917
+ .split('{REVIEW_TIER}').join(tier.tier)
918
+ .split('{REVIEW_TIER_REASON}').join(tier.reason)
919
+ .split('{STAGE_REVIEW_RUN_ID}').join(reviewRunId)
920
+ } catch (e) {
921
+ // 降级 self,避免 prompt 残留占位符
922
+ promptText = promptText
923
+ .split('{REVIEW_TIER}').join('self')
924
+ .split('{REVIEW_TIER_REASON}').join('分级异常降级 self: ' + e.message)
925
+ .split('{STAGE_REVIEW_RUN_ID}').join('review-unknown')
926
+ }
927
+ }
928
+
896
929
  // 注入模块上下文(brainstorm/plan/execute 阶段,基于 Module Context Index)
897
930
  if (['brainstorm', 'plan', 'execute'].includes(stageName) && projectName) {
898
931
  const effectiveSpecBase = platformOpts?.specRoot || join(cwd, '.sillyspec')
@@ -1511,6 +1544,15 @@ export async function runCommand(args, cwd, specDir = null) {
1511
1544
  }
1512
1545
  }
1513
1546
 
1547
+ // execute 阶段必须带 --change:不允许自动检测或默认值,变更名必须由 agent 显式传入
1548
+ // --status 豁免(纯查看不需要指定变更)
1549
+ if (stageName === 'execute' && !changeName && !isStatus) {
1550
+ console.error('❌ execute 阶段必须用 --change <变更名> 指定要操作的变更。')
1551
+ console.error(' agent 必须传参,不设默认值、不做自动检测。')
1552
+ console.error(' 请加 --change <变更名> 重新执行。')
1553
+ process.exit(1)
1554
+ }
1555
+
1514
1556
  let progress = await pm.read(cwd, changeName)
1515
1557
 
1516
1558
  if (!progress) {
@@ -1844,7 +1886,7 @@ async function runStage(pm, progress, stageName, cwd, changeName, skipApproval =
1844
1886
  if (stageName === 'execute') {
1845
1887
  const { generateExecuteRunId } = await import('./task-review.js')
1846
1888
  const execSpecBase = platformOpts?.specRoot || join(cwd, '.sillyspec')
1847
- const runtimeRoot = join(execSpecBase, '.runtime')
1889
+ const runtimeRoot = platformOpts?.runtimeRoot || join(execSpecBase, '.runtime')
1848
1890
  const runIdFile = join(runtimeRoot, `current-execute-run-id-${changeName}`)
1849
1891
  mkdirSync(runtimeRoot, { recursive: true })
1850
1892
  // 优先读取已有的变更专属标记文件
@@ -3305,6 +3347,56 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
3305
3347
  }
3306
3348
  }
3307
3349
 
3350
+ // ── Stage Review Gate:brainstorm/plan/propose/execute 独立审查(按 tier 分级)──
3351
+ // tier=self 放行+审计打印;tier=independent 必须 review.json 且 verdict 非 fail,fail-closed
3352
+ if (['brainstorm', 'plan', 'propose', 'execute'].includes(stageName)) {
3353
+ try {
3354
+ const { classifyReviewTier } = await import('./review-tier.js')
3355
+ const { validateStageReview, getLatestStageReviewRunId, printStageReviewResult } = await import('./stage-review.js')
3356
+ const effectiveSpecBase = platformOpts?.specRoot || specBase
3357
+ const reviewChangeDir = resolveChangeDir(cwd, progress, platformOpts?.specRoot)
3358
+ const designPath = reviewChangeDir ? join(reviewChangeDir, 'design.md') : null
3359
+ let planLevel = null
3360
+ if (reviewChangeDir) {
3361
+ const planPath = join(reviewChangeDir, 'plan.md')
3362
+ if (existsSync(planPath)) {
3363
+ const fmLine = readFileSync(planPath, 'utf8').split('\n').find(l => l.trim().startsWith('plan_level:'))
3364
+ if (fmLine) planLevel = fmLine.split(':')[1].trim()
3365
+ }
3366
+ }
3367
+ const tier = classifyReviewTier({ planLevel, designPath })
3368
+ const runtimeRoot = platformOpts?.runtimeRoot || join(effectiveSpecBase, '.runtime')
3369
+
3370
+ if (tier.tier === 'self') {
3371
+ console.log('\nℹ️ Stage Review: ' + stageName + ' tier=self(' + tier.reason + '),已降级为当前 agent 自审,不强制独立子代理。')
3372
+ } else {
3373
+ const reviewRunId = getLatestStageReviewRunId(runtimeRoot, stageName)
3374
+ const reviewType = stageName === 'brainstorm' ? 'design'
3375
+ : stageName === 'plan' ? 'plan'
3376
+ : stageName === 'propose' ? 'proposal'
3377
+ : 'acceptance'
3378
+ const searchDirs = [effectiveSpecBase, reviewChangeDir, cwd].filter(Boolean)
3379
+ const reviewResult = validateStageReview({ stage: stageName, reviewType, runtimeRoot, reviewRunId, searchDirs })
3380
+ printStageReviewResult(reviewResult, { stage: stageName })
3381
+ if (!reviewResult.ok) {
3382
+ rollbackStageCompletion(stageData, steps, currentIdx)
3383
+ progress.lastActive = new Date().toLocaleString('zh-CN', { hour12: false })
3384
+ await pm._write(cwd, progress, changeName)
3385
+ triggerSync(cwd, changeName, platformOpts)
3386
+ return { stageCompleted: false, currentIdx, nextPendingIdx: currentIdx }
3387
+ }
3388
+ }
3389
+ } catch (e) {
3390
+ // fail-closed:Gate 自身异常阻断完成,不静默放行(与 Task Review Gate 一致)
3391
+ console.error('❌ Stage Review Gate 异常,阻断 ' + stageName + ' 完成: ' + e.message)
3392
+ rollbackStageCompletion(stageData, steps, currentIdx)
3393
+ progress.lastActive = new Date().toLocaleString('zh-CN', { hour12: false })
3394
+ await pm._write(cwd, progress, changeName)
3395
+ triggerSync(cwd, changeName, platformOpts)
3396
+ return { stageCompleted: false, currentIdx, nextPendingIdx: currentIdx }
3397
+ }
3398
+ }
3399
+
3308
3400
  // ── Execute Task Review Gate:所有 task 必须有 review.json 且 verdict 通过 ──
3309
3401
  if (stageName === 'execute') {
3310
3402
  try {
@@ -3315,7 +3407,7 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
3315
3407
 
3316
3408
  if (planPath && existsSync(planPath)) {
3317
3409
  const planContent = readFileSync(planPath, 'utf8')
3318
- const runtimeRoot = join(effectiveSpecBase, '.runtime')
3410
+ const runtimeRoot = platformOpts?.runtimeRoot || join(effectiveSpecBase, '.runtime')
3319
3411
 
3320
3412
  // execute run id:从变更专属标记文件读取
3321
3413
  const runIdFile = join(runtimeRoot, `current-execute-run-id-${changeName}`)
@@ -3513,7 +3605,7 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
3513
3605
  if (wf && changeName) {
3514
3606
  const raw = JSON.stringify(wf)
3515
3607
  const resolved = JSON.parse(raw.replace(/<change-name>/g, changeName))
3516
- const result = runPostCheck(resolved, cwd, 'sillyspec', {}, specBase)
3608
+ const result = runPostCheck(resolved, cwd, progress.project || basename(cwd), {}, specBase)
3517
3609
  // 只报告 impact-analyzer 的结果(doc-syncer 是后续步骤)
3518
3610
  const impactResult = (result.roles || []).find(r => r.id === 'impact-analyzer')
3519
3611
  if (impactResult) {
@@ -0,0 +1,300 @@
1
+ /**
2
+ * SillySpec Stage Review Gate — 阶段级审查校验(文档型)
3
+ *
4
+ * 与 task-review.js 的关系:
5
+ * task-review 校验 execute 每个 task 的 review.json(git 代码 diff 证据:base/head)。
6
+ * stage-review 校验 brainstorm/plan/propose/execute-acceptance 的阶段级 review.json
7
+ * (文档证据:reviewedFiles + docHash)。两者 verdict 语义一致(pass/fail/cannot_verify
8
+ * 三态 + cannot_verify 必须带 requiredEvidence 的反逃逸规则),复用 task-review 导出的
9
+ * VALID_VERDICTS / REVIEW_SCHEMA_VERSION 常量;task-review.js 本身不改、execute 路径零风险。
10
+ *
11
+ * 目录结构:
12
+ * .sillyspec/.runtime/stage-reviews/<stage>-<reviewRunId>/review.json
13
+ * (平台模式落 <runtimeRoot>/stage-reviews/...,与 execute-runs 同构)
14
+ */
15
+
16
+ import { existsSync, readFileSync, mkdirSync, readdirSync } from 'fs'
17
+ import { join } from 'path'
18
+ import { createHash } from 'crypto'
19
+ import { VALID_VERDICTS, REVIEW_SCHEMA_VERSION } from './task-review.js'
20
+
21
+ // 文档型 stage review 的合法 reviewType
22
+ export const STAGE_REVIEW_TYPES = ['design', 'plan', 'proposal', 'code', 'acceptance']
23
+
24
+ // checklist 每项 result 的合法值
25
+ const CHECKLIST_RESULTS = ['pass', 'gap', 'fail']
26
+
27
+ /**
28
+ * 计算文件内容的 sha256(用于 docHash 真实性校验,防 agent 凭空伪造 review.json)
29
+ * @param {string} filePath - 绝对路径
30
+ * @returns {string|null} sha256 hex;文件不存在返回 null
31
+ */
32
+ export function computeDocHash(filePath) {
33
+ if (!filePath || !existsSync(filePath)) return null
34
+ const content = readFileSync(filePath)
35
+ return createHash('sha256').update(content).digest('hex')
36
+ }
37
+
38
+ /**
39
+ * 校验文档型 stage review.json 的 schema
40
+ *
41
+ * 与 task-review.validateReviewSchema 的区别:
42
+ * - 不要求 git base/head(证据形态不同)
43
+ * - 改要求 reviewedFiles(被审查文档路径数组)+ docHash(主文档内容 sha256)
44
+ * - 新增 reviewType(design/plan/proposal/code/acceptance)+ checklist
45
+ * - 复用 VALID_VERDICTS 三态 + cannot_verify 联动规则(task-review.js:65-69)
46
+ *
47
+ * @param {object} review - 解析后的 JSON 对象
48
+ * @returns {{ ok: boolean, errors: string[] }}
49
+ */
50
+ export function validateStageReviewSchema(review) {
51
+ const errors = []
52
+ if (!review || typeof review !== 'object') {
53
+ return { ok: false, errors: ['stage review.json 不是有效 JSON 对象'] }
54
+ }
55
+
56
+ if (review.schemaVersion !== REVIEW_SCHEMA_VERSION) {
57
+ errors.push(`schemaVersion 应为 ${REVIEW_SCHEMA_VERSION},实际为 ${review.schemaVersion}`)
58
+ }
59
+
60
+ if (!STAGE_REVIEW_TYPES.includes(review.reviewType)) {
61
+ errors.push(`reviewType 无效:${review.reviewType}(应为 ${STAGE_REVIEW_TYPES.join('/')})`)
62
+ }
63
+
64
+ if (!VALID_VERDICTS.includes(review.specVerdict)) {
65
+ errors.push(`specVerdict 无效:${review.specVerdict}(应为 ${VALID_VERDICTS.join('/')})`)
66
+ }
67
+ if (!VALID_VERDICTS.includes(review.qualityVerdict)) {
68
+ errors.push(`qualityVerdict 无效:${review.qualityVerdict}(应为 ${VALID_VERDICTS.join('/')})`)
69
+ }
70
+
71
+ // cannot_verify 必须提供非空 requiredEvidence(与 task-review:65-69 一致,反逃逸)
72
+ if (review.specVerdict === 'cannot_verify' || review.qualityVerdict === 'cannot_verify') {
73
+ if (!Array.isArray(review.requiredEvidence) || review.requiredEvidence.length === 0) {
74
+ errors.push('cannot_verify 的 verdict 必须提供非空的 requiredEvidence 数组')
75
+ }
76
+ }
77
+
78
+ // reviewedFiles 必须是非空数组
79
+ if (!Array.isArray(review.reviewedFiles) || review.reviewedFiles.length === 0) {
80
+ errors.push('缺少 reviewedFiles 字段(应为被审查文档路径数组,[0] 为主审查对象,docHash 对应它)')
81
+ }
82
+
83
+ // docHash 非空
84
+ if (!review.docHash || typeof review.docHash !== 'string') {
85
+ errors.push('缺少 docHash 字段(被审查主文档内容的 sha256)')
86
+ }
87
+
88
+ // checklist 若提供,校验每项结构
89
+ if (review.checklist !== undefined) {
90
+ if (!Array.isArray(review.checklist)) {
91
+ errors.push('checklist 若提供必须是数组')
92
+ } else {
93
+ for (let i = 0; i < review.checklist.length; i++) {
94
+ const item = review.checklist[i]
95
+ if (!item || typeof item !== 'object' || !item.item) {
96
+ errors.push(`checklist[${i}] 缺少 item 描述`)
97
+ continue
98
+ }
99
+ if (item.result !== undefined && !CHECKLIST_RESULTS.includes(item.result)) {
100
+ errors.push(`checklist[${i}].result 无效:${item.result}(应为 ${CHECKLIST_RESULTS.join('/')})`)
101
+ }
102
+ }
103
+ }
104
+ }
105
+
106
+ return { ok: errors.length === 0, errors }
107
+ }
108
+
109
+ /**
110
+ * docHash 真实性交叉校验:review 声称审查了某文档,重算该文档 sha256 对比。
111
+ *
112
+ * 对应 task-review.verifyReviewGitEvidence 的"防伪造"职责——agent 不能凭空编一份
113
+ * review.json 贴个假 hash。约定 docHash 对应 reviewedFiles[0](主审查对象)。
114
+ *
115
+ * searchDirs 依次尝试解析 reviewedFiles 的相对路径(specBase / changeDir / cwd 都可能)。
116
+ * 主文档在所有基准下都找不到时降级 warning,不阻断(不因路径基准差异误杀)。
117
+ *
118
+ * @param {object} review - 已通过 schema 校验
119
+ * @param {string[]} searchDirs - 解析相对路径的候选基准目录(绝对路径)
120
+ * @returns {{ ok: boolean, errors: string[], warnings: string[] }}
121
+ */
122
+ export function verifyStageReviewDocHash(review, searchDirs) {
123
+ const errors = []
124
+ const warnings = []
125
+ const files = Array.isArray(review.reviewedFiles) ? review.reviewedFiles : []
126
+ if (files.length === 0) {
127
+ return { ok: false, errors: ['reviewedFiles 为空,无法做 docHash 校验'], warnings }
128
+ }
129
+
130
+ const primaryRel = files[0]
131
+
132
+ for (const base of searchDirs || []) {
133
+ const abs = join(base, primaryRel)
134
+ const actual = computeDocHash(abs)
135
+ if (actual === null) continue
136
+ // 找到主文档,比对 hash
137
+ if (review.docHash !== actual) {
138
+ errors.push(`docHash 与主审查文档 ${primaryRel} 的实际内容不匹配 — review.json 疑似伪造(未真正读取文档)`)
139
+ return { ok: false, errors, warnings }
140
+ }
141
+ return { ok: true, errors, warnings }
142
+ }
143
+
144
+ // 所有基准都找不到主文档:路径基准差异,降级 warning
145
+ warnings.push(`主审查文档 ${primaryRel} 在候选目录下均不存在,跳过 docHash 校验(可能路径基准不同)`)
146
+ return { ok: true, errors, warnings }
147
+ }
148
+
149
+ /**
150
+ * 生成 stage review run id
151
+ * @returns {string} 如 'review-2026-07-16-143000'
152
+ */
153
+ export function generateStageReviewRunId() {
154
+ const now = new Date()
155
+ const pad = (n) => String(n).padStart(2, '0')
156
+ return `review-${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`
157
+ }
158
+
159
+ /**
160
+ * 获取最新的 stage review run id(扫描 stage-reviews/<stage>-review-* 目录取最新)
161
+ *
162
+ * @param {string} runtimeRoot - .runtime 绝对路径
163
+ * @param {string} stage - brainstorm|plan|propose|execute
164
+ * @returns {string|null} run id(如 'review-2026-07-16-143000'),无则 null
165
+ */
166
+ export function getLatestStageReviewRunId(runtimeRoot, stage) {
167
+ const dir = join(runtimeRoot, 'stage-reviews')
168
+ if (!existsSync(dir)) return null
169
+ try {
170
+ const entries = readdirSync(dir, { withFileTypes: true })
171
+ .filter(e => e.isDirectory() && e.name.startsWith(`${stage}-review-`))
172
+ .map(e => e.name)
173
+ .sort()
174
+ .reverse()
175
+ if (!entries[0]) return null
176
+ // entries[0] = "plan-review-2026-...",剥掉 `${stage}-` 前缀得到 runId "review-2026-..."
177
+ return entries[0].slice(stage.length + 1)
178
+ } catch {
179
+ return null
180
+ }
181
+ }
182
+
183
+ /**
184
+ * stage-level review 总校验(brainstorm/plan/propose/execute-acceptance 的 done gate)
185
+ *
186
+ * 规则(与 task-review.validateTaskReviews 对称):
187
+ * - review.json 缺失 → error 阻断
188
+ * - 解析失败 / schema 失败 → error 阻断
189
+ * - reviewType 与期望不符 → error(agent 复制模板未改)
190
+ * - specVerdict 或 qualityVerdict 为 fail → error 阻断
191
+ * - cannot_verify + 非空 requiredEvidence → warning
192
+ * - cannot_verify + 空 requiredEvidence → error(schema 已拦,双保险)
193
+ * - docHash 与主文档实际内容不符 → error(伪造)
194
+ *
195
+ * 调用方约定:先用 classifyReviewTier 判定 tier——tier=self 不调用本函数(直接放行),
196
+ * tier=independent 才调用。
197
+ *
198
+ * @param {object} opts
199
+ * @param {string} opts.stage - brainstorm|plan|propose|execute
200
+ * @param {string} opts.reviewType - design|plan|proposal|code|acceptance
201
+ * @param {string} opts.runtimeRoot - .runtime 绝对路径(平台模式为 runtimeRoot)
202
+ * @param {string} opts.reviewRunId - stage review run id(review-<ts>)
203
+ * @param {string[]} [opts.searchDirs] - docHash 校验的候选基准目录
204
+ * @param {boolean} [opts.verifyDocHash=true] - 是否做 docHash 真实性校验
205
+ * @returns {{ ok: boolean, errors: string[], warnings: string[], review: object|null }}
206
+ */
207
+ export function validateStageReview(opts) {
208
+ const { stage, reviewType, runtimeRoot, reviewRunId, searchDirs = [], verifyDocHash = true } = opts
209
+ const errors = []
210
+ const warnings = []
211
+
212
+ const reviewDir = join(runtimeRoot, 'stage-reviews', `${stage}-${reviewRunId}`)
213
+ const reviewPath = join(reviewDir, 'review.json')
214
+
215
+ if (!reviewRunId || !existsSync(reviewPath)) {
216
+ return {
217
+ ok: false,
218
+ errors: [`缺少 ${stage} 阶段的 stage review.json — tier=independent 要求独立审查子代理产出(期望路径:${reviewPath})`],
219
+ warnings,
220
+ review: null,
221
+ }
222
+ }
223
+
224
+ let raw
225
+ try {
226
+ raw = readFileSync(reviewPath, 'utf8')
227
+ } catch (e) {
228
+ return { ok: false, errors: [`${stage} review.json 读取失败: ${e.message}`], warnings, review: null }
229
+ }
230
+
231
+ let parsed
232
+ try {
233
+ parsed = JSON.parse(raw)
234
+ } catch (e) {
235
+ return { ok: false, errors: [`${stage} review.json 解析失败: ${e.message}`], warnings, review: null }
236
+ }
237
+
238
+ const schemaResult = validateStageReviewSchema(parsed)
239
+ if (!schemaResult.ok) {
240
+ return { ok: false, errors: [`${stage} review.json schema 校验失败 — ${schemaResult.errors.join('; ')}`], warnings, review: parsed }
241
+ }
242
+
243
+ // reviewType 与期望一致性
244
+ if (parsed.reviewType && parsed.reviewType !== reviewType) {
245
+ errors.push(`${stage} review.json 的 reviewType 为 "${parsed.reviewType}",与期望 "${reviewType}" 不符 — agent 可能复制模板未修改`)
246
+ }
247
+
248
+ // fail verdict
249
+ if (parsed.specVerdict === 'fail' || parsed.qualityVerdict === 'fail') {
250
+ errors.push(`${stage} review 未通过 — spec: ${parsed.specVerdict}, quality: ${parsed.qualityVerdict}`)
251
+ if (parsed.reviewerNotes) errors.push(`${stage}: ${parsed.reviewerNotes}`)
252
+ }
253
+
254
+ // docHash 真实性(fail verdict 已错时跳过,避免噪声)
255
+ if (errors.length === 0 && verifyDocHash && searchDirs.length > 0) {
256
+ const dh = verifyStageReviewDocHash(parsed, searchDirs)
257
+ for (const w of dh.warnings) warnings.push(`${stage}: ${w}`)
258
+ if (!dh.ok) {
259
+ for (const err of dh.errors) errors.push(`${stage}: ${err}`)
260
+ }
261
+ }
262
+
263
+ // cannot_verify → warning(requiredEvidence 非空已由 schema 保证)
264
+ if (errors.length === 0 && (parsed.specVerdict === 'cannot_verify' || parsed.qualityVerdict === 'cannot_verify')) {
265
+ const verdicts = []
266
+ if (parsed.specVerdict === 'cannot_verify') verdicts.push('spec')
267
+ if (parsed.qualityVerdict === 'cannot_verify') verdicts.push('quality')
268
+ warnings.push(`${stage}: ${verdicts.join('+')}=cannot_verify,requiredEvidence 需在后续阶段满足`)
269
+ }
270
+
271
+ return { ok: errors.length === 0, errors, warnings, review: parsed }
272
+ }
273
+
274
+ /**
275
+ * 打印 stage review 校验结果
276
+ * @param {{ ok: boolean, errors: string[], warnings: string[] }} result
277
+ * @param {object} context - { stage, reviewRunId } 用于错误提示路径
278
+ */
279
+ export function printStageReviewResult(result, context = {}) {
280
+ const { stage } = context
281
+ if (result.ok && result.warnings.length === 0) {
282
+ console.log(`\n✅ Stage Review Gate — ${stage} 阶段独立审查通过`)
283
+ return
284
+ }
285
+
286
+ if (result.errors.length > 0) {
287
+ console.error(`\n🚫 Stage Review Gate — ${stage} FAILED`)
288
+ for (const err of result.errors) {
289
+ console.error(` - ${err}`)
290
+ }
291
+ console.error(`\n 提示:tier=independent 要求独立审查子代理产出 review.json,补全后重新 --done`)
292
+ }
293
+
294
+ if (result.warnings.length > 0) {
295
+ console.warn(`\n⚠️ Stage Review Gate — ${stage} WARNING`)
296
+ for (const w of result.warnings) {
297
+ console.warn(` - ${w}`)
298
+ }
299
+ }
300
+ }
@@ -430,6 +430,11 @@ design.md 文件路径 + 自审结果
430
430
  ### 定位
431
431
  这是设计完成后的质量门,不是需求探索。目标不是继续发散,而是找出 design.md 内部、四件套之间、文档与外部约束之间的结构性矛盾。
432
432
 
433
+ ### 审查执行方式(CLI 按变更规模判定,占位符由 run.js 注入)
434
+ tier: {REVIEW_TIER}({REVIEW_TIER_REASON})
435
+ - tier=self:当前 agent 直接执行下方交叉审查(小变更)
436
+ - tier=independent:必须用 Agent tool 启动一个独立的设计审查子代理(独立上下文,不共享你的分析与倾向),子代理按下方"交叉审查模型"审查 design.md 并输出 review.json 到 {SPEC_ROOT}/.runtime/stage-reviews/brainstorm-{STAGE_REVIEW_RUN_ID}/review.json。字段:reviewType=design;reviewedFiles=[changes/<change>/design.md];docHash=design.md 的 sha256(必须真实读取文件计算,禁止编造,CLI 会重算对比);specVerdict/qualityVerdict=pass|fail|cannot_verify;checklist 按"定义层/一致性层/可行性层"每条给 pass|gap|fail。子代理只产出 review + Unresolved Blockers,**是否调用 sillyspec run brainstorm --wait 仍由你(主 agent)根据其 verdict 决定**(子代理不直接操作 CLI 状态机)。
437
+
433
438
  ### 默认行为
434
439
  1. 默认必须执行一次交叉审查;不要让用户凭主观判断决定"要不要 Grill"。
435
440
  2. 只有以下情况可以轻量跳过,并必须记录原因:
@@ -215,9 +215,10 @@ const acceptanceSteps = [
215
215
  mode: 'acceptance',
216
216
  prompt: `对照 design.md 检查所有实现是否与设计一致。
217
217
 
218
- ### 执行方式
219
- 本步骤由当前 agent 汇总执行,不需要为每个检查项启动独立子代理。
220
- 如需深入验证某个模块,可启动单个 QA 子代理统一处理。
218
+ ### 执行方式(CLI 按变更规模判定,占位符由 run.js 注入)
219
+ tier: {REVIEW_TIER}({REVIEW_TIER_REASON})
220
+ - tier=self:当前 agent 汇总执行(对照 design.md 逐项检查 + 偏差说明)
221
+ - tier=independent:必须用 Agent tool 启动一个独立的 QA 子代理(独立上下文,不共享实现者的分析),子代理对照 design.md 逐项检查实现一致性并输出 review.json 到 {SPEC_ROOT}/.runtime/stage-reviews/execute-{STAGE_REVIEW_RUN_ID}/review.json。字段:reviewType=acceptance;reviewedFiles=[changes/<change>/design.md] + git diff 涉及的源码文件;docHash=design.md 的 sha256(禁止编造);specVerdict/qualityVerdict=pass|fail|cannot_verify;checklist 按每个设计要点给 pass|gap|fail。该 acceptance review 同时覆盖"代码审查"视角(风格/bug/安全/冗余),后续代码审查步骤仅需轻量复审。
221
222
 
222
223
  ### 操作
223
224
  1. 读取 design.md(技术方案)
@@ -154,14 +154,14 @@ needs_human_review: true | false
154
154
 
155
155
  const stepGeneratePlan = {
156
156
  id: 'generate_plan',
157
- name: '生成分级计划与自检',
158
- prompt: `根据上一步的 plan_level 结果,按对应级别生成计划,然后立即自检。
157
+ name: '生成分级计划',
158
+ prompt: `根据上一步的 plan_level 结果,按对应级别生成计划。
159
159
 
160
160
  ### 操作
161
161
  1. 读取上一步输出的 plan_level 分类结果
162
162
  2. 读取 tasks.md 和 design.md 了解需求范围
163
163
  3. 按 plan_level 选择对应模板输出
164
- 4. 生成后立即自检(见下方自检清单)
164
+ 4. 保存 plan.md(审查在下一步"审查计划"独立进行,不在本步自审——避免生成与自审同一次输出)
165
165
 
166
166
  ---
167
167
 
@@ -319,53 +319,59 @@ full 计划的约束:
319
319
 
320
320
  ---
321
321
 
322
- ### 自检(生成后立即执行,不另开步骤)
323
-
324
- 读取上一步的 plan_level 分类结果,按级别执行对应的自检:
325
-
326
- #### plan_level = none
327
- - [ ] plan.md 文件存在且包含 plan_level: none
328
- - [ ] 给出了可操作的修改建议(2-5 条)
329
- - [ ] 不含 Wave、Mermaid、估时、任务总表、依赖关系等完整蓝图内容
330
- - [ ] 建议了直接 execute
331
- - [ ] 包含至少一个 \`- [ ] task-XX:\` 格式的 checkbox 任务(execute 解析依赖此格式)
322
+ ### 输出
323
+ plan_level + 计划内容(审查在下一步独立进行)`,
324
+ outputHint: 'plan_level + 计划内容',
325
+ optional: false
326
+ }
332
327
 
333
- #### plan_level = light
334
- - [ ] 输出明确标注 plan_level: light
335
- - [ ] 有来源、范围、任务列表、验收标准四个部分
336
- - [ ] 来源直接引用已有文档,未重新扩写
337
- - [ ] 任务列表清晰且无实现细节
338
- - [ ] 任务使用 checkbox 格式(\`- [ ] task-XX:\`),不是纯编号列表
339
- - [ ] 验收标准具体可验证(非笼统表述)
340
- - [ ] 如果存在 decisions.md,所有当前版本 D-xxx@vN 在 plan.md 中可追踪
341
- - [ ] 不存在 P0/P1 unresolved blocker
342
- - [ ] 没有 Mermaid 图、估时、风险分析
343
- - [ ] 没有函数签名、代码示例等实现细节
344
- - [ ] plan.md 与 design.md 的文件变更清单一致
345
- - [ ] 包含至少一个 \`- [ ] task-XX:\` 格式的 checkbox 任务(execute 解析依赖此格式)
328
+ // ═══════════════════════════════════════════════════════════════
329
+ // 2.5 步(LLM):审查计划(独立于生成,按规模分级)
330
+ // ═══════════════════════════════════════════════════════════════
346
331
 
347
- #### plan_level = full
348
- - [ ] 每个 task 有编号(task-01、task-02 ...)
349
- - [ ] 每个 task Wave 下有 checkbox(\`- [ ] task-XX:\` 格式,execute 解析依赖此格式)
350
- - [ ] 已标注 Wave 分组和依赖关系
351
- - [ ] 有任务总表(含优先级、依赖列,**无估时列**)
352
- - [ ] 有关键路径标注
353
- - [ ] 有全局验收标准
354
- - [ ] 如果存在 decisions.md,任务总表或覆盖矩阵覆盖全部当前版本 D-xxx@vN
355
- - [ ] 不存在 P0/P1 unresolved blocker
356
- - [ ] (brownfield)全局验收包含兼容性条款
357
- - [ ] 没有实现细节(接口定义、代码示例等不应该在 plan.md 里)
358
- - [ ] plan.md 与 design.md 的文件变更清单一致
359
- - [ ] 如果涉及构造函数/接口/DTO/client 方法变更,是否搜索了所有调用点并纳入任务范围?
360
- - [ ] 调用点搜索命令的输出是否记录在 plan.md task-NN.md 中?
361
- - [ ] 跨任务契约自检:若 task-A 的产出(接口/DTO/响应)被 task-B 消费,consumer 是否在 TaskCard expects_from 里声明所需字段、provider 是否在 provides 里承诺这些字段、两边字段是否一致?(plan-postcheck 会硬校验,此处先自查)
362
- - [ ] 文件覆盖自检:design.md「文件变更清单」中的每个源码文件,是否都被至少一个 task allowed_paths 覆盖?(plan-postcheck 会硬校验,漏覆盖 = execute 必然漏改,此处先自查)
363
- - [ ] 如果有 Mermaid 图,依赖关系确实非平凡(非线性/非全并行)
364
- - [ ] 没有泛泛风险分析(如"需要充分测试")
332
+ /**
333
+ * 审查计划 step —— 把"审查"从生成 step 拆出(原 stepGeneratePlan 生成+自检同次输出是最严重的 self-review)。
334
+ * 按规模分级:tier=self 当前 agent 自审;tier=independent 强制独立子代理 + review.json。
335
+ * 占位符 {REVIEW_TIER} / {REVIEW_TIER_REASON} / {STAGE_REVIEW_RUN_ID} 由 run.js 注入。
336
+ */
337
+ const stepReviewPlan = {
338
+ id: 'review_plan',
339
+ name: '审查计划',
340
+ prompt: `对上一步生成的 plan.md 做审查。生成与审查分离——不在生成 plan 的同一上下文里自审,避免确认偏差。
341
+
342
+ ### 当前审查分级(CLI 按变更规模判定,占位符由 run.js 注入)
343
+ tier: {REVIEW_TIER}({REVIEW_TIER_REASON})
344
+ - tier=self:当前 agent 直接执行下方审查清单(小变更,独立审查仪式成本 > 收益)
345
+ - tier=independent:必须用 Agent tool 启动一个独立的计划审查子代理(独立上下文,不共享你生成 plan 时的分析与倾向),由子代理执行下方审查清单并输出 review.json
346
+
347
+ ### 审查清单(读取 plan.md 的 plan_level,逐条核对)
348
+ - [ ] task 编号与 Wave checkbox 格式正确,execute 依赖此格式解析任务
349
+ - [ ] plan_level 档位与实际复杂度匹配(none/light/full 没选错)
350
+ - [ ] 跨任务契约:task-A 的产出(接口/DTO/响应)被 task-B 消费时,consumer 是否在 TaskCard expects_from 声明所需字段、provider 是否在 provides 承诺、两边字段一致?(plan-postcheck 会硬校验,此处是独立视角复查)
351
+ - [ ] 文件覆盖:design.md 文件变更清单中的每个源码文件,是否都被至少一个 task 的 allowed_paths 覆盖?(漏覆盖 = execute 必然漏改)
352
+ - [ ] 不存在 P0/P1 unresolved blocker 残留
353
+ - [ ] 没有实现细节泄漏到 plan.md(接口签名/代码示例应在 tasks/task-NN.md)
354
+ - [ ] 关键路径与 Wave 依赖合理(无循环依赖、无遗漏前置)
355
+
356
+ ### tier=independent 时:启动 plan-review 子代理
357
+ 用 Agent tool 启动子代理(subagent_type: general),prompt 要点:
358
+ 1. 独立读取 {SPEC_ROOT}/changes/<change>/plan.md + design.md + tasks/*.md(不要让生成者喂结论给你,自己读原始文件)
359
+ 2. 执行上方审查清单,每条给 pass/gap/fail + 证据
360
+ 3. 输出 review.json 到 {SPEC_ROOT}/.runtime/stage-reviews/plan-{STAGE_REVIEW_RUN_ID}/review.json,字段:
361
+ - schemaVersion: 1
362
+ - reviewType: plan ; stage: plan
363
+ - specVerdict / qualityVerdict: pass | fail | cannot_verify
364
+ - reviewedFiles: [changes/<change>/plan.md]
365
+ - docHash: plan.md 内容的 sha256(必须真实读取文件计算,禁止编造——CLI 会重算对比)
366
+ - checklist: 每条 { item, result: pass|gap|fail, note }
367
+ - requiredEvidence: cannot_verify 时必填非空
368
+ - reviewerNotes: 说明
369
+ 4. verdict=fail 时在 reviewerNotes 写明阻断项
365
370
 
366
371
  ### 输出
367
- plan_level + 计划内容 + 自检结果(一次输出)`,
368
- outputHint: '计划内容 + 自检结果',
372
+ - tier=self:审查清单结果(每条状态 + 偏差说明)
373
+ - tier=independent:子代理产出的 review.json 路径 + verdict 摘要`,
374
+ outputHint: 'plan 审查结果(self=清单 / independent=review.json 路径+verdict)',
369
375
  optional: false
370
376
  }
371
377
 
@@ -524,7 +530,7 @@ export function buildPostcheckStep(changeDir) {
524
530
  // 向后兼容:导出 fixedPrefix / fixedSuffix(供 run.js 切片用)
525
531
  // ═══════════════════════════════════════════════════════════════
526
532
 
527
- export const fixedPrefix = [stepClassify, stepGeneratePlan]
533
+ export const fixedPrefix = [stepClassify, stepGeneratePlan, stepReviewPlan]
528
534
 
529
535
  export const fixedSuffix = [] // postcheck 是动态生成的(需要 changeDir)
530
536
 
@@ -138,7 +138,12 @@ export const definition = {
138
138
  },
139
139
  {
140
140
  name: '自检门控',
141
- prompt: `自检生成的规范文件。
141
+ prompt: `自检生成的规范文件(按规模分级)。
142
+
143
+ ### 当前审查分级(CLI 按变更规模判定,占位符由 run.js 注入)
144
+ tier: {REVIEW_TIER}({REVIEW_TIER_REASON})
145
+ - tier=self:当前 agent 直接执行下方 checklist
146
+ - tier=independent:必须用 Agent tool 启动一个独立的规范审查子代理(独立上下文,不共享你的分析),子代理执行下方 checklist 并输出 review.json 到 {SPEC_ROOT}/.runtime/stage-reviews/propose-{STAGE_REVIEW_RUN_ID}/review.json。字段:reviewType=proposal;reviewedFiles=[proposal.md, design.md, requirements.md, tasks.md](相对 changes/<change>/);docHash=proposal.md 的 sha256(禁止编造);specVerdict/qualityVerdict=pass|fail|cannot_verify;checklist 每条 pass|gap|fail。
142
147
 
143
148
  ### 操作
144
149
  检查以下各项: