@xulthekl/team-flow 0.47.0 → 0.49.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 (60) 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 +2 -2
  9. package/CHANGELOG.md +54 -0
  10. package/GEMINI.md +1 -1
  11. package/INSTALL.md +2 -2
  12. package/README.md +3 -3
  13. package/docs/README_en.md +1 -1
  14. package/docs/solutions/INDEX.md +0 -14
  15. package/gemini-extension.json +1 -1
  16. package/hooks/session-start +2 -2
  17. package/llms.txt +1 -1
  18. package/package.json +1 -1
  19. package/plugin.json +1 -1
  20. package/scripts/ensure-branch.mjs +94 -4
  21. package/scripts/guard/checks/compound-captured.mjs +6 -3
  22. package/scripts/lib/cmd-repo-layout.mjs +51 -0
  23. package/scripts/lib/cmd-runtime.mjs +1 -0
  24. package/scripts/lib/config-loader.mjs +17 -1
  25. package/scripts/lib/conventions-generator.mjs +61 -2
  26. package/scripts/lib/execution-plan.mjs +82 -32
  27. package/scripts/lib/execution-recommendation.mjs +4 -18
  28. package/scripts/lib/glaf4-delegation.mjs +64 -26
  29. package/scripts/lib/hash.mjs +41 -2
  30. package/scripts/lib/prototype-sync.mjs +2 -1
  31. package/scripts/lib/sdd-overlay.mjs +3 -2
  32. package/scripts/lib/solutions-capture.mjs +2 -1
  33. package/scripts/lib/solutions-index-gen.mjs +2 -1
  34. package/scripts/lib/solutions-inject.mjs +2 -1
  35. package/scripts/lib/solutions-promote.mjs +90 -12
  36. package/scripts/lib/test-record.mjs +67 -21
  37. package/scripts/team-flow.mjs +3 -0
  38. package/skills/build-executor/SKILL.md +8 -2
  39. package/skills/ce-compound/references/promotion-rules.md +1 -1
  40. package/skills/ce-compound/references/schema.yaml +10 -1
  41. package/skills/contract-builder/references/glaf4-delegation.md +11 -7
  42. package/skills/release-archivist/SKILL.md +7 -8
  43. package/skills/release-archivist/references/closing-procedures.md +7 -2
  44. package/skills/workflow-orchestrator/SKILL.md +7 -0
  45. package/skills/workflow-start/SKILL.md +12 -1
  46. package/templates/learnings.md +51 -0
  47. package/docs/solutions/cross-phase/2026-07-28-no-summary.md +0 -17
  48. package/docs/solutions/cross-phase/2026-07-29-no-summary.md +0 -17
  49. package/docs/solutions/cross-phase/2026-07-30-no-summary.md +0 -17
  50. package/docs/solutions/cross-phase/2026-07-31-no-summary.md +0 -17
  51. package/docs/solutions/cross-phase/2026-08-01-no-summary.md +0 -17
  52. package/docs/solutions/cross-phase/2026-08-03-no-summary.md +0 -17
  53. package/docs/solutions/cross-phase/2026-08-04-no-summary.md +0 -17
  54. package/docs/solutions/cross-phase/2026-08-05-no-summary.md +0 -17
  55. package/docs/solutions/cross-phase/2026-08-06-no-summary.md +0 -17
  56. package/docs/solutions/cross-phase/2026-08-07-no-summary.md +0 -17
  57. package/docs/solutions/cross-phase/2026-08-17-no-summary.md +0 -17
  58. package/docs/solutions/cross-phase/2026-08-18-no-summary.md +0 -17
  59. package/docs/solutions/cross-phase/2026-08-19-no-summary.md +0 -17
  60. package/docs/solutions/cross-phase/2026-08-21-no-summary.md +0 -17
@@ -6,7 +6,8 @@
6
6
  * 用法:tf solutions promote <change-dir> [--dir <solutions-dir>]
7
7
  *
8
8
  * 功能:
9
- * 1. 读取 specs/<cap>/learnings.md
9
+ * 1. 读取 change 根 learnings.md(v0.49.0 §83.3.5:统一路径——此前注释误写 specs/<cap>/,
10
+ * 与本文件第 58 行的实际实现及 compound-captured guard 均不一致)
10
11
  * 2. 逐条检查晋升条件(severity ≥ medium 且 type = pitfall/pattern)
11
12
  * 3. 检查与全局 INDEX 是否重复(domain+type 匹配)
12
13
  * 4. 符合条件 → 写入全局 docs/solutions/
@@ -19,6 +20,7 @@
19
20
 
20
21
  import { readFileSync, writeFileSync, existsSync, appendFileSync, mkdirSync } from 'node:fs';
21
22
  import { join, basename } from 'node:path';
23
+ import { pathToFileURL } from 'node:url';
22
24
 
23
25
  function parseFrontmatter(content) {
24
26
  // learnings.md 中每个条目以 `## 标题` 开头,frontmatter 紧跟标题行之后;
@@ -43,7 +45,59 @@ function parseLearnings(content) {
43
45
  const title = lines[0]?.trim() || '';
44
46
  const fm = parseFrontmatter(s);
45
47
  return { title, fm, body: s.trim() };
46
- }).filter(e => e.title && e.title !== 'Learnings');
48
+ // v0.49.0 §83.3.5:排除文件级标题(`# Session Learnings` 会被 split 成首个 section),
49
+ // 否则它会被当成一条无 frontmatter 的条目计入 skipped
50
+ }).filter(e => e.title && e.title !== 'Learnings' && !e.title.startsWith('#'));
51
+ }
52
+
53
+ /**
54
+ * v0.49.0 §83.3.5:把解析出的条目重整为可写入全局文件的形式 ——
55
+ * ① 补回 `## ` 标题前缀(parseLearnings 按 `^## ` 分割,body 首行不含标记)
56
+ * ② 剔除 body 中已含的原 frontmatter 块(原实现直接写 body → 产出双 frontmatter)
57
+ */
58
+ function renderEntryBody(learning) {
59
+ const body = learning.body
60
+ .replace(/^[^\n]*\n?/, '')
61
+ .replace(/(?:^|\n)---\n[\s\S]*?\n---[ \t]*\n?/, '\n')
62
+ .trim();
63
+ return `## ${learning.title}\n\n${body}`;
64
+ }
65
+
66
+ const SEVERITY_LADDER = { low: 'medium', medium: 'high' };
67
+
68
+ /**
69
+ * v0.49.0 §83.3.5:重复命中时按 promotion-rules.md 承诺真正落盘(原实现只 `updated++`,
70
+ * 不更新 INDEX 行、不标记条目)——升级 INDEX 行 severity + 条目文件 confirmed 计数。
71
+ */
72
+ function confirmExistingEntry(solutionsDir, indexContent, domain, type) {
73
+ const lines = indexContent.split('\n');
74
+ for (let i = 0; i < lines.length; i += 1) {
75
+ // INDEX 列:| date | phase | domain | type | severity | summary | file |
76
+ const cols = lines[i].split('|').map(column => column.trim());
77
+ if (cols.length < 9 || cols[3] !== domain || cols[4] !== type) continue;
78
+
79
+ const currentSeverity = cols[5];
80
+ const nextSeverity = SEVERITY_LADDER[currentSeverity] ?? currentSeverity;
81
+ if (nextSeverity !== currentSeverity) {
82
+ lines[i] = lines[i].replace(`| ${currentSeverity} |`, `| ${nextSeverity} |`);
83
+ }
84
+ markEntryConfirmed(join(solutionsDir, cols[7]));
85
+ return { indexContent: lines.join('\n'), from: currentSeverity, to: nextSeverity, file: cols[7] };
86
+ }
87
+ return null;
88
+ }
89
+
90
+ function markEntryConfirmed(filePath) {
91
+ if (!existsSync(filePath)) return;
92
+ const content = readFileSync(filePath, 'utf-8');
93
+ const block = content.match(/^---\n([\s\S]*?)\n---/);
94
+ if (!block) return;
95
+ const counter = block[1].match(/^confirmed:\s*(\d+)\s*$/m);
96
+ const next = counter ? parseInt(counter[1], 10) + 1 : 2;
97
+ const frontmatter = counter
98
+ ? block[1].replace(/^confirmed:\s*\d+\s*$/m, `confirmed: ${next}`)
99
+ : `${block[1]}\nconfirmed: ${next}`;
100
+ writeFileSync(filePath, content.replace(block[0], `---\n${frontmatter}\n---`), 'utf-8');
47
101
  }
48
102
 
49
103
  export function run(args = {}) {
@@ -58,7 +112,7 @@ export function run(args = {}) {
58
112
  const learningsPath = join(changeDir, 'learnings.md');
59
113
  if (!existsSync(learningsPath)) {
60
114
  console.log(`No learnings.md found in ${changeDir}, nothing to promote`);
61
- return { promoted: 0, updated: 0 };
115
+ return { promoted: 0, updated: 0, skipped: 0 };
62
116
  }
63
117
 
64
118
  const content = readFileSync(learningsPath, 'utf-8');
@@ -66,15 +120,17 @@ export function run(args = {}) {
66
120
 
67
121
  if (learnings.length === 0) {
68
122
  console.log('No learning entries found');
69
- return { promoted: 0, updated: 0 };
123
+ return { promoted: 0, updated: 0, skipped: 0 };
70
124
  }
71
125
 
72
126
  // 读取全局 INDEX
73
127
  const indexPath = join(solutionsDir, 'INDEX.md');
74
- const indexContent = existsSync(indexPath) ? readFileSync(indexPath, 'utf-8') : '';
128
+ let indexContent = existsSync(indexPath) ? readFileSync(indexPath, 'utf-8') : '';
75
129
 
76
130
  let promoted = 0;
77
131
  let updated = 0;
132
+ // v0.49.0 §83.3.5:跳过原因可见化(原实现静默 continue,用户看不出为什么一条都没晋升)
133
+ const skipped = [];
78
134
 
79
135
  for (const learning of learnings) {
80
136
  const severity = learning.fm?.severity || 'low';
@@ -85,12 +141,27 @@ export function run(args = {}) {
85
141
  // 晋升条件:severity ≥ medium 且 type = pitfall/pattern
86
142
  const sevOk = severity === 'high' || severity === 'medium';
87
143
  const typeOk = type === 'pitfall' || type === 'pattern';
88
- if (!sevOk || !typeOk) continue;
144
+ if (!sevOk || !typeOk) {
145
+ skipped.push({
146
+ title: learning.title,
147
+ reason: learning.fm
148
+ ? `severity=${severity}, type=${type}(需 severity ≥ medium 且 type = pitfall|pattern)`
149
+ : 'no frontmatter(缺 severity/type,按默认 low/insight 处理)',
150
+ });
151
+ continue;
152
+ }
89
153
 
90
154
  // 检查重复:domain+type 匹配
91
155
  const dupRegex = new RegExp(`\\| ${domain} \\| ${type} \\|`);
92
156
  if (dupRegex.test(indexContent)) {
93
- console.log(`Duplicate found (${domain}/${type}), marking as confirmed pattern`);
157
+ const outcome = confirmExistingEntry(solutionsDir, indexContent, domain, type);
158
+ if (!outcome) {
159
+ skipped.push({ title: learning.title, reason: `duplicate ${domain}/${type} 但 INDEX 行无法定位` });
160
+ continue;
161
+ }
162
+ indexContent = outcome.indexContent;
163
+ writeFileSync(indexPath, indexContent, 'utf-8');
164
+ console.log(`Confirmed: ${domain}/${type} severity ${outcome.from} → ${outcome.to}(${outcome.file})`);
94
165
  updated++;
95
166
  continue;
96
167
  }
@@ -115,22 +186,29 @@ date: ${date}
115
186
  source: ${basename(changeDir)}
116
187
  ---
117
188
 
118
- ${learning.body}
189
+ ${renderEntryBody(learning)}
119
190
  `;
120
191
  writeFileSync(filePath, fileContent, 'utf-8');
121
192
 
122
193
  // 追加 INDEX
123
- appendFileSync(indexPath, `| ${date} | ${phase} | ${domain} | ${type} | ${severity} | ${learning.title.slice(0, 80)} | ${phase}/${fileName} |\n`, 'utf-8');
194
+ const indexRow = `| ${date} | ${phase} | ${domain} | ${type} | ${severity} | ${learning.title.slice(0, 80)} | ${phase}/${fileName} |\n`;
195
+ appendFileSync(indexPath, indexRow, 'utf-8');
196
+ indexContent += indexRow;
124
197
 
125
198
  console.log(`Promoted: ${phase}/${fileName}`);
126
199
  promoted++;
127
200
  }
128
201
 
129
- console.log(`\nPromotion complete: ${promoted} promoted, ${updated} updated as confirmed`);
130
- return { promoted, updated };
202
+ // v0.49.0 §83.3.5:解析/晋升/确认/跳过汇总(原实现只报 promoted/updated
203
+ console.log(`\nPromotion complete: ${promoted} promoted, ${updated} confirmed, ${skipped.length} skipped`);
204
+ if (skipped.length > 0) {
205
+ console.log('Skipped entries:');
206
+ for (const item of skipped) console.log(` - ${item.title}: ${item.reason}`);
207
+ }
208
+ return { promoted, updated, skipped: skipped.length };
131
209
  }
132
210
 
133
- if (process.env.NODE_ENV !== 'test' && process.argv[1]?.includes('solutions-promote')) {
211
+ if (process.env.NODE_ENV !== 'test' && process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
134
212
  const args = { _: [] };
135
213
  for (let i = 2; i < process.argv.length; i++) {
136
214
  if (process.argv[i].startsWith('--')) {
@@ -33,30 +33,54 @@ export function parseMavenSurefire(text) {
33
33
  return { total, passed: total - failures - errors - skipped, failed: failures + errors, skipped };
34
34
  }
35
35
 
36
- /** maven surefire XML 报告目录(target/surefire-reports):累加 <testsuite> 属性。 */
37
- export function parseSurefireReportDir(dir) {
36
+ /**
37
+ * 通用 junit XML 解析:累加 <testsuite> 的 tests/failures/errors/skipped 属性。
38
+ *
39
+ * v0.49.0 §83.3.6(来源:workflow-feedback 20260909):maven-surefire 与 pytest 的
40
+ * junit XML 结构同构(<testsuites>/<testsuite>)。此前该逻辑只服务 maven-surefire,
41
+ * pytest runner 只认 terminal summary —— 同一 CLI 对不同 runner 的结构化支持不对称。
42
+ */
43
+ export function parseJunitXml(text) {
38
44
  let total = 0; let failures = 0; let errors = 0; let skipped = 0;
39
45
  let found = false;
40
- for (const name of readdirSync(dir)) {
41
- if (!name.endsWith('.xml')) continue;
42
- const content = readFileSync(join(dir, name), 'utf-8');
43
- for (const tag of content.matchAll(/<testsuite\b[^>]*>/g)) {
44
- const attrs = tag[0];
45
- const pick = (key) => {
46
- const am = attrs.match(new RegExp(`\\b${key}="(\\d+)"`));
47
- return am ? parseInt(am[1], 10) : 0;
48
- };
49
- found = true;
50
- total += pick('tests');
51
- failures += pick('failures');
52
- errors += pick('errors');
53
- skipped += pick('skipped');
54
- }
46
+ for (const tag of text.matchAll(/<testsuite\b[^>]*>/g)) {
47
+ const attrs = tag[0];
48
+ const pick = (key) => {
49
+ const am = attrs.match(new RegExp(`\\b${key}="(\\d+)"`));
50
+ return am ? parseInt(am[1], 10) : 0;
51
+ };
52
+ found = true;
53
+ total += pick('tests');
54
+ failures += pick('failures');
55
+ errors += pick('errors');
56
+ skipped += pick('skipped');
55
57
  }
56
58
  if (!found) return null;
57
59
  return { total, passed: total - failures - errors - skipped, failed: failures + errors, skipped };
58
60
  }
59
61
 
62
+ /** junit XML 内容特征(用于嗅探;只检查头部,避免大报告全量扫描)。 */
63
+ function looksLikeJunitXml(text) {
64
+ return /<testsuites?\b/.test(text.slice(0, 512));
65
+ }
66
+
67
+ /** maven surefire XML 报告目录(target/surefire-reports):累加目录内所有 XML。 */
68
+ export function parseSurefireReportDir(dir) {
69
+ let aggregated = null;
70
+ for (const name of readdirSync(dir)) {
71
+ if (!name.endsWith('.xml')) continue;
72
+ const parsed = parseJunitXml(readFileSync(join(dir, name), 'utf-8'));
73
+ if (!parsed) continue;
74
+ aggregated = aggregated === null ? parsed : {
75
+ total: aggregated.total + parsed.total,
76
+ passed: aggregated.passed + parsed.passed,
77
+ failed: aggregated.failed + parsed.failed,
78
+ skipped: aggregated.skipped + parsed.skipped,
79
+ };
80
+ }
81
+ return aggregated;
82
+ }
83
+
60
84
  /** jest --json 输出。 */
61
85
  export function parseJest(text) {
62
86
  let data;
@@ -69,8 +93,18 @@ export function parseJest(text) {
69
93
  return { total, passed, failed, skipped };
70
94
  }
71
95
 
72
- /** pytest terminal summary:"== 5 passed, 2 failed, 1 skipped in 3.21s ==" */
96
+ /**
97
+ * pytest 输出:junit XML(`--junitxml=<path>`)或 terminal summary
98
+ * ("== 5 passed, 2 failed, 1 skipped in 3.21s ==")。
99
+ *
100
+ * v0.49.0 §83.3.6:XML 优先 —— 结构稳定,不受 `-q`/`-v`/颜色码/`--tb` 影响,
101
+ * 且 CI 流水线天然产出 XML(来源:workflow-feedback 20260909)。
102
+ */
73
103
  export function parsePytest(text) {
104
+ if (looksLikeJunitXml(text)) {
105
+ const xml = parseJunitXml(text);
106
+ if (xml) return xml;
107
+ }
74
108
  const lines = text.split('\n');
75
109
  const summaryLine = [...lines].reverse().find(l => /=+\s.*(passed|failed|error|no tests ran)/.test(l));
76
110
  if (!summaryLine) return null;
@@ -142,9 +176,16 @@ export function classifyFailure(text) {
142
176
  return 'unknown';
143
177
  }
144
178
 
145
- /** auto 识别:按内容特征匹配 runner。 */
179
+ /**
180
+ * auto 识别:按内容特征匹配 runner。
181
+ *
182
+ * v0.49.0 §83.3.6:junit XML 特征优先嗅探。surefire 与 pytest 的 XML 同构,auto 模式
183
+ * 统一归入 maven-surefire(解析结果相同);需要把 runner 记为 pytest 时显式传
184
+ * `--runner pytest`(同样支持 XML)。
185
+ */
146
186
  export function detectRunner(text) {
147
187
  const trimmed = text.trim();
188
+ if (looksLikeJunitXml(trimmed) && parseJunitXml(trimmed)) return 'maven-surefire';
148
189
  if (trimmed.startsWith('{') && parseJest(trimmed)) return 'jest';
149
190
  if (/Tests run:\s*\d+,\s*Failures:/.test(trimmed)) return 'maven-surefire';
150
191
  if (parsePytest(trimmed)) return 'pytest';
@@ -164,7 +205,8 @@ export async function run(args) {
164
205
  allowPositionals: true,
165
206
  });
166
207
 
167
- const usageMsg = 'Usage: tf test record <change-dir> --from <runner-output-file|surefire-report-dir> [--runner auto|maven-surefire|jest|pytest] [--json]';
208
+ const usageMsg = 'Usage: tf test record <change-dir> --from <runner-output-file|surefire-report-dir> [--runner auto|maven-surefire|jest|pytest] [--json]\n'
209
+ + ' maven-surefire: console summary, surefire XML dir/file; pytest: terminal summary or junit XML (--junitxml); jest: --json';
168
210
  if (positionals[0] !== 'record') {
169
211
  console.error(usageMsg);
170
212
  process.exit(2);
@@ -222,7 +264,11 @@ export async function run(args) {
222
264
  stats = PARSERS[runner](text);
223
265
  }
224
266
  if (!stats) {
225
- console.error(`Could not parse ${runner} output in ${fromPath} — no recognizable test summary found.`);
267
+ // v0.49.0 §83.3.6:错误信息附输入形态指引(原提示对"传了 XML"的用户无帮助)
268
+ const hint = runner === 'pytest'
269
+ ? 'pytest accepts a terminal summary or a junit XML report (pytest --junitxml=<path>).'
270
+ : `Supported runners: ${SUPPORTED_RUNNERS.join(', ')} (or pass --runner explicitly).`;
271
+ console.error(`Could not parse ${runner} output in ${fromPath} — no recognizable test summary found.\n${hint}`);
226
272
  process.exit(1);
227
273
  }
228
274
 
@@ -42,6 +42,7 @@ const COMMANDS = {
42
42
  'test-matrix-export': () => import('./lib/test-matrix-export.mjs'),
43
43
  'glaf4-evidence-export': () => import('./lib/glaf4-evidence-export.mjs'),
44
44
  'glaf4-delegation': () => import('./lib/glaf4-delegation.mjs'),
45
+ 'repo-layout': () => import('./lib/cmd-repo-layout.mjs'),
45
46
  test: () => import('./lib/test-record.mjs'),
46
47
  };
47
48
 
@@ -76,6 +77,8 @@ Commands:
76
77
  Aggregate glaf4-dev run PASS evidence → surefire line (v2.1 §7; feed test record --from)
77
78
  glaf4-delegation <sub> <dir> [--mode <m> --write-set <json> | --run-dir <run> | --delegation-mode <mode>]
78
79
  GLAF4 委托协议 (v2.1 §4/6.3/6.6/6.7): detect / confirm / verify-write-set / verify-tasks / record-partial / reset
80
+ repo-layout detect <root>
81
+ Detect project layout (single/monorepo/multi-repo) + code repos, write repo_layout config (v1.0 §3)
79
82
  test record <dir> --from <runner-output> [--runner auto|maven-surefire|jest|pytest]
80
83
  Record programmatic test evidence (v0.13 §50; feeds tests-passing gate)
81
84
  config [options] Display or modify configuration
@@ -11,6 +11,8 @@ Controls the implementation phase. Uses `execution-contract.md` as the workflow
11
11
 
12
12
  Read: `execution-contract.md`, `tasks.md`, relevant `specs/`, relevant `design.md`. (Skip contract/spec requirements when workflow is `tweak`.)
13
13
 
14
+ **复利经验注入(v0.49.0 §83.3.5)**:运行 `tf solutions inject --phase cross-phase`(失败静默)——注入 `docs/solutions/` 中跨阶段可复用的历史经验(踩坑/模式),供实施时规避已知问题。注入内容是**经验提示而非约束**:与当前 change 的契约/规格冲突时以契约/规格为准,并把冲突回报给 workflow-start。
15
+
14
16
  When `architecture/sql/` exists, also read:
15
17
  - `architecture/sql/ddl/*.sql` — executable DDL scripts; reference directly for DB schema change tasks instead of hand-writing DDL
16
18
  - `architecture/sql/migration/*.sql` — data migration scripts; execute as-is for data transformation tasks
@@ -95,7 +97,7 @@ The recommendation uses task count, configured `execution.inlineThreshold`, and
95
97
  | **Inline** | Recommended for a single sequential task; always available for a user-confirmed choice |
96
98
  | **Batch Inline** | Recommended for a bounded sequential batch; it remains serial and is never presented as parallel |
97
99
 
98
- Do not transition to `executing` until `execution show` reports `current: true` and the phase guard passes. A revised plan must repeat `tf execution recommend` and use `tf execution revise --confirm`; it creates a new revision and invalidates receipts from the prior revision.
100
+ Do not transition to `executing` until `execution show` reports `current: true` and the phase guard passes. A revised plan must repeat `tf execution recommend` and use `tf execution revise --confirm`; it creates a new revision. Receipts carrying a `wave_fingerprint` stay valid while their wave definition is unchanged (v0.49.0 §83.3.2); legacy receipts without one still fall back to the plan-hash judgement and are invalidated by a revision.
99
101
 
100
102
  ## Batch Inline Execution
101
103
 
@@ -123,7 +125,11 @@ For full/hotfix by default. Execute waves as dispatched by workflow-start.
123
125
  1. Read the current plan with `tf execution show <change-dir> --json`; only waves shown with `current: true` and `eligible: true` may start. A `retryable: true` wave may only be repaired and re-reviewed; do not dispatch its dependents until its replacement receipt is `pass`. The CLI encodes dependencies in `--wave <id>:<strategy>:<tasks>[:<depends-on,...>]` and rejects a review receipt for a wave whose prerequisites lack current `pass` receipts.
124
126
  2. A `parallel` wave may dispatch independent tasks simultaneously only when the platform supports concurrent dispatch. If it does not, disclose the unavailable capability and execute the same wave one task at a time without changing its stored strategy.
125
127
  3. A `serial` wave dispatches one task at a time in listed order.
126
- 4. After every wave, notify workflow-start via SendMessage that the wave is complete and needs review. Include:
128
+ 4. After every wave, write back the tasks.md checkboxes for that wave, then notify workflow-start via SendMessage that the wave is complete and needs review.
129
+
130
+ **tasks.md 勾选回写(v0.49.0 §83.3.4)**:按 `wave.tasks` 匹配 tasks.md 对应行,把 `- [ ]` 改为 `- [x]`(只改勾选标记,不动任务文本)。这是执行进度的权威登记——`tasks-complete` guard 在 closing 时要求零未勾选项,留到收尾轮必然遗忘。若该 change 的 tasks.md 不使用 checkbox 形态(如以 `### Task N.M` 标题组织),零 `- [x]` 会使该 guard 无条件 FAIL:此时与 workflow-start 确认改用 checkbox 形态或走显式豁免,不要静默跳过。A1 修复后勾选不再使 plan 过期(§83.3.1),因此不会阻塞本 wave 的 receipt 登记(receipt 由 workflow-start 写入)。
131
+
132
+ Notify with:
127
133
  - Wave ID
128
134
  - Worktree path
129
135
  - Branch
@@ -10,7 +10,7 @@ tf solutions promote <change-dir>
10
10
 
11
11
  ## 晋升来源
12
12
 
13
- 检查 `specs/<cap>/learnings.md` 中的经验。
13
+ 检查 change 根目录 `learnings.md` 中的经验(v0.49.0 §83.3.5 统一路径:代码与 guard 均只认 change 根,`specs/<cap>/` 下从未落地)。
14
14
 
15
15
  ## 晋升条件
16
16
 
@@ -1,5 +1,14 @@
1
1
  # Documentation schema for learnings written by ce-compound
2
- # Treat this as the canonical frontmatter contract for docs/solutions/.
2
+ # Treat this as the canonical frontmatter contract for docs/solutions/ entries
3
+ # written by ce-compound.
4
+ #
5
+ # v0.49.0 §83.3.5(来源:workflow-feedback 20260909):docs/solutions/ 有两条产出通道,
6
+ # frontmatter 契约不同 ——
7
+ # ① ce-compound 手工条目:使用本文件的 schema(module / problem_type / component / ...)
8
+ # ② tf solutions promote 自动晋升条目:phase / domain / type / severity / date / source
9
+ # (见 scripts/lib/solutions-promote.mjs,字段源自 change 根 learnings.md)
10
+ # 共同消费面 = docs/solutions/INDEX.md(tf solutions inject 只读该文件,不读条目 frontmatter)。
11
+ # 下方 schema 仅适用于通道 ①。
3
12
  #
4
13
  # The schema has two tracks based on problem_type:
5
14
  # Bug track — problem_type is a defect or failure (build_error, test_failure, etc.)
@@ -9,17 +9,19 @@
9
9
  contract-builder 产出 execution-contract 时(DP-4 前)实时探测——**显式运行 CLI 命令**(第三轮复评接线统一:探测必须写 config,纯库函数不落盘):
10
10
 
11
11
  ```bash
12
- tf glaf4-delegation detect <target_root> [--delegation-mode <auto|manual|off>]
12
+ tf glaf4-delegation detect <target_root> [--delegation-mode <auto|manual|off>] [--target-repo <模块路径>]
13
13
  ```
14
14
 
15
+ **target_root 语义(multi-repo-support-design v1.0 §4.1,cli-command-1 修复)**:传入**项目根或 change 目录均可**——detect 自动解析:change 目录(含 `changes/`)→ 项目根,config 主副本写**项目根** `.team-flow/team-flow.config.json`(不写 change 目录)。**多仓库项目(`repo_layout: multi-repo`)推荐传 `--target-repo <模块路径>`**(如 change 涉及 `batch-fytx-msg`),或依赖自动扫描:按 `repo_layout.repos` 清单聚合判定——任一代码仓库是 Java → 项目级 `is_glaf4_java=true`。
16
+
15
17
  `detect` 内部调用 `conventions-generator.mjs` 的 `detectGlaf4Delegation(root)` 解析 `~/.claude/plugins/installed_plugins.json`(glaf4-dev 条目含 installPath + version),产出三态:
16
18
 
17
- - `is_glaf4_java`(技术栈命中:pom.xml/build.gradle + Spring Boot/JUnit5/Mockito
19
+ - `is_glaf4_java`(技术栈命中:pom.xml/build.gradle + Spring Boot/JUnit5/Mockito;**多仓库下按 repos 清单聚合,任一仓库 Java → true**)
18
20
  - `glaf4_dev_available`(插件可用,含 version)
19
21
  - `delegation_mode`(auto/manual/off,用户可开关)
20
22
 
21
- 探测结果写 `team-flow.config.json` 的 `glaf4_dev: {enabled, version, delegation_mode}` 段
22
- (git root ≠ target_root 时写 git root 冗余副本,monorepo 下多服务 OR 聚合;未传 `--delegation-mode` 保留已有值)。
23
+ 探测结果写 `<项目根>/.team-flow/team-flow.config.json` 的 `glaf4_dev: {enabled, version, delegation_mode}` 段
24
+ (git root ≠ target_root 时写 git root 冗余副本;未传 `--delegation-mode` 保留已有值)。
23
25
 
24
26
  **判定**:`is_glaf4_java && glaf4_dev_available && delegation_mode !== 'off'` → 产出 GLAF4 Delegation 段。
25
27
  **依赖**:`recommendExecutionModesForChange` 读 `config.glaf4_dev.enabled` 决定是否注入 glaf4-delegation——**必须**先跑 detect 写 config,否则委托链不可达。
@@ -110,15 +112,15 @@ bridging:contract-builder 产非空骨架矩阵 test-matrix.md(过 test-matr
110
112
  → tf state rebuild(过 test-matrix-complete 出口门)
111
113
  ```
112
114
 
113
- ## 8. closing 时序协议(v2.1 §6.3,I5 固化防 plan 过期卡死)
115
+ ## 8. closing 时序协议(v2.1 §6.3,I5 固化防 plan 过期卡死;v0.49.0 §83.3 修订)
114
116
 
115
- **顺序不可乱**——tasks.md 翻转/矩阵回灌改变 artifacts_hash → plan 过期(receipt 失效);必须先 refresh-hash 再记录 review:
117
+ **顺序不可乱**——矩阵回灌(改写 execution-contract.md `## Test Matrix` 段)改变 contract_hash → plan 过期;必须先 refresh-hash 再记录 review:
116
118
 
117
119
  ```
118
120
  run 终局 PASS
119
121
  → 矩阵回灌(test-matrix-export v2 + 覆盖 test-matrix.md)
120
122
  → tf state rebuild(只改 state 文件,不动 plan JSON)
121
- → tf execution refresh-hash(更新 plan.artifacts_hash,防 plan 过期)
123
+ → tf execution refresh-hash(同步 plan 与内嵌 recommendation_receipt 的制品 hash)
122
124
  → 委托代理 commit glaf4-dev 产物
123
125
  → code-reviewer 独立审查 base..head(报告落 .superpowers/sdd/reviews/)
124
126
  → tf execution review --wave w1 --verdict pass
@@ -128,3 +130,5 @@ run 终局 PASS
128
130
  ```
129
131
 
130
132
  > `tf state rebuild` 不能替代 `refresh-hash`(前者不动 plan JSON);review 需真实 git base≠head,必须 commit 后取 SHA。
133
+ >
134
+ > **v0.49.0 §83.3 变更**:① tasks.md 勾选回写不再改变 artifacts_hash(§83.3.1),勾选可与 receipt 登记同轮;② receipt 有效性判据改为 wave 定义指纹(§83.3.2)——refresh-hash / revise 不再失效已有 pass receipt;③ refresh-hash 同步内嵌 recommendation_receipt,命令在其目标场景下可用(§83.3.3)。
@@ -205,13 +205,13 @@ During closing, promote change-level learnings to the global solutions library:
205
205
  tf solutions promote <change-dir>
206
206
  ```
207
207
 
208
- This checks `specs/<cap>/learnings.md` for entries meeting promotion criteria:
208
+ This checks the change's `learnings.md` (change root; v0.49.0 §83.3.5 path unification) for entries meeting promotion criteria:
209
209
  - severity ≥ medium AND type = pitfall/pattern → promote to global `docs/solutions/`
210
210
  - domain+type matches existing global entry → mark as "confirmed pattern", upgrade severity
211
211
 
212
212
  Report promotion results in the closing summary. Promotion is advisory — failures do not block closing.
213
213
 
214
- ### Compound Capture Check (v0.5)
214
+ ### Compound Capture Check (v0.5;v0.49.0 §83.3.5 明确写入方)
215
215
 
216
216
  Before finalizing the closing, check if this change produced any learnable moments:
217
217
 
@@ -220,15 +220,14 @@ Before finalizing the closing, check if this change produced any learnable momen
220
220
  - Did scope expand beyond the original proposal?
221
221
  - Did execution-contract drift from proposal?
222
222
 
223
- If any of the above occurred, capture the learning:
224
- ```bash
225
- tf solutions capture \
226
- --phase cross-phase --domain <domain> --type pitfall \
227
- --severity medium --summary "<what happened and how to prevent it>"
228
- ```
223
+ If any of the above occurred, **write them to the change's `learnings.md` (change root)** — one `## <title>` entry per learning, each carrying a frontmatter block (`phase` / `domain` / `type` / `severity` / `date`). Format reference: `tf runtime asset read templates/learnings.md`. This is the file the `compound-captured` guard checks and `tf solutions promote` reads; entries without frontmatter are skipped by promote (reason listed in its output).
224
+
225
+ `tf solutions capture` writes to global `docs/solutions/` — it does **not** create `learnings.md`. Use it only for product-level learnings that belong directly in the global library.
229
226
 
230
227
  This check is advisory — skip silently if no learnable moments are detected.
231
228
 
229
+ > 详细收尾流程(Prototype Sync / Compound Promotion / Compound Capture / Worktree Deisolation / Code Landing)见 `references/closing-procedures.md`。
230
+
232
231
  ### 阶段产物同步门禁点(v0.37.0 §68.2 G5)
233
232
 
234
233
  回写链(arch-merge → prototype-sync → test-merge → compound promotion)全部完成后、`tf state transition closing` 之前,**阻塞确认**(AskUserQuestion)是否同步 change 实施结果(团队协作:落地结果是团队最需要看的内容):
@@ -30,7 +30,7 @@ Promotion criteria:
30
30
 
31
31
  Report promotion results in the closing summary. Promotion is advisory — failures do not block closing.
32
32
 
33
- ## Compound Capture Check (v0.5)
33
+ ## Compound Capture Check (v0.5;v0.49.0 §83.3.5 明确写入方)
34
34
 
35
35
  Before finalizing the closing, check if this change produced any learnable moments:
36
36
 
@@ -39,12 +39,17 @@ Before finalizing the closing, check if this change produced any learnable momen
39
39
  - Did scope expand beyond the original proposal?
40
40
  - Did execution-contract drift from proposal?
41
41
 
42
- If any occurred, capture the learning:
42
+ If any occurred, **write them to the change's `learnings.md` (change root)** — one `## <title>` entry per learning, each carrying a frontmatter block (`phase` / `domain` / `type` / `severity` / `date`). Format reference: `tf runtime asset read templates/learnings.md`. This is the file the `compound-captured` guard checks and `tf solutions promote` reads; entries without frontmatter are skipped by promote (reason listed in its output).
43
+
44
+ **条目质量**:写「正确做法」前先验证它在系统层面成立——局部观察得出的 workaround 若被沉淀为通则,会被后续 change 当经验复用,反而固化缺陷(v0.49.0 §83.2.2 实证)。
45
+
46
+ For product-level learnings that belong directly in the global library (not change-scoped):
43
47
  ```bash
44
48
  tf solutions capture \
45
49
  --phase cross-phase --domain <domain> --type pitfall \
46
50
  --severity medium --summary "<what happened and how to prevent it>"
47
51
  ```
52
+ `tf solutions capture` writes to global `docs/solutions/` — it does **not** create `learnings.md`.
48
53
 
49
54
  Advisory — skip silently if no learnable moments detected.
50
55
 
@@ -42,6 +42,13 @@ Do NOT invoke for:
42
42
  **conventions 注入(v0.11 §33)**:
43
43
  - 读取 conventions 配置,注入为需求分析上下文
44
44
 
45
+ **项目模式确认(multi-repo-support-design v1.0 §3.3,2026-08-21)**:S1 路由时确认项目布局,写 config 供 detect/review/isolate 按模式路由——
46
+ 1. 定位项目根(`detectWorkspaceRoot` / `getGitRoot`);`<项目根>/.team-flow/team-flow.config.json` 已有 `repo_layout` 段 → 直接读取(已固化跳过)
47
+ 2. 否则运行 `tf repo-layout detect <项目根>`:判定 `single`/`monorepo`/`multi-repo` + 代码仓库清单,写 `<项目根>/.team-flow/team-flow.config.json` 的 `repo_layout` 段
48
+ 3. 多仓库项目向用户展示判定结果(AskUserQuestion 确认);单仓库自动;修正 repos 清单编辑 config 的 `repo_layout.repos`
49
+ 4. 影响:detect(按仓库清单探测 GLAF4)/ review(`--repo` 放宽 git 约束)/ isolate(共享分支检测)
50
+ 5. 边界:本步骤识别存量结构;全新项目骨架引导归 workflow-bootstrap B1.5
51
+
45
52
  ### S2: PRD + 原型阶段
46
53
 
47
54
  **⛔ MANDATORY:执行S2阶段前,必须先读取 `references/s2-prd-prototype-loop.md`**
@@ -34,6 +34,16 @@ Run DP-0 when: change folder doesn't exist, planning artifacts missing/empty, or
34
34
 
35
35
  若 `change-brief.md` 存在且 `upstream_source == orchestrator`(本 change 由 orchestrator S4 分发),**继承** brief 的 scope/约束/AC 为默认值,DP-0 从"从零问"改为"一次确认继承或修正",**不**重新问"你想做什么"。否则(手动新建 / hotfix / 单 change 快速通道 / 存量 change 无 brief)照常 DP-0。**主信号 = brief 文件存在**(非 yaml 字段——可天然排除无 brief 的快速通道,且对存量手动 change 零误伤);双源冲突以 `requirement/vN/plan.md` 为准并触发 Brief drift(见「Staleness Detection」)。完整判定伪代码与主信号理由见 `references/routing-rules.md`「DP-0」。
36
36
 
37
+ ### 项目模式确认(multi-repo-support-design v1.0 §3.3,2026-08-21)
38
+
39
+ DP-0 时确认项目布局(`single`/`monorepo`/`multi-repo`),写 config 供 detect/review/isolate 按模式路由:
40
+ 1. **定位项目根**:`detectWorkspaceRoot(changeDir)`(change 目录 → `changes/` 父目录);非标准布局回退 `getGitRoot`
41
+ 2. **已固化跳过**:`<项目根>/.team-flow/team-flow.config.json` 已有 `repo_layout` 段 → 直接读取,跳过 3-4
42
+ 3. **运行 `tf repo-layout detect <项目根>`**:判定 `single`/`monorepo`/`multi-repo` + 代码仓库清单,写 `<项目根>/.team-flow/team-flow.config.json` 的 `repo_layout` 段
43
+ 4. **多仓库项目首次确认**:向用户展示判定结果(`multi-repo + N 个代码仓库`),AskUserQuestion 确认;单仓库自动。**修正 repos 清单**:确认后编辑 `<项目根>/.team-flow/team-flow.config.json` 的 `repo_layout.repos`(缺省接受检测结果)
44
+ 5. **与 workflow-bootstrap 边界**:本步骤识别**存量结构**(single/monorepo/multi-repo 探测 + repos 清单);全新项目骨架引导(生成 pom.xml/package.json)归 workflow-bootstrap B1.5
45
+ 6. 判定结果影响:detect(按仓库清单探测 GLAF4)/ review(`--repo` 放宽 git 约束)/ isolate(共享分支检测)
46
+
37
47
  Ask (manual path): change name + one-sentence intent, known constraints, related optimizations (include or stay focused?), communication preference (ask per decision or draft for review).
38
48
 
39
49
  After confirmation:
@@ -104,6 +114,7 @@ Contract exists and approved, contract matches artifacts. Include `DP-4: 执行
104
114
  2. 对每个 wave:
105
115
  - dispatch build-executor 执行当前 wave
106
116
  - 等待 build-executor 完成(task-notification)
117
+ - **确认该 wave 的 tasks.md 勾选已回写**(v0.49.0 §83.3.4):build-executor 的 wave 完成协议包含回写;未回写则 SendMessage 让其补做。`tasks-complete` 在 closing 强制要求零未勾选项,留到收尾轮必然遗忘
107
118
  - dispatch code-reviewer 审查当前 wave
108
119
  - 等待 review receipt + verdict=pass
109
120
  3. 所有 wave 完成后,继续下一步路由
@@ -227,7 +238,7 @@ build-executor 修复 findings 后,workflow-start 必须:
227
238
  - **No routing to spec-writer without architecture-design gate pass** (v0.9 §26): `arch_design_decision` must be `required` or `skipped` (not `null`). hotfix/tweak 不豁免
228
239
  - **No arch state write without auto-review PASS** (v0.28.1 §36): when `decision: required`, auto-review MUST complete with PASS or PASS_WITH_WARNINGS before writing `arch_design_decision` to yaml. FAIL → loop fix (≤3 rounds) or escalate to human
229
240
  - **No routing past DP-A without user confirmation** (v0.29.0 §37): architecture-design 四步协议完成后,必须经 DP-A 用户确认门(AskUserQuestion)才能路由到 spec-writer。用户选择"需要调整"时,修改必须通过子代理执行,修改后重新 auto-review + 重新 DP-A 确认
230
- - **Artifact Ownership — 主代理不得直接修改子代理产物** (v0.29.0 §37): 子代理是其产物的唯一负责人(architecture-design → `architecture/` 目录,spec-writer → `proposal.md`/`specs/`/`design.md`/`tasks.md`,contract-builder → `execution-contract.md`,build-executor → 代码文件)。主代理(workflow-start)不得通过 Read + Edit/Write 直接修改子代理的产物文件。修改必须通过 `SendMessage` 恢复原子代理(优先)或启动新子代理执行。**v0.39.0 强化**:主代理 MUST NOT 直接 Edit/Write 任何文件 under `changes/<name>/` 或 `.worktrees/`——无论改动量大小,必须通过 SendMessage 委托子代理执行。例外:仅当子代理无法启动且用户明确授权时,主代理可直接修改,但必须在修改后重新触发对应的 review 验证
241
+ - **Artifact Ownership — 主代理不得直接修改子代理产物** (v0.29.0 §37): 子代理是其产物的唯一负责人(architecture-design → `architecture/` 目录,spec-writer → `proposal.md`/`specs/`/`design.md`/`tasks.md`,contract-builder → `execution-contract.md`,build-executor → 代码文件)。**tasks.md 的勾选状态属执行期写入区**(v0.49.0 §83.3.4):任务文本归 spec-writer,勾选标记由 build-executor 在 wave 完成时回写(主代理只检查不代写)。主代理(workflow-start)不得通过 Read + Edit/Write 直接修改子代理的产物文件。修改必须通过 `SendMessage` 恢复原子代理(优先)或启动新子代理执行。**v0.39.0 强化**:主代理 MUST NOT 直接 Edit/Write 任何文件 under `changes/<name>/` 或 `.worktrees/`——无论改动量大小,必须通过 SendMessage 委托子代理执行。例外:仅当子代理无法启动且用户明确授权时,主代理可直接修改,但必须在修改后重新触发对应的 review 验证
231
242
  - **修复后必须 re-review** (v0.39.0): build-executor 修复 findings 后,workflow-start 必须 dispatch code-reviewer re-review 确认修复有效,记录 review receipt(verdict=pass)。禁止在 review receipt 未记录或 verdict!=pass 时进入下一阶段
232
243
  - **No cross-change artifact modification (v0.30.0)**: 处理 change X 时 **MUST NOT** 编辑 `changes/Y/`(Y≠X)下任何产物。发现跨 change 依赖/问题时:① 在 change X 的 ADR/proposal 中显式声明依赖;② change Y 进入 workflow-start 时,扫描是否有其他 change 声明了对 Y 的依赖并提醒用户;③ change Y 的产物修改必须走 Y 自己的完整流程(architecture-design → auto-review → DP-A),不得绕过。跨 change 一致性核查可调度 `cross-change-consistency-checker` agent(来源:workflow-feedback 2026-08-01)
233
244
 
@@ -0,0 +1,51 @@
1
+ # Session Learnings — <change-name>
2
+
3
+ > change 级经验台账(**change 根目录 `learnings.md`**)。
4
+ >
5
+ > **位置权威(v0.49.0 §83.3.5)**:`changes/<name>/learnings.md`。代码(`solutions-promote.mjs`)与 `compound-captured` guard 均只认此路径;文档曾宣称的 `specs/<cap>/learnings.md` 从未落地。
6
+ >
7
+ > **消费链路**:本文件 → `tf solutions promote`(release-archivist closing 调用)→ 全局 `docs/solutions/` → `tf solutions inject`(各阶段入口注入)。
8
+ >
9
+ > **晋升前提**:只有带完整 frontmatter 的条目才会被晋升。缺字段时按默认 `severity=low` / `type=insight` 处理,不满足晋升条件即跳过——promote 会在输出中列出跳过原因,不再静默。
10
+ >
11
+ > **注意**:`tf solutions capture` 写入的是全局 `docs/solutions/`,**不会**创建本文件。
12
+
13
+ ## 1. <条目标题:一句话说清问题或模式>
14
+
15
+ ---
16
+ phase: cross-phase # 阶段名(prd/plan/architecture/prototype/spec/build/review/cross-phase)或具体领域阶段
17
+ domain: <领域> # 如 jest / sdd / spring-boot;注入时按领域过滤
18
+ type: pitfall # pitfall | pattern | insight
19
+ severity: medium # high | medium | low
20
+ date: YYYY-MM-DD
21
+ ---
22
+
23
+ - **教训来源**:<具体事件——哪个 wave / 哪条命令 / 什么现象>
24
+ - **正确做法**:<可执行的行动,而非泛泛原则>
25
+ - **边界**:<什么情况下不适用;避免把局部观察当通则>
26
+
27
+ ## 2. <下一条目>
28
+
29
+ ---
30
+ phase: cross-phase
31
+ domain: <领域>
32
+ type: pattern
33
+ severity: high
34
+ date: YYYY-MM-DD
35
+ ---
36
+
37
+ - **教训来源**:...
38
+ - **正确做法**:...
39
+ - **边界**:...
40
+
41
+ ---
42
+
43
+ ## 晋升条件(`tf solutions promote`)
44
+
45
+ | 条件 | 行为 |
46
+ |---|---|
47
+ | `severity` ≥ medium 且 `type` = pitfall / pattern | 晋升到全局 `docs/solutions/<phase>/`,并追加 INDEX 行 |
48
+ | 与全局 INDEX 已有条目 domain + type 匹配 | 不新建文件:升级已有条目 severity(low→medium→high)+ 条目 frontmatter `confirmed` 计数 +1 + 更新 INDEX 行 |
49
+ | 其他 | 保留在本文件;promote 输出中列为 skipped 并附原因 |
50
+
51
+ **条目质量要求**:写「正确做法」前先验证它在系统层面成立——局部观察得出的 workaround 若被沉淀为通则,会被后续 change 当经验复用,反而固化缺陷(v0.49.0 §83.2.2 的实证教训)。
@@ -1,17 +0,0 @@
1
- ---
2
- phase: cross-phase
3
- domain: general
4
- type: insight
5
- severity: medium
6
- date: 2026-07-28
7
- source:
8
- ---
9
-
10
- ## 问题描述
11
- (no summary)
12
-
13
- ## 根因/模式
14
- (待补充)
15
-
16
- ## 预防措施/应用方式
17
- (待补充)
@@ -1,17 +0,0 @@
1
- ---
2
- phase: cross-phase
3
- domain: general
4
- type: insight
5
- severity: medium
6
- date: 2026-07-29
7
- source:
8
- ---
9
-
10
- ## 问题描述
11
- (no summary)
12
-
13
- ## 根因/模式
14
- (待补充)
15
-
16
- ## 预防措施/应用方式
17
- (待补充)