@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.
Files changed (90) hide show
  1. package/.claude/always/phase-guard.md +1 -1
  2. package/.claude-plugin/marketplace.json +3 -3
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +2 -2
  5. package/.cursor-plugin/marketplace.json +2 -2
  6. package/.cursor-plugin/plugin.json +2 -2
  7. package/.github/plugin/marketplace.json +2 -2
  8. package/AGENTS.md +3 -3
  9. package/CHANGELOG.md +69 -0
  10. package/GEMINI.md +1 -1
  11. package/HANDOFF.md +1 -1
  12. package/INSTALL.md +1 -1
  13. package/README.md +2 -2
  14. package/agents/contract-builder.md +6 -1
  15. package/docs/README_en.md +1 -1
  16. package/docs/solutions/INDEX.md +2 -0
  17. package/docs/solutions/cross-phase/2026-08-01-no-summary.md +17 -0
  18. package/docs/solutions/cross-phase/2026-08-03-no-summary.md +17 -0
  19. package/gemini-extension.json +2 -2
  20. package/hooks/pre-tool-use-guard +43 -2
  21. package/hooks/session-start +2 -2
  22. package/llms.txt +1 -1
  23. package/package.json +6 -2
  24. package/plugin.json +1 -1
  25. package/scripts/guard/checks/test-gate-exemptions.mjs +53 -0
  26. package/scripts/guard/checks/test-matrix-complete.mjs +54 -30
  27. package/scripts/guard/checks/test-matrix-ready.mjs +60 -0
  28. package/scripts/guard/checks/tests-passing.mjs +112 -24
  29. package/scripts/guard/guard.mjs +11 -2
  30. package/scripts/lib/cmd-doctor.mjs +43 -1
  31. package/scripts/lib/cmd-execution.mjs +24 -1
  32. package/scripts/lib/cmd-state.mjs +28 -5
  33. package/scripts/lib/cmd-validate.mjs +42 -0
  34. package/scripts/lib/cmd-version.mjs +26 -21
  35. package/scripts/lib/execution-plan.mjs +23 -0
  36. package/scripts/lib/state-loader.mjs +16 -2
  37. package/scripts/lib/test-record.mjs +212 -0
  38. package/scripts/team-flow.mjs +3 -0
  39. package/skills/contract-builder/SKILL.md +9 -10
  40. package/skills/release-archivist/SKILL.md +16 -9
  41. package/skills/session-handoff/SKILL.md +1 -0
  42. package/skills/workflow-start/SKILL.md +5 -3
  43. package/skills/workflow-start/references/routing-rules.md +10 -6
  44. package/tests/e2e.test.mjs +0 -266
  45. package/tests/lib/cmd-audit.test.mjs +0 -202
  46. package/tests/lib/cmd-checkpoint.test.mjs +0 -98
  47. package/tests/lib/cmd-config.test.mjs +0 -101
  48. package/tests/lib/cmd-doctor.test.mjs +0 -450
  49. package/tests/lib/cmd-execution.test.mjs +0 -647
  50. package/tests/lib/cmd-handoff.test.mjs +0 -137
  51. package/tests/lib/cmd-inject.test.mjs +0 -218
  52. package/tests/lib/cmd-install-workbuddy.test.mjs +0 -113
  53. package/tests/lib/cmd-install-zcode.test.mjs +0 -42
  54. package/tests/lib/cmd-isolate.test.mjs +0 -68
  55. package/tests/lib/cmd-list.test.mjs +0 -127
  56. package/tests/lib/cmd-runtime.test.mjs +0 -83
  57. package/tests/lib/cmd-state-missing.test.mjs +0 -65
  58. package/tests/lib/cmd-state.test.mjs +0 -365
  59. package/tests/lib/cmd-sync-paths.test.mjs +0 -84
  60. package/tests/lib/cmd-validate-paths.test.mjs +0 -90
  61. package/tests/lib/config-loader.test.mjs +0 -175
  62. package/tests/lib/ensure-branch.test.mjs +0 -110
  63. package/tests/lib/execution-control-plane.test.mjs +0 -168
  64. package/tests/lib/execution-plan.test.mjs +0 -474
  65. package/tests/lib/guard-compound-captured.test.mjs +0 -92
  66. package/tests/lib/guard-specs-merged.test.mjs +0 -110
  67. package/tests/lib/guard-tests-passing.test.mjs +0 -114
  68. package/tests/lib/guard-transitions.test.mjs +0 -262
  69. package/tests/lib/guard.test.mjs +0 -613
  70. package/tests/lib/hash.test.mjs +0 -138
  71. package/tests/lib/infer-workflow.test.mjs +0 -161
  72. package/tests/lib/install-git-hooks.test.mjs +0 -20
  73. package/tests/lib/marketplace-release-docs.test.mjs +0 -31
  74. package/tests/lib/minimality-discipline.test.mjs +0 -37
  75. package/tests/lib/model-profiles-docs.test.mjs +0 -33
  76. package/tests/lib/node20-compatibility.test.mjs +0 -73
  77. package/tests/lib/node20-test-entry.test.mjs +0 -17
  78. package/tests/lib/platform-runtime-distribution.test.mjs +0 -157
  79. package/tests/lib/raw-mode-smoke.test.mjs +0 -42
  80. package/tests/lib/sdd-overlay.test.mjs +0 -118
  81. package/tests/lib/solutions-capture.test.mjs +0 -108
  82. package/tests/lib/solutions-index-gen.test.mjs +0 -147
  83. package/tests/lib/solutions-inject.test.mjs +0 -115
  84. package/tests/lib/solutions-promote.test.mjs +0 -200
  85. package/tests/lib/spec-paths.test.mjs +0 -80
  86. package/tests/lib/state-loader.test.mjs +0 -251
  87. package/tests/lib/token-baseline.test.mjs +0 -72
  88. package/tests/lib/token-rules.test.mjs +0 -94
  89. package/tests/lib/verify-marketplace-release.test.mjs +0 -128
  90. 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?)0\.\d+\.\d+(`?)/g, replacement: '$10.%MINOR%.%PATCH%$2' },
23
- { file: 'INSTALL.md', pattern: /(当前发布版本:\*\*v)0\.\d+\.\d+(\*\*)/g, replacement: '$10.%MINOR%.%PATCH%$2' },
24
- { file: 'docs/README_en.md', pattern: /(Current: `v)0\.\d+\.\d+(`)/g, replacement: '$10.%MINOR%.%PATCH%$2' },
25
- { file: 'hooks/session-start', pattern: /(# v)0\.\d+\.\d+(: conditional injection)/g, replacement: '$10.%MINOR%.%PATCH%$2' },
26
- { file: 'llms.txt', pattern: /(Current version: v)0\.\d+\.\d+(\.)/g, replacement: '$10.%MINOR%.%PATCH%$2' },
27
- { file: '.claude/always/phase-guard.md', pattern: /(# team-flow v)0\.\d+\.\d+( \|)/g, replacement: '$10.%MINOR%.%PATCH%$2' },
28
- { file: 'GEMINI.md', pattern: /(# team-flow v)0\.\d+\.\d+( \|)/g, replacement: '$10.%MINOR%.%PATCH%$2' },
29
- { file: 'skills/workflow-start/SKILL.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)0\.\d+\.\d+( tf)/g, replacement: '$10.%MINOR%.%PATCH%$2' },
30
- { file: 'skills/need-explorer/SKILL.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)0\.\d+\.\d+( tf)/g, replacement: '$10.%MINOR%.%PATCH%$2' },
31
- { file: 'skills/spec-writer/SKILL.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)0\.\d+\.\d+( tf)/g, replacement: '$10.%MINOR%.%PATCH%$2' },
32
- { file: 'skills/contract-builder/SKILL.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)0\.\d+\.\d+( tf)/g, replacement: '$10.%MINOR%.%PATCH%$2' },
33
- { file: 'skills/build-executor/SKILL.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)0\.\d+\.\d+( tf)/g, replacement: '$10.%MINOR%.%PATCH%$2' },
34
- { file: 'skills/build-executor/implementer-prompt.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)0\.\d+\.\d+( tf)/g, replacement: '$10.%MINOR%.%PATCH%$2' },
35
- { file: 'skills/build-executor/task-reviewer-prompt.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)0\.\d+\.\d+( tf)/g, replacement: '$10.%MINOR%.%PATCH%$2' },
36
- { file: 'skills/code-reviewer/SKILL.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)0\.\d+\.\d+( tf)/g, replacement: '$10.%MINOR%.%PATCH%$2' },
37
- { file: 'skills/code-reviewer/code-reviewer-prompt.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)0\.\d+\.\d+( tf)/g, replacement: '$10.%MINOR%.%PATCH%$2' },
38
- { file: 'skills/bug-investigator/SKILL.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)0\.\d+\.\d+( tf)/g, replacement: '$10.%MINOR%.%PATCH%$2' },
39
- { file: 'skills/release-archivist/SKILL.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)0\.\d+\.\d+( tf)/g, replacement: '$10.%MINOR%.%PATCH%$2' },
40
- { file: 'skills/spec-merger/SKILL.md', pattern: /(npx --yes --package @xulthekl\/team-flow@)0\.\d+\.\d+( tf)/g, replacement: '$10.%MINOR%.%PATCH%$2' },
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
+ }
@@ -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` 做精细矩阵设计。用户同意后由 glaf4-tests design stage 生成 `test-matrix.json`,再转换为 team-flow 格式的 `test-matrix.md`。
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 **SKIP** (legacy change compatibility).
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_*` 决策字段与 `test_result`;**MUST NOT** 修改 `.team-flow.yaml` 的 `state`/`workflow` 核心字段——closing 转换由主代理经 `tf state transition` 执行(来源:workflow-feedback 2026-08-01)。
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
- After recording a PASS outcome, also record it as the verification gate so the
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
- **触发条件**:`project_type == glaf4-java`(Spring Boot / JUnit 5 / Mockito)AND contract-builder 需要生成 test-matrix。
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. `scripts/lib/test-matrix-export.mjs` 转换为 team-flow 格式的 `test-matrix.md`
324
+ 5. `tf test-matrix-export <test-matrix.json> <change-dir>/test-matrix.md` 转换为 team-flow 格式
321
325
  6. 用户确认(轻量版 DP-A)
322
- 7. 写入 `test-matrix.md`,contract 中添加 `## Test Matrix` 段引用
326
+ 7. contract 中添加 `## Test Matrix` 段引用 + `tf state rebuild` 捕获 test_matrix_hash
323
327
 
324
- **非 GLAF4 项目**:contract-builder 使用内置 test-strategy skill 直接生成 `test-matrix.md`。
328
+ **非 GLAF4 项目**:contract-builder 使用预加载的 test-strategy skill 直接生成 `test-matrix.md`(同等强制)。
325
329
 
326
- **设计决策**:glaf4-tests 对接为**可选增强**,不是必经路径。
330
+ **用户拒绝 glaf4-tests 时**:回退内置 test-strategy 生成(不是跳过矩阵)。确无测试需求的特例走显式 skip + 理由。