@xulthekl/team-flow 0.44.0 → 0.46.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 (55) hide show
  1. package/.claude/always/phase-guard.md +1 -1
  2. package/.claude-plugin/marketplace.json +3 -3
  3. package/.claude-plugin/plugin.json +2 -2
  4. package/.codex-plugin/plugin.json +2 -2
  5. package/.cursor-plugin/marketplace.json +1 -1
  6. package/.cursor-plugin/plugin.json +2 -2
  7. package/.github/plugin/marketplace.json +2 -2
  8. package/AGENTS.md +8 -7
  9. package/CHANGELOG.md +32 -0
  10. package/GEMINI.md +1 -1
  11. package/INSTALL.md +1 -1
  12. package/README.md +3 -3
  13. package/agents/architecture-design.md +1 -1
  14. package/docs/README_en.md +1 -1
  15. package/docs/solutions/INDEX.md +2 -0
  16. package/docs/solutions/cross-phase/2026-08-18-no-summary.md +17 -0
  17. package/docs/solutions/cross-phase/2026-08-19-no-summary.md +17 -0
  18. package/gemini-extension.json +2 -2
  19. package/hooks/session-start +2 -2
  20. package/llms.txt +1 -1
  21. package/package.json +2 -2
  22. package/plugin.json +2 -2
  23. package/scripts/guard/checks/delegation-status.mjs +56 -0
  24. package/scripts/guard/checks/test-gate-exemptions.mjs +13 -0
  25. package/scripts/guard/checks/test-matrix-complete.mjs +6 -0
  26. package/scripts/guard/checks/test-matrix-ready.mjs +6 -0
  27. package/scripts/guard/checks/tests-passing.mjs +6 -1
  28. package/scripts/guard/guard.mjs +3 -1
  29. package/scripts/lib/cmd-state.mjs +3 -0
  30. package/scripts/lib/config-loader.mjs +11 -0
  31. package/scripts/lib/conventions-generator.mjs +91 -0
  32. package/scripts/lib/execution-plan.mjs +2 -1
  33. package/scripts/lib/execution-recommendation.mjs +20 -3
  34. package/scripts/lib/glaf4-delegation.mjs +488 -0
  35. package/scripts/lib/glaf4-evidence-export.mjs +267 -0
  36. package/scripts/lib/state-loader.mjs +13 -0
  37. package/scripts/lib/test-matrix-export.mjs +151 -8
  38. package/scripts/team-flow.mjs +6 -0
  39. package/skills/architecture-design/SKILL.md +1 -1
  40. package/skills/architecture-design/references/s3.5-architecture-template.md +1 -1
  41. package/skills/architecture-design/references/s3.5-product-architecture.md +2 -2
  42. package/skills/contract-builder/SKILL.md +2 -2
  43. package/skills/contract-builder/references/glaf4-delegation.md +130 -0
  44. package/skills/project-initialize/SKILL.md +61 -0
  45. package/skills/project-initialize/references/architecture-choice.md +55 -0
  46. package/skills/project-initialize/references/glaf4-initialize-delegation.md +55 -0
  47. package/skills/project-initialize/references/service-layout.md +63 -0
  48. package/skills/workflow-bootstrap/SKILL.md +1 -1
  49. package/skills/workflow-orchestrator/SKILL.md +12 -6
  50. package/skills/workflow-orchestrator/references/s3-plan-pipeline.md +2 -2
  51. package/skills/workflow-orchestrator/references/s4-split-validate.md +1 -1
  52. package/skills/workflow-orchestrator/references/state-model.md +2 -2
  53. package/skills/workflow-start/SKILL.md +7 -1
  54. package/skills/workflow-start/references/routing-rules.md +12 -12
  55. package/templates/execution-contract.md +13 -1
@@ -0,0 +1,267 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * glaf4-evidence-export — glaf4-dev run 证据 → surefire 兼容汇总(v2.1 §7 接口④)
4
+ *
5
+ * 零 glaf4-dev 改动:只读 glaf4-dev run 目录内部结构(有界演进,§8)。
6
+ * 用法:tf glaf4-evidence-export <run-dir> <output-file> [--mode <mode>] [--json]
7
+ *
8
+ * 流程(v2.1 §7):
9
+ * 1. 读取 run 终局 final-result.json,仅 status=PASS(防模型手写;FAIL/BLOCKED 走 6.7 恢复路径)
10
+ * 2. 按模式→最终测试证据阶段映射聚合(MODE_STAGES 实证)
11
+ * 3. 遍历 stages/{stage}/attempts/attempt-<n>/test-result.json,只聚合每 batch 最终 GREEN(passed=true)
12
+ * 4. 去重(executed_classes)后再求和
13
+ * 5. 输出 surefire 兼容行 `Tests run: N, Failures: F, Errors: E, Skipped: S`
14
+ * 6. 落盘 → 用户 `tf test record <change-dir> --from <output-file>` 解析落盘 test_result
15
+ *
16
+ * total=0 边界(v2.1 §7):tests-passing 硬拒 total≤0,聚合 total=0 时 WARN + 不落盘,
17
+ * 交由委托协议走 test_matrix_skipped 显式跳过(注意 skip 禁令的 TEST_BOOTSTRAP 例外)。
18
+ */
19
+
20
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
21
+ import { join } from 'node:path';
22
+
23
+ /**
24
+ * 模式 → 最终测试证据阶段映射(与 glaf4-dev scripts/runtime/stage_contract.py MODE_STAGES 对齐)
25
+ * 语义耦合声明(v2.1 §8.7):stage 名(green/write-unit/write-integration/baseline-green)
26
+ * 不随 run.json/final-result.json 的 schema_version 钉扎,glaf4-dev 调整 stage 名则此处静默断裂,
27
+ * 需经接口版本契约校验面显式暴露。
28
+ */
29
+ const MODE_TO_EVIDENCE_STAGES = {
30
+ FEATURE_TDD: ['green'],
31
+ BUGFIX_REGRESSION: ['green'],
32
+ COMPLETE_UNIT_TESTS: ['write-unit', 'green'],
33
+ COMPLETE_INTEGRATION_TESTS: ['write-integration', 'green'],
34
+ REFACTOR_PROTECTED: ['baseline-green', 'green'],
35
+ };
36
+
37
+ // 无测试委托阶段(TEST_BOOTSTRAP 零测试,skip 禁令唯一例外)
38
+ const ZERO_TEST_MODES = ['TEST_BOOTSTRAP', 'PROJECT_INITIALIZE'];
39
+
40
+ /**
41
+ * 解析 attempt-<n> 的数字序号
42
+ */
43
+ function attemptNumber(dirName) {
44
+ const m = dirName.match(/^attempt-(\d+)$/);
45
+ return m ? parseInt(m[1], 10) : 0;
46
+ }
47
+
48
+ /**
49
+ * 遍历 run-dir/stages/{stage}/attempts/attempt-<n>/test-result.json
50
+ * 只收集 passed=true 的结果,按 stage+batch 分组取 attempt 最大的(避免 RED 尝试污染)
51
+ */
52
+ function collectGreenTestResults(runDir, stages) {
53
+ const results = [];
54
+ for (const stage of stages) {
55
+ const stageDir = join(runDir, 'stages', stage);
56
+ if (!existsSync(stageDir)) continue;
57
+ const attemptsDir = join(stageDir, 'attempts');
58
+ if (!existsSync(attemptsDir)) continue;
59
+ const attempts = readdirSync(attemptsDir).filter(d => d.startsWith('attempt-'));
60
+ for (const attempt of attempts) {
61
+ const trPath = join(attemptsDir, attempt, 'test-result.json');
62
+ if (!existsSync(trPath)) continue;
63
+ try {
64
+ const tr = JSON.parse(readFileSync(trPath, 'utf-8'));
65
+ if (tr.passed === true) {
66
+ results.push({ ...tr, _attempt: attemptNumber(attempt) });
67
+ }
68
+ } catch {
69
+ // 损坏的 test-result.json 忽略(最终裁决由 final-result.json 承载)
70
+ }
71
+ }
72
+ }
73
+ // 按 stage_id+batch_id 分组,取每组 attempt 最大的 passed=true 结果
74
+ const byBatch = new Map();
75
+ for (const tr of results) {
76
+ const key = `${tr.stage_id || ''}:${tr.batch_id || ''}`;
77
+ const existing = byBatch.get(key);
78
+ if (!existing || tr._attempt > existing._attempt) byBatch.set(key, tr);
79
+ }
80
+ return [...byBatch.values()];
81
+ }
82
+
83
+ /**
84
+ * I2 修复(2026-08-18):聚合 run 内 test-result.json 的 environment_error=true
85
+ * (final-result.schema.json 无该字段,设计 §6.7 环境分流依赖它——从 attempt 级 test-result 聚合,forward-compatible)
86
+ * @param {string} runDir glaf4-dev run 目录
87
+ * @returns {boolean}
88
+ */
89
+ export function collectEnvironmentErrors(runDir) {
90
+ const stagesDir = join(runDir, 'stages');
91
+ if (!existsSync(stagesDir)) return false;
92
+ const stages = readdirSync(stagesDir);
93
+ for (const stage of stages) {
94
+ const attemptsDir = join(stagesDir, stage, 'attempts');
95
+ if (!existsSync(attemptsDir)) continue;
96
+ const attempts = readdirSync(attemptsDir);
97
+ for (const attempt of attempts) {
98
+ const trPath = join(attemptsDir, attempt, 'test-result.json');
99
+ if (!existsSync(trPath)) continue;
100
+ try {
101
+ const tr = JSON.parse(readFileSync(trPath, 'utf-8'));
102
+ if (tr.environment_error === true) return true;
103
+ } catch {
104
+ // 损坏忽略
105
+ }
106
+ }
107
+ }
108
+ return false;
109
+ }
110
+
111
+ /**
112
+ * 按 executed_classes 去重(FEATURE_TDD 多批次交付中同一测试类可能重复执行,求和虚增)
113
+ */
114
+ function dedupeByClasses(results) {
115
+ const seen = new Set();
116
+ const out = [];
117
+ for (const tr of results) {
118
+ const classes = (tr.executed_classes || []).slice().sort().join('\u0000');
119
+ if (seen.has(classes)) continue;
120
+ seen.add(classes);
121
+ out.push(tr);
122
+ }
123
+ return out;
124
+ }
125
+
126
+ /**
127
+ * 聚合 glaf4-dev run 证据
128
+ * @param {string} runDir glaf4-dev run 目录(<root>/.glaf4-dev/runs/<run-id>)
129
+ * @param {string} [mode] 显式模式覆盖(默认从 final-result.json 读取)
130
+ * @returns {object} 聚合结果
131
+ */
132
+ export function aggregateRunEvidence(runDir, mode) {
133
+ const finalPath = join(runDir, 'final-result.json');
134
+ if (!existsSync(finalPath)) {
135
+ return { ok: false, status: 'NO_FINAL_RESULT', error: `final-result.json 不存在: ${finalPath}` };
136
+ }
137
+
138
+ let finalResult;
139
+ try {
140
+ finalResult = JSON.parse(readFileSync(finalPath, 'utf-8'));
141
+ } catch (err) {
142
+ return { ok: false, status: 'CORRUPT_FINAL_RESULT', error: `final-result.json 解析失败: ${err.message}` };
143
+ }
144
+
145
+ if (finalResult.status !== 'PASS') {
146
+ // I2 修复(2026-08-18):环境分流——final-result 无 environment_error 字段(schema 实证),
147
+ // 从 test-result 聚合 + final-result forward-compatible 读取。环境错误直接转降级,不浪费重试轮次(v2.1 §6.7)。
148
+ const environmentError = finalResult.environment_error === true
149
+ || collectEnvironmentErrors(runDir);
150
+ return {
151
+ ok: false,
152
+ status: `NOT_PASS:${finalResult.status || 'UNKNOWN'}`,
153
+ mode: finalResult.mode,
154
+ environment_error: environmentError,
155
+ error: environmentError
156
+ ? `run 终局 ${finalResult.status}(environment_error=true,环境问题如 JAVA/依赖缺失)→ 直接转降级或转人工(v2.1 §6.7 环境分流),不浪费重试轮次`
157
+ : `run 终局 ${finalResult.status},证据回灌仅接受 PASS(FAIL/BLOCKED 走恢复路径)`,
158
+ };
159
+ }
160
+
161
+ const resolvedMode = mode || finalResult.mode;
162
+ if (ZERO_TEST_MODES.includes(resolvedMode)) {
163
+ return {
164
+ ok: false,
165
+ status: `ZERO_TEST_MODE:${resolvedMode}`,
166
+ mode: resolvedMode,
167
+ error: `${resolvedMode} 无测试设计合同,走 test_matrix_skipped 显式跳过(skip 禁令唯一例外)`,
168
+ };
169
+ }
170
+
171
+ const stages = MODE_TO_EVIDENCE_STAGES[resolvedMode];
172
+ if (!stages) {
173
+ return {
174
+ ok: false,
175
+ status: `UNSUPPORTED_MODE:${resolvedMode}`,
176
+ mode: resolvedMode,
177
+ error: `模式 ${resolvedMode} 无证据阶段映射(支持: ${Object.keys(MODE_TO_EVIDENCE_STAGES).join(', ')})`,
178
+ };
179
+ }
180
+
181
+ const greenResults = dedupeByClasses(collectGreenTestResults(runDir, stages));
182
+
183
+ let testsRun = 0;
184
+ let testsFailed = 0;
185
+ let testsErrors = 0;
186
+ let testsSkipped = 0;
187
+ for (const tr of greenResults) {
188
+ testsRun += tr.tests_run || 0;
189
+ testsFailed += tr.tests_failed || 0;
190
+ testsErrors += tr.tests_errors || 0;
191
+ testsSkipped += tr.tests_skipped || 0;
192
+ }
193
+
194
+ if (testsRun === 0) {
195
+ return {
196
+ ok: false,
197
+ status: 'ZERO_TESTS',
198
+ mode: resolvedMode,
199
+ error: '聚合 total=0(无 GREEN test-result),tests-passing 硬拒 total≤0;确无测试时走 test_matrix_skipped 显式跳过',
200
+ aggregated: { tests_run: 0, tests_failed: 0, tests_errors: 0, tests_skipped: 0 },
201
+ green_results_count: greenResults.length,
202
+ };
203
+ }
204
+
205
+ return {
206
+ ok: true,
207
+ status: 'PASS',
208
+ mode: resolvedMode,
209
+ run_id: finalResult.run_id,
210
+ surefire_line: `Tests run: ${testsRun}, Failures: ${testsFailed}, Errors: ${testsErrors}, Skipped: ${testsSkipped}`,
211
+ aggregated: {
212
+ tests_run: testsRun,
213
+ tests_failed: testsFailed,
214
+ tests_errors: testsErrors,
215
+ tests_skipped: testsSkipped,
216
+ },
217
+ green_results_count: greenResults.length,
218
+ };
219
+ }
220
+
221
+ /**
222
+ * CLI entry point
223
+ */
224
+ export async function run(args) {
225
+ const positionals = args.filter(a => !a.startsWith('--'));
226
+ const flags = new Set(args.filter(a => a.startsWith('--') && !a.startsWith('--mode')));
227
+ const modeFlagIndex = args.indexOf('--mode');
228
+ const mode = modeFlagIndex >= 0 ? args[modeFlagIndex + 1] : undefined;
229
+
230
+ const runDir = positionals[0];
231
+ const outputFile = positionals[1];
232
+
233
+ if (!runDir || !outputFile) {
234
+ console.error('Usage: tf glaf4-evidence-export <run-dir> <output-file> [--mode <mode>] [--json]');
235
+ process.exit(2);
236
+ }
237
+
238
+ const json = flags.has('--json');
239
+ const result = aggregateRunEvidence(runDir, mode);
240
+
241
+ if (!result.ok) {
242
+ console.error(`❌ glaf4-evidence-export: ${result.error}`);
243
+ if (json) console.log(JSON.stringify(result));
244
+ process.exit(1);
245
+ }
246
+
247
+ // 落盘 surefire 兼容文件,供 `tf test record --from` 解析
248
+ const outDir = outputFile.substring(0, outputFile.lastIndexOf('/'));
249
+ if (outDir && outDir !== outputFile) mkdirSync(outDir, { recursive: true });
250
+ writeFileSync(outputFile, `${result.surefire_line}\n`, 'utf-8');
251
+
252
+ if (json) {
253
+ console.log(JSON.stringify(result));
254
+ } else {
255
+ console.log(`✅ glaf4-evidence-export: ${result.surefire_line}`);
256
+ console.log(` 证据来源: ${result.run_id} (mode: ${result.mode}, ${result.green_results_count} 个 GREEN test-result)`);
257
+ console.log(` 落盘: ${outputFile} → 执行 tf test record <change-dir> --from ${outputFile}`);
258
+ }
259
+ }
260
+
261
+ // 直接运行(与 conventions-generator.mjs 同构)
262
+ if (import.meta.url === `file://${process.argv[1]}`) {
263
+ run(process.argv.slice(2)).catch(err => {
264
+ console.error(err);
265
+ process.exit(1);
266
+ });
267
+ }
@@ -40,6 +40,13 @@ const BUILTIN_DEFAULTS = {
40
40
  dp_3_timestamp: null,
41
41
  dp_4_result: null,
42
42
  dp_4_timestamp: null,
43
+ // GLAF4 委托决策(v2.1 §6.6:dp_4_glaf4_mode 枚举标量入 state;write_set_boundary 落契约/JSON,state 只存引用 hash)
44
+ dp_4_glaf4_mode: null,
45
+ dp_4_write_set_hash: null,
46
+ // GLAF4 委托状态位(v2.1 §6.7:failed 时 guard 阻断 closing 直到人工重置)
47
+ delegation_status: null,
48
+ delegation_retry_count: null,
49
+ delegation_partial_writes: null,
43
50
  dp_5_result: null,
44
51
  dp_5_timestamp: null,
45
52
  dp_6_result: null,
@@ -150,6 +157,12 @@ export function writeState(changeDir, state) {
150
157
  lines.push(`dp_3_timestamp: ${state.dp_3_timestamp ?? 'null'}`);
151
158
  lines.push(`dp_4_result: ${state.dp_4_result ?? 'null'}`);
152
159
  lines.push(`dp_4_timestamp: ${state.dp_4_timestamp ?? 'null'}`);
160
+ // v2.1 §6.6:GLAF4 委托决策字段(枚举标量入 state;write_set_boundary 不落 YAML,只存引用 hash)
161
+ lines.push(`dp_4_glaf4_mode: ${state.dp_4_glaf4_mode ?? 'null'}`);
162
+ lines.push(`dp_4_write_set_hash: ${state.dp_4_write_set_hash ?? 'null'}`);
163
+ lines.push(`delegation_status: ${state.delegation_status ?? 'null'}`);
164
+ lines.push(`delegation_retry_count: ${state.delegation_retry_count ?? 'null'}`);
165
+ lines.push(`delegation_partial_writes: ${state.delegation_partial_writes ?? 'null'}`);
153
166
  lines.push(`dp_5_result: ${state.dp_5_result ?? 'null'}`);
154
167
  lines.push(`dp_5_timestamp: ${state.dp_5_timestamp ?? 'null'}`);
155
168
  lines.push(`dp_6_result: ${state.dp_6_result ?? 'null'}`);
@@ -2,14 +2,20 @@
2
2
  /**
3
3
  * test-matrix-export — glaf4 test-matrix.json → team-flow test-matrix.md 格式转换
4
4
  *
5
- * v0.12 §46.1 定义,v0.31.0 实现为 CLI 子命令
5
+ * v0.12 §46.1 定义,v0.31.0 实现为 CLI 子命令;v2(glaf4-dev 集成 v2.1 §5)支持 test-matrix-vnext(schema_version "2.0")
6
6
  * 用法:tf test-matrix-export <input.json> <output.md> [--change-id <id>] [--batch-id <id>]
7
7
  *
8
8
  * 功能:
9
- * - 读取 glaf4 的 test-matrix.json
9
+ * - 读取 glaf4 的 test-matrix.json(v1 运行级合同 或 vNext 设计合同)
10
10
  * - 保留全部 14 字段 + 包装 team_flow_context
11
11
  * - 从 test_kind 派生 test_tier(unit/integration)
12
12
  * - 输出 team-flow 格式的 test-matrix.md
13
+ *
14
+ * vNext 兼容(v2.1 §5):
15
+ * - 顶层无 target → 模块名从 case.target 推导(取最后大写开头段 = 类名)
16
+ * - kind → work_mode 映射:TDD/REGRESSION/CHARACTERIZATION 直传,REFACTOR → TDD(原始语义记 footer)
17
+ * - write_targets 数组 → 每个 target 拆一行,test_file 取文件名、test_method_name 取 `#方法` 锚点
18
+ * - case_id 与 test-merge rewriteIndex 正则 `\S+-\S+-\S+-\d+` 兼容性 WARN(直传不阻断)
13
19
  */
14
20
 
15
21
  import { readFileSync, writeFileSync } from 'node:fs';
@@ -59,6 +65,52 @@ const TEST_KIND_TO_TIER = {
59
65
  playwright_integration: 'e2e',
60
66
  };
61
67
 
68
+ /**
69
+ * kind → work_mode 映射(v2.1 §5)
70
+ * TDD/REGRESSION/CHARACTERIZATION 直传;REFACTOR → TDD(有损映射,原始语义记 footer)
71
+ */
72
+ const KIND_TO_WORK_MODE = {
73
+ TDD: 'TDD',
74
+ REGRESSION: 'REGRESSION',
75
+ CHARACTERIZATION: 'CHARACTERIZATION',
76
+ REFACTOR: 'TDD',
77
+ };
78
+
79
+ /**
80
+ * test-merge rewriteIndex 的 case_id 兼容正则(test-merge.mjs rewriteIndex)
81
+ * case_id 形如 `xxx-yyy-zzz-123`,不匹配则 rewriteIndex 统计会漏(WARN 不阻断)
82
+ */
83
+ const CASE_ID_REWRITE_INDEX_PATTERN = /^\S+-\S+-\S+-\d+$/;
84
+
85
+ /**
86
+ * 解析 write_targets 锚点 `文件#方法`
87
+ * @param {string} target 如 "src/test/java/com/example/OrderServiceTest.java#testCreateOrder"
88
+ * @returns {{ test_file: string, test_method_name: string }}
89
+ */
90
+ function parseWriteTarget(target) {
91
+ const [filePart, methodPart] = target.split('#');
92
+ return {
93
+ test_file: filePart ? basename(filePart) : '',
94
+ test_method_name: methodPart || '',
95
+ };
96
+ }
97
+
98
+ /**
99
+ * 从 case.target 推导模块名(vNext 顶层无 target)
100
+ * target 形如 "OrderService" | "OrderService.createOrder" | "com.example.service.OrderService.createOrder"
101
+ * → 取最后一个大写开头的段(类名)
102
+ * @param {string} target
103
+ * @returns {string}
104
+ */
105
+ function extractModuleFromTarget(target) {
106
+ if (!target) return 'Unknown';
107
+ const segments = target.split('.');
108
+ for (let i = segments.length - 1; i >= 0; i--) {
109
+ if (/^[A-Z]/.test(segments[i])) return segments[i];
110
+ }
111
+ return segments[segments.length - 1];
112
+ }
113
+
62
114
  /**
63
115
  * 从 glaf4 case 派生 test_tier
64
116
  */
@@ -96,7 +148,8 @@ function caseToRow(c) {
96
148
  }
97
149
 
98
150
  /**
99
- * 提取模块名(从 target case_id 前缀)
151
+ * 提取模块名(从 target / case.target / case_id 前缀)
152
+ * vNext 顶层无 target,从 case.target 推导(v2.1 §5)
100
153
  */
101
154
  function extractModuleName(matrix) {
102
155
  if (matrix.target) {
@@ -104,6 +157,10 @@ function extractModuleName(matrix) {
104
157
  const parts = matrix.target.split('.');
105
158
  return parts[parts.length - 1];
106
159
  }
160
+ // vNext:从 case.target 推导(取最后大写开头段 = 类名)
161
+ if (matrix.cases?.[0]?.target) {
162
+ return extractModuleFromTarget(matrix.cases[0].target);
163
+ }
107
164
  // fallback: from first case_id
108
165
  if (matrix.cases?.[0]?.case_id) {
109
166
  const id = matrix.cases[0].case_id;
@@ -113,10 +170,71 @@ function extractModuleName(matrix) {
113
170
  return 'Unknown';
114
171
  }
115
172
 
173
+ /**
174
+ * 转换单个 vNext case 为 markdown 行(write_targets 数组 → 多行拆分)
175
+ */
176
+ function vnextCaseToRows(c) {
177
+ const tier = deriveTestTier(c.test_kind);
178
+ const workMode = KIND_TO_WORK_MODE[c.kind] || c.kind || 'TDD';
179
+ const mock = Array.isArray(c.collaborators)
180
+ ? c.collaborators.join(', ')
181
+ : (c.mock || '');
182
+ const targets = Array.isArray(c.write_targets) && c.write_targets.length > 0
183
+ ? c.write_targets
184
+ : [''];
185
+ return targets.map((t) => {
186
+ const { test_file, test_method_name } = parseWriteTarget(t);
187
+ return [
188
+ c.case_id || '',
189
+ escapeCell(c.behavior || c.display_name || ''),
190
+ c.design_method || '',
191
+ escapeCell(typeof c.input === 'object' ? JSON.stringify(c.input) : (c.input || '')),
192
+ escapeCell(typeof c.expected === 'object' ? JSON.stringify(c.expected) : (c.expected || '')),
193
+ c.test_kind || '',
194
+ tier,
195
+ escapeCell(mock),
196
+ workMode,
197
+ test_file,
198
+ test_method_name,
199
+ '', // run_command:vNext 无此字段
200
+ ].join(' | ');
201
+ });
202
+ }
203
+
204
+ /**
205
+ * vNext 检测:schema_version === "2.0"(test-matrix-vnext 设计合同)
206
+ */
207
+ function isVnextMatrix(matrix) {
208
+ return matrix.schema_version === '2.0';
209
+ }
210
+
211
+ /**
212
+ * 接口版本契约门(v2.1 §8.3,C2 修复 2026-08-18):
213
+ * schema_version 必须在 ['1.0','2.0'] 内;未定义视为 v1 兼容(glaf4 早期矩阵)。
214
+ * 未来版本(如 2.1/3.0)显式抛错 BLOCK,不静默按 v1 解析——防 glaf4-dev 升级后静默错接。
215
+ * @param {object} matrix
216
+ * @throws {Error} 不兼容版本
217
+ */
218
+ export function validateSchemaVersion(matrix) {
219
+ const raw = matrix?.schema_version;
220
+ if (raw === undefined || raw === null) return '1.0'; // 早期矩阵无版本字段,按 v1 兼容
221
+ // Minor#3(复评 2026-08-18):版本归一化——设计约定字符串 "2.0",数字 2 或字符串 '2' 均归一为 '2.0'
222
+ const version = typeof raw === 'number' ? `${raw}.0` : String(raw);
223
+ if (version === '1' || version === '2') return `${version}.0`;
224
+ if (version === '1.0' || version === '2.0') return version;
225
+ throw new Error(
226
+ `不兼容的 test-matrix schema_version "${version}"(支持: 1.0 / 2.0)。`
227
+ + 'glaf4-dev 升级了矩阵 schema?请升级 test-matrix-export 后重试(v2.1 §8.3 接口版本契约,不静默错接)。'
228
+ );
229
+ }
230
+
116
231
  /**
117
232
  * 转换 glaf4 matrix → team-flow markdown
233
+ * 支持 v1(schema_version "1.0")与 vNext(schema_version "2.0"),结构锚点对齐(v2.1 §5)
118
234
  */
119
235
  export function convert(matrix, options = {}) {
236
+ const schemaVersion = validateSchemaVersion(matrix); // C2 版本门:不兼容版本 BLOCK
237
+ const vnext = schemaVersion === '2.0'; // 用归一化版本判断(数字 2 → '2.0' 正确走 vNext 分支)
120
238
  const moduleName = extractModuleName(matrix);
121
239
  const cases = matrix.cases || [];
122
240
 
@@ -125,7 +243,7 @@ export function convert(matrix, options = {}) {
125
243
  const integrationCases = cases.filter(c => deriveTestTier(c.test_kind) === 'integration');
126
244
  const e2eCases = cases.filter(c => deriveTestTier(c.test_kind) === 'e2e');
127
245
 
128
- // 提取 complexity
246
+ // 提取 complexity(vNext 顶层无 target_complexity,默认 medium)
129
247
  const complexity = matrix.target_complexity || {};
130
248
  let complexityTier = 'medium';
131
249
  if (complexity.is_trivial) complexityTier = 'trivial';
@@ -133,9 +251,18 @@ export function convert(matrix, options = {}) {
133
251
  complexityTier = 'complex';
134
252
  }
135
253
 
136
- // 提取 candidate ledger
254
+ // 提取 candidate ledger(vNext 无 source_candidate_ledger)
137
255
  const candidateLedger = matrix.source_candidate_ledger || [];
138
256
 
257
+ // vNext:case_id 与 test-merge rewriteIndex 正则兼容性 WARN(v2.1 §5)
258
+ if (vnext) {
259
+ for (const c of cases) {
260
+ if (c.case_id && !CASE_ID_REWRITE_INDEX_PATTERN.test(c.case_id)) {
261
+ console.warn(`⚠️ test-matrix-export: case_id "${c.case_id}" 不匹配 test-merge rewriteIndex 正则 (\\S+-\\S+-\\S+-\\d+),复利 INDEX 统计可能漏计`);
262
+ }
263
+ }
264
+ }
265
+
139
266
  // 构建 markdown
140
267
  const lines = [];
141
268
  lines.push(`# Test Matrix — ${options.changeId || 'unknown'}`);
@@ -148,6 +275,9 @@ export function convert(matrix, options = {}) {
148
275
  lines.push(`- E2E cases: ${e2eCases.length} (${Math.round(e2eCases.length / cases.length * 100) || 0}%)`);
149
276
  lines.push(`- Deferred items: ${candidateLedger.filter(c => c.decision === 'deferred').length}`);
150
277
  lines.push(`- Matrix revision: 1`);
278
+ if (vnext) {
279
+ lines.push(`- Schema version: 2.0 (test-matrix-vnext)`);
280
+ }
151
281
  lines.push('');
152
282
 
153
283
  // Candidate Coverage Ledger
@@ -162,7 +292,7 @@ export function convert(matrix, options = {}) {
162
292
  lines.push('');
163
293
  }
164
294
 
165
- // Cases
295
+ // Cases(结构锚点 `### {module}` 必须保留,test-merge 依赖分段)
166
296
  lines.push('## Cases');
167
297
  lines.push('');
168
298
  lines.push(`### ${moduleName}`);
@@ -170,7 +300,10 @@ export function convert(matrix, options = {}) {
170
300
  lines.push('| case_id | behavior | design_method | input | expected | test_kind | test_tier | mock | work_mode | test_file | test_method_name | run_command |');
171
301
  lines.push('|---|---|---|---|---|---|---|---|---|---|---|---|');
172
302
  for (const c of cases) {
173
- lines.push(`| ${caseToRow(c)} |`);
303
+ const rows = vnext ? vnextCaseToRows(c) : [caseToRow(c)];
304
+ for (const row of rows) {
305
+ lines.push(`| ${row} |`);
306
+ }
174
307
  }
175
308
  lines.push('');
176
309
 
@@ -195,7 +328,17 @@ export function convert(matrix, options = {}) {
195
328
  if (options.batchId) lines.push(` batch_id: ${options.batchId}`);
196
329
  lines.push(` closing_stage: test-merge`);
197
330
  if (options.ledgerRef) lines.push(` ledger_ref: ${options.ledgerRef}`);
198
- lines.push(` source: glaf4-tests-export`);
331
+ lines.push(` source: ${vnext ? 'glaf4-dev-vnext' : 'glaf4-tests-export'}`);
332
+ if (vnext) {
333
+ lines.push(` schema_version: ${matrix.schema_version}`);
334
+ if (matrix.mode) lines.push(` mode: ${matrix.mode}`);
335
+ if (matrix.run_id) lines.push(` run_id: ${matrix.run_id}`);
336
+ // REFACTOR 原始语义记 footer(work_mode 已映射为 TDD,v2.1 §5)
337
+ const refactorCases = cases.filter(c => c.kind === 'REFACTOR').map(c => c.case_id);
338
+ if (refactorCases.length > 0) {
339
+ lines.push(` refactor_kind_mapped: ${refactorCases.join(', ')}`);
340
+ }
341
+ }
199
342
  lines.push('-->');
200
343
 
201
344
  return lines.join('\n') + '\n';
@@ -40,6 +40,8 @@ const COMMANDS = {
40
40
  arch: () => import('./lib/cmd-arch.mjs'),
41
41
  'test-merge': () => import('./lib/test-merge.mjs'),
42
42
  'test-matrix-export': () => import('./lib/test-matrix-export.mjs'),
43
+ 'glaf4-evidence-export': () => import('./lib/glaf4-evidence-export.mjs'),
44
+ 'glaf4-delegation': () => import('./lib/glaf4-delegation.mjs'),
43
45
  test: () => import('./lib/test-record.mjs'),
44
46
  };
45
47
 
@@ -70,6 +72,10 @@ Commands:
70
72
  Merge test matrix results into global docs/test-ledger/
71
73
  test-matrix-export <input.json> <output.md> [--change-id <id>]
72
74
  Convert glaf4 test-matrix.json to team-flow test-matrix.md
75
+ glaf4-evidence-export <run-dir> <output-file> [--mode <mode>]
76
+ Aggregate glaf4-dev run PASS evidence → surefire line (v2.1 §7; feed test record --from)
77
+ glaf4-delegation <sub> <dir> [--mode <m> --write-set <json> | --run-dir <run> | --delegation-mode <mode>]
78
+ GLAF4 委托协议 (v2.1 §4/6.3/6.6/6.7): detect / confirm / verify-write-set / verify-tasks / record-partial / reset
73
79
  test record <dir> --from <runner-output> [--runner auto|maven-surefire|jest|pytest]
74
80
  Record programmatic test evidence (v0.13 §50; feeds tests-passing gate)
75
81
  config [options] Display or modify configuration
@@ -176,7 +176,7 @@ change 进入 closing 阶段时,由 release-archivist 调用 `tf arch-merge` C
176
176
  | 变更级(默认) | workflow-start(exploring→specifying) | 五项检查 | `changes/<name>/architecture/` 三件套 + sql/ | architecture-design 子代理 |
177
177
  | 产品级(product 模式) | workflow-orchestrator S3.5(architecture 阶段) | 8 步全系统结构级设计(无五项检查) | `docs/architecture/iterations/vN/architecture.md`(6 产物快照) | architecture-design product 模式子代理(唯一 owner) |
178
178
 
179
- **产品级模式执行**:按 `references/s3.5-product-architecture.md` SOP(8 步),模板用 `references/s3.5-architecture-template.md`,加载协议用 `references/s3.5-loading-protocol.md`。产出经 architecture-reviewer product 视角评审(`review_mode: product`)PASS 才进 S4。时序遵守 P1:只写快照不写全局。
179
+ **产品级模式执行**:按 `references/s3.5-product-architecture.md` SOP(8 步),模板用 `references/s3.5-architecture-template.md`,加载协议用 `references/s3.5-loading-protocol.md`。产出经 architecture-reviewer product 视角评审(`review_mode: product`)PASS 才进 S3(2026-08-19:ARCH 上移 S3 前,原"进 S4")。时序遵守 P1:只写快照不写全局。
180
180
 
181
181
  ### 独立调用
182
182
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 产品级架构设计(architecture 阶段)产出的权威文档模板。基于 4A + DDD 方法论,覆盖 6 类产物。
4
4
  > 位置:`docs/architecture/iterations/vN/architecture.md`(预测态快照,P1:不写全局当前态)。
5
- > 产出后须经产品级评审门(architecture-reviewer product 视角)PASS 才进 S4
5
+ > 产出后须经产品级评审门(architecture-reviewer product 视角)PASS 才进 S3(2026-08-19:ARCH 上移 S3 前,原"进 S4")。
6
6
 
7
7
  ## 文档骨架
8
8
 
@@ -1,6 +1,6 @@
1
1
  # S3.5 产品级架构设计 SOP(v0.35.0,v0.14 §59/§60)
2
2
 
3
- > architecture 阶段(S3 之后、S4 之前)的执行 SOP。产出 `docs/architecture/iterations/vN/architecture.md`(预测态快照)。
3
+ > architecture 阶段(2026-08-19 调整:S2 之后、S3 之前——ARCH 上移,S3 计划/S4 拆分是最终任务拆分须基于架构;原" S3 之后、S4 之前"历史位置)的执行 SOP。产出 `docs/architecture/iterations/vN/architecture.md`(预测态快照)。
4
4
  > **owner**:architecture-design skill 的 **product 模式**子代理(唯一 owner,声明见 architecture-design/SKILL.md「独立调用」扩展)。
5
5
  > **时序(P1)**:只写快照不写全局;change 关闭时 arch-merge 回写实际增量;迭代收尾快照标 `archived` 退役。
6
6
 
@@ -48,7 +48,7 @@
48
48
  | A5 | 与全局基线一致性(旧项目逆向重建产物 vs 现状代码) |
49
49
  | A6 | conventions 合规 |
50
50
 
51
- 规则:≤3 轮修复循环 + 收敛检测(连续两轮不一致项不缩小 → 转人工);PASS 才进 S4
51
+ 规则:≤3 轮修复循环 + 收敛检测(连续两轮不一致项不缩小 → 转人工);PASS 才进 S3(2026-08-19:ARCH 上移 S3 前,原"进 S4")。
52
52
  **skip 时**:architecture-reviewer 不执行,但 skip 必须物化(iterations/vN/SKIPPED 标记 + 理由)。
53
53
 
54
54
  ### 复利捕获(Step 9,v0.36.3 复利链路修复)
@@ -118,9 +118,9 @@ hotfix/tweak workflow 不生成 test-matrix.md(guard 豁免);full workflow
118
118
  tf state set <change-dir> test_matrix_skipped true && tf state set <change-dir> test_matrix_skip_reason "<一句话说明>"
119
119
  ```
120
120
 
121
- ### GLAF4 Java 路由
121
+ ### GLAF4 Java 路由(v2.1 §6 实施委托交接)
122
122
 
123
- 当项目技术栈为 GLAF4 Java(Spring Boot / JUnit 5 / Mockito)时,提示用户是否调用 `glaf4-test:glaf4-tests` 做精细矩阵设计。用户同意后:`glaf4-tests-design` 生成 `test-matrix.json` `glaf4-tests-validate` 校验 `tf test-matrix-export <test-matrix.json> <change-dir>/test-matrix.md` 转换为 team-flow 格式。完整 7 步协议见 workflow-start `references/routing-rules.md`「Route to glaf4-tests」。
123
+ 当项目技术栈为 GLAF4 Java(pom.xml/build.gradle + Spring Boot/JUnit5/Mockito)时,运行时探测 glaf4-dev 插件(`detectGlaf4Delegation`)。若 `is_glaf4_java && glaf4_dev_available && delegation_mode !== 'off'`,产出 `## GLAF4 Delegation` 段(mode + write_set_boundary + 权限位),作为**实施委托交接**——GLAF4 change 的生产+测试整体委托 glaf4-dev 七模式流,build-executor 退为兜底。完整协议见 `references/glaf4-delegation.md`(§6.1-§6.7)。矩阵设计委托(glaf4-test)已统一到 glaf4-dev(v2.1 §1.3)。**GLAF4 委托 change 例外**:上方 Hotfix/Tweak 的显式 skip 豁免不适用——委托路径下 test-matrix 是审计基线,`test_matrix_skipped` 被委托协议禁止(唯一例外 TEST_BOOTSTRAP 零测试,v2.1 §6.3 skip 禁令)。
124
124
 
125
125
  ## Approval Model (DP-3)
126
126