@xulthekl/team-flow 0.44.0 → 0.45.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.
- package/.claude/always/phase-guard.md +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/marketplace.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.github/plugin/marketplace.json +2 -2
- package/CHANGELOG.md +16 -0
- package/GEMINI.md +1 -1
- package/INSTALL.md +1 -1
- package/README.md +1 -1
- package/docs/README_en.md +1 -1
- package/docs/solutions/INDEX.md +1 -0
- package/docs/solutions/cross-phase/2026-08-18-no-summary.md +17 -0
- package/gemini-extension.json +1 -1
- package/hooks/session-start +2 -2
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/scripts/guard/checks/delegation-status.mjs +56 -0
- package/scripts/guard/checks/test-gate-exemptions.mjs +13 -0
- package/scripts/guard/checks/test-matrix-complete.mjs +6 -0
- package/scripts/guard/checks/test-matrix-ready.mjs +6 -0
- package/scripts/guard/checks/tests-passing.mjs +6 -1
- package/scripts/guard/guard.mjs +3 -1
- package/scripts/lib/cmd-state.mjs +3 -0
- package/scripts/lib/config-loader.mjs +11 -0
- package/scripts/lib/conventions-generator.mjs +91 -0
- package/scripts/lib/execution-plan.mjs +2 -1
- package/scripts/lib/execution-recommendation.mjs +20 -3
- package/scripts/lib/glaf4-delegation.mjs +488 -0
- package/scripts/lib/glaf4-evidence-export.mjs +267 -0
- package/scripts/lib/state-loader.mjs +13 -0
- package/scripts/lib/test-matrix-export.mjs +151 -8
- package/scripts/team-flow.mjs +6 -0
- package/skills/contract-builder/SKILL.md +2 -2
- package/skills/contract-builder/references/glaf4-delegation.md +130 -0
- package/skills/workflow-start/SKILL.md +1 -1
- package/skills/workflow-start/references/routing-rules.md +12 -12
- 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
|
|
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
|
-
|
|
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';
|
package/scripts/team-flow.mjs
CHANGED
|
@@ -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
|
|
@@ -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(
|
|
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
|
|