kld-sdd 2.6.17 → 2.6.21

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,11 +1,11 @@
1
1
  ---
2
2
  name: opsx-consistency-check
3
- description: "Spec 一致性校验 - 验证代码实现与 spec 文档的一致性"
3
+ description: "Spec 双向一致性校验 - 验证代码实现与 spec 文档的双向一致性"
4
4
  argument-hint: "[change-name] [file...]"
5
5
  license: MIT
6
6
  metadata:
7
7
  author: sdd-team
8
- version: "1.0"
8
+ version: "2.0"
9
9
  allowed-tools:
10
10
  - Bash
11
11
  - Read
@@ -13,15 +13,19 @@ allowed-tools:
13
13
  - Edit
14
14
  ---
15
15
 
16
- 你是一个 SDD(Specification-Driven Development)Spec 一致性校验专家。激活本技能后,你将对比 spec 文档与代码实现,验证代码是否完全符合 spec 定义。
16
+ 你是一个 SDD(Specification-Driven Development)Spec 双向一致性校验专家。激活本技能后,你将对比 spec 文档与代码实现,进行**双向校验**:
17
+
18
+ - **spec → code**:验证代码是否完全符合 spec 定义(代码是否遗漏了 spec 的要求)
19
+ - **code → spec**:验证 spec 是否准确反映代码实现(spec 是否遗漏了代码的实际行为)
17
20
 
18
21
  > **对抗性审核原则**
19
22
  >
20
23
  > 假设 spec 和代码是由其他人(或其他 AI)生成的,你的任务是**严格找出不一致、遗漏和潜在问题**,而不是确认一致性。
21
24
  >
22
25
  > - 不要假设"既然 spec 这样写了,代码肯定也这样实现了"
26
+ > - 不要假设"既然代码这样写了,spec 肯定也覆盖了"
23
27
  > - 不要跳过看似正确但未经逐行比对的细节
24
- > - 对每个断言都要找到**具体的代码证据**,找不到就标记为 missed
28
+ > - 对每个断言都要找到**具体的证据**(代码行号或 spec 章节),找不到就标记为 missed
25
29
  > - 宁可误报(false positive),不可漏报(false negative)
26
30
 
27
31
  ---
@@ -30,9 +34,9 @@ allowed-tools:
30
34
 
31
35
  | 维度 | 内容 |
32
36
  |------|------|
33
- | 核心问题 | 代码实现是否符合 spec 定义 |
34
- | 关键输出 | 结构化校验报告(置信度 + 维度详情 + 怀疑清单 + 建议) |
35
- | 校验维度 | 接口契约、业务规则、校验规则、错误码、前后端一致性 |
37
+ | 核心问题 | spec 与代码是否双向一致(代码是否符合 spec + spec 是否反映了代码) |
38
+ | 关键输出 | 结构化校验报告(置信度 + 双向维度详情 + 怀疑清单 + 建议) |
39
+ | 校验维度 | 接口契约、业务规则、校验规则、错误码、前后端一致性(双向合并判定) |
36
40
  | 上游依赖 | spec.md + 代码文件 |
37
41
  | 校验方式 | **纯 AI 语义分析**(零外部依赖) |
38
42
 
@@ -50,11 +54,20 @@ allowed-tools:
50
54
 
51
55
  若用户提供了具体文件路径,只校验这些文件对应的 spec。
52
56
 
53
- 若均未提供,列出所有活跃变更供用户选择:
57
+ 若均未提供,执行以下流程:
58
+
59
+ 1. 获取活跃变更列表:
54
60
  ```bash
55
61
  ls openspec/changes/ | grep -v archive
56
62
  ```
57
63
 
64
+ 2. 根据变更数量处理:
65
+ - **0 个活跃变更** → 提示"无活跃变更,跳过校验"并结束
66
+ - **1 个活跃变更** → 自动选择该变更,向用户确认后执行
67
+ - **多个活跃变更** → **必须**使用 `AskUserQuestion` 让用户选择,禁止自动推断
68
+
69
+ > **⛔ 强制约束**:多 change 场景下,禁止根据当前打开的文件、git diff 或其他上下文自动推断要校验的 change。必须由用户显式选择。
70
+
58
71
  **1.2 确认审核模式**
59
72
 
60
73
  | 模式 | 参数 | 说明 |
@@ -74,102 +87,49 @@ ls openspec/changes/ | grep -v archive
74
87
 
75
88
  #### 2.0 多仓库检测(强制前置步骤)
76
89
 
77
- > **背景**:项目可能采用多仓库(multi-repo)结构,即工作区内包含多个独立的 git 仓库(如前端、后端各自独立仓库,spec 文档在根仓库)。单仓库场景下本步骤无副作用。
78
-
79
- **检测与采集流程**:
90
+ > 项目可能采用多仓库结构(前端、后端各自独立仓库)。单仓库场景下本步骤无副作用。
80
91
 
81
- **Step A — 发现所有 git 仓库**:
82
- ```bash
83
- # 1. 获取根仓库的 git top-level
84
- git rev-parse --show-toplevel
85
- # 结果记为 ROOT_REPO
86
-
87
- # 2. 扫描工作区下所有子目录中的 .git(目录或文件, submodule 的 .git 是文件)
88
- # 找出所有独立 git 仓库(排除 ROOT_REPO 自身)
89
- find . -name ".git" -maxdepth 3 -not -path "./.git" | ...
90
- # 对每个找到的子仓库,用 git rev-parse --show-toplevel 确认其根路径
91
- ```
92
-
93
- **Step B — 逐仓库采集变更**:
92
+ **采集流程**:
94
93
 
95
- 对**每个** git 仓库(包括根仓库和所有子仓库),分别执行:
96
- ```bash
97
- # 进入该仓库目录
98
- cd <repo-root>
99
- # 获取该仓库的所有变更文件(暂存 + 未暂存)
100
- git diff HEAD --name-only
101
- ```
94
+ 1. 检查是否存在 `.sdd-workspace.yaml`,若存在则读取 `code_repos` 列表(如 `backend`、`frontend`)
95
+ 2. 对每个仓库执行 `git diff HEAD --name-only` 获取变更文件
96
+ 3. 将路径归一化为**相对于工作区根目录**的路径(如 `backend/src/main/java/.../UserController.java`)
97
+ 4. 合并为 `ALL_CHANGED_FILES`
102
98
 
103
- **Step C 路径归一化**:
104
-
105
- 将各子仓库的变更文件路径转换为**相对于工作区根目录**的绝对路径或相对路径:
106
- - 根仓库的文件:路径不变(如 `openspec/changes/...`)
107
- - 子仓库的文件:拼接子仓库相对路径 + 文件路径(如 `sdd-demo2/src/main/java/.../UserController.java`)
108
-
109
- **Step D — 合并变更列表**:
110
-
111
- 将所有仓库的变更文件合并为统一的 `ALL_CHANGED_FILES` 列表,每个条目格式为:
112
- ```
113
- <workspace-relative-path> ← <repo-root-relative-path>
114
- ```
115
- 例如:
116
- ```
117
- sdd-demo2/src/main/java/.../UserController.java ← sdd-demo2 (repo)
118
- sdd-demo1/src/api/user.js ← sdd-demo1 (repo)
119
- ```
120
-
121
- > **单仓库兼容**:若 Step A 未发现任何子仓库,则退化为仅在根仓库执行 `git diff HEAD --name-only`,行为与原有逻辑完全一致。
99
+ > **单仓库兼容**:若不存在 `.sdd-workspace.yaml`,直接对工作区根目录执行 `git diff HEAD --name-only`。
122
100
 
123
101
  根据校验范围获取变更文件列表:
124
102
 
125
- **若指定了 change-name**:
126
- ```bash
127
- # 按 Step A-D 采集所有仓库的变更文件,合并为 ALL_CHANGED_FILES
128
- ```
129
-
130
- **若未指定变更**:
131
- ```bash
132
- # 同样按 Step A-D 采集所有仓库的变更文件
133
- ```
134
-
135
- **若指定了具体文件**:
136
- 直接使用用户指定的文件列表。
103
+ - **指定了 change-name 或未指定变更**:按上述流程采集所有仓库的变更文件,合并为 `ALL_CHANGED_FILES`
104
+ - **指定了具体文件**:直接使用用户指定的文件列表
137
105
 
138
106
  #### 2.1 变更文件筛选(强制)
139
107
 
140
108
  > **核心原则**:变更列表中可能包含多个变更的文件,也可能包含不涉及 spec 一致性校验的文件(如文档、配置、脚本等)。必须筛选出**与当前变更相关且可进行 spec 一致性校验**的文件。
141
109
 
142
- **筛选规则 — 排除以下文件**:
143
-
144
- | 排除类型 | 文件模式 | 说明 |
145
- |---------|---------|------|
146
- | 非当前变更的代码 | 路径不属于当前 change-name spec 覆盖范围 | 根据 spec 的模块/路径定义判断 |
147
- | 文档文件 | `*.md`, `*.txt`, `*.rst`(非 spec 文件) | 文档不参与一致性校验 |
148
- | 配置文件 | `*.yml`, `*.yaml`, `*.properties`, `*.ini`, `*.toml`, `*.json`(非路由/接口定义) | 配置文件通常不涉及 spec 接口契约校验 |
149
- | 脚本文件 | `*.bat`, `*.sh`, `*.cjs`, `*.mjs`, `*.ps1` | 工具脚本不参与一致性校验 |
150
- | 类型声明文件 | `*.d.ts`, `*.d.tsx` | TypeScript 类型声明文件不涉及 spec 校验 |
151
- | 依赖管理 | `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`, `*.lock`, `go.sum`, `Cargo.lock` | 锁文件不参与校验 |
152
- | 构建产物 | `dist/`, `build/`, `target/`, `out/`, `*.min.js`, `*.min.css` | 编译/打包产物不参与校验 |
153
- | 测试文件 | `*test.*`, `*spec.*`, `*_test.*`, `__tests__/`, `tests/`, `test/` | 测试代码不参与本次校验(由 opsx-test 负责) |
154
- | 已删除文件 | git status 为 `deleted` 的文件 | 已删除的文件无法进行校验 |
155
- | 工具链配置 | `.gitignore`, `.editorconfig`, `.prettierrc`, `.eslintrc.*`, `tsconfig.*`, `*.config.js/ts` | 开发工具配置不涉及业务 spec |
156
-
157
- **筛选规则 — 保留以下文件**:
158
-
159
- | 保留类型 | 文件模式 | 说明 |
160
- |---------|---------|------|
161
- | 业务代码 | 与当前变更 spec 直接相关的源代码文件 | 根据 spec 覆盖范围判断 |
110
+ **筛选规则**:
111
+
112
+ | 操作 | 文件模式 | 说明 |
113
+ |------|---------|------|
114
+ | 保留 | 业务代码(`*.java` / `*.ts` / `*.tsx` / `*.js` / `*.jsx` / `*.vue` / `*.py`,排除 `*.d.ts`) | 与当前变更 spec 直接相关的源代码 |
115
+ | 排除 | `*.md`, `*.txt`, `*.rst`(非 spec 文件) | 文档不参与校验 |
116
+ | 排除 | `*.yml`, `*.yaml`, `*.properties`, `*.ini`, `*.toml`, `*.json`(非路由/接口定义) | 配置文件不涉及接口契约 |
117
+ | 排除 | `*.bat`, `*.sh`, `*.cjs`, `*.mjs`, `*.ps1` | 工具脚本不参与校验 |
118
+ | 排除 | `*.d.ts`, `*.d.tsx` | 类型声明文件不涉及 spec 校验 |
119
+ | 排除 | `*.lock`, `package-lock.json` | 锁文件不参与校验 |
120
+ | 排除 | `dist/`, `build/`, `target/`, `out/`, `*.min.js`, `*.min.css` | 构建产物不参与校验 |
121
+ | 排除 | `*test.*`, `*spec.*`, `*_test.*`, `__tests__/`, `tests/`, `test/` | 测试代码由 opsx-test 负责 |
122
+ | 排除 | git status 为 `deleted` 的文件 | 已删除文件无法校验 |
123
+ | 排除 | `.gitignore`, `.editorconfig`, `.prettierrc`, `.eslintrc.*`, `tsconfig.*` | 工具链配置不涉及业务 spec |
124
+ | 排除 | 路径不属于当前 change-name 的 spec 覆盖范围的文件 | 非当前变更的代码 |
162
125
 
163
126
  **各语言业务代码识别**:
164
127
 
165
128
  | 语言 | 业务代码扩展名 | 典型目录模式(参考) |
166
129
  |------|--------------|-------------------|
167
130
  | Java | `*.java` | controller, service, repository, model, dto, entity, config, filter, handler |
168
- | TypeScript/JavaScript | `*.ts`, `*.tsx`, `*.js`, `*.jsx`(排除 `*.d.ts`) | api, service, store, hook, component, page, controller, model, util |
131
+ | TypeScript/JavaScript | `*.ts`, `*.tsx`, `*.js`, `*.jsx`, `*.vue`(排除 `*.d.ts`) | api, service, store, hook, component, page, controller, model, util |
169
132
  | Python | `*.py` | views, controllers, services, models, serializers, handlers, routers |
170
- | Go | `*.go`(排除 `*_test.go`) | handler, service, repository, model, controller, api |
171
- | Rust | `*.rs` | handler, service, model, controller, api |
172
- | C# | `*.cs` | Controllers, Services, Repositories, Models, DTOs |
173
133
 
174
134
  **筛选方法**:
175
135
  1. 获取变更文件完整列表(按步骤 2.0 的多仓库流程,合并为 `ALL_CHANGED_FILES`)
@@ -186,10 +146,13 @@ sdd-demo1/src/api/user.js ← sdd-demo1 (repo)
186
146
 
187
147
  ### 3. 读取 Spec 文档
188
148
 
189
- 按以下顺序读取 spec 内容(排除 archive 和 logs):
149
+ 读取当前变更的 spec 和全局 spec:
190
150
 
191
151
  ```bash
192
- find openspec -name "*.md" -not -path "*/archive/*" -not -path "*/logs/*"
152
+ # 变更级 spec
153
+ find openspec/changes/<change-name>/specs -name "spec.md"
154
+ # 全局 spec
155
+ find openspec/specs -name "spec.md"
193
156
  ```
194
157
 
195
158
  重点提取:
@@ -197,36 +160,25 @@ find openspec -name "*.md" -not -path "*/archive/*" -not -path "*/logs/*"
197
160
  - 业务规则(条件判断、状态流转、权限控制)
198
161
  - 校验规则(字段约束、非空校验、格式要求)
199
162
  - 错误码定义(错误码、触发条件、响应格式)
163
+ - 前后端一致性(前端请求/响应字段与后端 DTO 字段的对应关系)
200
164
 
201
165
  ### 4. 识别项目语言并确定文件范围
202
166
 
203
- **4.1 识别项目语言**
167
+ **4.1 识别项目语言与关联文件扫描**
204
168
 
205
169
  > **多仓库场景**:每个子仓库可能使用不同的技术栈(如前端 Vue、后端 Java),需分别识别。
206
170
 
207
- | 项目标识 | 语言 | 代码文件 | 路由定义示例 |
208
- |---------|------|---------|-------------|
209
- | `pom.xml` / `build.gradle` | Java | `*.java` | `@RequestMapping` / `@PostMapping` |
210
- | `package.json` | TypeScript/JS | `*.ts`/`*.tsx`/`*.js`/`*.jsx` | `router.get()` / `@Get()` / `app.get()` |
211
- | `requirements.txt` / `pyproject.toml` | Python | `*.py` | `@app.route()` / `@router.get()` |
212
- | `go.mod` | Go | `*.go` | `r.GET()` / `e.GET()` |
213
- | `Cargo.toml` | Rust | `*.rs` | `#[get()]` / `Router::route()` |
214
- | `*.csproj` / `*.sln` | C# | `*.cs` | `[HttpGet]` / `[HttpPost]` |
171
+ | 项目标识 | 语言 | 代码文件 | 路由定义示例 | 引用语句 |
172
+ |---------|------|---------|-------------|---------|
173
+ | `pom.xml` / `build.gradle` | Java | `*.java` | `@RequestMapping` / `@PostMapping` | `import` |
174
+ | `package.json` | TypeScript/JS/Vue | `*.ts`/`*.tsx`/`*.js`/`*.jsx`/`*.vue` | `router.get()` / `@Get()` / `app.get()` | `import ... from` |
175
+ | `requirements.txt` / `pyproject.toml` | Python | `*.py` | `@app.route()` / `@router.get()` | `import` / `from ... import` |
215
176
 
216
177
  **重要**:后端项目必须同时校验前端 TypeScript/JavaScript 文件(若存在),确保前后端字段对齐。
217
178
 
218
179
  **4.2 扫描关联文件**
219
180
 
220
- 从 `CHANGED_FILES` 中的代码文件出发,扫描 import/引用关系,找出直接关联的文件:
221
-
222
- | 语言 | 代码文件 | 引用语句 |
223
- |------|---------|---------|
224
- | Java | `*.java` | `import` |
225
- | TypeScript/JS | `*.ts`/`*.tsx`/`*.js`/`*.jsx` | `import ... from` |
226
- | Python | `*.py` | `import` / `from ... import` |
227
- | Go | `*.go` | `import "..."` |
228
- | Rust | `*.rs` | `use` / `mod` |
229
- | C# | `*.cs` | `using` |
181
+ 从 `CHANGED_FILES` 中的代码文件出发,根据上表"引用语句"列扫描 import/引用关系,找出直接关联的文件。
230
182
 
231
183
  **关联文件筛选规则**:
232
184
  - 仅包含**直接引用**的文件(一层深度,不递归)
@@ -246,106 +198,131 @@ find openspec -name "*.md" -not -path "*/archive/*" -not -path "*/logs/*"
246
198
  > **范围约束(强制)**:
247
199
  > - **分析范围**:`REFERENCE_FILES`(变更文件 + 关联文件),用于理解完整上下文
248
200
  > - **报告范围**:仅 `CHANGED_FILES`,**报告中的所有差异项只能引用 `CHANGED_FILES` 中的文件**
249
- >
250
- > **关键规则**:
251
- > - 若校验发现问题的代码位于 `REFERENCE_FILES` 但**不在** `CHANGED_FILES` 中(即该文件不属于当前变更范围),**不得**将其作为差异项报告
252
- > - 此类问题应归入"怀疑清单",标注为"⚠️ 该问题位于非变更文件 `<文件路径>` 中,建议检查该文件是否属于当前变更"
253
- > - 例如:后端 `AuthController.java` 在 `CHANGED_FILES` 中,但前端 `authService.ts` 不在 `CHANGED_FILES` 中,则前后端不一致的问题**不能**作为差异报告,只能放入怀疑清单
201
+ > - 若问题代码位于 `REFERENCE_FILES` 但不在 `CHANGED_FILES` 中,**不得**作为差异报告,只能放入"怀疑清单"
254
202
 
255
- ### 6. 执行五维一致性校验
203
+ ### 6. 执行双向一致性校验
256
204
 
257
- 基于代码内容,逐行对比 spec 定义进行校验。
205
+ 基于代码内容和 spec 定义,进行双向校验。
258
206
 
259
- > **对抗性审核要求**:对每个检查项,必须在代码中找到**具体行号**作为证据。找不到证据的,一律标记为 ❌ missed。不要因为"看起来应该实现了"就标记为 ✅。
207
+ > **对抗性审核要求**:
208
+ > - 对每个检查项,必须找到**具体证据**(代码行号或 spec 章节)。找不到证据的,标记为 ❌ 或 ⚠️。
260
209
 
261
210
  > **逐字段对比强制约束**:
262
211
  > - 响应结构必须**逐字段**对比,不允许"大致匹配"
263
212
  > - 字段名必须**精确匹配**(`token` ≠ `accessToken`,`id` ≠ `userId`)
264
213
  > - 嵌套结构的每一层都必须校验
265
- > - 若代码中构造响应的方式是 Map/字典/结构体/序列化对象(如 Java `Map.of()`、Python `dict`、Go `map[string]interface{}`、C# `new {}`),必须提取所有 key 并与 spec 逐一对比
214
+ > - 若代码中构造响应的方式是 Map/字典/结构体/序列化对象(如 Java `Map.of()`、Python `dict`),必须提取所有 key 并与 spec 逐一对比
215
+
216
+ > **证据标注要求**:每个检查项必须标注具体证据位置(代码文件:行号 或 spec 文件:章节),找不到证据的标记为 ❌。
266
217
 
267
218
  #### 6.1 后端接口契约校验
268
219
 
269
220
  对 `CHANGED_FILES` 中的后端代码文件 + 关联 DTO/Model 的数据,进行以下校验:
270
221
 
271
- - [ ] API 路径:路由定义与 spec 定义的路径一致(证据:代码文件:行号)
272
- - [ ] HTTP 方法:路由声明的 HTTP 方法与 spec 定义的方法匹配(证据:代码文件:行号)
273
- - [ ] 请求参数:请求体/查询参数的字段名、类型与 spec 定义的参数一致(证据:DTO/Model文件:行号)
274
- - [ ] 参数约束:字段上的校验注解/装饰器/类型约束与 spec 定义的约束一致(证据:DTO/Model文件:行号)
275
- - [ ] 响应结构:**逐字段**对比代码中构造的响应与 spec 定义的响应字段(证据:代码文件:行号)
276
- - [ ] HTTP 状态码:代码中返回的 HTTP 状态码与 spec 定义的状态码一致(证据:代码文件:行号)
277
- - [ ] 错误码:代码中抛出的业务错误码与 spec 定义一致(证据:代码文件:行号)
222
+ - [ ] API 路径与 spec 一致
223
+ - [ ] HTTP 方法与 spec 一致
224
+ - [ ] 请求参数字段名、类型与 spec 一致
225
+ - [ ] 参数约束(校验注解/装饰器/类型)与 spec 一致
226
+ - [ ] 响应结构**逐字段**与 spec 一致
227
+ - [ ] HTTP 状态码与 spec 一致
228
+ - [ ] 错误码与 spec 一致
229
+ - [ ] 代码中额外的接口/端点已在 spec 中记录
278
230
 
279
231
  #### 6.2 前端接口契约校验
280
232
 
281
- 对 `CHANGED_FILES` 中的前端代码文件(TypeScript/JavaScript/HTML 等),进行以下校验:
233
+ 对 `CHANGED_FILES` 中的前端代码文件(TypeScript/JavaScript/Vue 等),进行以下校验:
282
234
 
283
- - [ ] API 路径:HTTP 请求调用的 URL 与 spec 定义的路径一致(证据:代码文件:行号)
284
- - [ ] HTTP 方法:请求声明的 HTTP 方法与 spec 定义的方法匹配(证据:代码文件:行号)
285
- - [ ] 请求字段:请求体/查询参数中的字段名与 spec 定义的参数名一致(证据:代码文件:行号)
286
- - [ ] 校验规则:代码中的字段校验逻辑与 spec 定义的字段约束一致(证据:代码文件:行号)
287
- - [ ] 存储字段:本地存储操作的 key 与 spec 定义的响应字段对应(证据:代码文件:行号)
288
- - [ ] 响应处理:代码中读取响应字段的名称与后端实际返回的字段名一致(证据:代码文件:行号)
235
+ - [ ] API 路径与 spec 一致
236
+ - [ ] HTTP 方法与 spec 一致
237
+ - [ ] 请求字段与 spec 一致
238
+ - [ ] 校验规则与 spec 一致
239
+ - [ ] localStorage keys 与 spec 响应字段对应
240
+ - [ ] 响应字段读取与后端返回一致
241
+ - [ ] 代码中额外的前端行为已在 spec 中记录
289
242
 
290
243
  #### 6.3 业务规则校验
291
244
 
292
- - [ ] spec 中的条件判断在代码中有对应实现(证据:代码文件:行号)
293
- - [ ] spec 中的状态流转在代码中体现(证据:代码文件:行号)
294
- - [ ] spec 中的权限要求在代码中有校验(证据:代码文件:行号)
295
- - [ ] 条件分支逻辑正确性(如 if/else 链的条件判断顺序是否导致不可达分支)
245
+ - [ ] 条件判断与 spec 一致
246
+ - [ ] 状态流转与 spec 一致
247
+ - [ ] 权限要求与 spec 一致
248
+ - [ ] 条件分支逻辑正确性(无不可达分支)
249
+ - [ ] 代码中额外的业务逻辑已在 spec 中记录
296
250
 
297
251
  #### 6.4 校验规则
298
252
 
299
- - [ ] spec 中的字段约束(长度、格式、范围)在代码中有对应校验(证据:代码文件:行号)
300
- - [ ] spec 中标记为必填的字段有非空检查(证据:代码文件:行号)
253
+ - [ ] 字段约束(长度、格式、范围)与 spec 一致
254
+ - [ ] 必填字段有非空检查
255
+ - [ ] 代码中额外的校验规则已在 spec 中记录
301
256
 
302
257
  #### 6.5 错误码校验
303
258
 
304
- - [ ] spec 中定义的错误码在代码中实现(证据:代码文件:行号)
305
- - [ ] 错误码的触发条件与 spec 描述一致(证据:代码文件:行号)
306
- - [ ] 错误响应格式与 spec 定义一致(证据:代码文件:行号)
259
+ - [ ] 错误码与 spec 一致
260
+ - [ ] 错误触发条件与 spec 一致
261
+ - [ ] 错误响应格式与 spec 一致
262
+ - [ ] 代码中额外的错误码已在 spec 中记录
307
263
 
308
264
  #### 6.6 前后端一致性校验
309
265
 
310
266
  对 `CHANGED_FILES` 中同时包含前端和后端文件时,进行跨文件对比:
311
267
 
312
- > **多仓库场景**:前端和后端可能分别位于不同的子仓库(如 `sdd-demo1/` 和 `sdd-demo2/`),但只要它们的变更文件都在 `CHANGED_FILES` 中,就可以进行跨文件对比。
313
-
314
268
  - [ ] 前端请求字段 vs 后端 DTO 字段:字段名必须精确匹配(如前端 `phone` vs 后端 `username` 为不匹配)
315
269
  - [ ] 前端响应字段读取 vs 后端响应构造:字段名必须精确匹配
316
270
  - [ ] 前端校验规则 vs 后端校验规则:同一字段的校验约束必须一致
317
271
 
318
- > **强制约束**:
319
- > - 若跨文件校验涉及的某个文件不在 `CHANGED_FILES` 中,**不得**将其作为差异项报告
320
- > - 此类问题应归入"怀疑清单",标注为"⚠️ 该问题涉及非变更文件 `<文件路径>`,建议检查该文件是否属于当前变更"
321
- > - 例如:后端 Controller 在 `CHANGED_FILES` 中,但前端 service 不在,则前后端字段不一致的问题不能报告为差异
272
+ > **强制约束**:若跨文件校验涉及的某个文件不在 `CHANGED_FILES` 中,**不得**将其作为差异项报告,只能归入"怀疑清单"。
322
273
 
323
274
  #### 6.7 生成怀疑清单
324
275
 
325
- 校验完成后,**必须**生成一份怀疑清单,列出以下潜在风险:
276
+ 校验完成后,**必须**生成一份怀疑清单。清单中的差异项**必须**使用判定标准中定义的四类问题分类(见"一、问题分类"),潜在风险使用补充类型。
326
277
 
327
- | 怀疑类型 | 说明 |
328
- |---------|---------|
278
+ **核心差异分类**(必须使用,与判定标准一致):
279
+
280
+ | 类型 | 说明 | 置信度影响 |
281
+ |------|------|-----------|
282
+ | **代码有 spec 没有(业务)** | 代码实现了业务功能但 spec 未描述 | 业务功能→low,辅助功能→medium |
283
+ | **代码有 spec 没有(技术)** | 代码实现了纯技术支撑逻辑,不涉及业务契约 | 不降级 |
284
+ | **spec 有代码没有** | spec 定义了但代码未实现 | 关键→low,非关键→medium |
285
+ | **不一致** | 代码和 spec 都有但不匹配 | 关键→low,非关键→medium |
286
+
287
+ **补充怀疑类型**(用于潜在风险,不影响置信度):
288
+
289
+ | 类型 | 说明 |
290
+ |------|------|
329
291
  | Spec 歧义 | spec 中描述模糊、可能有多种理解的点 |
330
292
  | 隐含假设 | spec 未明确定义但代码做了假设的点 |
331
293
  | 边界遗漏 | spec 未覆盖但代码可能遇到的边界条件 |
332
294
  | 命名不一致 | 前后端字段名、错误码命名风格差异 |
333
295
  | 跨 Spec 矛盾 | 不同 spec 之间可能存在的定义冲突 |
334
296
  | **非变更文件问题** | 校验中发现的问题涉及的文件不在 `CHANGED_FILES` 中(不属于当前变更范围),无法作为差异报告,需提醒用户检查该文件 |
297
+ | **技术实现建议** | 代码中的纯技术实现(工具方法、防御性代码、日志等)虽不影响置信度,但若存在可优化空间,可在此记录建议 |
335
298
 
336
299
  每条怀疑项标注风险等级(P0-P3)和建议验证方式。
337
300
 
338
- > **非变更文件问题格式**:
339
- > ```
340
- > ⚠️ 该问题涉及非变更文件 `<文件路径>`,建议检查该文件是否属于当前变更
341
- > 问题描述:<具体问题>
342
- > 建议:若该文件属于当前变更,请将其加入变更范围后重新执行校验
343
- > ```
344
-
345
301
  ### 7. 输出校验报告
346
302
 
347
303
  **所有场景(high/medium/low)都必须生成报告文件**,便于追溯和审计。
348
304
 
305
+
306
+ > **无代码变更场景**:当 `CHANGED_FILES` 为空(所有变更文件均为文档/配置等非代码文件)时,仍需生成报告文件:
307
+ > - 置信度:`high`
308
+ > - 所有维度:`pass`
309
+ > - summary 所有计数:`0`
310
+ > - 报告中注明"本次变更无代码文件,跳过一致性校验"
311
+
312
+ #### 7.0 清理旧报告(强制前置步骤)
313
+
314
+ 生成新报告前,**必须先删除**该 change-name 下所有已存在的报告文件,避免两种模式的报告同时存在导致 git hook 报错:
315
+
316
+ ```bash
317
+ # 删除所有可能的报告文件(若存在)
318
+ rm -f openspec/changes/<change-name>/consistency-report.md
319
+ rm -f openspec/changes/<change-name>/consistency-report-result.json
320
+ rm -f openspec/changes/<change-name>/consistency-report-self-review.md
321
+ rm -f openspec/changes/<change-name>/consistency-report-self-review-result.json
322
+ ```
323
+
324
+ > **原因**:git hook 检测到同一 change-name 下同时存在独立审核和自审模式的报告时,会拒绝提交/推送并要求用户手动删除。
325
+
349
326
  #### 7.1 生成 Markdown 报告
350
327
 
351
328
  将报告写入 md 文件,按审核模式区分文件名:
@@ -374,6 +351,7 @@ JSON 文件结构(必须严格遵循):
374
351
  "confidence": "high | medium | low",
375
352
  "overallResult": "pass | warning | fail",
376
353
  "reviewMode": "independent-review | self-review",
354
+ "humanConfirmed": false,
377
355
  "generatedAt": "<YYYY-MM-DD HH:mm:ss>",
378
356
  "repositories": [
379
357
  { "name": "<仓库名或路径>", "path": "<仓库相对路径>", "changedFiles": 0 }
@@ -389,28 +367,33 @@ JSON 文件结构(必须严格遵循):
389
367
  "totalChecks": 0,
390
368
  "passed": 0,
391
369
  "failed": 0,
392
- "warnings": 0
370
+ "warnings": 0,
371
+ "undocumentedBusiness": 0,
372
+ "undocumentedTechnical": 0,
373
+ "specMissing": 0,
374
+ "inconsistent": 0
393
375
  }
394
376
  }
395
377
  ```
396
378
 
397
379
  **字段说明**:
398
- - `confidence`:整体置信度,必须为 `high` / `medium` / `low` 之一(与 md 报告中的置信度一致)
399
- - `overallResult`:总体结果,`pass`(high 置信度)/ `warning`(medium 置信度)/ `fail`(low 置信度)
400
- - `reviewMode`:审核模式,`independent-review` `self-review`
401
- - `generatedAt`:生成时间,格式 `YYYY-MM-DD HH:mm:ss`
402
- - `repositories`:参与校验的仓库列表(多仓库场景),每项包含仓库名、相对路径、变更文件数;单仓库场景仅包含根仓库
403
- - `dimensions`:各维度校验结果
404
- - `summary`:校验统计摘要
380
+ - `confidence`:`high` / `medium` / `low`
381
+ - `overallResult`:`pass` / `warning` / `fail`
382
+ - `reviewMode`:`independent-review` / `self-review`
383
+ - `humanConfirmed`:自审模式下必填,用户确认断言后设为 `true`;独立审核模式可省略
384
+ - `generatedAt`:`YYYY-MM-DD HH:mm:ss`
385
+ - `repositories`:参与校验的仓库列表
386
+ - `dimensions`:各维度校验结果(`status` + `issues`)
387
+ - `summary`:按四类问题分类计数(`undocumentedBusiness` / `undocumentedTechnical` / `specMissing` / `inconsistent`)
405
388
 
406
- > **强制约束**:JSON 文件必须与 md 报告同时生成。缺少 JSON 文件时,git hook 将输出警告但仍放行(向后兼容)。
389
+ > **强制约束**:JSON 文件必须与 md 报告同时生成,供 git hook 读取置信度进行门禁判定。
407
390
 
408
391
  报告模板:
409
392
 
410
393
  > **时间格式强制约束**:`生成时间` 必须通过时间工具(如 MCP `get_current_time` 或系统命令 `date`)获取当前精确时间,格式为 `YYYY-MM-DD HH:mm:ss`。**禁止**仅填写日期而省略时分秒。
411
394
 
412
395
  ```markdown
413
- # Spec 一致性校验报告
396
+ # Spec 双向一致性校验报告
414
397
 
415
398
  **变更名称**:<change-name>
416
399
  **生成时间**:<YYYY-MM-DD HH:mm:ss>(必须通过时间工具获取,禁止仅填日期)
@@ -418,45 +401,33 @@ JSON 文件结构(必须严格遵循):
418
401
  **仓库结构**:<单仓库 / 多仓库(列出各仓库路径)>
419
402
  **校验范围**:<diff 涉及的文件列表,多仓库场景下标注各文件所属仓库>
420
403
 
421
- ## 后端接口契约
422
-
423
- | 接口 | 路径 | 方法 | 请求参数 | 响应类型 | 响应字段 | HttpStatus |
424
- |------|------|------|---------|---------|---------|------------|
425
- | [接口名] | [路径] | [方法] | [DTO名] | [类型] | [字段列表] | [状态码] |
426
-
427
- ### 后端契约校验
428
-
429
- | 检查项 | 代码实际值 | Spec 期望值 | 状态 |
430
- |--------|-----------|------------|------|
431
- | [检查项] | [值] | [值] | ✅/❌ |
432
-
433
- ## 前端接口契约
434
-
435
- | 函数 | URL | 方法 | body 字段 | 正则表达式 | localStorage keys |
436
- |------|-----|------|----------|-----------|------------------|
437
- | [函数名] | [URL] | [方法] | [字段列表] | [正则] | [key列表] |
438
-
439
- ### 前端契约校验
440
-
441
- | 检查项 | 代码实际值 | Spec 期望值 | 状态 |
442
- |--------|-----------|------------|------|
443
- | [检查项] | [值] | [值] | ✅/❌ |
444
-
445
- ## DTO 字段约束
446
-
447
- | DTO | 字段 | 类型 | 约束 |
448
- |-----|------|------|------|
449
- | [DTO名] | [字段名] | [类型] | [注解/约束] |
450
-
451
404
  ## 校验结果
452
405
 
453
406
  | 维度 | 状态 | 详情 | 证据 |
454
407
  |------|------|------|------|
455
- | 接口契约 | ✅/⚠️/❌ | [具体问题] | [代码文件:行号] |
456
- | 业务规则 | ✅/⚠️/❌ | [具体问题] | [代码文件:行号] |
457
- | 校验规则 | ✅/⚠️/❌ | [具体问题] | [代码文件:行号] |
458
- | 错误码 | ✅/⚠️/❌ | [具体问题] | [代码文件:行号] |
459
- | 前后端一致性 | ✅/⚠️/❌ | [具体问题] | [代码文件:行号] |
408
+ | 接口契约 | ✅/⚠️/❌ | [具体问题] | [文件:行号/章节] |
409
+ | 业务规则 | ✅/⚠️/❌ | [具体问题] | [文件:行号/章节] |
410
+ | 校验规则 | ✅/⚠️/❌ | [具体问题] | [文件:行号/章节] |
411
+ | 错误码 | ✅/⚠️/❌ | [具体问题] | [文件:行号/章节] |
412
+ | 前后端一致性 | ✅/⚠️/❌ | [具体问题] | [文件:行号/章节] |
413
+
414
+ > 置信度为 medium 或 low 时,在此说明原因(如:接口契约=warning,因缺少辅助接口 /api/export 的 spec 记录)
415
+
416
+ ### 差异与怀疑清单
417
+
418
+ > 以下列出代码与 spec 之间的所有差异,以及校验过程中发现的潜在风险。
419
+ > **类型**必须使用判定标准中定义的四类问题分类(见"一、问题分类")。
420
+
421
+ | # | 类型 | 风险等级 | 位置 | 描述 | 建议操作 |
422
+ |---|------|---------|------|------|---------|
423
+ | 1 | 代码有 spec 没有(业务) | P0-P3 | [代码文件:行号] | [代码实现了业务功能但 spec 未描述] | 补充到 spec [章节] |
424
+ | 2 | 代码有 spec 没有(技术) | — | [代码文件:行号] | [代码实现了纯技术支撑逻辑,不涉及业务契约] | 无需补充 spec(纯技术实现不要求覆盖) |
425
+ | 3 | spec 有代码没有 | P0-P3 | [spec 文件:章节] | [spec 定义了但代码未实现] | 补充代码实现或更新 spec |
426
+ | 4 | 不一致 | P0-P3 | [代码文件:行号 / spec 文件:章节] | [代码和 spec 都有但不匹配] | 修改代码或 spec 使之一致 |
427
+ | 5 | Spec 歧义 | P2 | [spec 文件:章节] | [描述模糊,可能有多种理解] | 明确 spec 描述 |
428
+ | 6 | 隐含假设 | P1 | [代码文件:行号] | [spec 未明确定义但代码做了假设] | 补充到 spec 或确认假设正确 |
429
+ | 7 | 边界遗漏 | P2 | [代码文件:行号] | [spec 未覆盖但代码可能遇到的边界条件] | 补充边界处理到 spec |
430
+ | 8 | 非变更文件问题 | P1 | [文件路径] | [问题涉及的文件不在当前变更范围] | 检查该文件是否属于当前变更 |
460
431
 
461
432
  **置信度**:high / medium / low
462
433
 
@@ -481,18 +452,10 @@ JSON 文件结构(必须严格遵循):
481
452
 
482
453
  **校验结果**:✅ 符合 / ❌ 不符合
483
454
 
484
- ## 怀疑清单
485
-
486
- > 以下列出校验过程中发现的潜在风险,需要人工确认或进一步验证。
487
-
488
- | # | 怀疑类型 | 风险等级 | 描述 | 建议验证方式 |
489
- |---|---------|---------|------|-------------|
490
- | 1 | Spec 歧义 | P2 | [描述] | [建议] |
491
- | 2 | 隐含假设 | P1 | [描述] | [建议] |
492
-
493
455
  ## 建议操作
494
456
 
495
457
  - [具体修复建议及对应文件:行号]
458
+ - [spec 补充建议及对应章节]
496
459
  ```
497
460
 
498
461
  > **注意**:若使用自审模式(`--mode=self-review`),报告需额外包含"人工确认"章节:
@@ -508,6 +471,8 @@ JSON 文件结构(必须严格遵循):
508
471
  >
509
472
  > **人工确认方式**:
510
473
  > - 逐条核对上述断言,在"人工确认"列填写 `✅ 已确认` 或 `❌ 有误`
474
+ > - 全部确认后,将 JSON 结果文件中的 `humanConfirmed` 字段设为 `true`
475
+ > - **git hook 会检查此字段**:自审模式下 `humanConfirmed` 不为 `true` 将阻止提交
511
476
  > ```
512
477
 
513
478
  同时在对话中输出报告摘要。
@@ -520,9 +485,10 @@ JSON 文件结构(必须严格遵循):
520
485
  > - B. 运行 `/opsx:test` 执行测试验证"
521
486
 
522
487
  **有问题**:
523
- > " 发现 [N] 个不一致项需要修复:
524
- > - 问题 1:[描述] → 建议修改 [文件:行号]
525
- > - 问题 2:[描述] → 建议修改 [文件:行号]
488
+ > " 发现 [N] 个问题需要处理:
489
+ > - 代码有 spec 没有(业务):[M] → [简要描述]
490
+ > - spec 有代码没有:[M] → [简要描述]
491
+ > - 不一致:[M] 项 → [简要描述]
526
492
  >
527
493
  > 请选择:
528
494
  > - A. 逐个修复
@@ -532,50 +498,7 @@ JSON 文件结构(必须严格遵循):
532
498
 
533
499
  ## 判定标准
534
500
 
535
- ### 一、维度内部判定
536
-
537
- | 维度 | pass | warning | fail |
538
- |------|------|---------|------|
539
- | **接口契约** | 所有检查项 ✅ | 有 ⚠️ 但无 ❌ | 有任何 ❌ |
540
- | **业务规则** | 所有规则实现 | 非关键规则缺失 | 关键规则缺失 |
541
- | **校验规则** | 所有约束实现 | 非关键约束缺失 | 必填字段无校验 |
542
- | **错误码** | 所有错误码实现 | 非关键错误码缺失 | 关键错误码缺失 |
543
- | **前后端一致性** | 所有字段匹配 | 部分字段不匹配 | 核心字段不匹配 |
544
-
545
- ### 二、整体置信度判定
546
-
547
- ```
548
- low(拒绝):
549
- - 接口契约 = fail
550
- - 或 业务规则 = fail(关键规则缺失)
551
- - 或 校验规则 = fail(必填字段无校验)
552
-
553
- medium(警告):
554
- - 接口契约 ≠ fail
555
- - 且 任一维度 = warning 或 fail(非关键)
556
-
557
- high(通过):
558
- - 所有维度 = pass
559
- ```
560
-
561
- ### 三、关键 vs 非关键 定义
562
-
563
- | 维度 | 关键(→ low) | 非关键(→ medium) |
564
- |------|--------------|-------------------|
565
- | **业务规则** | 认证、授权、加密、核心业务流程 | 日志记录、审计、性能优化 |
566
- | **校验规则** | 必填字段、安全相关约束(如密码强度) | 格式美化、长度限制 |
567
- | **错误码** | 影响前端决策的错误码(如 401、403) | 提示性错误码(如参数格式提示) |
568
- | **前后端一致性** | 核心字段(token、userId、username) | 次要字段(createdAt 格式、可选字段) |
569
-
570
- ### 四、示例
571
-
572
- | 场景 | 各维度状态 | 置信度 | 原因 |
573
- |------|-----------|--------|------|
574
- | 响应字段名错误 | 接口契约=fail | **low** | 接口契约一票否决 |
575
- | 缺少密码复杂度校验 | 校验规则=fail | **low** | 安全相关约束缺失 |
576
- | 缺少某个提示性错误码 | 错误码=warning | **medium** | 非关键错误码缺失 |
577
- | 前端字段名与后端不一致 | 前后端一致性=warning | **medium** | 非核心字段不匹配 |
578
- | 所有维度完美匹配 | 全部=pass | **high** | 完全符合 spec |
501
+ > 详见 [./reference.md](./reference.md),包含:问题分类、置信度判定规则、关键性判断标准、维度判定参考、示例。
579
502
 
580
503
  ---
581
504
 
@@ -584,9 +507,16 @@ high(通过):
584
507
  - 本 Skill 是**只读检查**操作,不修改任何代码或 spec 文件
585
508
  - 只校验与当前变更相关的代码,不对未变更代码做判断
586
509
  - 如果 spec 中没有定义某个接口/规则,不误报为不匹配
510
+ - **纯技术实现不要求 spec 覆盖**:工具方法、防御性代码、日志打印、框架样板、DTO 转换、配置类等不影响业务契约的代码,归类为"代码有 spec 没有(技术)",不降级置信度
587
511
  - 对于 medium 和 low 的情况,必须给出具体的差异说明和改进建议
588
- - 如果只涉及文档变更(无代码变更),直接返回 high
512
+ - 如果只涉及文档变更(无代码变更),**仍需生成报告文件**(置信度 high,所有维度 pass,summary 全为 0),确保 git hook 能正常通过
589
513
  - **响应结构必须逐字段对比**,字段名必须精确匹配,不允许"大致匹配"
590
514
  - **前后端字段名必须精确匹配**,不允许语义等价判断(如 `phone` ≠ `username`)
591
515
  - **报告范围强制约束**:报告中的所有差异项只能引用 `CHANGED_FILES` 中的文件。若问题代码位于关联文件(`REFERENCE_FILES` 但不在 `CHANGED_FILES` 中),不得作为差异报告,只能放入怀疑清单并提示用户检查该文件是否属于当前变更
592
516
  - **多仓库路径约束**:多仓库场景下,报告中的文件路径必须使用**工作区相对路径**(如 `sdd-demo2/src/main/java/.../UserController.java`),而非子仓库内的相对路径
517
+
518
+ ---
519
+
520
+ ## 渐进披露
521
+
522
+ - Read `reference.md` 仅在需要参考判定标准时 — 含问题分类、置信度判定规则、关键性判断标准、维度判定参考、示例。