@xulthekl/team-flow 0.30.0 → 0.31.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 +2 -2
- 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/AGENTS.md +2 -1
- package/CHANGELOG.md +38 -0
- package/GEMINI.md +1 -1
- package/INSTALL.md +1 -1
- package/README.md +2 -2
- package/agents/build-executor.md +1 -0
- package/docs/README_en.md +1 -1
- 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 +2 -2
- package/scripts/guard/checks/test-matrix-complete.mjs +67 -0
- package/scripts/guard/guard.mjs +5 -1
- package/scripts/lib/cmd-state.mjs +9 -4
- package/scripts/lib/hash.mjs +11 -0
- package/scripts/lib/state-loader.mjs +12 -1
- package/scripts/lib/test-matrix-export.mjs +231 -0
- package/scripts/lib/test-merge.mjs +540 -0
- package/scripts/team-flow.mjs +6 -0
- package/skills/build-executor/implementer-prompt.md +38 -3
- package/skills/code-reviewer/SKILL.md +28 -1
- package/skills/code-reviewer/code-reviewer-prompt.md +10 -0
- package/skills/contract-builder/SKILL.md +76 -0
- package/skills/release-archivist/SKILL.md +35 -2
- package/skills/spec-writer/SKILL.md +3 -1
- package/skills/test-strategy/SKILL.md +70 -0
- package/skills/test-strategy/references/adversarial-patterns.md +0 -0
- package/skills/test-strategy/references/complexity-grading.md +137 -0
- package/skills/test-strategy/references/design-methods-detail.md +183 -0
- package/skills/workflow-orchestrator/references/s1-path-router.md +4 -0
- package/skills/workflow-start/references/routing-rules.md +17 -0
- package/tests/lib/cmd-install-workbuddy.test.mjs +1 -1
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* test-matrix-export — glaf4 test-matrix.json → team-flow test-matrix.md 格式转换
|
|
4
|
+
*
|
|
5
|
+
* v0.12 §46.1 定义,v0.31.0 实现为 CLI 子命令
|
|
6
|
+
* 用法:tf test-matrix-export <input.json> <output.md> [--change-id <id>] [--batch-id <id>]
|
|
7
|
+
*
|
|
8
|
+
* 功能:
|
|
9
|
+
* - 读取 glaf4 的 test-matrix.json
|
|
10
|
+
* - 保留全部 14 字段 + 包装 team_flow_context
|
|
11
|
+
* - 从 test_kind 派生 test_tier(unit/integration)
|
|
12
|
+
* - 输出 team-flow 格式的 test-matrix.md
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
16
|
+
import { resolve, basename } from 'node:path';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 解析 CLI 参数
|
|
20
|
+
*/
|
|
21
|
+
function parseArgv(argv) {
|
|
22
|
+
const parsed = { _: [] };
|
|
23
|
+
for (let i = 0; i < argv.length; i++) {
|
|
24
|
+
if (argv[i] === '--change-id' && argv[i + 1]) {
|
|
25
|
+
parsed.changeId = argv[++i];
|
|
26
|
+
} else if (argv[i] === '--batch-id' && argv[i + 1]) {
|
|
27
|
+
parsed.batchId = argv[++i];
|
|
28
|
+
} else if (argv[i] === '--ledger-ref' && argv[i + 1]) {
|
|
29
|
+
parsed.ledgerRef = argv[++i];
|
|
30
|
+
} else if (!argv[i].startsWith('--')) {
|
|
31
|
+
parsed._.push(argv[i]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* test_kind → test_tier 映射(v0.12 §42.4)
|
|
39
|
+
*/
|
|
40
|
+
const TEST_KIND_TO_TIER = {
|
|
41
|
+
// unit tier
|
|
42
|
+
pure_unit: 'unit',
|
|
43
|
+
mockito_unit: 'unit',
|
|
44
|
+
spring_assisted_unit: 'unit',
|
|
45
|
+
vitest_unit: 'unit',
|
|
46
|
+
vue_test_utils_mount: 'unit',
|
|
47
|
+
// integration tier
|
|
48
|
+
api_mockmvc_standalone: 'integration',
|
|
49
|
+
api_mockmvc_slice: 'integration',
|
|
50
|
+
api_mockmvc_boot: 'integration',
|
|
51
|
+
service_social: 'integration',
|
|
52
|
+
repository_h2: 'integration',
|
|
53
|
+
rabbitmq_social: 'integration',
|
|
54
|
+
redis_social: 'integration',
|
|
55
|
+
external_api_stub: 'integration',
|
|
56
|
+
test_infrastructure: 'integration',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 从 glaf4 case 派生 test_tier
|
|
61
|
+
*/
|
|
62
|
+
function deriveTestTier(testKind) {
|
|
63
|
+
return TEST_KIND_TO_TIER[testKind] || 'unit';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 转义 markdown 表格单元格
|
|
68
|
+
*/
|
|
69
|
+
function escapeCell(str) {
|
|
70
|
+
if (typeof str !== 'string') return String(str ?? '');
|
|
71
|
+
return str.replace(/\|/g, '\\|').replace(/\n/g, ' ');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 转换单个 case 为 markdown 行
|
|
76
|
+
*/
|
|
77
|
+
function caseToRow(c) {
|
|
78
|
+
const tier = deriveTestTier(c.test_kind);
|
|
79
|
+
return [
|
|
80
|
+
c.case_id || '',
|
|
81
|
+
escapeCell(c.behavior || c.display_name || ''),
|
|
82
|
+
c.design_method || '',
|
|
83
|
+
escapeCell(typeof c.input === 'object' ? JSON.stringify(c.input) : (c.input || '')),
|
|
84
|
+
escapeCell(typeof c.expected === 'object' ? JSON.stringify(c.expected) : (c.expected || '')),
|
|
85
|
+
c.test_kind || '',
|
|
86
|
+
tier,
|
|
87
|
+
escapeCell(Array.isArray(c.required_stubs) ? c.required_stubs.join(', ') : (c.mock || '')),
|
|
88
|
+
c.work_mode || 'TDD',
|
|
89
|
+
c.test_file || '',
|
|
90
|
+
c.test_method_name || '',
|
|
91
|
+
c.run_command || '',
|
|
92
|
+
].join(' | ');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 提取模块名(从 target 或 case_id 前缀)
|
|
97
|
+
*/
|
|
98
|
+
function extractModuleName(matrix) {
|
|
99
|
+
if (matrix.target) {
|
|
100
|
+
// e.g., "com.example.service.OrderService" → "OrderService"
|
|
101
|
+
const parts = matrix.target.split('.');
|
|
102
|
+
return parts[parts.length - 1];
|
|
103
|
+
}
|
|
104
|
+
// fallback: from first case_id
|
|
105
|
+
if (matrix.cases?.[0]?.case_id) {
|
|
106
|
+
const id = matrix.cases[0].case_id;
|
|
107
|
+
const match = id.match(/^([A-Z][a-zA-Z]+)/);
|
|
108
|
+
if (match) return match[1];
|
|
109
|
+
}
|
|
110
|
+
return 'Unknown';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 转换 glaf4 matrix → team-flow markdown
|
|
115
|
+
*/
|
|
116
|
+
export function convert(matrix, options = {}) {
|
|
117
|
+
const moduleName = extractModuleName(matrix);
|
|
118
|
+
const cases = matrix.cases || [];
|
|
119
|
+
|
|
120
|
+
// 统计
|
|
121
|
+
const unitCases = cases.filter(c => deriveTestTier(c.test_kind) === 'unit');
|
|
122
|
+
const integrationCases = cases.filter(c => deriveTestTier(c.test_kind) === 'integration');
|
|
123
|
+
|
|
124
|
+
// 提取 complexity
|
|
125
|
+
const complexity = matrix.target_complexity || {};
|
|
126
|
+
let complexityTier = 'medium';
|
|
127
|
+
if (complexity.is_trivial) complexityTier = 'trivial';
|
|
128
|
+
else if ((complexity.public_methods || 0) > 15 || (complexity.lines || 0) > 800 || (complexity.param_count || 0) > 6) {
|
|
129
|
+
complexityTier = 'complex';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 提取 candidate ledger
|
|
133
|
+
const candidateLedger = matrix.source_candidate_ledger || [];
|
|
134
|
+
|
|
135
|
+
// 构建 markdown
|
|
136
|
+
const lines = [];
|
|
137
|
+
lines.push(`# Test Matrix — ${options.changeId || 'unknown'}`);
|
|
138
|
+
lines.push('');
|
|
139
|
+
lines.push('## Summary');
|
|
140
|
+
lines.push(`- Total cases: ${cases.length}`);
|
|
141
|
+
lines.push(`- Modules covered: 1 (${moduleName}, complexity: ${complexityTier})`);
|
|
142
|
+
lines.push(`- Unit cases: ${unitCases.length} (${Math.round(unitCases.length / cases.length * 100) || 0}%)`);
|
|
143
|
+
lines.push(`- Integration cases: ${integrationCases.length} (${Math.round(integrationCases.length / cases.length * 100) || 0}%)`);
|
|
144
|
+
lines.push(`- Deferred items: ${candidateLedger.filter(c => c.decision === 'deferred').length}`);
|
|
145
|
+
lines.push(`- Matrix revision: 1`);
|
|
146
|
+
lines.push('');
|
|
147
|
+
|
|
148
|
+
// Candidate Coverage Ledger
|
|
149
|
+
if (candidateLedger.length > 0) {
|
|
150
|
+
lines.push('## Candidate Coverage Ledger');
|
|
151
|
+
lines.push('');
|
|
152
|
+
lines.push('| candidate | category | decision | case_ids | reason |');
|
|
153
|
+
lines.push('|---|---|---|---|---|');
|
|
154
|
+
for (const c of candidateLedger) {
|
|
155
|
+
lines.push(`| ${escapeCell(c.candidate)} | ${c.category || ''} | ${c.decision} | ${escapeCell(Array.isArray(c.case_ids) ? c.case_ids.join(', ') : (c.case_ids || ''))} | ${escapeCell(c.reason || '')} |`);
|
|
156
|
+
}
|
|
157
|
+
lines.push('');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Cases
|
|
161
|
+
lines.push('## Cases');
|
|
162
|
+
lines.push('');
|
|
163
|
+
lines.push(`### ${moduleName}`);
|
|
164
|
+
lines.push('');
|
|
165
|
+
lines.push('| case_id | behavior | design_method | input | expected | test_kind | test_tier | mock | work_mode | test_file | test_method_name | run_command |');
|
|
166
|
+
lines.push('|---|---|---|---|---|---|---|---|---|---|---|---|');
|
|
167
|
+
for (const c of cases) {
|
|
168
|
+
lines.push(`| ${caseToRow(c)} |`);
|
|
169
|
+
}
|
|
170
|
+
lines.push('');
|
|
171
|
+
|
|
172
|
+
// Deferred Items
|
|
173
|
+
const deferred = candidateLedger.filter(c => c.decision === 'deferred');
|
|
174
|
+
if (deferred.length > 0) {
|
|
175
|
+
lines.push('## Deferred Items');
|
|
176
|
+
lines.push('');
|
|
177
|
+
lines.push('| case_id | behavior | design_method | reason | deferred_since |');
|
|
178
|
+
lines.push('|---|---|---|---|---|');
|
|
179
|
+
for (const d of deferred) {
|
|
180
|
+
lines.push(`| ${escapeCell(Array.isArray(d.case_ids) ? d.case_ids[0] : (d.case_ids || ''))} | ${escapeCell(d.candidate)} | — | ${escapeCell(d.reason || '')} | ${new Date().toISOString().slice(0, 10)} |`);
|
|
181
|
+
}
|
|
182
|
+
lines.push('');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// team_flow_context footer
|
|
186
|
+
lines.push('---');
|
|
187
|
+
lines.push('');
|
|
188
|
+
lines.push('<!-- team_flow_context:');
|
|
189
|
+
lines.push(` change_id: ${options.changeId || 'unknown'}`);
|
|
190
|
+
if (options.batchId) lines.push(` batch_id: ${options.batchId}`);
|
|
191
|
+
lines.push(` closing_stage: test-merge`);
|
|
192
|
+
if (options.ledgerRef) lines.push(` ledger_ref: ${options.ledgerRef}`);
|
|
193
|
+
lines.push(` source: glaf4-tests-export`);
|
|
194
|
+
lines.push('-->');
|
|
195
|
+
|
|
196
|
+
return lines.join('\n') + '\n';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* CLI entry point
|
|
201
|
+
*/
|
|
202
|
+
export async function run(args) {
|
|
203
|
+
const argv = parseArgv(args || process.argv.slice(2));
|
|
204
|
+
const inputFile = argv._[0];
|
|
205
|
+
const outputFile = argv._[1];
|
|
206
|
+
|
|
207
|
+
if (!inputFile) {
|
|
208
|
+
console.error('Usage: tf test-matrix-export <input.json> <output.md> [--change-id <id>] [--batch-id <id>]');
|
|
209
|
+
process.exit(2);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!outputFile) {
|
|
213
|
+
console.error('Usage: tf test-matrix-export <input.json> <output.md>');
|
|
214
|
+
process.exit(2);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
const raw = readFileSync(resolve(inputFile), 'utf-8');
|
|
219
|
+
const matrix = JSON.parse(raw);
|
|
220
|
+
const md = convert(matrix, {
|
|
221
|
+
changeId: argv.changeId || basename(inputFile, '.json'),
|
|
222
|
+
batchId: argv.batchId,
|
|
223
|
+
ledgerRef: argv.ledgerRef,
|
|
224
|
+
});
|
|
225
|
+
writeFileSync(resolve(outputFile), md, 'utf-8');
|
|
226
|
+
console.log(`✅ test-matrix-export: ${inputFile} → ${outputFile}`);
|
|
227
|
+
} catch (err) {
|
|
228
|
+
console.error(`❌ test-matrix-export error: ${err.message}`);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
}
|