@xulthekl/team-flow 0.31.0 → 0.32.2
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 +1 -1
- 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 +2 -2
- package/AGENTS.md +3 -3
- package/CHANGELOG.md +69 -0
- package/GEMINI.md +1 -1
- package/HANDOFF.md +1 -1
- package/INSTALL.md +1 -1
- package/README.md +2 -2
- package/agents/contract-builder.md +6 -1
- package/docs/README_en.md +1 -1
- package/docs/solutions/INDEX.md +2 -0
- package/docs/solutions/cross-phase/2026-08-01-no-summary.md +17 -0
- package/docs/solutions/cross-phase/2026-08-03-no-summary.md +17 -0
- package/gemini-extension.json +2 -2
- package/hooks/pre-tool-use-guard +43 -2
- package/hooks/session-start +2 -2
- package/llms.txt +1 -1
- package/package.json +6 -2
- package/plugin.json +1 -1
- package/scripts/guard/checks/test-gate-exemptions.mjs +53 -0
- package/scripts/guard/checks/test-matrix-complete.mjs +54 -30
- package/scripts/guard/checks/test-matrix-ready.mjs +60 -0
- package/scripts/guard/checks/tests-passing.mjs +112 -24
- package/scripts/guard/guard.mjs +11 -2
- package/scripts/lib/cmd-doctor.mjs +43 -1
- package/scripts/lib/cmd-execution.mjs +24 -1
- package/scripts/lib/cmd-state.mjs +28 -5
- package/scripts/lib/cmd-validate.mjs +42 -0
- package/scripts/lib/cmd-version.mjs +26 -21
- package/scripts/lib/execution-plan.mjs +23 -0
- package/scripts/lib/state-loader.mjs +16 -2
- package/scripts/lib/test-record.mjs +212 -0
- package/scripts/team-flow.mjs +3 -0
- package/skills/contract-builder/SKILL.md +9 -10
- package/skills/release-archivist/SKILL.md +16 -9
- package/skills/session-handoff/SKILL.md +1 -0
- package/skills/workflow-start/SKILL.md +5 -3
- package/skills/workflow-start/references/routing-rules.md +10 -6
- package/tests/e2e.test.mjs +0 -266
- package/tests/lib/cmd-audit.test.mjs +0 -202
- package/tests/lib/cmd-checkpoint.test.mjs +0 -98
- package/tests/lib/cmd-config.test.mjs +0 -101
- package/tests/lib/cmd-doctor.test.mjs +0 -450
- package/tests/lib/cmd-execution.test.mjs +0 -647
- package/tests/lib/cmd-handoff.test.mjs +0 -137
- package/tests/lib/cmd-inject.test.mjs +0 -218
- package/tests/lib/cmd-install-workbuddy.test.mjs +0 -113
- package/tests/lib/cmd-install-zcode.test.mjs +0 -42
- package/tests/lib/cmd-isolate.test.mjs +0 -68
- package/tests/lib/cmd-list.test.mjs +0 -127
- package/tests/lib/cmd-runtime.test.mjs +0 -83
- package/tests/lib/cmd-state-missing.test.mjs +0 -65
- package/tests/lib/cmd-state.test.mjs +0 -365
- package/tests/lib/cmd-sync-paths.test.mjs +0 -84
- package/tests/lib/cmd-validate-paths.test.mjs +0 -90
- package/tests/lib/config-loader.test.mjs +0 -175
- package/tests/lib/ensure-branch.test.mjs +0 -110
- package/tests/lib/execution-control-plane.test.mjs +0 -168
- package/tests/lib/execution-plan.test.mjs +0 -474
- package/tests/lib/guard-compound-captured.test.mjs +0 -92
- package/tests/lib/guard-specs-merged.test.mjs +0 -110
- package/tests/lib/guard-tests-passing.test.mjs +0 -114
- package/tests/lib/guard-transitions.test.mjs +0 -262
- package/tests/lib/guard.test.mjs +0 -613
- package/tests/lib/hash.test.mjs +0 -138
- package/tests/lib/infer-workflow.test.mjs +0 -161
- package/tests/lib/install-git-hooks.test.mjs +0 -20
- package/tests/lib/marketplace-release-docs.test.mjs +0 -31
- package/tests/lib/minimality-discipline.test.mjs +0 -37
- package/tests/lib/model-profiles-docs.test.mjs +0 -33
- package/tests/lib/node20-compatibility.test.mjs +0 -73
- package/tests/lib/node20-test-entry.test.mjs +0 -17
- package/tests/lib/platform-runtime-distribution.test.mjs +0 -157
- package/tests/lib/raw-mode-smoke.test.mjs +0 -42
- package/tests/lib/sdd-overlay.test.mjs +0 -118
- package/tests/lib/solutions-capture.test.mjs +0 -108
- package/tests/lib/solutions-index-gen.test.mjs +0 -147
- package/tests/lib/solutions-inject.test.mjs +0 -115
- package/tests/lib/solutions-promote.test.mjs +0 -200
- package/tests/lib/spec-paths.test.mjs +0 -80
- package/tests/lib/state-loader.test.mjs +0 -251
- package/tests/lib/token-baseline.test.mjs +0 -72
- package/tests/lib/token-rules.test.mjs +0 -94
- package/tests/lib/verify-marketplace-release.test.mjs +0 -128
- package/tests/tsconfig.json +0 -12
|
@@ -19,25 +19,30 @@ const MANIFESTS = [
|
|
|
19
19
|
// ── Text files with regex patterns (first capture group = version to replace) ──
|
|
20
20
|
// Note: CLAUDE.md is intentionally gitignored (project-local AI instructions).
|
|
21
21
|
const TEXT_FILES = [
|
|
22
|
-
{ file: 'README.md', pattern: /(当前版本:`v?)
|
|
23
|
-
{ file: 'INSTALL.md', pattern: /(当前发布版本:\*\*v)
|
|
24
|
-
{ file: 'docs/README_en.md', pattern: /(Current: `v)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
{ file: '
|
|
30
|
-
{ file: '
|
|
31
|
-
{ file: '
|
|
32
|
-
{ file: '
|
|
33
|
-
{ file: '
|
|
34
|
-
{ file: 'skills/
|
|
35
|
-
{ file: 'skills/
|
|
36
|
-
{ file: 'skills/
|
|
37
|
-
{ file: 'skills/
|
|
38
|
-
{ file: 'skills/
|
|
39
|
-
{ file: 'skills/
|
|
40
|
-
{ file: 'skills/
|
|
22
|
+
{ file: 'README.md', pattern: /(当前版本:`v?)\d+\.\d+\.\d+(`?)/g, replacement: '$1%MAJOR%.%MINOR%.%PATCH%$2' },
|
|
23
|
+
{ file: 'INSTALL.md', pattern: /(当前发布版本:\*\*v)\d+\.\d+\.\d+(\*\*)/g, replacement: '$1%MAJOR%.%MINOR%.%PATCH%$2' },
|
|
24
|
+
{ file: 'docs/README_en.md', pattern: /(Current: `v)\d+\.\d+\.\d+(`)/g, replacement: '$1%MAJOR%.%MINOR%.%PATCH%$2' },
|
|
25
|
+
// v0.32.2(P2-40):hooks/session-start 两处版本号都纳入同步——
|
|
26
|
+
// ① 头部注释行(后缀用通配 `:.*`,防止注释措辞变更导致 pattern 腐化失配,
|
|
27
|
+
// v0.27.2~v0.32.1 四次发版中断的根因即旧 pattern 写死了后缀措辞);
|
|
28
|
+
// ② PLUGIN_VERSION 变量(check-version-consistency 的门禁判据)。
|
|
29
|
+
{ file: 'hooks/session-start', pattern: /(# v)\d+\.\d+\.\d+(:.*)/g, replacement: '$1%MAJOR%.%MINOR%.%PATCH%$2' },
|
|
30
|
+
{ file: 'hooks/session-start', pattern: /(PLUGIN_VERSION=")\d+\.\d+\.\d+(")/g, replacement: '$1%MAJOR%.%MINOR%.%PATCH%$2' },
|
|
31
|
+
{ file: 'llms.txt', pattern: /(Current version: v)\d+\.\d+\.\d+(\.)/g, replacement: '$1%MAJOR%.%MINOR%.%PATCH%$2' },
|
|
32
|
+
{ file: '.claude/always/phase-guard.md', pattern: /(# team-flow v)\d+\.\d+\.\d+( \|)/g, replacement: '$1%MAJOR%.%MINOR%.%PATCH%$2' },
|
|
33
|
+
{ file: 'GEMINI.md', pattern: /(# team-flow v)\d+\.\d+\.\d+( \|)/g, replacement: '$1%MAJOR%.%MINOR%.%PATCH%$2' },
|
|
34
|
+
{ file: 'skills/workflow-start/SKILL.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)\d+\.\d+\.\d+( tf)/g, replacement: '$1%MAJOR%.%MINOR%.%PATCH%$2' },
|
|
35
|
+
{ file: 'skills/need-explorer/SKILL.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)\d+\.\d+\.\d+( tf)/g, replacement: '$1%MAJOR%.%MINOR%.%PATCH%$2' },
|
|
36
|
+
{ file: 'skills/spec-writer/SKILL.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)\d+\.\d+\.\d+( tf)/g, replacement: '$1%MAJOR%.%MINOR%.%PATCH%$2' },
|
|
37
|
+
{ file: 'skills/contract-builder/SKILL.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)\d+\.\d+\.\d+( tf)/g, replacement: '$1%MAJOR%.%MINOR%.%PATCH%$2' },
|
|
38
|
+
{ file: 'skills/build-executor/SKILL.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)\d+\.\d+\.\d+( tf)/g, replacement: '$1%MAJOR%.%MINOR%.%PATCH%$2' },
|
|
39
|
+
{ file: 'skills/build-executor/implementer-prompt.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)\d+\.\d+\.\d+( tf)/g, replacement: '$1%MAJOR%.%MINOR%.%PATCH%$2' },
|
|
40
|
+
{ file: 'skills/build-executor/task-reviewer-prompt.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)\d+\.\d+\.\d+( tf)/g, replacement: '$1%MAJOR%.%MINOR%.%PATCH%$2' },
|
|
41
|
+
{ file: 'skills/code-reviewer/SKILL.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)\d+\.\d+\.\d+( tf)/g, replacement: '$1%MAJOR%.%MINOR%.%PATCH%$2' },
|
|
42
|
+
{ file: 'skills/code-reviewer/code-reviewer-prompt.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)\d+\.\d+\.\d+( tf)/g, replacement: '$1%MAJOR%.%MINOR%.%PATCH%$2' },
|
|
43
|
+
{ file: 'skills/bug-investigator/SKILL.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)\d+\.\d+\.\d+( tf)/g, replacement: '$1%MAJOR%.%MINOR%.%PATCH%$2' },
|
|
44
|
+
{ file: 'skills/release-archivist/SKILL.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)\d+\.\d+\.\d+( tf)/g, replacement: '$1%MAJOR%.%MINOR%.%PATCH%$2' },
|
|
45
|
+
{ file: 'skills/spec-merger/SKILL.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)\d+\.\d+\.\d+( tf)/g, replacement: '$1%MAJOR%.%MINOR%.%PATCH%$2' },
|
|
41
46
|
];
|
|
42
47
|
|
|
43
48
|
function getNestedValue(obj, pathParts) {
|
|
@@ -72,7 +77,7 @@ export async function run(args) {
|
|
|
72
77
|
console.error(`Invalid semver: ${newVersion}`);
|
|
73
78
|
process.exit(2);
|
|
74
79
|
}
|
|
75
|
-
const [, , minor, patch] = match;
|
|
80
|
+
const [, major, minor, patch] = match;
|
|
76
81
|
|
|
77
82
|
console.log(`Version sync → ${newVersion}${dryRun ? ' (dry run)' : ''}\n`);
|
|
78
83
|
let changed = 0;
|
|
@@ -122,7 +127,7 @@ export async function run(args) {
|
|
|
122
127
|
|
|
123
128
|
const content = readFileSync(filePath, 'utf-8');
|
|
124
129
|
const replacement = entry.replacement
|
|
125
|
-
.replace('%MINOR%', minor)
|
|
130
|
+
.replace('%MAJOR%', major).replace('%MINOR%', minor)
|
|
126
131
|
.replace('%PATCH%', patch);
|
|
127
132
|
|
|
128
133
|
const newContent = content.replace(entry.pattern, replacement);
|
|
@@ -132,12 +132,26 @@ export function recordReview(changeDir, waveId, receipt) {
|
|
|
132
132
|
for (const field of ['base', 'head']) requireText(receipt?.[field], `receipt.${field}`);
|
|
133
133
|
const report = validateReviewReportEvidence(changeDir, receipt?.report);
|
|
134
134
|
const { base, head } = validateReviewRange(changeDir, receipt.base, receipt.head);
|
|
135
|
+
// v0.13 §51.2:测试统计为可选证据字段(提供时必须为非负整数)。
|
|
136
|
+
// 硬门禁在入口(test-matrix-ready)与 closing(tests-passing),此处只沉淀证据,
|
|
137
|
+
// 避免单 wave 零测试造成 BUG-A 式过严死锁(DDL/脚手架类 wave 可能合理无测试)。
|
|
138
|
+
let tests;
|
|
139
|
+
if (receipt?.tests !== undefined) {
|
|
140
|
+
if (!isObject(receipt.tests)) throw new Error('receipt.tests must be an object');
|
|
141
|
+
for (const key of ['total', 'passed', 'failed']) {
|
|
142
|
+
if (!Number.isInteger(receipt.tests[key]) || receipt.tests[key] < 0) {
|
|
143
|
+
throw new Error(`receipt.tests.${key} must be a non-negative integer`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
tests = { total: receipt.tests.total, passed: receipt.tests.passed, failed: receipt.tests.failed };
|
|
147
|
+
}
|
|
135
148
|
|
|
136
149
|
const savedReceipt = {
|
|
137
150
|
status: receipt.status,
|
|
138
151
|
base,
|
|
139
152
|
head,
|
|
140
153
|
report,
|
|
154
|
+
...(tests ? { tests } : {}),
|
|
141
155
|
plan_hash: plan.hash,
|
|
142
156
|
plan_revision: plan.revision,
|
|
143
157
|
recorded_at: new Date().toISOString(),
|
|
@@ -252,6 +266,15 @@ function validateReviewRange(changeDir, base, head) {
|
|
|
252
266
|
const gitRoot = getGitRoot(changeDir);
|
|
253
267
|
const resolvedBase = resolveGitCommit(gitRoot, base, 'base');
|
|
254
268
|
const resolvedHead = resolveGitCommit(gitRoot, head, 'head');
|
|
269
|
+
// v0.13 §51.1:禁止空 diff review(base === head)。
|
|
270
|
+
// C1-domain-policy 现场曾出现 6 个 receipt 全部 base==head==初始 commit,
|
|
271
|
+
// 即 review 对空 diff 进行。本校验同时强制"每 wave 落 commit"的纪律。
|
|
272
|
+
if (resolvedBase === resolvedHead) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
'Review receipt base must differ from head — head must contain new commits over base; '
|
|
275
|
+
+ 'commit the wave changes before recording a review'
|
|
276
|
+
);
|
|
277
|
+
}
|
|
255
278
|
try {
|
|
256
279
|
execFileSync('git', ['-C', gitRoot, 'merge-base', '--is-ancestor', resolvedBase, resolvedHead], {
|
|
257
280
|
stdio: 'ignore',
|
|
@@ -61,9 +61,14 @@ const BUILTIN_DEFAULTS = {
|
|
|
61
61
|
dp_a_adjustments: null,
|
|
62
62
|
// Compound engineering capture gate (v0.24.0 复利贯穿强制化)
|
|
63
63
|
compound_skipped: null,
|
|
64
|
-
// Test matrix gate (v0.12 §42.5 + §45.4)
|
|
64
|
+
// Test matrix gate (v0.12 §42.5 + §45.4; v0.13 §48 补 skip 理由字段)
|
|
65
65
|
test_matrix_hash: null,
|
|
66
66
|
test_matrix_skipped: null,
|
|
67
|
+
test_matrix_skip_reason: null,
|
|
68
|
+
// Test evidence (v0.13 §50:tf test record 落盘的 runner 输出证据路径)
|
|
69
|
+
test_evidence_path: null,
|
|
70
|
+
// 注意:schema_version 故意不在 BUILTIN_DEFAULTS 中(v0.13 §48.1)——
|
|
71
|
+
// 它只由 `tf state init` 在 change 创建时打戳,字段缺失本身就是"存量 change"信号。
|
|
67
72
|
};
|
|
68
73
|
|
|
69
74
|
/**
|
|
@@ -108,6 +113,11 @@ export function writeState(changeDir, state) {
|
|
|
108
113
|
lines.push(`state: ${state.state || 'exploring'}`);
|
|
109
114
|
lines.push(`workflow: ${state.workflow || 'auto'}`);
|
|
110
115
|
lines.push(`revision: ${state.revision ?? 'null'}`);
|
|
116
|
+
// v0.13 §48.1:schema_version 仅由 tf state init 在 change 创建时打戳;
|
|
117
|
+
// 缺失 = 存量 change(测试门禁豁免键)。rebuild/set 不追加,故条件序列化。
|
|
118
|
+
if (state.schema_version != null) {
|
|
119
|
+
lines.push(`schema_version: ${state.schema_version}`);
|
|
120
|
+
}
|
|
111
121
|
lines.push('');
|
|
112
122
|
lines.push('# === Hashes (fast staleness detection) ===');
|
|
113
123
|
lines.push(`artifacts_hash: ${state.artifacts_hash ?? 'null'}`);
|
|
@@ -166,9 +176,13 @@ export function writeState(changeDir, state) {
|
|
|
166
176
|
lines.push('# === Compound engineering capture gate (v0.24.0) ===');
|
|
167
177
|
lines.push(`compound_skipped: ${state.compound_skipped ?? 'null'}`);
|
|
168
178
|
lines.push('');
|
|
169
|
-
lines.push('# === Test matrix gate (v0.12 §42.5) ===');
|
|
179
|
+
lines.push('# === Test matrix gate (v0.12 §42.5 + v0.13 §48) ===');
|
|
170
180
|
lines.push(`test_matrix_hash: ${state.test_matrix_hash ?? 'null'}`);
|
|
171
181
|
lines.push(`test_matrix_skipped: ${state.test_matrix_skipped ?? 'null'}`);
|
|
182
|
+
lines.push(`test_matrix_skip_reason: ${state.test_matrix_skip_reason ?? 'null'}`);
|
|
183
|
+
lines.push('');
|
|
184
|
+
lines.push('# === Test evidence (v0.13 §50) ===');
|
|
185
|
+
lines.push(`test_evidence_path: ${state.test_evidence_path ?? 'null'}`);
|
|
172
186
|
|
|
173
187
|
fs.writeFileSync(filePath, lines.join('\n') + '\n', 'utf-8');
|
|
174
188
|
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// scripts/lib/test-record.mjs — tf test record:程序化测试证据记录(v0.13 §50)
|
|
2
|
+
//
|
|
3
|
+
// 设计增强方案 v0.13 §50(BUG-A 政策修正):tests-passing 门禁锚定客观证据,
|
|
4
|
+
// 不再锚定 LLM 自述。本命令解析主流测试 runner 的输出,把结构化结果写入
|
|
5
|
+
// state.test_result(recorded-by=tf-test-record),并把原始输出落盘为证据文件。
|
|
6
|
+
// 不提供手工自述通道(--manual):确无自动化测试的 change 的合法出路是
|
|
7
|
+
// 显式 skip(test_matrix_skipped=true + test_matrix_skip_reason),可审计、可巡检。
|
|
8
|
+
//
|
|
9
|
+
// 扩展性:新增 runner 只需在 PARSERS 注册表加一个解析器(零依赖)。
|
|
10
|
+
import { parseArgs } from 'node:util';
|
|
11
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import path, { join } from 'node:path';
|
|
13
|
+
import { readState, writeState } from './state-loader.mjs';
|
|
14
|
+
|
|
15
|
+
export const SUPPORTED_RUNNERS = ['maven-surefire', 'jest', 'pytest'];
|
|
16
|
+
|
|
17
|
+
// ── 解析器(全部返回 { total, passed, failed, skipped } 或 null)────────────
|
|
18
|
+
|
|
19
|
+
/** maven surefire 控制台汇总行(多模块累加):"Tests run: 42, Failures: 0, Errors: 0, Skipped: 2" */
|
|
20
|
+
export function parseMavenSurefire(text) {
|
|
21
|
+
const re = /Tests run:\s*(\d+),\s*Failures:\s*(\d+),\s*Errors:\s*(\d+),\s*Skipped:\s*(\d+)/g;
|
|
22
|
+
let m;
|
|
23
|
+
let total = 0; let failures = 0; let errors = 0; let skipped = 0;
|
|
24
|
+
let found = false;
|
|
25
|
+
while ((m = re.exec(text)) !== null) {
|
|
26
|
+
found = true;
|
|
27
|
+
total += parseInt(m[1], 10);
|
|
28
|
+
failures += parseInt(m[2], 10);
|
|
29
|
+
errors += parseInt(m[3], 10);
|
|
30
|
+
skipped += parseInt(m[4], 10);
|
|
31
|
+
}
|
|
32
|
+
if (!found) return null;
|
|
33
|
+
return { total, passed: total - failures - errors - skipped, failed: failures + errors, skipped };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** maven surefire XML 报告目录(target/surefire-reports):累加 <testsuite> 属性。 */
|
|
37
|
+
export function parseSurefireReportDir(dir) {
|
|
38
|
+
let total = 0; let failures = 0; let errors = 0; let skipped = 0;
|
|
39
|
+
let found = false;
|
|
40
|
+
for (const name of readdirSync(dir)) {
|
|
41
|
+
if (!name.endsWith('.xml')) continue;
|
|
42
|
+
const content = readFileSync(join(dir, name), 'utf-8');
|
|
43
|
+
for (const tag of content.matchAll(/<testsuite\b[^>]*>/g)) {
|
|
44
|
+
const attrs = tag[0];
|
|
45
|
+
const pick = (key) => {
|
|
46
|
+
const am = attrs.match(new RegExp(`\\b${key}="(\\d+)"`));
|
|
47
|
+
return am ? parseInt(am[1], 10) : 0;
|
|
48
|
+
};
|
|
49
|
+
found = true;
|
|
50
|
+
total += pick('tests');
|
|
51
|
+
failures += pick('failures');
|
|
52
|
+
errors += pick('errors');
|
|
53
|
+
skipped += pick('skipped');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!found) return null;
|
|
57
|
+
return { total, passed: total - failures - errors - skipped, failed: failures + errors, skipped };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** jest --json 输出。 */
|
|
61
|
+
export function parseJest(text) {
|
|
62
|
+
let data;
|
|
63
|
+
try { data = JSON.parse(text); } catch { return null; }
|
|
64
|
+
if (typeof data?.numTotalTests !== 'number') return null;
|
|
65
|
+
const total = data.numTotalTests;
|
|
66
|
+
const failed = data.numFailedTests ?? 0;
|
|
67
|
+
const passed = data.numPassedTests ?? 0;
|
|
68
|
+
const skipped = (data.numPendingTests ?? 0) + (data.numTodoTests ?? 0);
|
|
69
|
+
return { total, passed, failed, skipped };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** pytest terminal summary:"== 5 passed, 2 failed, 1 skipped in 3.21s ==" */
|
|
73
|
+
export function parsePytest(text) {
|
|
74
|
+
const lines = text.split('\n');
|
|
75
|
+
const summaryLine = [...lines].reverse().find(l => /=+\s.*(passed|failed|error|no tests ran)/.test(l));
|
|
76
|
+
if (!summaryLine) return null;
|
|
77
|
+
const pick = (label) => {
|
|
78
|
+
const m = summaryLine.match(new RegExp(`(\\d+)\\s+${label}`));
|
|
79
|
+
return m ? parseInt(m[1], 10) : 0;
|
|
80
|
+
};
|
|
81
|
+
if (/no tests ran/.test(summaryLine)) return { total: 0, passed: 0, failed: 0, skipped: 0 };
|
|
82
|
+
const passed = pick('passed') + pick('xpassed');
|
|
83
|
+
const failed = pick('failed') + pick('error');
|
|
84
|
+
const skipped = pick('skipped') + pick('xfailed');
|
|
85
|
+
const total = passed + failed + skipped;
|
|
86
|
+
if (total === 0) return null;
|
|
87
|
+
return { total, passed, failed, skipped };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const PARSERS = {
|
|
91
|
+
'maven-surefire': parseMavenSurefire,
|
|
92
|
+
jest: parseJest,
|
|
93
|
+
pytest: parsePytest,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/** auto 识别:按内容特征匹配 runner。 */
|
|
97
|
+
export function detectRunner(text) {
|
|
98
|
+
const trimmed = text.trim();
|
|
99
|
+
if (trimmed.startsWith('{') && parseJest(trimmed)) return 'jest';
|
|
100
|
+
if (/Tests run:\s*\d+,\s*Failures:/.test(trimmed)) return 'maven-surefire';
|
|
101
|
+
if (parsePytest(trimmed)) return 'pytest';
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── CLI ─────────────────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
export async function run(args) {
|
|
108
|
+
const { positionals, values } = parseArgs({
|
|
109
|
+
args,
|
|
110
|
+
options: {
|
|
111
|
+
from: { type: 'string' },
|
|
112
|
+
runner: { type: 'string', default: 'auto' },
|
|
113
|
+
json: { type: 'boolean', default: false },
|
|
114
|
+
},
|
|
115
|
+
allowPositionals: true,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const usageMsg = 'Usage: tf test record <change-dir> --from <runner-output-file|surefire-report-dir> [--runner auto|maven-surefire|jest|pytest] [--json]';
|
|
119
|
+
if (positionals[0] !== 'record') {
|
|
120
|
+
console.error(usageMsg);
|
|
121
|
+
process.exit(2);
|
|
122
|
+
}
|
|
123
|
+
const changeDirArg = positionals[1];
|
|
124
|
+
if (!changeDirArg) {
|
|
125
|
+
console.error(usageMsg);
|
|
126
|
+
process.exit(2);
|
|
127
|
+
}
|
|
128
|
+
const changeDir = path.resolve(changeDirArg);
|
|
129
|
+
if (!existsSync(join(changeDir, '.team-flow.yaml'))) {
|
|
130
|
+
console.error(`No state file at ${join(changeDir, '.team-flow.yaml')}. Run 'tf state init <change-dir>' first.`);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
if (!values.from) {
|
|
134
|
+
console.error(usageMsg);
|
|
135
|
+
process.exit(2);
|
|
136
|
+
}
|
|
137
|
+
const fromPath = path.resolve(values.from);
|
|
138
|
+
if (!existsSync(fromPath)) {
|
|
139
|
+
console.error(`Runner output not found: ${fromPath}`);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
const validRunners = ['auto', ...SUPPORTED_RUNNERS];
|
|
143
|
+
if (!validRunners.includes(values.runner)) {
|
|
144
|
+
console.error(`--runner must be one of: ${validRunners.join(', ')}`);
|
|
145
|
+
process.exit(2);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 解析 runner 输出
|
|
149
|
+
let stats = null;
|
|
150
|
+
let runner = values.runner;
|
|
151
|
+
const fromStat = statSync(fromPath);
|
|
152
|
+
if (fromStat.isDirectory()) {
|
|
153
|
+
if (runner !== 'auto' && runner !== 'maven-surefire') {
|
|
154
|
+
console.error('Directory input is only supported for maven-surefire XML report directories');
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
runner = 'maven-surefire';
|
|
158
|
+
stats = parseSurefireReportDir(fromPath);
|
|
159
|
+
} else {
|
|
160
|
+
const text = readFileSync(fromPath, 'utf-8');
|
|
161
|
+
if (runner === 'auto') {
|
|
162
|
+
runner = detectRunner(text);
|
|
163
|
+
if (!runner) {
|
|
164
|
+
console.error(
|
|
165
|
+
`Could not auto-detect a supported test runner in ${fromPath}.\n`
|
|
166
|
+
+ `Supported runners: ${SUPPORTED_RUNNERS.join(', ')} (or pass --runner explicitly).\n`
|
|
167
|
+
+ '手工自述通道已关闭(v0.13 §50):确无自动化测试的 change 请显式跳过:\n'
|
|
168
|
+
+ " tf state set <dir> test_matrix_skipped true + test_matrix_skip_reason '<理由>'"
|
|
169
|
+
);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
stats = PARSERS[runner](text);
|
|
174
|
+
}
|
|
175
|
+
if (!stats) {
|
|
176
|
+
console.error(`Could not parse ${runner} output in ${fromPath} — no recognizable test summary found.`);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 判定:failed==0 且 total>0 才算 pass(total=0 空真拒绝,v0.13 §50.2)
|
|
181
|
+
const verdict = stats.failed === 0 && stats.total > 0 ? 'pass' : 'fail';
|
|
182
|
+
const ts = new Date().toISOString();
|
|
183
|
+
const record = `${verdict}: total=${stats.total} passed=${stats.passed} failed=${stats.failed} skipped=${stats.skipped} runner=${runner} recorded-by=tf-test-record ts=${ts}`;
|
|
184
|
+
|
|
185
|
+
// 原始证据落盘(tests-passing 门禁与 doctor 巡检都要求该文件存在)
|
|
186
|
+
const evidenceDir = join(changeDir, '.superpowers', 'test-evidence');
|
|
187
|
+
mkdirSync(evidenceDir, { recursive: true });
|
|
188
|
+
const safeTs = ts.replace(/[:.]/g, '-');
|
|
189
|
+
const evidenceRel = join('.superpowers', 'test-evidence', `${safeTs}-${runner}.txt`).split(path.sep).join('/');
|
|
190
|
+
const raw = fromStat.isFile()
|
|
191
|
+
? readFileSync(fromPath, 'utf-8')
|
|
192
|
+
: `(maven-surefire XML report directory: ${fromPath})`;
|
|
193
|
+
const MAX_EVIDENCE = 512 * 1024;
|
|
194
|
+
writeFileSync(join(changeDir, evidenceRel), raw.length > MAX_EVIDENCE ? `${raw.slice(0, MAX_EVIDENCE)}\n...[truncated]` : raw, 'utf-8');
|
|
195
|
+
|
|
196
|
+
// 程序化写入状态(test_result 不在 SETTABLE_FIELDS,只能经本命令写入)
|
|
197
|
+
const state = readState(changeDir);
|
|
198
|
+
state.test_result = record;
|
|
199
|
+
state.test_evidence_path = evidenceRel;
|
|
200
|
+
writeState(changeDir, state);
|
|
201
|
+
|
|
202
|
+
if (values.json) {
|
|
203
|
+
console.log(JSON.stringify({ ok: true, verdict, ...stats, runner, test_result: record, test_evidence_path: evidenceRel }));
|
|
204
|
+
} else {
|
|
205
|
+
console.log(`${verdict === 'pass' ? '✅' : '❌'} test_result recorded (${runner}): total=${stats.total} passed=${stats.passed} failed=${stats.failed} skipped=${stats.skipped}`);
|
|
206
|
+
console.log(` evidence: ${evidenceRel}`);
|
|
207
|
+
if (verdict === 'fail') {
|
|
208
|
+
console.log(' closing 将被 tests-passing 门禁阻断:修复失败后重新运行测试套件并再次 tf test record。');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
process.exit(verdict === 'pass' ? 0 : 1);
|
|
212
|
+
}
|
package/scripts/team-flow.mjs
CHANGED
|
@@ -36,6 +36,7 @@ const COMMANDS = {
|
|
|
36
36
|
'arch-merge': () => import('./lib/arch-merge.mjs'),
|
|
37
37
|
'test-merge': () => import('./lib/test-merge.mjs'),
|
|
38
38
|
'test-matrix-export': () => import('./lib/test-matrix-export.mjs'),
|
|
39
|
+
test: () => import('./lib/test-record.mjs'),
|
|
39
40
|
};
|
|
40
41
|
|
|
41
42
|
const HELP = `team-flow (tf) — Unified workflow plugin CLI
|
|
@@ -56,6 +57,8 @@ Commands:
|
|
|
56
57
|
Merge test matrix results into global docs/test-ledger/
|
|
57
58
|
test-matrix-export <input.json> <output.md> [--change-id <id>]
|
|
58
59
|
Convert glaf4 test-matrix.json to team-flow test-matrix.md
|
|
60
|
+
test record <dir> --from <runner-output> [--runner auto|maven-surefire|jest|pytest]
|
|
61
|
+
Record programmatic test evidence (v0.13 §50; feeds tests-passing gate)
|
|
59
62
|
config [options] Display or modify configuration
|
|
60
63
|
config --resolve-model <profile> Resolve a configured model profile without switching models
|
|
61
64
|
state <sub> <dir> Manage .team-flow.yaml state (init|check|transition|get|rebuild)
|
|
@@ -11,12 +11,7 @@ Read before generating: `proposal.md`, `specs/`, `design.md`, `tasks.md`, then l
|
|
|
11
11
|
|
|
12
12
|
**Architecture Design Outputs (v0.9 §26)**: 若 `architecture/` 目录存在,同时读取 `architecture/architecture.md` / `database.md` / `api.md`,作为执行契约的架构约束补充输入——确保 execution-contract.md 的 Implementation Constraints 段包含架构设计的关键约束(聚合边界/CQRS 分流/API 契约/schema 变更)。`architecture/` 不存在时跳过。
|
|
13
13
|
|
|
14
|
-
**Test Ledger Injection (v0.12 §43.5)**: 若 `docs/test-ledger/`
|
|
15
|
-
1. `docs/test-ledger/INDEX.md` → 获取相关模块的测试覆盖概览(coverage_status)
|
|
16
|
-
2. `docs/test-ledger/baselines/{module}.md` → 获取已有 case,避免重复设计;读取 Deferred Items → 评估本次是否可解决
|
|
17
|
-
3. 将已有 baseline 和 deferred 项作为 test-matrix.md 生成的增量输入
|
|
18
|
-
|
|
19
|
-
`docs/test-ledger/` 不存在时跳过(首次使用或无历史数据)。
|
|
14
|
+
**Test Ledger Injection (v0.12 §43.5)**: 若 `docs/test-ledger/` 存在,读取 `INDEX.md`(模块覆盖概览 coverage_status)+ `baselines/{module}.md`(已有 case 避免重复设计;Deferred Items 评估本次是否可解决),作为 test-matrix.md 生成的增量输入。不存在时跳过(首次使用或无历史数据)。
|
|
20
15
|
|
|
21
16
|
## Artifact Mapping
|
|
22
17
|
|
|
@@ -41,10 +36,12 @@ Before finalizing:
|
|
|
41
36
|
|
|
42
37
|
Must make obvious: approved behavior, out-of-scope, constraints, batches, test obligations, review gates, and conditions that force a rewind to planning. Prefer compression over repeating planning details.
|
|
43
38
|
|
|
44
|
-
## Test Matrix Generation (v0.12 §42)
|
|
39
|
+
## Test Matrix Generation (v0.12 §42, v0.13 §52 B1 强制化)
|
|
45
40
|
|
|
46
41
|
`test-matrix.md` 是 `execution-contract.md` 的**附属产物**(不是独立第 6 核心产物),在 contract 的 `## Test Matrix` 段引用。
|
|
47
42
|
|
|
43
|
+
**full workflow 必须生成(MUST,非建议)**:契约声明 `## Test Matrix` 段 ∧ `test-matrix.md` 存在非空 ∧ `tf state rebuild` 捕获 hash。缺契约段或矩阵文件 → `test-matrix-ready` 阻断进入 executing(入口不查 hash);三者任一缺失或 hash 不一致 → `test-matrix-complete` 阻断 closing(v0.13 §48/§49;C1-domain-policy 事件教训:矩阵生成曾是建议级,导致零测试通过全部门禁)。确无测试需求的 change 只有一条出路:显式 skip 并留理由(见 Hotfix/Tweak Exemption)。
|
|
44
|
+
|
|
48
45
|
### Generation Protocol
|
|
49
46
|
|
|
50
47
|
1. **输入来源**:specs/(Scenario + Unit/Integration 标签)、tasks.md(batch + file structure)、test-strategy skill(design_method 规则)、test-ledger baselines(增量输入)
|
|
@@ -101,14 +98,14 @@ See `test-matrix.md` for the full test case matrix (附属产物, 独立 hash).
|
|
|
101
98
|
|
|
102
99
|
### Hotfix/Tweak Exemption
|
|
103
100
|
|
|
104
|
-
hotfix/tweak workflow 不生成 test-matrix.md(guard
|
|
101
|
+
hotfix/tweak workflow 不生成 test-matrix.md(guard 豁免);full workflow 确无自动化测试需求的特例(纯文档/纯配置)同理。设置显式 skip + 理由(v0.13 §48.2 可审计豁免,**不允许静默不生成**):
|
|
105
102
|
```bash
|
|
106
|
-
tf state set <change-dir> test_matrix_skipped true
|
|
103
|
+
tf state set <change-dir> test_matrix_skipped true && tf state set <change-dir> test_matrix_skip_reason "<一句话说明>"
|
|
107
104
|
```
|
|
108
105
|
|
|
109
106
|
### GLAF4 Java 路由
|
|
110
107
|
|
|
111
|
-
当项目技术栈为 GLAF4 Java(Spring Boot / JUnit 5 / Mockito)时,提示用户是否调用 `glaf4-test:glaf4-tests`
|
|
108
|
+
当项目技术栈为 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」。
|
|
112
109
|
|
|
113
110
|
## Approval Model (DP-3)
|
|
114
111
|
|
|
@@ -127,6 +124,8 @@ Refresh if: scope changed in proposal, requirements changed in specs, constraint
|
|
|
127
124
|
|
|
128
125
|
Generate minimal contract: Intent Lock (one sentence), Task List (numbered), Approval Gate (DP-3). Skip Scope Fence, Build Rules, Review Gates, Test Evidence. Still requires DP-3 approval.
|
|
129
126
|
|
|
127
|
+
**非存量 hotfix/tweak 必须设置显式 skip(v0.13 §50)**:hotfix/tweak 不生成矩阵,但 closing 的 tests-passing 门禁仍考核非存量 change——生成最小契约后立即执行 Hotfix/Tweak Exemption 段的两条 `tf state set`(`test_matrix_skipped=true` + 理由),否则 release-archivist 轻量收尾会被门禁阻断。
|
|
128
|
+
|
|
130
129
|
## Guardrails
|
|
131
130
|
|
|
132
131
|
- **状态字段禁写(v0.30.0)**:仅写本 skill 的 `dp_3_*` 决策字段;**MUST NOT** 修改 `state`/`workflow` 核心字段——状态转换由主代理经 `tf state transition` 执行。`tf state init` 只创建状态文件,不改 `state` 值(来源:workflow-feedback 2026-08-01)。
|
|
@@ -29,7 +29,15 @@ Claiming work is complete without verification is dishonesty, not efficiency. Be
|
|
|
29
29
|
## Verification Steps
|
|
30
30
|
|
|
31
31
|
### Step 1: Test Suite
|
|
32
|
-
Run full test suite. Record total/passed/failed/skipped. Zero failures = PASS
|
|
32
|
+
Run full test suite fresh (e.g. `mvn test` / `npm test`). Record total/passed/failed/skipped. **Zero failures AND total > 0 = PASS.**
|
|
33
|
+
|
|
34
|
+
**⛔ 空真拒绝(v0.13 §50,C1-domain-policy 事件修复)**:0 个测试时"0 失败"在逻辑上为真,但不是证据——**0 tests ≠ PASS**。发现测试套件为空或项目无测试文件时:要么回 build-executor 补测试(按 test-matrix.md),要么该 change 本应在 bridging 阶段显式 skip(`test_matrix_skipped=true` + 理由);两种情况都不得写 pass。
|
|
35
|
+
|
|
36
|
+
**程序化记录(v0.13 §50)**:跑完测试套件后,把 runner 输出落盘并用命令写入证据(`test_result` 已不可手工 `tf state set`):
|
|
37
|
+
```bash
|
|
38
|
+
tf test record <change-dir> --from <runner-output-file> # 支持 maven-surefire/jest/pytest,auto 识别
|
|
39
|
+
```
|
|
40
|
+
该命令写入结构化 `test_result`(recorded-by=tf-test-record)+ 证据文件(`.superpowers/test-evidence/`),是 `tests-passing` 门禁对非存量 change 的唯一合法凭证。
|
|
33
41
|
|
|
34
42
|
### Step 2: Completeness
|
|
35
43
|
Compare contract batches against actual diff. Every SHALL/MUST must have implementation evidence. Missing = Critical severity.
|
|
@@ -45,7 +53,9 @@ If `test-matrix.md` exists:
|
|
|
45
53
|
- Matrix coverage ≥ 90% with reasonable deferred items → **CONDITIONAL (WARN)**
|
|
46
54
|
- Matrix coverage < 90% OR unexplained gaps → **FAIL**
|
|
47
55
|
|
|
48
|
-
If `test-matrix.md` does NOT exist
|
|
56
|
+
If `test-matrix.md` does NOT exist(v0.13 §52 B2 修订):
|
|
57
|
+
- legacy change(.team-flow.yaml 无 `schema_version` 字段)或 `test_matrix_skipped=true` → **SKIP**
|
|
58
|
+
- 非存量且未 skip → **FAIL**(矩阵缺失即验证失败;删除静默 SKIP——C1-domain-policy 事件教训)
|
|
49
59
|
|
|
50
60
|
Add a report row: `| Test Matrix | PASS/FAIL/WARN/SKIP | [reconciliation summary] |`
|
|
51
61
|
|
|
@@ -82,7 +92,7 @@ If an `e2e/` suite exists for the change, fold its report into verification (see
|
|
|
82
92
|
|
|
83
93
|
## Final Checks
|
|
84
94
|
|
|
85
|
-
**⛔ 状态字段禁写(v0.30.0)**:仅写本 skill 的 `dp_6_*`/`dp_7_*`
|
|
95
|
+
**⛔ 状态字段禁写(v0.30.0 + v0.13 §50)**:仅写本 skill 的 `dp_6_*`/`dp_7_*` 决策字段;测试证据经 `tf test record` 程序化写入(不再手工 set `test_result`);**MUST NOT** 修改 `.team-flow.yaml` 的 `state`/`workflow` 核心字段——closing 转换由主代理经 `tf state transition` 执行(来源:workflow-feedback 2026-08-01)。
|
|
86
96
|
|
|
87
97
|
- Tests passing? (cite command and output)
|
|
88
98
|
- All batches complete? (cite batch status)
|
|
@@ -98,12 +108,7 @@ tf state set <change-dir> dp_6_timestamp $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
|
98
108
|
```
|
|
99
109
|
If FAIL, do NOT proceed to DP-7. Route back or ask about abandonment.
|
|
100
110
|
|
|
101
|
-
|
|
102
|
-
`executing → closing` transition is allowed (the guard accepts either
|
|
103
|
-
`test_result: pass` or a `dp_6_result` starting with `pass`):
|
|
104
|
-
```bash
|
|
105
|
-
tf state set <change-dir> test_result pass
|
|
106
|
-
```
|
|
111
|
+
**测试门禁凭证(v0.13 §50 修订)**:`dp_6_result` 只是决策点记录,不再是 `tests-passing` 门禁的证据(BUG-A 等价通道仅对存量 change 保留)。非存量 change 的 `executing → closing` 放行凭证是 Step 1 中 `tf test record` 写入的结构化 `test_result`(+ 证据文件)。若 Step 1 尚未执行 `tf test record`,先补跑测试套件并记录,再守 DP-6。
|
|
107
112
|
|
|
108
113
|
### DP-7 (Archive Confirmation)
|
|
109
114
|
```bash
|
|
@@ -208,6 +213,8 @@ This check is advisory — skip if the user declines. If the user reports issues
|
|
|
208
213
|
|
|
209
214
|
Verify files exist and are non-empty, run `node --check` on code files, skip 5-step verification. Still record DP-6 and DP-7.
|
|
210
215
|
|
|
216
|
+
**⚠️ tests-passing 门禁不因轻量收尾而豁免(v0.13 §50)**:hotfix/tweak 的 `executing→closing` guard 同样挂 `tests-passing`。非存量 change 仅记 DP-6/DP-7 不足以放行——必须二选一:① `test_matrix_skipped=true` + `test_matrix_skip_reason` 已由 contract-builder 设置(hotfix/tweak 常规路径,先 `tf state get` 核实);② 有代码变更时补跑测试套件并 `tf test record <change-dir> --from <输出>`。
|
|
217
|
+
|
|
211
218
|
## Exception Handling
|
|
212
219
|
|
|
213
220
|
- **Parse failures**: Report exact file and section
|
|
@@ -95,6 +95,7 @@ argument-hint: "[下一个会话的关注点描述]"
|
|
|
95
95
|
3. **必须脱敏**:所有凭据值替换为 `${PLACEHOLDER}`
|
|
96
96
|
4. **不替代状态恢复**:`.team-flow/` yaml 的跨 session 恢复是自动的,handoff 补充非结构化隐性知识
|
|
97
97
|
5. **handoffs/ 目录加入 .gitignore**:交接文档是临时性质
|
|
98
|
+
6. **重构报告必须现场核对(v0.13 §52 B5)**:凡从会话转录(JSONL)重构的执行报告/交接摘要,其中引用的文件、大小、行数、时间戳、决策点记录,**采信前必须对照磁盘现场与状态文件核对**;无法核对的条目显式标注"未现场核对"。教训:C1-domain-policy 执行报告存在 10+ 处重构失真(虚构文件与数字),未核对的报告不能作为根因证据
|
|
98
99
|
|
|
99
100
|
## Output
|
|
100
101
|
|
|
@@ -54,12 +54,14 @@ Validate mode against artifact content. If hotfix/tweak criteria not met → upg
|
|
|
54
54
|
|
|
55
55
|
## Routing Rules
|
|
56
56
|
|
|
57
|
+
> **⚠️ 路由优先级(硬规则)**:路由按文档顺序从前到后评估,**第一个匹配的路由必须执行**。关键门控:`arch_design_decision` 为 `null` 时 MUST 路由到 `architecture-design`,即使后续路由(spec-writer / contract-builder / build-executor)的其他条件也满足——架构判定是 exploring→specifying 的硬前置,不可跳过。hotfix/tweak 走快速转换,guard 不挂 `arch-design` 维度(`exploring:bridging`/`exploring:approved-for-build` 为空维度),但 **SOP 层仍须过 architecture-design 判断门,不豁免**(v0.9 §26,见 Fast-Path Routing;hotfix 可能正是架构缺陷导致)——判断结果由 architecture-design 子代理写入 `arch_design_decision`,无"自动写 skipped"行为。
|
|
58
|
+
|
|
57
59
|
### Sub-agent Dispatch Protocol (v0.30.0)
|
|
58
60
|
|
|
59
61
|
产出型路由(spec-writer / contract-builder / build-executor / release-archivist)通过 `Agent(subagent_type: "team-flow:<name>")` **dispatch 子代理**执行——agent 定义经 `skills:` 预加载对应 SKILL.md,主代理**不得**凭记忆手写产物格式 prompt。need-explorer 为交互式澄清,**主进程执行**(需与用户对话;其 agent 定义 `team-flow:need-explorer` 仅作可 dispatch 后备)。
|
|
60
62
|
|
|
61
63
|
- **记录 agentId**:dispatch 后立即记录子代理 ID;修复一律 SendMessage 恢复原子代理(禁启动新子代理,见 v0.29.0 §37)
|
|
62
|
-
- **返回即验证(validation gate)**:子代理返回产物后**立即** `tf validate <change-dir>`;FAIL → SendMessage
|
|
64
|
+
- **返回即验证(validation gate)**:子代理返回产物后**立即** `tf validate <change-dir>`;FAIL → SendMessage 回**原**子代理修复,通过后才能继续/转换。把格式失败拦截在返回时,而非状态转换时(避免浪费整次执行后再失败)。**build-executor 附加验证(v0.13 §52 B3,SDD/full 模式)**:返回的 diff 中必须包含测试文件(src/test/ 或项目测试目录);零测试文件的实现返回一律 BLOCK,SendMessage 回原子代理按 test-matrix.md / TDD Iron Law 补齐——不得以"手动验证"替代(C1-domain-policy 事件教训)。例外:已显式记录 `test_matrix_skipped=true` + 理由的 change(纯文档/配置类)不要求测试文件
|
|
63
65
|
- **结果协议**:子代理终态须标注 `FINAL VERDICT: <DONE|BLOCKED|FAIL>`(审查类用 PASS/PASS_WITH_WARNINGS/FAIL);**SendMessage 报告为权威结果**,task-notification.result 仅内部元数据
|
|
64
66
|
- **子代理状态边界**:子代理 MUST NOT 修改 `.team-flow.yaml` 的 `state`/`workflow` 核心字段(状态转换是主代理专有职责,经 `tf state transition` 执行),只写自己的 `dp_N_*` 决策字段
|
|
65
67
|
|
|
@@ -84,10 +86,10 @@ Full protocol in `references/routing-rules.md`「Route to architecture-design」
|
|
|
84
86
|
Guard: `tf runtime guard check <dir> exploring specifying --json` → fail = BLOCK. **arch_design_decision must not be null** → fail = BLOCK (v0.9 §26). User knows what they want, artifacts missing/incomplete.
|
|
85
87
|
|
|
86
88
|
### Route to contract-builder (dispatch sub-agent)
|
|
87
|
-
Guard: `... check <dir> specifying bridging --json` → fail = BLOCK. Artifacts exist, implementation requested, contract missing/stale. Include `DP-3: 契约批准`.
|
|
89
|
+
Guard: `... check <dir> specifying bridging --json` → fail = BLOCK. Artifacts exist, implementation requested, contract missing/stale. Include `DP-3: 契约批准`. **full workflow 必产 test-matrix.md(v0.13 §52 B1)**:契约 `## Test Matrix` 段 + 非空矩阵文件 + `tf state rebuild` 捕获 hash;确无测试需求只能显式 skip + 理由(`test_matrix_skipped=true` + `test_matrix_skip_reason`)。GLAF4 Java 项目矩阵生成的工具选择见 `references/routing-rules.md`「Route to glaf4-tests」。
|
|
88
90
|
|
|
89
91
|
### Route to build-executor (dispatch sub-agent)
|
|
90
|
-
Contract exists and approved, contract matches artifacts. Include `DP-4: 执行模式选择`: propose waves, run `tf execution recommend <change-dir> [--wave ...]`, show the user every available mode plus evidence and the recommendation, then obtain a clear selection. The command saves a current receipt; before the first implementation edit, `build-executor` must run `tf execution plan <change-dir> --mode <selected> --confirm ...` (and `--acknowledge-recommendation` when the selected mode differs from the recommendation) using matching artifacts, contract, and waves, then `tf execution show <change-dir> --json`; report the saved revision, selected mode, recommendation alignment, ordered waves, and actual concurrent-dispatch capability. A revision must repeat recommend and confirmation. Do not transition to `executing` until `show` reports `current: true`; then run `... check <dir> approved-for-build executing --json` → fail = BLOCK.
|
|
92
|
+
Contract exists and approved, contract matches artifacts. Include `DP-4: 执行模式选择`: propose waves, run `tf execution recommend <change-dir> [--wave ...]`, show the user every available mode plus evidence and the recommendation, then obtain a clear selection. The command saves a current receipt; before the first implementation edit, `build-executor` must run `tf execution plan <change-dir> --mode <selected> --confirm ...` (and `--acknowledge-recommendation` when the selected mode differs from the recommendation) using matching artifacts, contract, and waves, then `tf execution show <change-dir> --json`; report the saved revision, selected mode, recommendation alignment, ordered waves, and actual concurrent-dispatch capability. A revision must repeat recommend and confirmation. Do not transition to `executing` until `show` reports `current: true`; then run `... check <dir> approved-for-build executing --json` → fail = BLOCK. **v0.13 §49 门禁前移**:该 guard 含 `test-matrix-ready` 维度——full 模式非存量 change 必须"带着矩阵开工"(矩阵存在非空 OR 显式 skip 附理由);FAIL 时回 bridging 让 contract-builder 补矩阵,或按指引显式 skip,禁止绕过。
|
|
91
93
|
|
|
92
94
|
### Route to bug-investigator
|
|
93
95
|
Execution hit blockage: test failure, unexpected behavior, build error, task cannot proceed. After debugging, route back to build-executor.
|
|
@@ -308,19 +308,23 @@ Inference: **hotfix** (≤2 tasks, ≤2 files, no schema/API/new modules), **twe
|
|
|
308
308
|
## Post-transition
|
|
309
309
|
💡 `tf inject <change-dir>` to update phase-guard artifacts.
|
|
310
310
|
|
|
311
|
-
## Route to glaf4-tests (v0.12 §46, v0.31.0)
|
|
311
|
+
## Route to glaf4-tests (v0.12 §46, v0.31.0, v0.13 §52 B3 归位)
|
|
312
312
|
|
|
313
|
-
|
|
313
|
+
**归位说明(v0.13)**:本段的触发主体是 **bridging 阶段的 contract-builder 子代理**(矩阵生成者),workflow-start 主路由表不包含 glaf4-tests 条目——放在本文件仅作 bridging 期参考。
|
|
314
|
+
|
|
315
|
+
**强制性澄清(v0.13 §52 B1)**:full workflow 生成 test-matrix.md 本身是**必选动作**(`test-matrix-ready`/`test-matrix-complete` 门禁强制);**可选的是工具选择**——GLAF4 Java 项目可选用 glaf4-tests 流水线做精细矩阵设计,非 GLAF4 项目用内置 test-strategy skill。两条路都必须产出非空 `test-matrix.md` + contract `## Test Matrix` 段 + `tf state rebuild`。
|
|
316
|
+
|
|
317
|
+
**触发条件**:`project_type == glaf4-java`(Spring Boot / JUnit 5 / Mockito)。
|
|
314
318
|
|
|
315
319
|
**协议**:
|
|
316
320
|
1. contract-builder 检测项目技术栈(读取 `pom.xml` / `build.gradle` 判断是否 GLAF4 Java)
|
|
317
321
|
2. GLAF4 项目 → 提示用户:「检测到 GLAF4 Java 项目,是否调用 `glaf4-test:glaf4-tests` 做精细测试矩阵设计?」
|
|
318
322
|
3. 用户同意 → 调用 `glaf4-test:glaf4-tests-design` 生成 `test-matrix.json`
|
|
319
323
|
4. `glaf4-tests-validate` 校验矩阵完整性
|
|
320
|
-
5. `
|
|
324
|
+
5. `tf test-matrix-export <test-matrix.json> <change-dir>/test-matrix.md` 转换为 team-flow 格式
|
|
321
325
|
6. 用户确认(轻量版 DP-A)
|
|
322
|
-
7.
|
|
326
|
+
7. contract 中添加 `## Test Matrix` 段引用 + `tf state rebuild` 捕获 test_matrix_hash
|
|
323
327
|
|
|
324
|
-
**非 GLAF4 项目**:contract-builder
|
|
328
|
+
**非 GLAF4 项目**:contract-builder 使用预加载的 test-strategy skill 直接生成 `test-matrix.md`(同等强制)。
|
|
325
329
|
|
|
326
|
-
|
|
330
|
+
**用户拒绝 glaf4-tests 时**:回退内置 test-strategy 生成(不是跳过矩阵)。确无测试需求的特例走显式 skip + 理由。
|