@xulthekl/team-flow 0.22.4 → 0.24.0

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.
Files changed (72) hide show
  1. package/.claude/always/phase-guard.md +1 -1
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +1 -1
  5. package/.cursor-plugin/marketplace.json +1 -1
  6. package/.cursor-plugin/plugin.json +1 -1
  7. package/.github/plugin/marketplace.json +2 -2
  8. package/AGENTS.md +3 -2
  9. package/CHANGELOG.md +103 -0
  10. package/GEMINI.md +1 -1
  11. package/HANDOFF.md +123 -98
  12. package/INSTALL.md +1 -1
  13. package/README.md +1 -1
  14. package/docs/README_en.md +1 -1
  15. package/docs/solutions/INDEX.md +5 -0
  16. package/docs/solutions/cross-phase/2026-07-28-no-summary.md +17 -0
  17. package/gemini-extension.json +1 -1
  18. package/hooks/pre-tool-use-guard +9 -9
  19. package/hooks/session-start +2 -2
  20. package/llms.txt +1 -1
  21. package/package.json +1 -1
  22. package/plugin.json +1 -1
  23. package/scripts/guard/checks/arch-design.mjs +79 -0
  24. package/scripts/guard/checks/compound-captured.mjs +70 -0
  25. package/scripts/guard/guard.mjs +6 -2
  26. package/scripts/lib/arch-merge.mjs +459 -0
  27. package/scripts/lib/cmd-state.mjs +5 -0
  28. package/scripts/lib/hash.mjs +18 -0
  29. package/scripts/lib/solutions-promote.mjs +1 -1
  30. package/scripts/lib/state-loader.mjs +16 -0
  31. package/scripts/team-flow.mjs +3 -0
  32. package/skills/architecture-design/SKILL.md +42 -3
  33. package/skills/architecture-design/templates/api.md +71 -0
  34. package/skills/architecture-design/templates/architecture.md +82 -0
  35. package/skills/architecture-design/templates/change-brief.md +29 -0
  36. package/skills/architecture-design/templates/database.md +69 -0
  37. package/skills/architecture-design/templates/index.md +33 -0
  38. package/skills/architecture-design/templates/physical-model.md +93 -0
  39. package/skills/bug-investigator/SKILL.md +1 -1
  40. package/skills/build-executor/SKILL.md +23 -19
  41. package/skills/build-executor/implementer-prompt.md +1 -1
  42. package/skills/build-executor/references/execution-modes.md +6 -6
  43. package/skills/build-executor/task-reviewer-prompt.md +1 -1
  44. package/skills/ce-brainstorm/SKILL.md +6 -0
  45. package/skills/code-reviewer/SKILL.md +6 -2
  46. package/skills/code-reviewer/code-reviewer-prompt.md +1 -1
  47. package/skills/contract-builder/SKILL.md +6 -6
  48. package/skills/need-explorer/SKILL.md +2 -2
  49. package/skills/release-archivist/SKILL.md +25 -12
  50. package/skills/release-archivist/references/closing-procedures.md +8 -8
  51. package/skills/spec-merger/SKILL.md +2 -2
  52. package/skills/spec-writer/SKILL.md +11 -9
  53. package/skills/workflow-bootstrap/SKILL.md +39 -5
  54. package/skills/workflow-bootstrap/references/b1-reconnaissance.md +22 -1
  55. package/skills/workflow-bootstrap/scripts/recon-probe.sh +122 -1
  56. package/skills/workflow-orchestrator/references/s2-prd-prototype-loop.md +1 -1
  57. package/skills/workflow-orchestrator/references/s4-split-validate.md +1 -1
  58. package/skills/workflow-orchestrator/references/s5-monitoring.md +1 -1
  59. package/skills/workflow-start/SKILL.md +44 -16
  60. package/skills/workflow-start/references/routing-rules.md +17 -17
  61. package/templates/api.md +177 -0
  62. package/templates/architecture.md +122 -0
  63. package/templates/change-brief.md +24 -0
  64. package/templates/database.md +114 -0
  65. package/tests/lib/guard-compound-captured.test.mjs +92 -0
  66. package/tests/lib/guard-specs-merged.test.mjs +2 -0
  67. package/tests/lib/guard-tests-passing.test.mjs +2 -0
  68. package/tests/lib/guard.test.mjs +2 -0
  69. package/tests/lib/solutions-capture.test.mjs +108 -0
  70. package/tests/lib/solutions-index-gen.test.mjs +147 -0
  71. package/tests/lib/solutions-inject.test.mjs +115 -0
  72. package/tests/lib/solutions-promote.test.mjs +200 -0
@@ -0,0 +1,459 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * arch-merge — change 完成后将架构增量合并回全局 docs/architecture/
4
+ *
5
+ * v0.10 §28.4 定义,v0.23.0 实现为 CLI 子命令
6
+ * 用法:tf arch-merge <change-dir> [--project-root <path>] [--dry-run]
7
+ *
8
+ * 功能(8 步流程):
9
+ * 1. 一致性预检(结构冲突 / 语义冲突 / 跨域一致性)
10
+ * 2. ARCHITECTURE.md 回写(当前态覆盖 + 演进日志 append)
11
+ * 3. PHYSICAL-MODEL.md 增量合并(变更涉及的表字段级合并)
12
+ * 4. schema-baseline.sql 同步(追加新 DDL,保持完整可执行)
13
+ * 5. 变更脚本归档(sql/ddl/ + sql/migration/ → changelog/)
14
+ * 6. API-INDEX.md 增量更新
15
+ * 7. INDEX.md 更新(摘要统计)
16
+ * 8. 单次 git commit(原子性提交)
17
+ *
18
+ * 回写顺序:arch-merge → prototype-sync(同一 change closing 内,顺序提交)
19
+ */
20
+
21
+ import { readFileSync, writeFileSync, existsSync, cpSync, mkdirSync, readdirSync, statSync } from 'node:fs';
22
+ import { join, basename, relative, dirname, resolve, sep } from 'node:path';
23
+ import { execSync } from 'node:child_process';
24
+
25
+ /**
26
+ * 解析 CLI 参数数组为结构化对象
27
+ */
28
+ function parseArgv(argv) {
29
+ const parsed = { _: [] };
30
+ for (let i = 0; i < argv.length; i++) {
31
+ if (argv[i] === '--project-root' && argv[i + 1] && !argv[i + 1].startsWith('--')) {
32
+ parsed.projectRoot = argv[++i];
33
+ } else if (argv[i] === '--dry-run') {
34
+ parsed.dryRun = true;
35
+ } else if (!argv[i].startsWith('--')) {
36
+ parsed._.push(argv[i]);
37
+ }
38
+ }
39
+ return parsed;
40
+ }
41
+
42
+ /**
43
+ * 从 change 目录提取 change name
44
+ */
45
+ function extractChangeName(changeDir) {
46
+ return basename(changeDir.replace(/\/$/, ''));
47
+ }
48
+
49
+ /**
50
+ * 读取 markdown 文件中的 frontmatter
51
+ */
52
+ function readFrontmatter(content) {
53
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
54
+ if (!match) return {};
55
+ const fm = {};
56
+ for (const line of match[1].split('\n')) {
57
+ const m = line.match(/^(\w+):\s*(.*)$/);
58
+ if (m) fm[m[1]] = m[2].trim();
59
+ }
60
+ return fm;
61
+ }
62
+
63
+ /**
64
+ * Step 1: 一致性预检
65
+ */
66
+ function preCheck(changeDir, archDir, projectRoot) {
67
+ const conflicts = [];
68
+
69
+ // 检查 architecture/ 目录是否存在
70
+ if (!existsSync(archDir)) {
71
+ return { pass: false, conflicts: ['architecture/ directory not found in change'] };
72
+ }
73
+
74
+ // 检查必要文件
75
+ const requiredFiles = ['architecture.md', 'database.md', 'api.md'];
76
+ for (const f of requiredFiles) {
77
+ const fp = join(archDir, f);
78
+ if (!existsSync(fp)) {
79
+ conflicts.push(`architecture/${f} missing`);
80
+ } else if (readFileSync(fp, 'utf-8').trim().length === 0) {
81
+ conflicts.push(`architecture/${f} empty`);
82
+ }
83
+ }
84
+
85
+ // TODO: 结构冲突检测(重复 BC/聚合 key)
86
+ // TODO: 语义冲突检测(同义异名)
87
+ // TODO: 跨域一致性检测(AA ≥ 1 IA 实体)
88
+
89
+ return { pass: conflicts.length === 0, conflicts };
90
+ }
91
+
92
+ /**
93
+ * Step 2: ARCHITECTURE.md 回写
94
+ * 当前态覆盖 + 演进日志 append
95
+ */
96
+ function mergeArchitecture(changeDir, archDir, globalArchDir, changeName) {
97
+ const srcPath = join(archDir, 'architecture.md');
98
+ const dstPath = join(globalArchDir, 'ARCHITECTURE.md');
99
+
100
+ if (!existsSync(srcPath)) return { merged: false, reason: 'no-source' };
101
+
102
+ const srcContent = readFileSync(srcPath, 'utf-8');
103
+
104
+ // 提取 To-Be 增量设计段
105
+ const toBeMatch = srcContent.match(/## 2\. To-Be 增量设计[\s\S]*?(?=\n## 3\.|\n---|\n$)/);
106
+ const toBeContent = toBeMatch ? toBeMatch[0] : '';
107
+
108
+ // 提取演进日志
109
+ const logMatch = srcContent.match(/## 5\. 演进日志[\s\S]*?(?=\n## |\n---|\n$)/);
110
+ const logEntries = logMatch ? logMatch[0] : '';
111
+
112
+ if (existsSync(dstPath)) {
113
+ const existing = readFileSync(dstPath, 'utf-8');
114
+
115
+ // 追加演进日志(append-only)
116
+ if (logEntries) {
117
+ writeFileSync(dstPath, existing + '\n\n' + logEntries, 'utf-8');
118
+ }
119
+ } else {
120
+ // 首次创建:直接使用 To-Be 段作为当前态
121
+ const header = `# Architecture Baseline\n\n> Last updated: change=${changeName}, ${new Date().toISOString().slice(0, 10)}\n\n`;
122
+ writeFileSync(dstPath, header + toBeContent + '\n\n' + logEntries, 'utf-8');
123
+ }
124
+
125
+ return { merged: true, file: 'ARCHITECTURE.md' };
126
+ }
127
+
128
+ /**
129
+ * Step 3: PHYSICAL-MODEL.md 增量合并
130
+ * 仅合并变更涉及的表,未涉及的保持不变
131
+ */
132
+ function mergePhysicalModel(changeDir, archDir, globalArchDir, changeName) {
133
+ const dbPath = join(archDir, 'database.md');
134
+ const dstPath = join(globalArchDir, 'PHYSICAL-MODEL.md');
135
+
136
+ if (!existsSync(dbPath)) return { merged: false, reason: 'no-source' };
137
+
138
+ const dbContent = readFileSync(dbPath, 'utf-8');
139
+
140
+ // 提取 Schema 变更表中的目标表名
141
+ const tableMatches = dbContent.matchAll(/\|\s*`(?:CREATE|ALTER)\s+TABLE`?\s*\|\s*(\w+)/gi);
142
+ const affectedTables = new Set();
143
+ for (const m of tableMatches) {
144
+ affectedTables.add(m[1]);
145
+ }
146
+
147
+ // TODO: 完整的字段级增量合并逻辑
148
+ // 当前实现:追加变更记录到演进日志段
149
+
150
+ if (existsSync(dstPath) && affectedTables.size > 0) {
151
+ const existing = readFileSync(dstPath, 'utf-8');
152
+ const footer = `\n\n<!-- arch-merge: change=${changeName}, ${new Date().toISOString().slice(0, 10)}, tables=${[...affectedTables].join(',')} -->\n`;
153
+ writeFileSync(dstPath, existing + footer, 'utf-8');
154
+ }
155
+
156
+ return { merged: true, file: 'PHYSICAL-MODEL.md', tables: [...affectedTables] };
157
+ }
158
+
159
+ /**
160
+ * Step 4: schema-baseline.sql 同步
161
+ */
162
+ function syncSchemaBaseline(changeDir, archDir, globalArchDir, changeName) {
163
+ const sqlDir = join(archDir, 'sql', 'ddl');
164
+ const dstPath = join(globalArchDir, 'schema-baseline.sql');
165
+
166
+ if (!existsSync(sqlDir)) return { synced: false, reason: 'no-ddl-dir' };
167
+
168
+ const ddlFiles = readdirSync(sqlDir).filter(f => f.endsWith('.sql'));
169
+ if (ddlFiles.length === 0) return { synced: false, reason: 'no-ddl-files' };
170
+
171
+ let baseline = '';
172
+ if (existsSync(dstPath)) {
173
+ baseline = readFileSync(dstPath, 'utf-8');
174
+ } else {
175
+ baseline = `-- Schema Baseline\n-- Auto-maintained by arch-merge\n-- Last updated: change=${changeName}, ${new Date().toISOString().slice(0, 10)}\n\n`;
176
+ }
177
+
178
+ // 追加新 DDL
179
+ for (const f of ddlFiles) {
180
+ const content = readFileSync(join(sqlDir, f), 'utf-8');
181
+ baseline += `\n-- === ${f} (change: ${changeName}) ===\n${content}\n`;
182
+ }
183
+
184
+ writeFileSync(dstPath, baseline, 'utf-8');
185
+ return { synced: true, file: 'schema-baseline.sql', ddlCount: ddlFiles.length };
186
+ }
187
+
188
+ /**
189
+ * Step 5: 变更脚本归档
190
+ */
191
+ function archiveChangeScripts(changeDir, archDir, globalArchDir, changeName) {
192
+ const report = { ddl: 0, migration: 0 };
193
+
194
+ for (const subDir of ['ddl', 'migration']) {
195
+ const srcDir = join(archDir, 'sql', subDir);
196
+ const dstDir = join(globalArchDir, 'changelog', subDir);
197
+
198
+ if (!existsSync(srcDir)) continue;
199
+
200
+ if (!existsSync(dstDir)) {
201
+ mkdirSync(dstDir, { recursive: true });
202
+ }
203
+
204
+ const files = readdirSync(srcDir).filter(f => f.endsWith('.sql'));
205
+ for (const f of files) {
206
+ cpSync(join(srcDir, f), join(dstDir, f));
207
+ report[subDir]++;
208
+ }
209
+ }
210
+
211
+ return report;
212
+ }
213
+
214
+ /**
215
+ * Step 6: API-INDEX.md 增量更新
216
+ */
217
+ function mergeApiIndex(changeDir, archDir, globalArchDir, changeName) {
218
+ const apiPath = join(archDir, 'api.md');
219
+ const dstPath = join(globalArchDir, 'API-INDEX.md');
220
+
221
+ if (!existsSync(apiPath)) return { merged: false, reason: 'no-source' };
222
+
223
+ const apiContent = readFileSync(apiPath, 'utf-8');
224
+
225
+ // 提取 To-Be 增量中的端点表格
226
+ const endpointMatches = apiContent.matchAll(/\|\s*(\/api\/[^\s|]+)\s*\|\s*(\w+)\s*\|/g);
227
+ const endpoints = [];
228
+ for (const m of endpointMatches) {
229
+ endpoints.push({ path: m[1], method: m[2] });
230
+ }
231
+
232
+ // TODO: 完整的增量合并逻辑(新增/修改/删除端点)
233
+ // 当前实现:追加记录
234
+
235
+ if (existsSync(dstPath) && endpoints.length > 0) {
236
+ const existing = readFileSync(dstPath, 'utf-8');
237
+ const footer = `\n\n<!-- arch-merge: change=${changeName}, ${new Date().toISOString().slice(0, 10)}, endpoints=${endpoints.length} -->\n`;
238
+ writeFileSync(dstPath, existing + footer, 'utf-8');
239
+ } else if (!existsSync(dstPath)) {
240
+ const header = `# API Index\n\n> Last updated: change=${changeName}, ${new Date().toISOString().slice(0, 10)}\n\n`;
241
+ const sections = `## Command API\n\n| 端点 | 方法 | 聚合 | 事务边界 | 说明 |\n|------|------|------|---------|------|\n\n## Read API\n\n| 端点 | 方法 | 聚合 | 数据来源 | 说明 |\n|------|------|------|---------|------|\n\n## Query API\n\n| 端点 | 方法 | 查询模型 | 阻断测试 | 说明 |\n|------|------|---------|---------|------|\n`;
242
+ writeFileSync(dstPath, header + sections, 'utf-8');
243
+ }
244
+
245
+ return { merged: true, file: 'API-INDEX.md', endpoints: endpoints.length };
246
+ }
247
+
248
+ /**
249
+ * Step 7: INDEX.md 更新
250
+ */
251
+ function updateIndex(globalArchDir, changeName) {
252
+ const indexPath = join(globalArchDir, 'INDEX.md');
253
+ const date = new Date().toISOString().slice(0, 10);
254
+
255
+ // 统计各产物
256
+ const stats = {};
257
+
258
+ // ARCHITECTURE.md
259
+ if (existsSync(join(globalArchDir, 'ARCHITECTURE.md'))) {
260
+ const content = readFileSync(join(globalArchDir, 'ARCHITECTURE.md'), 'utf-8');
261
+ const bcMatches = content.matchAll(/### \d+\.\s+(.+)/g);
262
+ stats.architecture = { bcs: [...bcMatches].length };
263
+ }
264
+
265
+ // PHYSICAL-MODEL.md
266
+ if (existsSync(join(globalArchDir, 'PHYSICAL-MODEL.md'))) {
267
+ const content = readFileSync(join(globalArchDir, 'PHYSICAL-MODEL.md'), 'utf-8');
268
+ const tableMatches = content.matchAll(/^### \d+\.\s+(.+) \((\w+)\)/gm);
269
+ stats.physicalModel = { tables: [...tableMatches].length };
270
+ }
271
+
272
+ // API-INDEX.md
273
+ if (existsSync(join(globalArchDir, 'API-INDEX.md'))) {
274
+ const content = readFileSync(join(globalArchDir, 'API-INDEX.md'), 'utf-8');
275
+ const epMatches = content.matchAll(/\|\s*\/api\//g);
276
+ stats.apiIndex = { endpoints: [...epMatches].length };
277
+ }
278
+
279
+ // 生成 INDEX.md
280
+ const lines = [
281
+ `# Architecture Index`,
282
+ ``,
283
+ `> 本文件始终加载,各产物按需 Read。`,
284
+ `> Last updated: change=${changeName}, ${date}`,
285
+ ``,
286
+ ];
287
+
288
+ if (stats.architecture) {
289
+ lines.push(
290
+ `## ARCHITECTURE.md`,
291
+ `- 限界上下文: ${stats.architecture.bcs}`,
292
+ `- 最后更新: change=${changeName}, ${date}`,
293
+ `- 读取建议: 按 BC 段落读取`,
294
+ ``
295
+ );
296
+ }
297
+
298
+ if (stats.physicalModel) {
299
+ lines.push(
300
+ `## PHYSICAL-MODEL.md`,
301
+ `- 表数量: ${stats.physicalModel.tables}`,
302
+ `- 最后更新: change=${changeName}, ${date}`,
303
+ `- 读取建议: 按业务域段落读取`,
304
+ ``
305
+ );
306
+ }
307
+
308
+ if (existsSync(join(globalArchDir, 'schema-baseline.sql'))) {
309
+ lines.push(
310
+ `## schema-baseline.sql`,
311
+ `- 最后更新: change=${changeName}, ${date}`,
312
+ `- 读取建议: 通常不需加载,DDL 由 sql/ 目录独立管理`,
313
+ ``
314
+ );
315
+ }
316
+
317
+ if (stats.apiIndex) {
318
+ lines.push(
319
+ `## API-INDEX.md`,
320
+ `- 端点数量: ${stats.apiIndex.endpoints}`,
321
+ `- 最后更新: change=${changeName}, ${date}`,
322
+ `- 读取建议: 按分流区域读取`,
323
+ ``
324
+ );
325
+ }
326
+
327
+ if (existsSync(join(globalArchDir, 'CONCEPTS.md'))) {
328
+ lines.push(
329
+ `## CONCEPTS.md`,
330
+ `- 最后更新: bootstrap`,
331
+ ``
332
+ );
333
+ }
334
+
335
+ writeFileSync(indexPath, lines.join('\n') + '\n', 'utf-8');
336
+ return { updated: true, stats };
337
+ }
338
+
339
+ /**
340
+ * Step 8: Git commit
341
+ */
342
+ function gitCommit(globalArchDir, changeName, dryRun) {
343
+ const commitMsg = `arch-merge: ${changeName} — sync architecture artifacts to global docs/architecture/`;
344
+
345
+ if (dryRun) {
346
+ console.log(` [DRY-RUN] Would commit: ${commitMsg}`);
347
+ return { committed: false, dryRun: true };
348
+ }
349
+
350
+ try {
351
+ execSync(`git add ${globalArchDir}/`, { stdio: 'pipe' });
352
+ execSync(`git commit -m "${commitMsg}"`, { stdio: 'pipe' });
353
+ return { committed: true, message: commitMsg };
354
+ } catch (e) {
355
+ return { committed: false, error: e.message };
356
+ }
357
+ }
358
+
359
+ /**
360
+ * 执行 arch-merge 合并
361
+ */
362
+ export function run(args = {}) {
363
+ if (Array.isArray(args)) {
364
+ args = parseArgv(args);
365
+ }
366
+
367
+ const changeDir = args._?.[0] || args.changeDir;
368
+ const dryRun = args.dryRun || false;
369
+
370
+ if (!changeDir) {
371
+ console.error('Usage: tf arch-merge <change-dir> [--project-root <path>] [--dry-run]');
372
+ process.exit(1);
373
+ }
374
+
375
+ // project-root: 显式指定 > 从 changeDir 向上推导(changes/xxx → 项目根)
376
+ let projectRoot = args.projectRoot;
377
+ if (!projectRoot) {
378
+ const abs = resolve(changeDir);
379
+ const changesIdx = abs.lastIndexOf(sep + 'changes' + sep);
380
+ projectRoot = changesIdx > 0 ? abs.slice(0, changesIdx) : process.cwd();
381
+ }
382
+
383
+ const changeName = extractChangeName(changeDir);
384
+ const archDir = join(changeDir, 'architecture');
385
+ const globalArchDir = join(projectRoot, 'docs', 'architecture');
386
+
387
+ console.log(`\narch-merge: ${changeName}`);
388
+ console.log(` Source: ${archDir}`);
389
+ console.log(` Target: ${globalArchDir}`);
390
+ if (dryRun) console.log(` Mode: DRY-RUN`);
391
+
392
+ // Step 1: 一致性预检
393
+ console.log('\n[Step 1] Pre-check...');
394
+ const preCheckResult = preCheck(changeDir, archDir, projectRoot);
395
+ if (!preCheckResult.pass) {
396
+ console.error(' Pre-check FAILED:');
397
+ for (const c of preCheckResult.conflicts) {
398
+ console.error(` - ${c}`);
399
+ }
400
+ return { merged: false, reason: 'pre-check-failed', conflicts: preCheckResult.conflicts };
401
+ }
402
+ console.log(' Pre-check passed');
403
+
404
+ // 确保全局目录存在
405
+ if (!existsSync(globalArchDir)) {
406
+ mkdirSync(globalArchDir, { recursive: true });
407
+ }
408
+
409
+ // Step 2: ARCHITECTURE.md
410
+ console.log('[Step 2] Merging ARCHITECTURE.md...');
411
+ const archResult = mergeArchitecture(changeDir, archDir, globalArchDir, changeName);
412
+ console.log(` ${archResult.merged ? '✓' : '✗'} ${archResult.file || archResult.reason}`);
413
+
414
+ // Step 3: PHYSICAL-MODEL.md
415
+ console.log('[Step 3] Merging PHYSICAL-MODEL.md...');
416
+ const pmResult = mergePhysicalModel(changeDir, archDir, globalArchDir, changeName);
417
+ console.log(` ${pmResult.merged ? '✓' : '✗'} ${pmResult.file || pmResult.reason}`);
418
+ if (pmResult.tables?.length > 0) {
419
+ console.log(` Affected tables: ${pmResult.tables.join(', ')}`);
420
+ }
421
+
422
+ // Step 4: schema-baseline.sql
423
+ console.log('[Step 4] Syncing schema-baseline.sql...');
424
+ const schemaResult = syncSchemaBaseline(changeDir, archDir, globalArchDir, changeName);
425
+ console.log(` ${schemaResult.synced ? '✓' : '✗'} ${schemaResult.file || schemaResult.reason}`);
426
+
427
+ // Step 5: 变更脚本归档
428
+ console.log('[Step 5] Archiving change scripts...');
429
+ const archiveResult = archiveChangeScripts(changeDir, archDir, globalArchDir, changeName);
430
+ console.log(` DDL: ${archiveResult.ddl}, Migration: ${archiveResult.migration}`);
431
+
432
+ // Step 6: API-INDEX.md
433
+ console.log('[Step 6] Merging API-INDEX.md...');
434
+ const apiResult = mergeApiIndex(changeDir, archDir, globalArchDir, changeName);
435
+ console.log(` ${apiResult.merged ? '✓' : '✗'} ${apiResult.file || apiResult.reason}`);
436
+
437
+ // Step 7: INDEX.md
438
+ console.log('[Step 7] Updating INDEX.md...');
439
+ const indexResult = updateIndex(globalArchDir, changeName);
440
+ console.log(` ${indexResult.updated ? '✓' : '✗'} Updated`);
441
+
442
+ // Step 8: Git commit
443
+ console.log('[Step 8] Git commit...');
444
+ const commitResult = gitCommit(globalArchDir, changeName, dryRun);
445
+ console.log(` ${commitResult.committed ? '✓' : (commitResult.dryRun ? '⊘ dry-run' : '✗')} ${commitResult.message || commitResult.error || ''}`);
446
+
447
+ console.log('\narch-merge complete.');
448
+ return {
449
+ merged: true,
450
+ changeName,
451
+ steps: { architecture: archResult, physicalModel: pmResult, schema: schemaResult, archive: archiveResult, apiIndex: apiResult, index: indexResult, commit: commitResult },
452
+ };
453
+ }
454
+
455
+ // 支持直接执行
456
+ if (process.argv[1]?.includes('arch-merge')) {
457
+ const args = parseArgv(process.argv.slice(2));
458
+ run(args);
459
+ }
@@ -23,6 +23,11 @@ const SETTABLE_FIELDS = [
23
23
  'dp_5_result', 'dp_5_timestamp', 'dp_5_decisions', 'dp_5_confirmed',
24
24
  'dp_6_result', 'dp_6_timestamp', 'dp_6_decisions', 'dp_6_confirmed',
25
25
  'dp_7_result', 'dp_7_timestamp', 'dp_7_decisions', 'dp_7_confirmed',
26
+ // Architecture design gate (v0.9 §26, v0.22.5 评审修复 F02)
27
+ 'arch_design_decision', 'arch_design_reason',
28
+ 'arch_design_timestamp', 'arch_design_artifacts',
29
+ // Compound engineering capture gate (v0.24.0 复利贯穿强制化)
30
+ 'compound_skipped',
26
31
  ];
27
32
 
28
33
  export async function run(args) {
@@ -37,6 +37,24 @@ export function computeArtifactsHash(changeDir) {
37
37
  hasContent = true;
38
38
  }
39
39
 
40
+ // architecture/*.md (v0.23.0 §30: arch-design artifacts纳入hash)
41
+ // 三个文件全空则不参与 hash(兼容 arch_design_decision=skipped)
42
+ // sql/ 目录下的 .sql 文件不纳入 hash——可执行制品由版本控制保证
43
+ const archDir = path.join(changeDir, 'architecture');
44
+ if (fs.existsSync(archDir)) {
45
+ const archFiles = ['architecture.md', 'database.md', 'api.md'];
46
+ for (const f of archFiles) {
47
+ const fp = path.join(archDir, f);
48
+ if (fs.existsSync(fp)) {
49
+ const content = fs.readFileSync(fp, 'utf-8');
50
+ if (content.trim().length > 0) {
51
+ hash.update(content);
52
+ hasContent = true;
53
+ }
54
+ }
55
+ }
56
+ }
57
+
40
58
  return hasContent ? `sha256:${hash.digest('hex')}` : null;
41
59
  }
42
60
 
@@ -130,7 +130,7 @@ ${learning.body}
130
130
  return { promoted, updated };
131
131
  }
132
132
 
133
- if (process.argv[1]?.includes('solutions-promote')) {
133
+ if (process.env.NODE_ENV !== 'test' && process.argv[1]?.includes('solutions-promote')) {
134
134
  const args = { _: [] };
135
135
  for (let i = 2; i < process.argv.length; i++) {
136
136
  if (process.argv[i].startsWith('--')) {
@@ -38,6 +38,13 @@ const BUILTIN_DEFAULTS = {
38
38
  dp_6_timestamp: null,
39
39
  dp_7_result: null,
40
40
  dp_7_timestamp: null,
41
+ // Architecture design gate (v0.9 §26, v0.22.5 评审修复 F02)
42
+ arch_design_decision: null,
43
+ arch_design_reason: null,
44
+ arch_design_timestamp: null,
45
+ arch_design_artifacts: null,
46
+ // Compound engineering capture gate (v0.24.0 复利贯穿强制化)
47
+ compound_skipped: null,
41
48
  };
42
49
 
43
50
  /**
@@ -106,6 +113,15 @@ export function writeState(changeDir, state) {
106
113
  lines.push(`dp_6_timestamp: ${state.dp_6_timestamp ?? 'null'}`);
107
114
  lines.push(`dp_7_result: ${state.dp_7_result ?? 'null'}`);
108
115
  lines.push(`dp_7_timestamp: ${state.dp_7_timestamp ?? 'null'}`);
116
+ lines.push('');
117
+ lines.push('# === Architecture design gate (v0.9 §26) ===');
118
+ lines.push(`arch_design_decision: ${state.arch_design_decision ?? 'null'}`);
119
+ lines.push(`arch_design_reason: ${state.arch_design_reason ?? 'null'}`);
120
+ lines.push(`arch_design_timestamp: ${state.arch_design_timestamp ?? 'null'}`);
121
+ lines.push(`arch_design_artifacts: ${state.arch_design_artifacts ?? 'null'}`);
122
+ lines.push('');
123
+ lines.push('# === Compound engineering capture gate (v0.24.0) ===');
124
+ lines.push(`compound_skipped: ${state.compound_skipped ?? 'null'}`);
109
125
 
110
126
  fs.writeFileSync(filePath, lines.join('\n') + '\n', 'utf-8');
111
127
  }
@@ -33,6 +33,7 @@ const COMMANDS = {
33
33
  'install-qoder': () => import('./lib/cmd-install-qoder.mjs'),
34
34
  'install-zcode': () => import('./lib/cmd-install-zcode.mjs'),
35
35
  'prototype-sync': () => import('./lib/prototype-sync.mjs'),
36
+ 'arch-merge': () => import('./lib/arch-merge.mjs'),
36
37
  };
37
38
 
38
39
  const HELP = `team-flow (tf) — Unified workflow plugin CLI
@@ -47,6 +48,8 @@ Commands:
47
48
  sync <change-dir> Merge delta specs into main specs
48
49
  prototype-sync <change-dir> [--source <path>] [--prototype-dir <path>]
49
50
  Merge UX delta into global prototype/ + design-system.md
51
+ arch-merge <change-dir> [--project-root <path>] [--dry-run]
52
+ Merge architecture delta into global docs/architecture/
50
53
  config [options] Display or modify configuration
51
54
  config --resolve-model <profile> Resolve a configured model profile without switching models
52
55
  state <sub> <dir> Manage .team-flow.yaml state (init|check|transition|get|rebuild)
@@ -112,6 +112,8 @@ artifacts: # required 时必填,skipped 时为空
112
112
  - architecture/api.md
113
113
  ```
114
114
 
115
+ **职责边界**:architecture-design 负责**判断+产出**(五项检查判断是否涉及架构变更,涉及则产出架构设计文档),workflow-start 负责**reasonableness check + 状态写入**(确认判断合理性后写入 yaml)。
116
+
115
117
  ### 产出目录
116
118
 
117
119
  架构设计产出放在 change 目录下的独立 `architecture/` 子目录:
@@ -121,20 +123,57 @@ changes/<name>/
121
123
  └── architecture/ # ★ 架构设计独立一等产物
122
124
  ├── architecture.md # DDD 增量(聚合/限界上下文/CQRS/Context Map)
123
125
  ├── database.md # DB 增量(实体/读写模型/schema 变更)
124
- └── api.md # API 增量(Command/Read/Query 分流 + 端点设计)
126
+ ├── api.md # 架构路由表(Command/Read/Query 分流 + 聚合归属 + 阻断测试结果)
127
+ └── sql/ # DDL / Migration 脚本(独立可执行文件,不嵌入 markdown)
128
+ ├── ddl/ # 表结构定义脚本
129
+ └── migration/ # 增量迁移脚本
125
130
  ```
126
131
 
127
132
  **语义分离**:架构设计与行为规格(`specs/`)是不同专业领域,独立目录避免混杂。
128
133
 
134
+ **api.md 定位——架构路由表**:
135
+ - 记录内容:Command/Read/Query 分类、聚合归属(哪个聚合拥有该操作)、阻断测试结果(写模型 vs 读模型分流依据)
136
+ - **不**重复 Swagger 管理的内容:请求/响应 schema、错误码定义、参数明细等由 Swagger/OpenAPI 规范承载
137
+ - frontmatter 须声明 `api_contract_manager: swagger`,表明详细契约由 Swagger 工具链管理
138
+ - sql/ 目录中的 DDL 和 migration 脚本为独立可执行 `.sql` 文件,不嵌入 markdown 文档
139
+
129
140
  **下游消费**:
130
141
  - `architecture/architecture.md` → spec-writer:design.md Decisions 约束
131
142
  - `architecture/database.md` → spec-writer:design.md + tasks.md 数据层约束
132
- - `architecture/api.md` → spec-writer:tasks.md 接口定义约束
133
- - 全部release-archivist → arch-merge → 全局 `docs/architecture/` 回写
143
+ - `architecture/api.md` → spec-writer:tasks.md 接口路由与分流约束(详细 schema 由 Swagger 提供)
144
+ - `architecture/sql/`build-executor:直接执行 DDL/migration 脚本
145
+ - 全部 → release-archivist → `tf arch-merge` → 全局 `docs/architecture/` 回写
146
+
147
+ ### 架构制品合并(arch-merge)
148
+
149
+ change 进入 closing 阶段时,由 release-archivist 调用 `tf arch-merge` CLI 命令,将 `changes/<name>/architecture/` 下的增量制品合并回全局 `docs/architecture/`,完成 As-Is → To-Be 基线更新。architecture-design 本身不执行合并,只负责产出增量制品。
134
150
 
135
151
  ### 独立调用
136
152
 
137
153
  除 workflow-start 编排调用外,本 skill 仍支持用户显式独立调用(`/team-flow:architecture-design`),此时不走五项检查判定门,直接执行完整 4A+DDD 设计。
138
154
 
155
+ ## 上下文加载协议
156
+
157
+ architecture-design 执行时的上下文组装:
158
+
159
+ **始终加载**:
160
+ 1. `Read docs/architecture/INDEX.md`(~50行摘要)
161
+ 2. `Read changes/<name>/change-brief.md`(如有)
162
+ 3. `Read changes/<name>/proposal.md`(如有)
163
+
164
+ **根据五项检查按需加载**:
165
+ 4. [聚合变更] → `Read PHYSICAL-MODEL.md` 对应业务域段落 + `Read ARCHITECTURE.md` 对应 BC 段落
166
+ 5. [读写模型变更] → `Read PHYSICAL-MODEL.md` 对应域段落
167
+ 6. [API 变更] → `Read API-INDEX.md` 对应分流区域
168
+ 7. [DB schema 变更] → `Read schema-baseline.sql` 对应表的 DDL
169
+ 8. [限界上下文变更] → `Read ARCHITECTURE.md` 全文
170
+
171
+ **绝不加载**:
172
+ - `baseline.md`(项目全景,arch-design 不需要)
173
+ - 未涉及的业务域段落
174
+ - 历史变更脚本(`changelog/`)
175
+
176
+ **上下文预算**:单次执行 ~2K tokens(L1 INDEX + 1-2 个 L2 域段 + L3 变更层)
177
+
139
178
  ## Scope & Limits
140
179
  本 skill 覆盖 4A+DDD 架构设计方法及其与 team-flow/compound-engineering 的衔接。落地实现结合项目具体工具;超出本范围见相关 skill 或直接问 agent。