@xulthekl/team-flow 0.48.0 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/always/phase-guard.md +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/marketplace.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.github/plugin/marketplace.json +2 -2
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +40 -0
- package/GEMINI.md +1 -1
- package/INSTALL.md +2 -2
- package/README.md +3 -3
- package/docs/README_en.md +1 -1
- package/docs/solutions/INDEX.md +0 -14
- package/gemini-extension.json +1 -1
- package/hooks/session-start +2 -2
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/scripts/guard/checks/compound-captured.mjs +6 -3
- package/scripts/lib/cmd-runtime.mjs +1 -0
- package/scripts/lib/execution-plan.mjs +72 -25
- package/scripts/lib/execution-recommendation.mjs +4 -18
- package/scripts/lib/hash.mjs +41 -2
- package/scripts/lib/prototype-sync.mjs +2 -1
- package/scripts/lib/sdd-overlay.mjs +3 -2
- package/scripts/lib/solutions-capture.mjs +2 -1
- package/scripts/lib/solutions-index-gen.mjs +2 -1
- package/scripts/lib/solutions-inject.mjs +2 -1
- package/scripts/lib/solutions-promote.mjs +90 -12
- package/scripts/lib/test-record.mjs +67 -21
- package/skills/build-executor/SKILL.md +8 -2
- package/skills/ce-compound/references/promotion-rules.md +1 -1
- package/skills/ce-compound/references/schema.yaml +10 -1
- package/skills/contract-builder/references/glaf4-delegation.md +5 -3
- package/skills/release-archivist/SKILL.md +7 -8
- package/skills/release-archivist/references/closing-procedures.md +7 -2
- package/skills/workflow-start/SKILL.md +2 -1
- package/templates/learnings.md +51 -0
- package/docs/solutions/cross-phase/2026-07-28-no-summary.md +0 -17
- package/docs/solutions/cross-phase/2026-07-29-no-summary.md +0 -17
- package/docs/solutions/cross-phase/2026-07-30-no-summary.md +0 -17
- package/docs/solutions/cross-phase/2026-07-31-no-summary.md +0 -17
- package/docs/solutions/cross-phase/2026-08-01-no-summary.md +0 -17
- package/docs/solutions/cross-phase/2026-08-03-no-summary.md +0 -17
- package/docs/solutions/cross-phase/2026-08-04-no-summary.md +0 -17
- package/docs/solutions/cross-phase/2026-08-05-no-summary.md +0 -17
- package/docs/solutions/cross-phase/2026-08-06-no-summary.md +0 -17
- package/docs/solutions/cross-phase/2026-08-07-no-summary.md +0 -17
- package/docs/solutions/cross-phase/2026-08-17-no-summary.md +0 -17
- package/docs/solutions/cross-phase/2026-08-18-no-summary.md +0 -17
- package/docs/solutions/cross-phase/2026-08-19-no-summary.md +0 -17
- package/docs/solutions/cross-phase/2026-08-21-no-summary.md +0 -17
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { readFileSync, writeFileSync, readdirSync, statSync, existsSync } from 'node:fs';
|
|
17
17
|
import { join } from 'node:path';
|
|
18
|
+
import { pathToFileURL } from 'node:url';
|
|
18
19
|
|
|
19
20
|
const PHASES = ['prd', 'plan', 'prototype', 'spec', 'build', 'review', 'cross-phase'];
|
|
20
21
|
const SEVERITY_ORDER = { high: 0, medium: 1, low: 2 };
|
|
@@ -104,7 +105,7 @@ export function run(args = {}) {
|
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
// CLI 直接执行
|
|
107
|
-
if (process.argv[1]
|
|
108
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
108
109
|
const args = {};
|
|
109
110
|
const dirIdx = process.argv.indexOf('--dir');
|
|
110
111
|
if (dirIdx > -1 && process.argv[dirIdx + 1]) args.dir = process.argv[dirIdx + 1];
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import { readFileSync, existsSync } from 'node:fs';
|
|
16
16
|
import { join } from 'node:path';
|
|
17
|
+
import { pathToFileURL } from 'node:url';
|
|
17
18
|
|
|
18
19
|
const SEVERITY_ORDER = { high: 0, medium: 1, low: 2 };
|
|
19
20
|
|
|
@@ -66,7 +67,7 @@ export function run(args = {}) {
|
|
|
66
67
|
return { entries: top5 };
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
if (process.argv[1]
|
|
70
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
70
71
|
const args = {};
|
|
71
72
|
for (let i = 2; i < process.argv.length; i += 2) {
|
|
72
73
|
const key = process.argv[i]?.replace('--', '');
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* 用法:tf solutions promote <change-dir> [--dir <solutions-dir>]
|
|
7
7
|
*
|
|
8
8
|
* 功能:
|
|
9
|
-
* 1. 读取 specs/<cap
|
|
9
|
+
* 1. 读取 change 根 learnings.md(v0.49.0 §83.3.5:统一路径——此前注释误写 specs/<cap>/,
|
|
10
|
+
* 与本文件第 58 行的实际实现及 compound-captured guard 均不一致)
|
|
10
11
|
* 2. 逐条检查晋升条件(severity ≥ medium 且 type = pitfall/pattern)
|
|
11
12
|
* 3. 检查与全局 INDEX 是否重复(domain+type 匹配)
|
|
12
13
|
* 4. 符合条件 → 写入全局 docs/solutions/
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
|
|
20
21
|
import { readFileSync, writeFileSync, existsSync, appendFileSync, mkdirSync } from 'node:fs';
|
|
21
22
|
import { join, basename } from 'node:path';
|
|
23
|
+
import { pathToFileURL } from 'node:url';
|
|
22
24
|
|
|
23
25
|
function parseFrontmatter(content) {
|
|
24
26
|
// learnings.md 中每个条目以 `## 标题` 开头,frontmatter 紧跟标题行之后;
|
|
@@ -43,7 +45,59 @@ function parseLearnings(content) {
|
|
|
43
45
|
const title = lines[0]?.trim() || '';
|
|
44
46
|
const fm = parseFrontmatter(s);
|
|
45
47
|
return { title, fm, body: s.trim() };
|
|
46
|
-
|
|
48
|
+
// v0.49.0 §83.3.5:排除文件级标题(`# Session Learnings` 会被 split 成首个 section),
|
|
49
|
+
// 否则它会被当成一条无 frontmatter 的条目计入 skipped
|
|
50
|
+
}).filter(e => e.title && e.title !== 'Learnings' && !e.title.startsWith('#'));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* v0.49.0 §83.3.5:把解析出的条目重整为可写入全局文件的形式 ——
|
|
55
|
+
* ① 补回 `## ` 标题前缀(parseLearnings 按 `^## ` 分割,body 首行不含标记)
|
|
56
|
+
* ② 剔除 body 中已含的原 frontmatter 块(原实现直接写 body → 产出双 frontmatter)
|
|
57
|
+
*/
|
|
58
|
+
function renderEntryBody(learning) {
|
|
59
|
+
const body = learning.body
|
|
60
|
+
.replace(/^[^\n]*\n?/, '')
|
|
61
|
+
.replace(/(?:^|\n)---\n[\s\S]*?\n---[ \t]*\n?/, '\n')
|
|
62
|
+
.trim();
|
|
63
|
+
return `## ${learning.title}\n\n${body}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const SEVERITY_LADDER = { low: 'medium', medium: 'high' };
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* v0.49.0 §83.3.5:重复命中时按 promotion-rules.md 承诺真正落盘(原实现只 `updated++`,
|
|
70
|
+
* 不更新 INDEX 行、不标记条目)——升级 INDEX 行 severity + 条目文件 confirmed 计数。
|
|
71
|
+
*/
|
|
72
|
+
function confirmExistingEntry(solutionsDir, indexContent, domain, type) {
|
|
73
|
+
const lines = indexContent.split('\n');
|
|
74
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
75
|
+
// INDEX 列:| date | phase | domain | type | severity | summary | file |
|
|
76
|
+
const cols = lines[i].split('|').map(column => column.trim());
|
|
77
|
+
if (cols.length < 9 || cols[3] !== domain || cols[4] !== type) continue;
|
|
78
|
+
|
|
79
|
+
const currentSeverity = cols[5];
|
|
80
|
+
const nextSeverity = SEVERITY_LADDER[currentSeverity] ?? currentSeverity;
|
|
81
|
+
if (nextSeverity !== currentSeverity) {
|
|
82
|
+
lines[i] = lines[i].replace(`| ${currentSeverity} |`, `| ${nextSeverity} |`);
|
|
83
|
+
}
|
|
84
|
+
markEntryConfirmed(join(solutionsDir, cols[7]));
|
|
85
|
+
return { indexContent: lines.join('\n'), from: currentSeverity, to: nextSeverity, file: cols[7] };
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function markEntryConfirmed(filePath) {
|
|
91
|
+
if (!existsSync(filePath)) return;
|
|
92
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
93
|
+
const block = content.match(/^---\n([\s\S]*?)\n---/);
|
|
94
|
+
if (!block) return;
|
|
95
|
+
const counter = block[1].match(/^confirmed:\s*(\d+)\s*$/m);
|
|
96
|
+
const next = counter ? parseInt(counter[1], 10) + 1 : 2;
|
|
97
|
+
const frontmatter = counter
|
|
98
|
+
? block[1].replace(/^confirmed:\s*\d+\s*$/m, `confirmed: ${next}`)
|
|
99
|
+
: `${block[1]}\nconfirmed: ${next}`;
|
|
100
|
+
writeFileSync(filePath, content.replace(block[0], `---\n${frontmatter}\n---`), 'utf-8');
|
|
47
101
|
}
|
|
48
102
|
|
|
49
103
|
export function run(args = {}) {
|
|
@@ -58,7 +112,7 @@ export function run(args = {}) {
|
|
|
58
112
|
const learningsPath = join(changeDir, 'learnings.md');
|
|
59
113
|
if (!existsSync(learningsPath)) {
|
|
60
114
|
console.log(`No learnings.md found in ${changeDir}, nothing to promote`);
|
|
61
|
-
return { promoted: 0, updated: 0 };
|
|
115
|
+
return { promoted: 0, updated: 0, skipped: 0 };
|
|
62
116
|
}
|
|
63
117
|
|
|
64
118
|
const content = readFileSync(learningsPath, 'utf-8');
|
|
@@ -66,15 +120,17 @@ export function run(args = {}) {
|
|
|
66
120
|
|
|
67
121
|
if (learnings.length === 0) {
|
|
68
122
|
console.log('No learning entries found');
|
|
69
|
-
return { promoted: 0, updated: 0 };
|
|
123
|
+
return { promoted: 0, updated: 0, skipped: 0 };
|
|
70
124
|
}
|
|
71
125
|
|
|
72
126
|
// 读取全局 INDEX
|
|
73
127
|
const indexPath = join(solutionsDir, 'INDEX.md');
|
|
74
|
-
|
|
128
|
+
let indexContent = existsSync(indexPath) ? readFileSync(indexPath, 'utf-8') : '';
|
|
75
129
|
|
|
76
130
|
let promoted = 0;
|
|
77
131
|
let updated = 0;
|
|
132
|
+
// v0.49.0 §83.3.5:跳过原因可见化(原实现静默 continue,用户看不出为什么一条都没晋升)
|
|
133
|
+
const skipped = [];
|
|
78
134
|
|
|
79
135
|
for (const learning of learnings) {
|
|
80
136
|
const severity = learning.fm?.severity || 'low';
|
|
@@ -85,12 +141,27 @@ export function run(args = {}) {
|
|
|
85
141
|
// 晋升条件:severity ≥ medium 且 type = pitfall/pattern
|
|
86
142
|
const sevOk = severity === 'high' || severity === 'medium';
|
|
87
143
|
const typeOk = type === 'pitfall' || type === 'pattern';
|
|
88
|
-
if (!sevOk || !typeOk)
|
|
144
|
+
if (!sevOk || !typeOk) {
|
|
145
|
+
skipped.push({
|
|
146
|
+
title: learning.title,
|
|
147
|
+
reason: learning.fm
|
|
148
|
+
? `severity=${severity}, type=${type}(需 severity ≥ medium 且 type = pitfall|pattern)`
|
|
149
|
+
: 'no frontmatter(缺 severity/type,按默认 low/insight 处理)',
|
|
150
|
+
});
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
89
153
|
|
|
90
154
|
// 检查重复:domain+type 匹配
|
|
91
155
|
const dupRegex = new RegExp(`\\| ${domain} \\| ${type} \\|`);
|
|
92
156
|
if (dupRegex.test(indexContent)) {
|
|
93
|
-
|
|
157
|
+
const outcome = confirmExistingEntry(solutionsDir, indexContent, domain, type);
|
|
158
|
+
if (!outcome) {
|
|
159
|
+
skipped.push({ title: learning.title, reason: `duplicate ${domain}/${type} 但 INDEX 行无法定位` });
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
indexContent = outcome.indexContent;
|
|
163
|
+
writeFileSync(indexPath, indexContent, 'utf-8');
|
|
164
|
+
console.log(`Confirmed: ${domain}/${type} severity ${outcome.from} → ${outcome.to}(${outcome.file})`);
|
|
94
165
|
updated++;
|
|
95
166
|
continue;
|
|
96
167
|
}
|
|
@@ -115,22 +186,29 @@ date: ${date}
|
|
|
115
186
|
source: ${basename(changeDir)}
|
|
116
187
|
---
|
|
117
188
|
|
|
118
|
-
${learning
|
|
189
|
+
${renderEntryBody(learning)}
|
|
119
190
|
`;
|
|
120
191
|
writeFileSync(filePath, fileContent, 'utf-8');
|
|
121
192
|
|
|
122
193
|
// 追加 INDEX
|
|
123
|
-
|
|
194
|
+
const indexRow = `| ${date} | ${phase} | ${domain} | ${type} | ${severity} | ${learning.title.slice(0, 80)} | ${phase}/${fileName} |\n`;
|
|
195
|
+
appendFileSync(indexPath, indexRow, 'utf-8');
|
|
196
|
+
indexContent += indexRow;
|
|
124
197
|
|
|
125
198
|
console.log(`Promoted: ${phase}/${fileName}`);
|
|
126
199
|
promoted++;
|
|
127
200
|
}
|
|
128
201
|
|
|
129
|
-
|
|
130
|
-
|
|
202
|
+
// v0.49.0 §83.3.5:解析/晋升/确认/跳过汇总(原实现只报 promoted/updated)
|
|
203
|
+
console.log(`\nPromotion complete: ${promoted} promoted, ${updated} confirmed, ${skipped.length} skipped`);
|
|
204
|
+
if (skipped.length > 0) {
|
|
205
|
+
console.log('Skipped entries:');
|
|
206
|
+
for (const item of skipped) console.log(` - ${item.title}: ${item.reason}`);
|
|
207
|
+
}
|
|
208
|
+
return { promoted, updated, skipped: skipped.length };
|
|
131
209
|
}
|
|
132
210
|
|
|
133
|
-
if (process.env.NODE_ENV !== 'test' && process.argv[1]
|
|
211
|
+
if (process.env.NODE_ENV !== 'test' && process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
134
212
|
const args = { _: [] };
|
|
135
213
|
for (let i = 2; i < process.argv.length; i++) {
|
|
136
214
|
if (process.argv[i].startsWith('--')) {
|
|
@@ -33,30 +33,54 @@ export function parseMavenSurefire(text) {
|
|
|
33
33
|
return { total, passed: total - failures - errors - skipped, failed: failures + errors, skipped };
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
/**
|
|
37
|
-
|
|
36
|
+
/**
|
|
37
|
+
* 通用 junit XML 解析:累加 <testsuite> 的 tests/failures/errors/skipped 属性。
|
|
38
|
+
*
|
|
39
|
+
* v0.49.0 §83.3.6(来源:workflow-feedback 20260909):maven-surefire 与 pytest 的
|
|
40
|
+
* junit XML 结构同构(<testsuites>/<testsuite>)。此前该逻辑只服务 maven-surefire,
|
|
41
|
+
* pytest runner 只认 terminal summary —— 同一 CLI 对不同 runner 的结构化支持不对称。
|
|
42
|
+
*/
|
|
43
|
+
export function parseJunitXml(text) {
|
|
38
44
|
let total = 0; let failures = 0; let errors = 0; let skipped = 0;
|
|
39
45
|
let found = false;
|
|
40
|
-
for (const
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
failures += pick('failures');
|
|
52
|
-
errors += pick('errors');
|
|
53
|
-
skipped += pick('skipped');
|
|
54
|
-
}
|
|
46
|
+
for (const tag of text.matchAll(/<testsuite\b[^>]*>/g)) {
|
|
47
|
+
const attrs = tag[0];
|
|
48
|
+
const pick = (key) => {
|
|
49
|
+
const am = attrs.match(new RegExp(`\\b${key}="(\\d+)"`));
|
|
50
|
+
return am ? parseInt(am[1], 10) : 0;
|
|
51
|
+
};
|
|
52
|
+
found = true;
|
|
53
|
+
total += pick('tests');
|
|
54
|
+
failures += pick('failures');
|
|
55
|
+
errors += pick('errors');
|
|
56
|
+
skipped += pick('skipped');
|
|
55
57
|
}
|
|
56
58
|
if (!found) return null;
|
|
57
59
|
return { total, passed: total - failures - errors - skipped, failed: failures + errors, skipped };
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
/** junit XML 内容特征(用于嗅探;只检查头部,避免大报告全量扫描)。 */
|
|
63
|
+
function looksLikeJunitXml(text) {
|
|
64
|
+
return /<testsuites?\b/.test(text.slice(0, 512));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** maven surefire XML 报告目录(target/surefire-reports):累加目录内所有 XML。 */
|
|
68
|
+
export function parseSurefireReportDir(dir) {
|
|
69
|
+
let aggregated = null;
|
|
70
|
+
for (const name of readdirSync(dir)) {
|
|
71
|
+
if (!name.endsWith('.xml')) continue;
|
|
72
|
+
const parsed = parseJunitXml(readFileSync(join(dir, name), 'utf-8'));
|
|
73
|
+
if (!parsed) continue;
|
|
74
|
+
aggregated = aggregated === null ? parsed : {
|
|
75
|
+
total: aggregated.total + parsed.total,
|
|
76
|
+
passed: aggregated.passed + parsed.passed,
|
|
77
|
+
failed: aggregated.failed + parsed.failed,
|
|
78
|
+
skipped: aggregated.skipped + parsed.skipped,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return aggregated;
|
|
82
|
+
}
|
|
83
|
+
|
|
60
84
|
/** jest --json 输出。 */
|
|
61
85
|
export function parseJest(text) {
|
|
62
86
|
let data;
|
|
@@ -69,8 +93,18 @@ export function parseJest(text) {
|
|
|
69
93
|
return { total, passed, failed, skipped };
|
|
70
94
|
}
|
|
71
95
|
|
|
72
|
-
/**
|
|
96
|
+
/**
|
|
97
|
+
* pytest 输出:junit XML(`--junitxml=<path>`)或 terminal summary
|
|
98
|
+
* ("== 5 passed, 2 failed, 1 skipped in 3.21s ==")。
|
|
99
|
+
*
|
|
100
|
+
* v0.49.0 §83.3.6:XML 优先 —— 结构稳定,不受 `-q`/`-v`/颜色码/`--tb` 影响,
|
|
101
|
+
* 且 CI 流水线天然产出 XML(来源:workflow-feedback 20260909)。
|
|
102
|
+
*/
|
|
73
103
|
export function parsePytest(text) {
|
|
104
|
+
if (looksLikeJunitXml(text)) {
|
|
105
|
+
const xml = parseJunitXml(text);
|
|
106
|
+
if (xml) return xml;
|
|
107
|
+
}
|
|
74
108
|
const lines = text.split('\n');
|
|
75
109
|
const summaryLine = [...lines].reverse().find(l => /=+\s.*(passed|failed|error|no tests ran)/.test(l));
|
|
76
110
|
if (!summaryLine) return null;
|
|
@@ -142,9 +176,16 @@ export function classifyFailure(text) {
|
|
|
142
176
|
return 'unknown';
|
|
143
177
|
}
|
|
144
178
|
|
|
145
|
-
/**
|
|
179
|
+
/**
|
|
180
|
+
* auto 识别:按内容特征匹配 runner。
|
|
181
|
+
*
|
|
182
|
+
* v0.49.0 §83.3.6:junit XML 特征优先嗅探。surefire 与 pytest 的 XML 同构,auto 模式
|
|
183
|
+
* 统一归入 maven-surefire(解析结果相同);需要把 runner 记为 pytest 时显式传
|
|
184
|
+
* `--runner pytest`(同样支持 XML)。
|
|
185
|
+
*/
|
|
146
186
|
export function detectRunner(text) {
|
|
147
187
|
const trimmed = text.trim();
|
|
188
|
+
if (looksLikeJunitXml(trimmed) && parseJunitXml(trimmed)) return 'maven-surefire';
|
|
148
189
|
if (trimmed.startsWith('{') && parseJest(trimmed)) return 'jest';
|
|
149
190
|
if (/Tests run:\s*\d+,\s*Failures:/.test(trimmed)) return 'maven-surefire';
|
|
150
191
|
if (parsePytest(trimmed)) return 'pytest';
|
|
@@ -164,7 +205,8 @@ export async function run(args) {
|
|
|
164
205
|
allowPositionals: true,
|
|
165
206
|
});
|
|
166
207
|
|
|
167
|
-
const usageMsg = 'Usage: tf test record <change-dir> --from <runner-output-file|surefire-report-dir> [--runner auto|maven-surefire|jest|pytest] [--json]'
|
|
208
|
+
const usageMsg = 'Usage: tf test record <change-dir> --from <runner-output-file|surefire-report-dir> [--runner auto|maven-surefire|jest|pytest] [--json]\n'
|
|
209
|
+
+ ' maven-surefire: console summary, surefire XML dir/file; pytest: terminal summary or junit XML (--junitxml); jest: --json';
|
|
168
210
|
if (positionals[0] !== 'record') {
|
|
169
211
|
console.error(usageMsg);
|
|
170
212
|
process.exit(2);
|
|
@@ -222,7 +264,11 @@ export async function run(args) {
|
|
|
222
264
|
stats = PARSERS[runner](text);
|
|
223
265
|
}
|
|
224
266
|
if (!stats) {
|
|
225
|
-
|
|
267
|
+
// v0.49.0 §83.3.6:错误信息附输入形态指引(原提示对"传了 XML"的用户无帮助)
|
|
268
|
+
const hint = runner === 'pytest'
|
|
269
|
+
? 'pytest accepts a terminal summary or a junit XML report (pytest --junitxml=<path>).'
|
|
270
|
+
: `Supported runners: ${SUPPORTED_RUNNERS.join(', ')} (or pass --runner explicitly).`;
|
|
271
|
+
console.error(`Could not parse ${runner} output in ${fromPath} — no recognizable test summary found.\n${hint}`);
|
|
226
272
|
process.exit(1);
|
|
227
273
|
}
|
|
228
274
|
|
|
@@ -11,6 +11,8 @@ Controls the implementation phase. Uses `execution-contract.md` as the workflow
|
|
|
11
11
|
|
|
12
12
|
Read: `execution-contract.md`, `tasks.md`, relevant `specs/`, relevant `design.md`. (Skip contract/spec requirements when workflow is `tweak`.)
|
|
13
13
|
|
|
14
|
+
**复利经验注入(v0.49.0 §83.3.5)**:运行 `tf solutions inject --phase cross-phase`(失败静默)——注入 `docs/solutions/` 中跨阶段可复用的历史经验(踩坑/模式),供实施时规避已知问题。注入内容是**经验提示而非约束**:与当前 change 的契约/规格冲突时以契约/规格为准,并把冲突回报给 workflow-start。
|
|
15
|
+
|
|
14
16
|
When `architecture/sql/` exists, also read:
|
|
15
17
|
- `architecture/sql/ddl/*.sql` — executable DDL scripts; reference directly for DB schema change tasks instead of hand-writing DDL
|
|
16
18
|
- `architecture/sql/migration/*.sql` — data migration scripts; execute as-is for data transformation tasks
|
|
@@ -95,7 +97,7 @@ The recommendation uses task count, configured `execution.inlineThreshold`, and
|
|
|
95
97
|
| **Inline** | Recommended for a single sequential task; always available for a user-confirmed choice |
|
|
96
98
|
| **Batch Inline** | Recommended for a bounded sequential batch; it remains serial and is never presented as parallel |
|
|
97
99
|
|
|
98
|
-
Do not transition to `executing` until `execution show` reports `current: true` and the phase guard passes. A revised plan must repeat `tf execution recommend` and use `tf execution revise --confirm`; it creates a new revision
|
|
100
|
+
Do not transition to `executing` until `execution show` reports `current: true` and the phase guard passes. A revised plan must repeat `tf execution recommend` and use `tf execution revise --confirm`; it creates a new revision. Receipts carrying a `wave_fingerprint` stay valid while their wave definition is unchanged (v0.49.0 §83.3.2); legacy receipts without one still fall back to the plan-hash judgement and are invalidated by a revision.
|
|
99
101
|
|
|
100
102
|
## Batch Inline Execution
|
|
101
103
|
|
|
@@ -123,7 +125,11 @@ For full/hotfix by default. Execute waves as dispatched by workflow-start.
|
|
|
123
125
|
1. Read the current plan with `tf execution show <change-dir> --json`; only waves shown with `current: true` and `eligible: true` may start. A `retryable: true` wave may only be repaired and re-reviewed; do not dispatch its dependents until its replacement receipt is `pass`. The CLI encodes dependencies in `--wave <id>:<strategy>:<tasks>[:<depends-on,...>]` and rejects a review receipt for a wave whose prerequisites lack current `pass` receipts.
|
|
124
126
|
2. A `parallel` wave may dispatch independent tasks simultaneously only when the platform supports concurrent dispatch. If it does not, disclose the unavailable capability and execute the same wave one task at a time without changing its stored strategy.
|
|
125
127
|
3. A `serial` wave dispatches one task at a time in listed order.
|
|
126
|
-
4. After every wave, notify workflow-start via SendMessage that the wave is complete and needs review.
|
|
128
|
+
4. After every wave, write back the tasks.md checkboxes for that wave, then notify workflow-start via SendMessage that the wave is complete and needs review.
|
|
129
|
+
|
|
130
|
+
**tasks.md 勾选回写(v0.49.0 §83.3.4)**:按 `wave.tasks` 匹配 tasks.md 对应行,把 `- [ ]` 改为 `- [x]`(只改勾选标记,不动任务文本)。这是执行进度的权威登记——`tasks-complete` guard 在 closing 时要求零未勾选项,留到收尾轮必然遗忘。若该 change 的 tasks.md 不使用 checkbox 形态(如以 `### Task N.M` 标题组织),零 `- [x]` 会使该 guard 无条件 FAIL:此时与 workflow-start 确认改用 checkbox 形态或走显式豁免,不要静默跳过。A1 修复后勾选不再使 plan 过期(§83.3.1),因此不会阻塞本 wave 的 receipt 登记(receipt 由 workflow-start 写入)。
|
|
131
|
+
|
|
132
|
+
Notify with:
|
|
127
133
|
- Wave ID
|
|
128
134
|
- Worktree path
|
|
129
135
|
- Branch
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Documentation schema for learnings written by ce-compound
|
|
2
|
-
# Treat this as the canonical frontmatter contract for docs/solutions
|
|
2
|
+
# Treat this as the canonical frontmatter contract for docs/solutions/ entries
|
|
3
|
+
# written by ce-compound.
|
|
4
|
+
#
|
|
5
|
+
# v0.49.0 §83.3.5(来源:workflow-feedback 20260909):docs/solutions/ 有两条产出通道,
|
|
6
|
+
# frontmatter 契约不同 ——
|
|
7
|
+
# ① ce-compound 手工条目:使用本文件的 schema(module / problem_type / component / ...)
|
|
8
|
+
# ② tf solutions promote 自动晋升条目:phase / domain / type / severity / date / source
|
|
9
|
+
# (见 scripts/lib/solutions-promote.mjs,字段源自 change 根 learnings.md)
|
|
10
|
+
# 共同消费面 = docs/solutions/INDEX.md(tf solutions inject 只读该文件,不读条目 frontmatter)。
|
|
11
|
+
# 下方 schema 仅适用于通道 ①。
|
|
3
12
|
#
|
|
4
13
|
# The schema has two tracks based on problem_type:
|
|
5
14
|
# Bug track — problem_type is a defect or failure (build_error, test_failure, etc.)
|
|
@@ -112,15 +112,15 @@ bridging:contract-builder 产非空骨架矩阵 test-matrix.md(过 test-matr
|
|
|
112
112
|
→ tf state rebuild(过 test-matrix-complete 出口门)
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
## 8. closing 时序协议(v2.1 §6.3,I5 固化防 plan
|
|
115
|
+
## 8. closing 时序协议(v2.1 §6.3,I5 固化防 plan 过期卡死;v0.49.0 §83.3 修订)
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
**顺序不可乱**——矩阵回灌(改写 execution-contract.md 的 `## Test Matrix` 段)改变 contract_hash → plan 过期;必须先 refresh-hash 再记录 review:
|
|
118
118
|
|
|
119
119
|
```
|
|
120
120
|
run 终局 PASS
|
|
121
121
|
→ 矩阵回灌(test-matrix-export v2 + 覆盖 test-matrix.md)
|
|
122
122
|
→ tf state rebuild(只改 state 文件,不动 plan JSON)
|
|
123
|
-
→ tf execution refresh-hash
|
|
123
|
+
→ tf execution refresh-hash(同步 plan 与内嵌 recommendation_receipt 的制品 hash)
|
|
124
124
|
→ 委托代理 commit glaf4-dev 产物
|
|
125
125
|
→ code-reviewer 独立审查 base..head(报告落 .superpowers/sdd/reviews/)
|
|
126
126
|
→ tf execution review --wave w1 --verdict pass
|
|
@@ -130,3 +130,5 @@ run 终局 PASS
|
|
|
130
130
|
```
|
|
131
131
|
|
|
132
132
|
> `tf state rebuild` 不能替代 `refresh-hash`(前者不动 plan JSON);review 需真实 git base≠head,必须 commit 后取 SHA。
|
|
133
|
+
>
|
|
134
|
+
> **v0.49.0 §83.3 变更**:① tasks.md 勾选回写不再改变 artifacts_hash(§83.3.1),勾选可与 receipt 登记同轮;② receipt 有效性判据改为 wave 定义指纹(§83.3.2)——refresh-hash / revise 不再失效已有 pass receipt;③ refresh-hash 同步内嵌 recommendation_receipt,命令在其目标场景下可用(§83.3.3)。
|
|
@@ -205,13 +205,13 @@ During closing, promote change-level learnings to the global solutions library:
|
|
|
205
205
|
tf solutions promote <change-dir>
|
|
206
206
|
```
|
|
207
207
|
|
|
208
|
-
This checks `
|
|
208
|
+
This checks the change's `learnings.md` (change root; v0.49.0 §83.3.5 path unification) for entries meeting promotion criteria:
|
|
209
209
|
- severity ≥ medium AND type = pitfall/pattern → promote to global `docs/solutions/`
|
|
210
210
|
- domain+type matches existing global entry → mark as "confirmed pattern", upgrade severity
|
|
211
211
|
|
|
212
212
|
Report promotion results in the closing summary. Promotion is advisory — failures do not block closing.
|
|
213
213
|
|
|
214
|
-
### Compound Capture Check (v0.5)
|
|
214
|
+
### Compound Capture Check (v0.5;v0.49.0 §83.3.5 明确写入方)
|
|
215
215
|
|
|
216
216
|
Before finalizing the closing, check if this change produced any learnable moments:
|
|
217
217
|
|
|
@@ -220,15 +220,14 @@ Before finalizing the closing, check if this change produced any learnable momen
|
|
|
220
220
|
- Did scope expand beyond the original proposal?
|
|
221
221
|
- Did execution-contract drift from proposal?
|
|
222
222
|
|
|
223
|
-
If any of the above occurred,
|
|
224
|
-
|
|
225
|
-
tf solutions capture
|
|
226
|
-
--phase cross-phase --domain <domain> --type pitfall \
|
|
227
|
-
--severity medium --summary "<what happened and how to prevent it>"
|
|
228
|
-
```
|
|
223
|
+
If any of the above occurred, **write them to the change's `learnings.md` (change root)** — one `## <title>` entry per learning, each carrying a frontmatter block (`phase` / `domain` / `type` / `severity` / `date`). Format reference: `tf runtime asset read templates/learnings.md`. This is the file the `compound-captured` guard checks and `tf solutions promote` reads; entries without frontmatter are skipped by promote (reason listed in its output).
|
|
224
|
+
|
|
225
|
+
`tf solutions capture` writes to global `docs/solutions/` — it does **not** create `learnings.md`. Use it only for product-level learnings that belong directly in the global library.
|
|
229
226
|
|
|
230
227
|
This check is advisory — skip silently if no learnable moments are detected.
|
|
231
228
|
|
|
229
|
+
> 详细收尾流程(Prototype Sync / Compound Promotion / Compound Capture / Worktree Deisolation / Code Landing)见 `references/closing-procedures.md`。
|
|
230
|
+
|
|
232
231
|
### 阶段产物同步门禁点(v0.37.0 §68.2 G5)
|
|
233
232
|
|
|
234
233
|
回写链(arch-merge → prototype-sync → test-merge → compound promotion)全部完成后、`tf state transition closing` 之前,**阻塞确认**(AskUserQuestion)是否同步 change 实施结果(团队协作:落地结果是团队最需要看的内容):
|
|
@@ -30,7 +30,7 @@ Promotion criteria:
|
|
|
30
30
|
|
|
31
31
|
Report promotion results in the closing summary. Promotion is advisory — failures do not block closing.
|
|
32
32
|
|
|
33
|
-
## Compound Capture Check (v0.5)
|
|
33
|
+
## Compound Capture Check (v0.5;v0.49.0 §83.3.5 明确写入方)
|
|
34
34
|
|
|
35
35
|
Before finalizing the closing, check if this change produced any learnable moments:
|
|
36
36
|
|
|
@@ -39,12 +39,17 @@ Before finalizing the closing, check if this change produced any learnable momen
|
|
|
39
39
|
- Did scope expand beyond the original proposal?
|
|
40
40
|
- Did execution-contract drift from proposal?
|
|
41
41
|
|
|
42
|
-
If any occurred,
|
|
42
|
+
If any occurred, **write them to the change's `learnings.md` (change root)** — one `## <title>` entry per learning, each carrying a frontmatter block (`phase` / `domain` / `type` / `severity` / `date`). Format reference: `tf runtime asset read templates/learnings.md`. This is the file the `compound-captured` guard checks and `tf solutions promote` reads; entries without frontmatter are skipped by promote (reason listed in its output).
|
|
43
|
+
|
|
44
|
+
**条目质量**:写「正确做法」前先验证它在系统层面成立——局部观察得出的 workaround 若被沉淀为通则,会被后续 change 当经验复用,反而固化缺陷(v0.49.0 §83.2.2 实证)。
|
|
45
|
+
|
|
46
|
+
For product-level learnings that belong directly in the global library (not change-scoped):
|
|
43
47
|
```bash
|
|
44
48
|
tf solutions capture \
|
|
45
49
|
--phase cross-phase --domain <domain> --type pitfall \
|
|
46
50
|
--severity medium --summary "<what happened and how to prevent it>"
|
|
47
51
|
```
|
|
52
|
+
`tf solutions capture` writes to global `docs/solutions/` — it does **not** create `learnings.md`.
|
|
48
53
|
|
|
49
54
|
Advisory — skip silently if no learnable moments detected.
|
|
50
55
|
|
|
@@ -114,6 +114,7 @@ Contract exists and approved, contract matches artifacts. Include `DP-4: 执行
|
|
|
114
114
|
2. 对每个 wave:
|
|
115
115
|
- dispatch build-executor 执行当前 wave
|
|
116
116
|
- 等待 build-executor 完成(task-notification)
|
|
117
|
+
- **确认该 wave 的 tasks.md 勾选已回写**(v0.49.0 §83.3.4):build-executor 的 wave 完成协议包含回写;未回写则 SendMessage 让其补做。`tasks-complete` 在 closing 强制要求零未勾选项,留到收尾轮必然遗忘
|
|
117
118
|
- dispatch code-reviewer 审查当前 wave
|
|
118
119
|
- 等待 review receipt + verdict=pass
|
|
119
120
|
3. 所有 wave 完成后,继续下一步路由
|
|
@@ -237,7 +238,7 @@ build-executor 修复 findings 后,workflow-start 必须:
|
|
|
237
238
|
- **No routing to spec-writer without architecture-design gate pass** (v0.9 §26): `arch_design_decision` must be `required` or `skipped` (not `null`). hotfix/tweak 不豁免
|
|
238
239
|
- **No arch state write without auto-review PASS** (v0.28.1 §36): when `decision: required`, auto-review MUST complete with PASS or PASS_WITH_WARNINGS before writing `arch_design_decision` to yaml. FAIL → loop fix (≤3 rounds) or escalate to human
|
|
239
240
|
- **No routing past DP-A without user confirmation** (v0.29.0 §37): architecture-design 四步协议完成后,必须经 DP-A 用户确认门(AskUserQuestion)才能路由到 spec-writer。用户选择"需要调整"时,修改必须通过子代理执行,修改后重新 auto-review + 重新 DP-A 确认
|
|
240
|
-
- **Artifact Ownership — 主代理不得直接修改子代理产物** (v0.29.0 §37): 子代理是其产物的唯一负责人(architecture-design → `architecture/` 目录,spec-writer → `proposal.md`/`specs/`/`design.md`/`tasks.md`,contract-builder → `execution-contract.md`,build-executor →
|
|
241
|
+
- **Artifact Ownership — 主代理不得直接修改子代理产物** (v0.29.0 §37): 子代理是其产物的唯一负责人(architecture-design → `architecture/` 目录,spec-writer → `proposal.md`/`specs/`/`design.md`/`tasks.md`,contract-builder → `execution-contract.md`,build-executor → 代码文件)。**tasks.md 的勾选状态属执行期写入区**(v0.49.0 §83.3.4):任务文本归 spec-writer,勾选标记由 build-executor 在 wave 完成时回写(主代理只检查不代写)。主代理(workflow-start)不得通过 Read + Edit/Write 直接修改子代理的产物文件。修改必须通过 `SendMessage` 恢复原子代理(优先)或启动新子代理执行。**v0.39.0 强化**:主代理 MUST NOT 直接 Edit/Write 任何文件 under `changes/<name>/` 或 `.worktrees/`——无论改动量大小,必须通过 SendMessage 委托子代理执行。例外:仅当子代理无法启动且用户明确授权时,主代理可直接修改,但必须在修改后重新触发对应的 review 验证
|
|
241
242
|
- **修复后必须 re-review** (v0.39.0): build-executor 修复 findings 后,workflow-start 必须 dispatch code-reviewer re-review 确认修复有效,记录 review receipt(verdict=pass)。禁止在 review receipt 未记录或 verdict!=pass 时进入下一阶段
|
|
242
243
|
- **No cross-change artifact modification (v0.30.0)**: 处理 change X 时 **MUST NOT** 编辑 `changes/Y/`(Y≠X)下任何产物。发现跨 change 依赖/问题时:① 在 change X 的 ADR/proposal 中显式声明依赖;② change Y 进入 workflow-start 时,扫描是否有其他 change 声明了对 Y 的依赖并提醒用户;③ change Y 的产物修改必须走 Y 自己的完整流程(architecture-design → auto-review → DP-A),不得绕过。跨 change 一致性核查可调度 `cross-change-consistency-checker` agent(来源:workflow-feedback 2026-08-01)
|
|
243
244
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Session Learnings — <change-name>
|
|
2
|
+
|
|
3
|
+
> change 级经验台账(**change 根目录 `learnings.md`**)。
|
|
4
|
+
>
|
|
5
|
+
> **位置权威(v0.49.0 §83.3.5)**:`changes/<name>/learnings.md`。代码(`solutions-promote.mjs`)与 `compound-captured` guard 均只认此路径;文档曾宣称的 `specs/<cap>/learnings.md` 从未落地。
|
|
6
|
+
>
|
|
7
|
+
> **消费链路**:本文件 → `tf solutions promote`(release-archivist closing 调用)→ 全局 `docs/solutions/` → `tf solutions inject`(各阶段入口注入)。
|
|
8
|
+
>
|
|
9
|
+
> **晋升前提**:只有带完整 frontmatter 的条目才会被晋升。缺字段时按默认 `severity=low` / `type=insight` 处理,不满足晋升条件即跳过——promote 会在输出中列出跳过原因,不再静默。
|
|
10
|
+
>
|
|
11
|
+
> **注意**:`tf solutions capture` 写入的是全局 `docs/solutions/`,**不会**创建本文件。
|
|
12
|
+
|
|
13
|
+
## 1. <条目标题:一句话说清问题或模式>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
phase: cross-phase # 阶段名(prd/plan/architecture/prototype/spec/build/review/cross-phase)或具体领域阶段
|
|
17
|
+
domain: <领域> # 如 jest / sdd / spring-boot;注入时按领域过滤
|
|
18
|
+
type: pitfall # pitfall | pattern | insight
|
|
19
|
+
severity: medium # high | medium | low
|
|
20
|
+
date: YYYY-MM-DD
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
- **教训来源**:<具体事件——哪个 wave / 哪条命令 / 什么现象>
|
|
24
|
+
- **正确做法**:<可执行的行动,而非泛泛原则>
|
|
25
|
+
- **边界**:<什么情况下不适用;避免把局部观察当通则>
|
|
26
|
+
|
|
27
|
+
## 2. <下一条目>
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
phase: cross-phase
|
|
31
|
+
domain: <领域>
|
|
32
|
+
type: pattern
|
|
33
|
+
severity: high
|
|
34
|
+
date: YYYY-MM-DD
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
- **教训来源**:...
|
|
38
|
+
- **正确做法**:...
|
|
39
|
+
- **边界**:...
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 晋升条件(`tf solutions promote`)
|
|
44
|
+
|
|
45
|
+
| 条件 | 行为 |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `severity` ≥ medium 且 `type` = pitfall / pattern | 晋升到全局 `docs/solutions/<phase>/`,并追加 INDEX 行 |
|
|
48
|
+
| 与全局 INDEX 已有条目 domain + type 匹配 | 不新建文件:升级已有条目 severity(low→medium→high)+ 条目 frontmatter `confirmed` 计数 +1 + 更新 INDEX 行 |
|
|
49
|
+
| 其他 | 保留在本文件;promote 输出中列为 skipped 并附原因 |
|
|
50
|
+
|
|
51
|
+
**条目质量要求**:写「正确做法」前先验证它在系统层面成立——局部观察得出的 workaround 若被沉淀为通则,会被后续 change 当经验复用,反而固化缺陷(v0.49.0 §83.2.2 的实证教训)。
|