kld-sdd 2.6.7 → 2.6.8

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.
@@ -0,0 +1,224 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * commit-msg hook,两种布局:
6
+ *
7
+ * - 多仓(代码仓有 .sdd.yaml,spec 是另一个 clone):
8
+ * 写 Spec-Revision(spec 干净 HEAD 的完整 SHA)+ Spec-Change(活动 change,可多条)。
9
+ * - 单仓(openspec 与代码同仓):
10
+ * spec 与代码在同一次 commit 里,指针自指无意义 → 只写 Spec-Change,不写 Spec-Revision,
11
+ * 且不做「spec 工作区干净」检查(提交进行中必然是脏的)。
12
+ *
13
+ * Spec-Change 来源:KLD_SDD_CHANGE(显式覆盖)> sdd.config.yaml active_changes。
14
+ * Not from branch name, ever.
15
+ *
16
+ * Authority: 10-最小化多仓库关联与变更命名方案.md
17
+ */
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+
22
+ function resolveOntologyDir() {
23
+ const deployed = path.join(__dirname, '..', 'ontology');
24
+ if (fs.existsSync(path.join(deployed, 'change-key.cjs'))) return deployed;
25
+ const fromPackageTemplates = path.join(__dirname, '..', '..', 'skywalk-sdd', 'ontology');
26
+ if (fs.existsSync(path.join(fromPackageTemplates, 'change-key.cjs'))) return fromPackageTemplates;
27
+ throw new Error('无法定位 skywalk-sdd/ontology');
28
+ }
29
+
30
+ const ontologyDir = resolveOntologyDir();
31
+ const changeKey = require(path.join(ontologyDir, 'change-key.cjs'));
32
+ const sddConfig = require(path.join(ontologyDir, 'sdd-config.cjs'));
33
+ const activeChanges = require(path.join(ontologyDir, 'active-changes.cjs'));
34
+ const { parseFrontmatter } = require(path.join(ontologyDir, 'artifact-parser.cjs'));
35
+
36
+ function projectRootFromHook() {
37
+ const explicit = process.argv.find((arg) => arg.startsWith('--project='));
38
+ if (explicit) return path.resolve(explicit.slice('--project='.length));
39
+ return path.resolve(__dirname, '..', '..');
40
+ }
41
+
42
+ function fail(message) {
43
+ console.error(`[kld-sdd commit-msg] ${message}`);
44
+ process.exit(1);
45
+ }
46
+
47
+ function isSddLinkedCodeRepo(codeRepoRoot) {
48
+ return fs.existsSync(path.join(codeRepoRoot, '.sdd.yaml'));
49
+ }
50
+
51
+ /**
52
+ * 决定本次提交按哪种布局处理。布局来自 .sdd.yaml 显式声明,不做启发式推断
53
+ * (否则单独诊断的 spec 仓 clone 会被误判成单仓)。
54
+ *
55
+ * 单仓优先于 specPath:即使用户把 sdd.specPath 指回本仓,也走单仓路径,
56
+ * 否则会因为「spec 工作区不干净」永久阻断提交。
57
+ */
58
+ function resolveLayout(repoRoot, env = process.env) {
59
+ if (!isSddLinkedCodeRepo(repoRoot)) {
60
+ return { mode: 'unlinked' };
61
+ }
62
+ if (sddConfig.isMonoLayout(repoRoot)) {
63
+ return { mode: 'mono', specPath: repoRoot };
64
+ }
65
+
66
+ const context = sddConfig.resolveCodeRepoContext(repoRoot, env);
67
+ if (!context.specPath) {
68
+ fail(context.message || '无法解析 spec clone 路径。请执行 kld-sdd link-spec --path=<spec>');
69
+ }
70
+ if (context.code === sddConfig.CODES.SPEC_PATH_INVALID) {
71
+ fail(context.message);
72
+ }
73
+ // specPath 指回本仓:等同单仓,不能按外部 spec 校验干净度
74
+ if (path.resolve(context.specPath) === path.resolve(repoRoot)) {
75
+ return { mode: 'mono', specPath: repoRoot, context };
76
+ }
77
+ return { mode: 'multi', specPath: context.specPath, context };
78
+ }
79
+
80
+ /**
81
+ * Explicit override: KLD_SDD_CHANGE, comma/space separated for several changes.
82
+ * Not from branch name, not from repo-wide git config.
83
+ */
84
+ function parseEnvChangeKeys(env = process.env) {
85
+ const raw = env.KLD_SDD_CHANGE && String(env.KLD_SDD_CHANGE).trim();
86
+ if (!raw) return null;
87
+ const keys = [];
88
+ for (const token of raw.split(/[,\s]+/).filter(Boolean)) {
89
+ const validated = changeKey.validate(token);
90
+ if (!validated.ok) {
91
+ fail(`KLD_SDD_CHANGE 无效: ${validated.message}`);
92
+ }
93
+ if (!keys.includes(validated.normalized)) keys.push(validated.normalized);
94
+ }
95
+ return keys.length ? keys : null;
96
+ }
97
+
98
+ /**
99
+ * Spec-Change values: explicit env wins, otherwise every active change
100
+ * registered in the spec repo's sdd.config.yaml.
101
+ */
102
+ function resolveChangeKeys(specRoot, env = process.env) {
103
+ const fromEnv = parseEnvChangeKeys(env);
104
+ if (fromEnv) {
105
+ for (const key of fromEnv) {
106
+ requireChangeDir(specRoot, key, { strict: true });
107
+ }
108
+ return { keys: fromEnv, source: 'env' };
109
+ }
110
+ const registered = activeChanges.activeChangeKeys(specRoot);
111
+ const keys = registered.filter((key) => requireChangeDir(specRoot, key, { strict: false }));
112
+ return { keys, source: 'sdd.config.yaml' };
113
+ }
114
+
115
+ /**
116
+ * @param {{ strict: boolean }} options strict=true 阻断提交;false 仅警告并跳过该条
117
+ */
118
+ function requireChangeDir(specRoot, key, options = { strict: true }) {
119
+ const proposalPath = path.join(specRoot, 'openspec', 'changes', key, 'proposal.md');
120
+ if (!fs.existsSync(proposalPath)) {
121
+ const message = `spec 中不存在 change: openspec/changes/${key}/proposal.md`;
122
+ if (options.strict) fail(message);
123
+ console.error(`[kld-sdd commit-msg] 警告: ${message}(已跳过该 Spec-Change)`);
124
+ return false;
125
+ }
126
+ const lines = fs.readFileSync(proposalPath, 'utf8').split(/\r?\n/);
127
+ const fm = parseFrontmatter(lines);
128
+ const declared = (fm['change-key'] || '').toLowerCase();
129
+ if (declared && declared !== key) {
130
+ const message = `proposal change-key (${declared}) 与目录 (${key}) 不一致`;
131
+ if (options.strict) fail(message);
132
+ console.error(`[kld-sdd commit-msg] 警告: ${message}(已跳过该 Spec-Change)`);
133
+ return false;
134
+ }
135
+ return true;
136
+ }
137
+
138
+ /**
139
+ * 重写 Trailer 区块:同名旧行先全部摘除,再作为连续区块追加到末尾。
140
+ *
141
+ * @param {Record<string, string|string[]>} trailers 数组值写成多条同名 Trailer
142
+ */
143
+ function upsertTrailers(message, trailers) {
144
+ let body = String(message || '').replace(/\s+$/g, '');
145
+ const block = [];
146
+
147
+ for (const [name, value] of Object.entries(trailers)) {
148
+ const linePattern = new RegExp(`^${name}:[^\\n]*\\n?`, 'gim');
149
+ body = body.replace(linePattern, '');
150
+ const values = (Array.isArray(value) ? value : [value]).filter(
151
+ (item) => item != null && String(item).length > 0,
152
+ );
153
+ for (const item of values) {
154
+ block.push(`${name}: ${item}`);
155
+ }
156
+ }
157
+
158
+ body = body.replace(/\s+$/g, '');
159
+ if (block.length) {
160
+ body += `\n\n${block.join('\n')}`;
161
+ }
162
+ body = body.replace(/\n{3,}/g, '\n\n');
163
+ if (!body.endsWith('\n')) body += '\n';
164
+ return body;
165
+ }
166
+
167
+ function main(env = process.env) {
168
+ const msgFile = process.argv[2];
169
+ if (!msgFile) {
170
+ fail('缺少 commit message 文件参数');
171
+ }
172
+
173
+ const repoRoot = projectRootFromHook();
174
+ const layout = resolveLayout(repoRoot, env);
175
+
176
+ // 未接入 SDD 的普通代码仓:不写 Trailer
177
+ if (layout.mode === 'unlinked') {
178
+ process.exit(0);
179
+ }
180
+
181
+ const trailers = {
182
+ // 多个活动 change 时写多条 Spec-Change
183
+ 'Spec-Change': resolveChangeKeys(layout.specPath, env).keys,
184
+ };
185
+
186
+ if (layout.mode === 'multi') {
187
+ const context = layout.context;
188
+ // remote mismatch:仍允许写本地 Revision(溯源本地快照);仅警告
189
+ if (context.code === sddConfig.CODES.REMOTE_MISMATCH) {
190
+ console.error(`[kld-sdd commit-msg] 警告: ${context.message}`);
191
+ }
192
+
193
+ if (!sddConfig.gitWorkingTreeClean(layout.specPath)) {
194
+ fail('spec 工作区不干净。请先提交或暂存 spec 变更后再提交代码。');
195
+ }
196
+
197
+ const revision = sddConfig.gitHeadSha(layout.specPath);
198
+ if (!revision || revision.length < 40) {
199
+ fail(`无法读取完整 Spec-Revision(需要完整 SHA): ${revision}`);
200
+ }
201
+ trailers['Spec-Revision'] = revision;
202
+ }
203
+
204
+ const original = fs.readFileSync(msgFile, 'utf8');
205
+ const updated = upsertTrailers(original, trailers);
206
+ fs.writeFileSync(msgFile, updated, 'utf8');
207
+ }
208
+
209
+ if (require.main === module) {
210
+ try {
211
+ main();
212
+ } catch (error) {
213
+ fail(error.message || String(error));
214
+ }
215
+ }
216
+
217
+ module.exports = {
218
+ upsertTrailers,
219
+ parseEnvChangeKeys,
220
+ resolveChangeKeys,
221
+ isSddLinkedCodeRepo,
222
+ resolveLayout,
223
+ main,
224
+ };
@@ -0,0 +1,13 @@
1
+ # 模块代号注册表(spec 仓库根目录)
2
+ # 只登记模块代号与中文名,不包含代码仓库、路径或 CI 配置。
3
+ version: 1
4
+
5
+ modules:
6
+ fi:
7
+ name: 财务核算
8
+ mm:
9
+ name: 物料管理
10
+ sd:
11
+ name: 销售与分销
12
+ cross:
13
+ name: 跨模块
@@ -1,6 +1,12 @@
1
1
  ---
2
2
  # 【用户选择配置 - 由 /opsx:propose 引导填写】
3
- change-id: "CHG-<CHANGE-SLUG>" # 创建时生成,后续不得修改
3
+ change-id: "CHG-<MODULE>-<YYMMDD>-<SLUG>" # 创建时由 change-key 派生一次,后续不得修改
4
+ change-key: "<module>-<yymmdd>-<slug>" # spec 仓目录名;可选写入代码 commit 的 Spec-Change(需 KLD_SDD_CHANGE)
5
+ title: "<中文标题>" # 人读名称,opsx-explore / 知识平台展示
6
+ module: "<module>" # modules.yaml 中的模块代号;跨模块用 cross
7
+ # affected-modules: # 仅 module=cross 时必填,至少两个已注册模块
8
+ # - fi
9
+ # - mm
4
10
  entity-id: "<8位十六进制ID>" # Change 的全局逻辑实体 ID,added 时生成
5
11
  version-id: "<UUID>" # 本次 Change 版本 UUID
6
12
  delta-state: "added"
@@ -0,0 +1,12 @@
1
+ # 活动变更登记表(spec 仓库根目录)
2
+ # 由 opsx-propose 隐式写入、opsx-archive 隐式移除,供代码仓 AI 与 commit-msg Hook 读取。
3
+ # 不登记代码仓清单、路径 glob 或 CI 配置。
4
+ version: 1
5
+
6
+ active_changes:
7
+ # 示例(propose 会自动写入,无需手填):
8
+ # - change-key: fi-260727-account-doc-head-create
9
+ # change-id: CHG-FI-260727-ACCOUNT-DOC-HEAD-CREATE
10
+ # title: 会计凭证头创建
11
+ # module: fi
12
+ # summary: 支持凭证头创建与字段校验
@@ -183,8 +183,8 @@ openspec list --json
183
183
 
184
184
  **执行每个任务**(a-g 步骤):
185
185
 
186
- a. **DAG 依赖检查 & 层级收集** — 收集依赖已满足的待执行任务;同层多个独立任务可并行派发子代理。
187
- b. **显示当前层级任务** — `📍 层级 [X]:准备派发 [K] 个子代理并行处理 [M] 个任务`。
186
+ a. **DAG 依赖检查 & 层级收集** — 收集依赖已满足的待执行任务;同层多个独立任务可并行派发子代理。**⛔ TDD 模式豁免**:当 `test-strategy=tdd` 时,同层的 RED→GREEN 对**不可并行**,必须逐对串行执行(见下方 S2)。仅同层的非 TDD 模块任务(UI/配置/SQL DDL)可并行。
187
+ b. **显示当前层级任务** — `📍 层级 [X]:准备派发 [K] 个子代理并行处理 [M] 个任务`。**TDD 模式下**:若本层含 RED→GREEN 对,显示 `📍 层级 [X]:[M] 个 RED→GREEN 对须逐对串行执行(TDD 串行约束),[K] 个非 TDD 任务可并行`。
188
188
  c. **🤖 派发子代理实现代码** — 使用 Agent 工具并行派发同层任务,⛔ 子代理阶段不创建 worktree/分支。**派发模板与状态处理见 `./reference.md`「§5c 子代理派发模板」+ `./implementer-prompt.md`**。
189
189
  d. **⛔ 编译检查门禁** — 每完成一个任务后必须编译通过;**详细见 `./checklist.md`「§5d 编译检查门禁」**。
190
190
  e. **⛔ 测试执行门禁** — 按 `proposal.md` 的 `test-strategy` 决定(tdd=强制, impl-first=警告, none=跳过);**详细见 `./checklist.md`「§5e 测试执行门禁」**。
@@ -285,7 +285,7 @@ g. **继续下一个层级** — 重新检查 DAG,找出依赖已满足的下
285
285
  - **⛔ 必须实时更新任务状态**:每完成一个任务立即改 tasks.md,两种格式同步。
286
286
  - **⛔ apply 结束前 checkbox 全量同步校验**:`stage_end` 前对比 telemetry `task_update` 记录数与 tasks.md `[x]` 数量,不一致则补齐(见 `./checklist.md` §5f.1)。
287
287
  - **⛔ task_update 后必须验证 checkbox 已更新**:执行 `check-task` 确认 tasks.md 对应行已变更;未更新则手动修改。
288
- - **⛔ TDD RED→GREEN 严格串行**:不适用同层并行派发;RED-N 确认失败后必须执行中断声明再进入 GREEN-N;GREEN 完成后必须通过 Scope 门禁再进入下一个 RED
288
+ - **⛔ TDD RED→GREEN 严格串行**:不适用同层并行派发;RED-N 确认失败后必须执行中断声明再进入 GREEN-N;GREEN 完成后必须通过 Scope 门禁再进入下一个 RED。同层存在多个 RED→GREEN 对时,必须逐对串行完成,禁止并行派发子代理处理多个 RED→GREEN 对。同层非 TDD 模块任务(UI/配置/SQL DDL)仍可并行。
289
289
  - **Git 只读策略**:禁止为了度量自动初始化 Git、创建分支或提交 commit;非 Git 项目用 `vcs_mode=no-git` 继续执行。
290
290
  - **⛔ Step 0.1 隔离校验必做**:建 worktree / 建议分支名前必须完成 proposal + 跨 cap spec 依赖校验并输出报告。
291
291
  - **Worktree 为加速手段,非必选项**:校验通过且解耦方可多 worktree;有依赖或共享修改面则串行。
@@ -130,7 +130,7 @@ description: opsx-apply 的阶段强制检查点与自检清单。仅在执行 a
130
130
  - [ ] ⛔ **必须实时更新任务状态**:每完成一个任务立即改 tasks.md,两种格式(`- [ ]`→`- [x]` 与 `**状态**: [ ]`→`[x]`)同步
131
131
  - [ ] ⛔ **apply 结束前 checkbox 全量同步校验**:见 §5f.1,`stage_end` 前对比 telemetry `task_update` 记录数与 tasks.md `[x]` 数量
132
132
  - [ ] ⛔ **task_update 后必须验证 checkbox 已更新**:执行 `check-task` 确认 tasks.md 对应行已变更;未更新则手动修改
133
- - [ ] ⛔ **TDD RED→GREEN 严格串行**:不适用同层并行派发;RED-N 确认失败后必须执行中断声明再进入 GREEN-N;GREEN 完成后必须通过 Scope 门禁再进入下一个 RED
133
+ - [ ] ⛔ **TDD RED→GREEN 严格串行**:不适用同层并行派发;RED-N 确认失败后必须执行中断声明再进入 GREEN-N;GREEN 完成后必须通过 Scope 门禁再进入下一个 RED。同层多个 RED→GREEN 对须逐对串行,禁止并行派发;同层非 TDD 模块任务仍可并行
134
134
  - [ ] ⛔ **TDD 节奏校验**:见 §5e.1,连续 RED-N/GREEN-N 的 `task_update` 时间戳须有可验证间距
135
135
  - [ ] **Git 只读策略**:禁止为了度量自动初始化 Git、创建分支或提交 commit;非 Git 项目用 `vcs_mode=no-git` 继续执行
136
136
  - [ ] ⛔ **Step 0.1 隔离校验必做**:建 worktree / 建议分支名前必须完成 proposal + 跨 cap spec 依赖校验并输出报告;未通过不得按 full 并行策略拆 `kld-sdd/<change>/<cap>`
@@ -118,6 +118,16 @@ node skywalk-sdd/log.cjs archive-docs --project=. --change=<变更名称> --reas
118
118
  - 最终中文报告生成到 `openspec/changes/archive/<日期>-<name>/reports/<name>-report.md` 及同名 `<name>-report.html`(默认同时生成 .md 与 .html 双产物,默认归档后 archive 目录,可用 --report-output 自定义)。
119
119
  - 执行日志 `openspec/changes/archive/<日期>-<name>/logs/execution-log.md` 随归档整目录迁移(人读审计层)。
120
120
 
121
+ ### 5.4 隐式注销活动变更
122
+
123
+ 归档成功后立即执行(不询问用户):
124
+
125
+ ```bash
126
+ node skywalk-sdd/ontology/cli.cjs active-change --remove --change=<变更名称> --project=.
127
+ ```
128
+
129
+ 把该 change 从 spec 仓 `sdd.config.yaml` 的 `active_changes` 移除,避免已归档 change 继续被代码 commit 写成 `Spec-Change`。该操作幂等;条目本就不存在时不报错。
130
+
121
131
  ### 5.5 收尾入库(opsx-kb-ingest)
122
132
 
123
133
  1. 确认 `${AGENT_SKILL_DIR}/opsx-kb-ingest/SKILL.md` 存在并 Read;按该技能完成 API Key / targets。
@@ -1,4 +1,4 @@
1
- ---
1
+ ---
2
2
  name: opsx-check
3
3
  description: "质量检查技能 - 验证文档完整性、一致性、算法正确性及可执行性"
4
4
  argument-hint: "[change-name] [上下文文件...]"
@@ -69,6 +69,43 @@ openspec list
69
69
  - `specs/<capability>/design.md`(实现方案)
70
70
  - `specs/<capability>/tasks.md`(或 task.md,兼容旧格式)(任务拆解)
71
71
 
72
+ ### 2.5 【多仓库关联配置诊断】(在五维检查之前)
73
+
74
+ 先执行轻量配置诊断,**不执行代码 diff**,只检查命名与引用配置:
75
+
76
+ ```bash
77
+ # 在 spec 仓库:
78
+ node skywalk-sdd/ontology/cli.cjs diagnose-naming --project=. --change=<change-key>
79
+
80
+ # 在代码仓库:
81
+ node skywalk-sdd/ontology/cli.cjs diagnose-naming --project=. --mode=code-repo
82
+ ```
83
+
84
+ 诊断结果三类:
85
+
86
+ | 状态 | 含义 |
87
+ |------|------|
88
+ | PASS | 配置完整 |
89
+ | FIXABLE | 修复方式唯一,用户确认后可自动修复(如安装 commit-msg Hook、设置 sdd.specPath) |
90
+ | NEEDS_INPUT | 多候选或团队级配置,必须询问用户 |
91
+
92
+ 规则:
93
+ - 只问缺失项,不重复询问已合法配置
94
+ - 本地 Git config / Hook 可在确认后修复
95
+ - `.sdd.yaml`、`modules.yaml`、proposal frontmatter 修改前必须展示变更摘要
96
+ - 已被代码 commit 引用的 change-id/change-key **禁止静默重命名**
97
+ - 用户拒绝修复时按严重程度记 warning/failure,然后继续后续文档检查
98
+ - spec 仓诊断跳过代码仓 Hook;代码仓诊断跳过 modules/change 命名(入口分离)
99
+ - 单仓(`.sdd.yaml` 声明 `layout: mono`,openspec 与代码同仓):`mode=mono-repo`,命名与 Hook 一并就地检查;该布局 commit 只写 `Spec-Change`,不写 `Spec-Revision`
100
+ - 若在个人工作目录发现未接入的新代码仓(缺 `.sdd.yaml` 或 commit-msg Hook):
101
+ 1. 向用户展示仓名列表;
102
+ 2. 用户确认后执行 `kld-sdd sync-repos`(只装 Hook/关联,不装 skills);
103
+ 3. 用户拒绝则记 warning,不阻断五维文档检查
104
+ - Trailer 协议:已接入仓 commit 必写 `Spec-Revision`;`Spec-Change` 来自 spec 仓 `sdd.config.yaml` 的 `active_changes`(多个则写多条),`KLD_SDD_CHANGE` 可显式覆盖,**不从分支名推断**
105
+ - 活动变更登记表诊断:`sdd.config.yaml` 是否存在且格式合法;当前 change 是否已登记(未登记则提示执行 `active-change --register`);登记项对应目录是否仍存在(已归档应执行 `active-change --remove`)
106
+
107
+ 最终检查报告必须包含「多仓库关联配置」章节(命令输出已按此格式打印)。
108
+
72
109
  ### 3. 【上下文加载】识别并读取用户提供的文件
73
110
 
74
111
  **自动识别上下文文件**:
@@ -117,7 +154,7 @@ openspec list
117
154
 
118
155
  #### 4.4a TDD 合规性检查(仅 test-strategy=tdd 时执行)
119
156
 
120
- ⛔ 执行 `opsx-tdd-core/checklist.md` §B(11 项)逐项检查。
157
+ ⛔ 执行 `opsx-tdd-core/checklist.md` §B(15 项)逐项检查。
121
158
 
122
159
  > 不在此内联复制,以 opsx-tdd-core/checklist.md §B 为唯一真相源。
123
160
  > 额外补充:还需检查 `opsx-tdd-rules/rules/exception-path-coverage.md`(异常路径覆盖门禁)。
@@ -1,4 +1,4 @@
1
- ---
1
+ ---
2
2
  name: opsx-explore
3
3
  description: "浏览变更技能 - 查看所有变更状态和 SDD 文档完整性概览"
4
4
  argument-hint: "[change-name]"
@@ -10,8 +10,6 @@ metadata:
10
10
  allowed-tools:
11
11
  - Bash
12
12
  - Read
13
- - Write
14
- - Edit
15
13
  ---
16
14
 
17
15
  你是一个 SDD(Specification-Driven Development)变更浏览专家。激活本技能后,你将展示项目中所有变更的状态概览,并引导用户执行下一步操作。
@@ -40,8 +38,15 @@ allowed-tools:
40
38
 
41
39
  ## 启动流程
42
40
 
43
- ### 1. 获取所有变更列表
41
+ ### 1. 获取所有变更列表(按模块中文分组)
44
42
 
43
+ 优先使用命名协议分组:
44
+ ```bash
45
+ node skywalk-sdd/ontology/cli.cjs list-changes --project=.
46
+ # 需要结构化数据时加 --json
47
+ ```
48
+
49
+ 也可辅助:
45
50
  ```bash
46
51
  openspec list
47
52
  ```
@@ -52,25 +57,40 @@ openspec list
52
57
  ### 2. 检查每个变更的 SDD 文档完整性
53
58
 
54
59
  对每个变更,检查以下文件是否存在:
55
- - `openspec/changes/<name>/proposal.md`(P)
56
- - `openspec/changes/<name>/specs/<capability>/spec.md`(S)
57
- - `openspec/changes/<name>/specs/<capability>/design.md`(D)
58
- - `openspec/changes/<name>/specs/<capability>/tasks.md`(T)
60
+ - `openspec/changes/<change-key>/proposal.md`(P)
61
+ - `openspec/changes/<change-key>/specs/<capability>/spec.md`(S)
62
+ - `openspec/changes/<change-key>/specs/<capability>/design.md`(D)
63
+ - `openspec/changes/<change-key>/specs/<capability>/tasks.md`(T)
59
64
 
60
65
  同时获取每个变更状态:
61
66
  ```bash
62
- openspec status --change "<name>" --json
67
+ openspec status --change "<change-key>" --json
63
68
  ```
64
69
 
65
- ### 3. 展示变更概览表
70
+ ### 3. 展示按模块分组的变更概览
71
+
72
+ 展示格式(title 来自 proposal frontmatter,目录定位使用 change-key):
73
+
74
+ ```text
75
+ 📦 财务核算 (fi)
76
+ ● 会计凭证头创建
77
+ fi-260727-account-doc-head-create
78
+ P✅ S✅ D✅ T❌ | 建议: /opsx-task fi-260727-account-doc-head-create
79
+
80
+ 📦 跨模块 (cross)
81
+ ● 凭证与库存联动
82
+ cross-260727-fi-mm-voucher-stock-link
83
+ affected: fi, mm
84
+
85
+ 📦 未分类
86
+ ● legacy-add-user-auth
87
+ legacy-add-user-auth
88
+ ```
66
89
 
67
- > "📋 **项目变更概览**(P=提案 S=规格 D=设计 T=任务):
68
- >
69
- > | 变更名称 | P | S | D | T | openspec状态 | 建议下一步 |
70
- > |---------|---|---|---|---|------------|---------|
71
- > | add-user-auth | ✅ | ✅ | ✅ | ❌ | PENDING | `/opsx-task add-user-auth` |
72
- > | payment-refund | ✅ | ❌ | ❌ | ❌ | PENDING | `/opsx-spec payment-refund` |
73
- > | points-exchange | ✅ | ✅ | ✅ | ✅ | IMPLEMENTING | `/opsx-check points-exchange` |"
90
+ 规则:
91
+ - 分组依据 `module`;中文组名来自 `modules.yaml`
92
+ - 缺少 change-key/title/module 的存量 change 归入「未分类」
93
+ - `module=cross` 时额外展示 `affected-modules`
74
94
 
75
95
  ### 4. 【交互引导】选择操作
76
96
 
@@ -54,13 +54,25 @@ allowed-tools:
54
54
 
55
55
  ## 启动流程
56
56
 
57
- ### 1. 输入处理
57
+ ### 1. 输入处理与 Change Key 生成
58
58
 
59
59
  当用户激活此 skill 时:
60
60
 
61
- **若提供了变更名称/描述**:
62
- - 解析为 kebab-case 名称(如 "add user authentication" → `add-user-auth`)
63
- - 跳转到第 2
61
+ **若提供了变更描述(中文优先)**:
62
+ 1. 保留中文描述作为 `title`(人读名称)
63
+ 2. 读取项目根 `modules.yaml`;不存在则先引导创建(可参考模板 `templates/modules.yaml`)
64
+ 3. 从描述推断模块代号;无法唯一判断时用 **AskUserQuestion** 让用户选择
65
+ 4. 涉及多个模块时使用保留代号 `cross`,并准备 `affected-modules`(≥2)
66
+ 5. 生成英文短 slug(小写、连字符、建议 ≤48 字符)
67
+ 6. 用本地日历日 `YYMMDD` 生成 change-key,并做冲突消解:
68
+ ```bash
69
+ node skywalk-sdd/ontology/cli.cjs change-key --generate --module=<code> --slug=<slug> --project=.
70
+ ```
71
+ 7. 得到:
72
+ - `change-key`:spec 仓目录名 `openspec/changes/<change-key>/`(权威名称;不等于代码分支名)
73
+ - `change-id`:`CHG-` + change-key 大写(仅创建时生成一次,之后永不改)
74
+ - 若在个人工作目录执行:可顺带提醒未接入的代码子仓执行 `kld-sdd sync-repos`(不装 skills)
75
+ 8. 跳转到第 2 步
64
76
 
65
77
  **若未提供任何输入**,使用 **AskUserQuestion** 询问:
66
78
  > "请描述本次变更的业务需求:
@@ -69,14 +81,15 @@ allowed-tools:
69
81
  > 3. 涉及哪些模块/系统?
70
82
  > 4. 有什么约束条件?(时间/技术/资源)"
71
83
 
72
- 从描述中推导 kebab-case 名称。
73
-
74
84
  **【澄清机制】若用户描述模糊,主动追问**:
75
85
  - 若目标不明确:"请用一句话明确本次变更要达成的具体目标"
76
86
  - 若影响范围不清:"请列出本次变更涉及的所有模块/服务"
77
87
  - 若约束未提及:"是否有时间限制、技术约束或依赖前提?"
78
88
 
79
- **重要**:未明确需求前不得继续。
89
+ **重要**:未明确需求前不得继续。change-key 必须通过校验,禁止手写不合规目录名:
90
+ ```bash
91
+ node skywalk-sdd/ontology/cli.cjs change-key --validate <change-key> --project=.
92
+ ```
80
93
 
81
94
  ### 2. 【上下文加载】识别并读取用户提供的文件
82
95
 
@@ -99,23 +112,36 @@ allowed-tools:
99
112
  ```bash
100
113
  openspec list --json 2>/dev/null || true
101
114
  ```
102
- 或直接探测 `openspec/changes/<name>/` 是否存在(含 `.openspec.yaml` 或 `proposal.md`)。排除 `logs/` 目录——`logs/` 是 telemetry 自动创建的,不代表变更已初始化。
115
+ 或直接探测 `openspec/changes/<change-key>/` 是否存在(含 `.openspec.yaml` 或 `proposal.md`)。排除 `logs/` 目录——`logs/` 是 telemetry 自动创建的,不代表变更已初始化。
103
116
 
104
117
  **第 2 步:根据检测结果决定**:
105
118
  - **不存在** → 直接执行第 3 步创建。
106
- - **已存在** → 先询问用户,**不要直接 new**:"变更 `<name>` 已存在,请选择:
119
+ - **已存在** → 先询问用户,**不要直接 new**:"变更 `<change-key>` 已存在,请选择:
107
120
  - A. 覆盖原有变更(删除重建)
108
121
  - B. 继续编辑现有变更
109
122
  - C. 取消操作"
110
123
  - **若目录仅含 `logs/`(无 `.openspec.yaml` 且无 `proposal.md`)**:提示用户"检测到残留空变更目录(仅含 telemetry 自动创建的 logs/),建议选 A 覆盖重建,避免复用空目录导致后续流程混淆"
111
- - 用户选 A → 先删除 `openspec/changes/<name>/` 再执行第 3 步;选 B → 跳过创建直接进入 §4;选 C → 终止。
124
+ - 用户选 A → 先删除 `openspec/changes/<change-key>/` 再执行第 3 步;选 B → 跳过创建直接进入 §4;选 C → 终止。
112
125
 
113
126
  **第 3 步:创建变更目录**(仅在不存在或用户确认覆盖后执行):
114
127
  ```bash
115
- openspec new change "<name>"
128
+ openspec new change "<change-key>"
116
129
  ```
117
130
 
118
- 此命令在 `openspec/changes/<name>/` 创建变更目录和 `.openspec.yaml`。
131
+ 此命令在 `openspec/changes/<change-key>/` 创建变更目录和 `.openspec.yaml`。目录名必须等于 change-key。
132
+
133
+ **第 4 步:隐式登记活动变更**(创建目录后立即执行,不询问用户):
134
+ ```bash
135
+ node skywalk-sdd/ontology/cli.cjs active-change --register --change=<change-key> \
136
+ --title="<中文标题>" --module=<code> --summary="<一句话摘要>" --project=.
137
+ ```
138
+
139
+ 写入 spec 仓根目录 `sdd.config.yaml` 的 `active_changes`。作用:
140
+
141
+ - 所有已接入代码仓的 AI 经 `sdd.specPath` 读到同一份,知道「当前在做哪个 change、中文叫什么」;
142
+ - 代码仓 `git commit` 时由 commit-msg Hook 自动写成 `Spec-Change` Trailer(多个活动 change 就写多条)。
143
+
144
+ 登记按 change-key 幂等;同一 change 重复 propose 只更新标题/摘要。**不需要用户手工编辑该文件**。
119
145
 
120
146
  ### 3.5 【首次检测】overview.md 全局契约空模板引导
121
147
 
@@ -238,7 +264,10 @@ node skywalk-sdd/context-client.cjs --mode=resolve \
238
264
 
239
265
  ## 本体语义生成契约
240
266
 
241
- - 创建 Change 时必须写入 `change-id: CHG-<CHANGE-SLUG>`;重新编辑时不得修改已有 Change ID。
267
+ - 创建 Change 时必须写入 `change-key`、中文 `title`、`module`,以及 `change-id: CHG-<MODULE>-<YYMMDD>-<SLUG>`(由初始 change-key 派生一次);重新编辑时不得修改已有 Change ID。
268
+ - 创建 Change 目录后必须隐式执行 `active-change --register`(见 §3 第 4 步),把 change-key/标题/摘要登记进 spec 仓 `sdd.config.yaml`;漏登记会导致代码 commit 缺少 `Spec-Change`。
269
+ - 跨模块 Change 使用 `module: cross`,并写入至少两个 `affected-modules`。
270
+ - 存量 Change 迁移目录时必须保留原 `change-id`,不得按新目录重新派生。
242
271
  - 每个新增 Capability 必须写成 `[CAP-<CAPABILITY>] <slug>: <说明>`。
243
272
  - 修改既有 Capability 时必须复用已有 CAP ID,不得修改或重新分配已有实体 ID。
244
273
  - 分配新 CAP ID 前必须扫描当前 proposal 和归档中的显式编号;不得只凭标题认定跨 Change 同一性。
@@ -175,6 +175,7 @@ Simple 模式或单文件能力域下,**不要拆成多个同文件任务**。
175
175
  5. 非 TDD 模块(前端 UI/配置/SQL DDL)不拆红绿
176
176
  6. Controller 层策略必须在 tasks.md §2.0 中声明(策略 A 或 B),两种策略都必须生成测试任务
177
177
  7. ⛔ **GREEN 任务 YAGNI 围栏**:每个 GREEN-N 任务描述末尾必须包含"不提前实现 [后续 RED 行为]"围栏声明。规则详见 `opsx-tdd-rules/rules/green-yagni-fence.md`
178
+ 8. ⛔ **TDD 模式 DAG 并行标注**:当 `test-strategy=tdd` 时,DAG 拓扑图中每个 RED→GREEN 对必须标注 `⛔ 串行:不可同层并行`。同层存在多个 RED→GREEN 对时,必须在拓扑图中显式注明"本层 RED→GREEN 对须逐对串行执行,禁止并行派发"。非 TDD 模块的同层任务(如 UI/配置/SQL DDL)仍可并行。
178
179
 
179
180
  ⛔ BEFORE 生成 TDD 任务,必须读取:
180
181
  1. opsx-tdd-core/reference.md §6(DAG 生成规则表)
@@ -24,7 +24,7 @@ description: "opsx-tdd-core 自检清单 — TDD 执行合规自检、合规性
24
24
  - [ ] REFACTOR 后全部测试仍绿
25
25
  - [ ] 未出现"先写生产代码再补测试"的情况
26
26
 
27
- ## §B TDD 合规性检查(11 项)
27
+ ## §B TDD 合规性检查(15 项)
28
28
 
29
29
  仅 `test-strategy=tdd` 时执行:
30
30