@xulthekl/team-flow 0.43.1 → 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 +3 -3
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +2 -2
- package/.cursor-plugin/marketplace.json +2 -2
- package/.cursor-plugin/plugin.json +2 -2
- package/.github/plugin/marketplace.json +3 -3
- package/AGENTS.md +13 -6
- package/CHANGELOG.md +31 -0
- package/GEMINI.md +1 -1
- package/INSTALL.md +26 -24
- package/README.md +6 -5
- package/agents/business-analysis.md +53 -0
- package/docs/README_en.md +1 -1
- package/docs/examples/add-dark-mode/specs/ui-theme/spec.md +15 -17
- package/docs/examples/refactor-auth-boundary/specs/auth-boundary/spec.md +15 -27
- package/docs/solutions/INDEX.md +2 -0
- package/docs/solutions/cross-phase/2026-08-17-no-summary.md +17 -0
- package/docs/solutions/cross-phase/2026-08-18-no-summary.md +17 -0
- package/docs/usage-guide.md +619 -0
- package/gemini-extension.json +2 -2
- package/hooks/session-start +2 -2
- package/llms.txt +1 -1
- package/package.json +2 -2
- package/plugin.json +2 -2
- 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/business-analysis/SKILL.md +80 -0
- package/skills/business-analysis/references/interaction-rules.md +86 -0
- package/skills/business-analysis/references/output-schema.md +88 -0
- package/skills/business-analysis/references/qa-checklist.md +31 -0
- package/skills/business-analysis/references/version-resolution.md +41 -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
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: business-analysis
|
|
3
|
+
description: "独立业务分析 skill:将任意用户输入整理为结构化 requirement/vN/business-analysis.md,产出 Requirements List 与 Scenario List,支持多轮单问澄清、待办记录、新增/更新模式,写入前必须阻塞用户确认。不写入 ledger.md,不替代 workflow-orchestrator/ce-brainstorm。"
|
|
4
|
+
argument-hint: "[需求/会议/文档描述] [vN 可选]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Business Analysis
|
|
8
|
+
|
|
9
|
+
把任意输入(一句话、文档、会议纪要)整理成 `requirement/vN/business-analysis.md`,只含需求列表与业务场景列表两大板块。
|
|
10
|
+
|
|
11
|
+
## Primary Goal
|
|
12
|
+
|
|
13
|
+
- 提取并结构化需求(REQ-xxx)与业务场景(SC-xxx)
|
|
14
|
+
- 通过多轮单问澄清补全缺失维度
|
|
15
|
+
- 用户无法回答的问题记录为 TODO
|
|
16
|
+
- 最终经用户阻塞确认后落盘
|
|
17
|
+
|
|
18
|
+
## Inputs
|
|
19
|
+
|
|
20
|
+
| 参数 | 说明 |
|
|
21
|
+
|---|---|
|
|
22
|
+
| `user_input` | 用户原始输入 |
|
|
23
|
+
| `version` | 目标版本,如 `v1`;未提供时自动解析 |
|
|
24
|
+
|
|
25
|
+
## Mode
|
|
26
|
+
|
|
27
|
+
- **Create**:`requirement/vN/business-analysis.md` 不存在时新建
|
|
28
|
+
- **Update**:已存在时读取现有条目,追加或修订
|
|
29
|
+
|
|
30
|
+
## Process
|
|
31
|
+
|
|
32
|
+
### 1. Resolve Version
|
|
33
|
+
|
|
34
|
+
按 `references/version-resolution.md` 解析目标 `vN`,判断 Create / Update 模式。若文件已存在,询问用户「继续完善 / 新建版本 / 覆盖」。
|
|
35
|
+
|
|
36
|
+
### 2. First-Pass Extraction
|
|
37
|
+
|
|
38
|
+
从 `user_input` 中提取候选 REQ 与 SC,识别缺失维度。先以 🔵 pending 状态写入内部草案。
|
|
39
|
+
|
|
40
|
+
### 3. One Question at a Time
|
|
41
|
+
|
|
42
|
+
每次只问 1 个问题,优先选择题,其次开放题。问题应针对当前最大缺失:角色、目标、触发条件、前置条件、约束、可测试验收标准。
|
|
43
|
+
|
|
44
|
+
### 4. TODO Handling
|
|
45
|
+
|
|
46
|
+
用户答不上来时,立即在 `## Open Questions / TODOs` 段落记录 `TODO-xxx`,状态 `pending`。相关 REQ/SC 保持 🔵 pending。
|
|
47
|
+
|
|
48
|
+
### 5. QA Check
|
|
49
|
+
|
|
50
|
+
每轮更新后运行 `references/qa-checklist.md` 的 5 项 Error 检查。未通过则继续提问,最多 3 轮自修正。
|
|
51
|
+
|
|
52
|
+
### 6. Final Confirmation
|
|
53
|
+
|
|
54
|
+
展示完整文档预览,用户选择:
|
|
55
|
+
- **Confirm**:写入文件,所有 🔵 翻转为 ✅ confirmed
|
|
56
|
+
- **Adjust**:返回对话循环
|
|
57
|
+
- **Abort**:保持 pending,不写入最终确认
|
|
58
|
+
|
|
59
|
+
## Output Contract
|
|
60
|
+
|
|
61
|
+
只写 `requirement/vN/business-analysis.md`。输出格式见 `references/output-schema.md`。
|
|
62
|
+
|
|
63
|
+
严格禁止:
|
|
64
|
+
- 写入 `ledger.md`
|
|
65
|
+
- 写入 PRD、流程、架构等下游制品
|
|
66
|
+
- 修改 `.team-flow.yaml`
|
|
67
|
+
|
|
68
|
+
## Anti-Patterns
|
|
69
|
+
|
|
70
|
+
- 一次问多个问题
|
|
71
|
+
- 使用泛化角色如 "user"
|
|
72
|
+
- Acceptance 写成不可测试的模糊描述
|
|
73
|
+
- 未确认直接落盘
|
|
74
|
+
|
|
75
|
+
## References
|
|
76
|
+
|
|
77
|
+
- `references/output-schema.md` — 输出文档格式
|
|
78
|
+
- `references/qa-checklist.md` — QA 检查标准
|
|
79
|
+
- `references/interaction-rules.md` — 多轮交互与确认规则
|
|
80
|
+
- `references/version-resolution.md` — 版本解析规则
|