@xulthekl/team-flow 0.36.4 → 0.38.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 +1 -0
- package/CHANGELOG.md +37 -0
- package/GEMINI.md +1 -1
- package/INSTALL.md +1 -1
- package/README.md +1 -1
- package/agents/architecture-reviewer.md +16 -6
- package/agents/change-split-auditor.md +11 -6
- package/agents/code-reviewer.md +7 -6
- package/agents/cross-change-consistency-checker.md +11 -6
- package/agents/prd-completeness-reviewer.md +11 -6
- package/agents/prototype-reviewer.md +11 -6
- package/docs/README_en.md +1 -1
- package/docs/solutions/INDEX.md +1 -0
- package/docs/solutions/cross-phase/2026-08-06-no-summary.md +17 -0
- 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/lib/cmd-prototype.mjs +227 -0
- package/scripts/lib/cmd-publish.mjs +254 -0
- package/scripts/lib/prototype-sync.mjs +41 -2
- package/scripts/lib/test-matrix-export.mjs +5 -0
- package/scripts/lib/test-merge.mjs +130 -93
- package/scripts/team-flow.mjs +8 -0
- package/skills/architecture-design/references/s3.5-product-architecture.md +9 -0
- package/skills/build-executor/SKILL.md +7 -0
- package/skills/build-executor/implementer-prompt.md +3 -1
- package/skills/ce-brainstorm/references/prototype-loop.md +2 -2
- package/skills/code-reviewer/SKILL.md +4 -3
- package/skills/code-reviewer/code-reviewer-prompt.md +2 -2
- package/skills/contract-builder/SKILL.md +19 -4
- package/skills/e2e/SKILL.md +2 -0
- package/skills/prototype/SKILL.md +4 -3
- package/skills/prototype/references/orchestration-flow.md +2 -2
- package/skills/release-archivist/SKILL.md +25 -6
- package/skills/spec-writer/SKILL.md +12 -8
- package/skills/test-strategy/SKILL.md +2 -1
- package/skills/test-strategy/references/design-methods-detail.md +1 -0
- package/skills/test-strategy/references/test-quality-rules.md +1 -0
- package/skills/workflow-orchestrator/references/s2-prd-prototype-loop.md +11 -2
- package/skills/workflow-orchestrator/references/s4-split-validate.md +9 -0
- package/skills/workflow-start/SKILL.md +10 -1
|
@@ -14,6 +14,14 @@
|
|
|
14
14
|
* 6. gitCommit — 单次原子提交
|
|
15
15
|
*
|
|
16
16
|
* 回写顺序:arch-merge → prototype-sync → test-merge → compound promotion
|
|
17
|
+
*
|
|
18
|
+
* v0.38.0(feedback 2026-08-05 修复 + E2E 层级):
|
|
19
|
+
* - resolveDeferred 只删 Deferred Items 段内被覆盖行(原全文件 regex 误删 Current Cases)
|
|
20
|
+
* - extractSummary 容忍 markdown bold(**87**)+ 识别 E2E cases 行
|
|
21
|
+
* - mergeExistingBaseline 覆盖式语义修正(原 startsWith('') 恒真导致旧行累积)
|
|
22
|
+
* - cells 提取按索引保留空列(原 filter(Boolean) 丢空列错位)
|
|
23
|
+
* - dry-run 真正跳过写盘(原仅 gitCommit 生效)
|
|
24
|
+
* - rewriteIndex 统计 test_tier=e2e 层级(unit/integration/e2e 三栏)
|
|
17
25
|
*/
|
|
18
26
|
|
|
19
27
|
import { readFileSync, writeFileSync, existsSync, cpSync, mkdirSync, readdirSync } from 'node:fs';
|
|
@@ -61,20 +69,24 @@ function preCheck(changeDir) {
|
|
|
61
69
|
|
|
62
70
|
/**
|
|
63
71
|
* 从 test-matrix.md 提取 Summary 统计信息
|
|
72
|
+
* 容忍 markdown bold(`**87**`)与普通数字两种格式(feedback 2026-08-05:C1 矩阵 LLM 手写加粗导致返回 0)
|
|
73
|
+
* 支持 E2E cases 行(v0.38.0:test_tier=e2e 记录,与复利 E2E 层级联动)
|
|
64
74
|
*/
|
|
65
|
-
function extractSummary(content) {
|
|
66
|
-
const summary = { totalCases: 0, modules: 0, unitCases: 0, integrationCases: 0, deferred: 0 };
|
|
75
|
+
export function extractSummary(content) {
|
|
76
|
+
const summary = { totalCases: 0, modules: 0, unitCases: 0, integrationCases: 0, e2eCases: 0, deferred: 0 };
|
|
67
77
|
const lines = content.split('\n');
|
|
68
78
|
for (const line of lines) {
|
|
69
|
-
const totalMatch = line.match(/Total cases:\s*(\d+)/i);
|
|
79
|
+
const totalMatch = line.match(/Total cases:\s*\*{0,2}\s*(\d+)/i);
|
|
70
80
|
if (totalMatch) summary.totalCases = parseInt(totalMatch[1], 10);
|
|
71
|
-
const modulesMatch = line.match(/Modules covered:\s*(\d+)/i);
|
|
81
|
+
const modulesMatch = line.match(/Modules covered:\s*\*{0,2}\s*(\d+)/i);
|
|
72
82
|
if (modulesMatch) summary.modules = parseInt(modulesMatch[1], 10);
|
|
73
|
-
const unitMatch = line.match(/Unit cases:\s*(\d+)/i);
|
|
83
|
+
const unitMatch = line.match(/Unit cases:\s*\*{0,2}\s*(\d+)/i);
|
|
74
84
|
if (unitMatch) summary.unitCases = parseInt(unitMatch[1], 10);
|
|
75
|
-
const intMatch = line.match(/Integration cases:\s*(\d+)/i);
|
|
85
|
+
const intMatch = line.match(/Integration cases:\s*\*{0,2}\s*(\d+)/i);
|
|
76
86
|
if (intMatch) summary.integrationCases = parseInt(intMatch[1], 10);
|
|
77
|
-
const
|
|
87
|
+
const e2eMatch = line.match(/E2E cases:\s*\*{0,2}\s*(\d+)/i);
|
|
88
|
+
if (e2eMatch) summary.e2eCases = parseInt(e2eMatch[1], 10);
|
|
89
|
+
const defMatch = line.match(/Deferred items:\s*\*{0,2}\s*(\d+)/i);
|
|
78
90
|
if (defMatch) summary.deferred = parseInt(defMatch[1], 10);
|
|
79
91
|
}
|
|
80
92
|
return summary;
|
|
@@ -173,10 +185,11 @@ function extractDeferredItems(content) {
|
|
|
173
185
|
|
|
174
186
|
/**
|
|
175
187
|
* Step 2: mergeBaselines — 增量合并到 baselines/{module}.md
|
|
188
|
+
* dryRun(feedback 2026-08-05):仅计算不写盘,返回 would-create/would-update 预览
|
|
176
189
|
*/
|
|
177
|
-
function mergeBaselines(ledgerDir, changeName, moduleSections, candidateLedger, deferredItems) {
|
|
190
|
+
export function mergeBaselines(ledgerDir, changeName, moduleSections, candidateLedger, deferredItems, dryRun) {
|
|
178
191
|
const baselinesDir = join(ledgerDir, 'baselines');
|
|
179
|
-
if (!existsSync(baselinesDir)) {
|
|
192
|
+
if (!existsSync(baselinesDir) && !dryRun) {
|
|
180
193
|
mkdirSync(baselinesDir, { recursive: true });
|
|
181
194
|
}
|
|
182
195
|
|
|
@@ -198,13 +211,13 @@ function mergeBaselines(ledgerDir, changeName, moduleSections, candidateLedger,
|
|
|
198
211
|
const existing = readFileSync(baselinePath, 'utf-8');
|
|
199
212
|
const updated = mergeExistingBaseline(existing, sectionContent, changeName, today, moduleCandidates, moduleDeferred);
|
|
200
213
|
if (!updated) continue; // no changes
|
|
201
|
-
writeFileSync(baselinePath, updated, 'utf-8');
|
|
202
|
-
results[moduleName] = 'updated';
|
|
214
|
+
if (!dryRun) writeFileSync(baselinePath, updated, 'utf-8');
|
|
215
|
+
results[moduleName] = dryRun ? 'would-update' : 'updated';
|
|
203
216
|
} else {
|
|
204
217
|
// 新建 baseline
|
|
205
218
|
const newBaseline = createNewBaseline(moduleName, sectionContent, changeName, today, moduleCandidates, moduleDeferred);
|
|
206
|
-
writeFileSync(baselinePath, newBaseline, 'utf-8');
|
|
207
|
-
results[moduleName] = 'created';
|
|
219
|
+
if (!dryRun) writeFileSync(baselinePath, newBaseline, 'utf-8');
|
|
220
|
+
results[moduleName] = dryRun ? 'would-create' : 'created';
|
|
208
221
|
}
|
|
209
222
|
}
|
|
210
223
|
return results;
|
|
@@ -213,7 +226,7 @@ function mergeBaselines(ledgerDir, changeName, moduleSections, candidateLedger,
|
|
|
213
226
|
/**
|
|
214
227
|
* 创建新 baseline 文件
|
|
215
228
|
*/
|
|
216
|
-
function createNewBaseline(moduleName, sectionContent, changeName, date, candidates, deferred) {
|
|
229
|
+
export function createNewBaseline(moduleName, sectionContent, changeName, date, candidates, deferred) {
|
|
217
230
|
const lines = [];
|
|
218
231
|
lines.push(`# ${moduleName} Test Baseline`);
|
|
219
232
|
lines.push('');
|
|
@@ -227,12 +240,13 @@ function createNewBaseline(moduleName, sectionContent, changeName, date, candida
|
|
|
227
240
|
lines.push('|---|---|---|---|---|---|---|---|---|');
|
|
228
241
|
|
|
229
242
|
// 从 sectionContent 提取 case 行并添加 source_change 列
|
|
243
|
+
// 空列保留(slice(1,-1) 去首尾空串),避免 filter(Boolean) 丢空列导致索引左移(feedback 2026-08-05)
|
|
230
244
|
const caseLines = sectionContent.split('\n').filter(l =>
|
|
231
245
|
l.startsWith('|') && !l.match(/^\|\s*[-:]+/) && !l.match(/\|\s*case_id/)
|
|
232
246
|
);
|
|
233
247
|
for (const line of caseLines) {
|
|
234
|
-
const cells = line.split('|').map(c => c.trim())
|
|
235
|
-
if (cells.length >=
|
|
248
|
+
const cells = line.split('|').slice(1, -1).map(c => c.trim());
|
|
249
|
+
if (cells.length >= 11) {
|
|
236
250
|
lines.push(`| ${cells[0]} | ${cells[1]} | ${cells[2]} | ${cells[5]} | ${cells[6]} | ${cells[8]} | ${cells[9]} | ${cells[10]} | ${changeName} |`);
|
|
237
251
|
}
|
|
238
252
|
}
|
|
@@ -258,57 +272,55 @@ function createNewBaseline(moduleName, sectionContent, changeName, date, candida
|
|
|
258
272
|
}
|
|
259
273
|
|
|
260
274
|
/**
|
|
261
|
-
* 合并现有 baseline
|
|
275
|
+
* 合并现有 baseline(Current Cases 覆盖式替换 + 追加 Evolution Log)
|
|
276
|
+
*
|
|
277
|
+
* feedback 2026-08-05 修复:
|
|
278
|
+
* 1. 原 `inFrontmatter && (line.startsWith('## ') || line.startsWith(''))` 中 startsWith('') 恒真 →
|
|
279
|
+
* Current Cases 旧行不被跳过而累积(覆盖式语义失效)。改为仅在 `## ` 标题退出。
|
|
280
|
+
* 2. cells 提取改用 slice(1,-1) 保留空列(filter(Boolean) 丢空列导致索引左移错位)。
|
|
281
|
+
* 3. Evolution Log 幂等:同 change 同日已记录则跳过 append。
|
|
262
282
|
*/
|
|
263
|
-
function mergeExistingBaseline(existing, sectionContent, changeName, date, candidates, deferred) {
|
|
283
|
+
export function mergeExistingBaseline(existing, sectionContent, changeName, date, candidates, deferred) {
|
|
264
284
|
const lines = [];
|
|
265
285
|
const existingLines = existing.split('\n');
|
|
286
|
+
const caseLines = sectionContent.split('\n').filter(l =>
|
|
287
|
+
l.startsWith('|') && !l.match(/^\|\s*[-:]+/) && !l.match(/\|\s*case_id/)
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
const renderCaseRow = (line) => {
|
|
291
|
+
const cells = line.split('|').slice(1, -1).map(c => c.trim());
|
|
292
|
+
if (cells.length < 11) return null;
|
|
293
|
+
return `| ${cells[0]} | ${cells[1]} | ${cells[2]} | ${cells[5]} | ${cells[6]} | ${cells[8]} | ${cells[9]} | ${cells[10]} | ${changeName} |`;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
let inCases = false; // Current Cases 段内 → 跳过旧行(覆盖式)
|
|
297
|
+
const logPattern = new RegExp(`\\*\\*${date}\\*\\* \\[${changeName}\\]:`);
|
|
266
298
|
|
|
267
|
-
// 更新 frontmatter
|
|
268
|
-
let inFrontmatter = false;
|
|
269
|
-
let frontmatterDone = false;
|
|
270
299
|
for (const line of existingLines) {
|
|
271
300
|
if (line.startsWith('> last_updated_by_change:')) {
|
|
272
301
|
lines.push(`> last_updated_by_change: ${changeName}`);
|
|
273
302
|
} else if (line.startsWith('> last_updated:')) {
|
|
274
303
|
lines.push(`> last_updated: ${date}`);
|
|
275
304
|
} else if (line.startsWith('## Current Cases')) {
|
|
276
|
-
|
|
277
|
-
lines.push(line);
|
|
278
|
-
lines.push('');
|
|
279
|
-
lines.push('| case_id | behavior | design_method | test_kind | test_tier | work_mode | test_file | test_method_name | source_change |');
|
|
280
|
-
lines.push('|---|---|---|---|---|---|---|---|---|');
|
|
281
|
-
inFrontmatter = true;
|
|
282
|
-
// 跳过旧表格
|
|
283
|
-
continue;
|
|
284
|
-
} else if (inFrontmatter && (line.startsWith('## ') || line.startsWith(''))) {
|
|
285
|
-
inFrontmatter = false;
|
|
286
|
-
// 插入新的 case 行
|
|
287
|
-
const caseLines = sectionContent.split('\n').filter(l =>
|
|
288
|
-
l.startsWith('|') && !l.match(/^\|\s*[-:]+/) && !l.match(/\|\s*case_id/)
|
|
289
|
-
);
|
|
305
|
+
lines.push(line, '', '| case_id | behavior | design_method | test_kind | test_tier | work_mode | test_file | test_method_name | source_change |', '|---|---|---|---|---|---|---|---|---|');
|
|
290
306
|
for (const cl of caseLines) {
|
|
291
|
-
const
|
|
292
|
-
if (
|
|
293
|
-
lines.push(`| ${cells[0]} | ${cells[1]} | ${cells[2]} | ${cells[5]} | ${cells[6]} | ${cells[8]} | ${cells[9]} | ${cells[10]} | ${changeName} |`);
|
|
294
|
-
}
|
|
307
|
+
const row = renderCaseRow(cl);
|
|
308
|
+
if (row) lines.push(row);
|
|
295
309
|
}
|
|
296
|
-
|
|
297
|
-
} else if (inFrontmatter && line.startsWith('|')) {
|
|
298
|
-
// 跳过旧表格行(已替换)
|
|
299
|
-
continue;
|
|
310
|
+
inCases = true;
|
|
300
311
|
} else if (line.startsWith('## Evolution Log')) {
|
|
312
|
+
// 优先于 inCases 退出分支:Evolution Log 标题也以 ## 开头,需先处理 append
|
|
313
|
+
inCases = false;
|
|
314
|
+
lines.push(line, '');
|
|
315
|
+
if (!existingLines.some(l => logPattern.test(l))) {
|
|
316
|
+
lines.push(`- **${date}** [${changeName}]: 新增 ${caseLines.length} case, defer ${deferred.length}`);
|
|
317
|
+
}
|
|
318
|
+
} else if (inCases && line.startsWith('## ')) {
|
|
319
|
+
inCases = false;
|
|
301
320
|
lines.push(line);
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
).length;
|
|
306
|
-
lines.push(`- **${date}** [${changeName}]: 新增 ${caseCount} case, defer ${deferred.length}`);
|
|
307
|
-
frontmatterDone = true;
|
|
308
|
-
} else if (frontmatterDone && line.startsWith('- **')) {
|
|
309
|
-
// 保留旧的 evolution log entries
|
|
310
|
-
lines.push(line);
|
|
311
|
-
} else if (!inFrontmatter) {
|
|
321
|
+
} else if (inCases) {
|
|
322
|
+
continue; // 跳过 Current Cases 段内旧表行/分隔行/空行
|
|
323
|
+
} else {
|
|
312
324
|
lines.push(line);
|
|
313
325
|
}
|
|
314
326
|
}
|
|
@@ -318,8 +330,12 @@ function mergeExistingBaseline(existing, sectionContent, changeName, date, candi
|
|
|
318
330
|
|
|
319
331
|
/**
|
|
320
332
|
* Step 3: resolveDeferred — 新 case 覆盖了旧 deferred 项
|
|
333
|
+
*
|
|
334
|
+
* feedback 2026-08-05 修复:原实现用全文件 regex `\|<caseId>\|[^\n]*\n` 删除匹配行,
|
|
335
|
+
* 把 baselines `Current Cases` 表的正常 case 行也一并误删(表体清空、Evolution Log 计数保留)。
|
|
336
|
+
* 改为逐行扫描,仅删除 `## Deferred Items` 段(到下一个 `## ` 标题之间)内被新 case 覆盖的行。
|
|
321
337
|
*/
|
|
322
|
-
function resolveDeferred(ledgerDir, changeName, newCaseIds) {
|
|
338
|
+
export function resolveDeferred(ledgerDir, changeName, newCaseIds, dryRun) {
|
|
323
339
|
const baselinesDir = join(ledgerDir, 'baselines');
|
|
324
340
|
if (!existsSync(baselinesDir)) return { resolved: 0 };
|
|
325
341
|
|
|
@@ -327,47 +343,58 @@ function resolveDeferred(ledgerDir, changeName, newCaseIds) {
|
|
|
327
343
|
for (const file of readdirSync(baselinesDir)) {
|
|
328
344
|
if (!file.endsWith('.md')) continue;
|
|
329
345
|
const fp = join(baselinesDir, file);
|
|
330
|
-
|
|
331
|
-
|
|
346
|
+
const content = readFileSync(fp, 'utf-8');
|
|
347
|
+
const lines = content.split('\n');
|
|
332
348
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
349
|
+
let inDeferred = false;
|
|
350
|
+
const kept = [];
|
|
351
|
+
let changed = false;
|
|
352
|
+
for (const line of lines) {
|
|
353
|
+
if (line.startsWith('## Deferred Items')) {
|
|
354
|
+
inDeferred = true;
|
|
355
|
+
kept.push(line);
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (inDeferred && line.startsWith('## ')) {
|
|
359
|
+
inDeferred = false;
|
|
360
|
+
kept.push(line);
|
|
361
|
+
continue;
|
|
340
362
|
}
|
|
363
|
+
if (inDeferred && line.startsWith('|') && !line.match(/^\|\s*[-:]/)) {
|
|
364
|
+
const cells = line.split('|').slice(1, -1).map(c => c.trim());
|
|
365
|
+
if (cells.length && newCaseIds.includes(cells[0])) {
|
|
366
|
+
changed = true;
|
|
367
|
+
resolved++;
|
|
368
|
+
continue; // 被新 case 覆盖 → 删除该 deferred 行
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
kept.push(line);
|
|
341
372
|
}
|
|
342
373
|
|
|
343
|
-
if (changed) {
|
|
344
|
-
writeFileSync(fp,
|
|
374
|
+
if (changed && !dryRun) {
|
|
375
|
+
writeFileSync(fp, kept.join('\n'), 'utf-8');
|
|
345
376
|
}
|
|
346
377
|
}
|
|
347
378
|
return { resolved };
|
|
348
379
|
}
|
|
349
380
|
|
|
350
|
-
function escapeRegex(str) {
|
|
351
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
352
|
-
}
|
|
353
|
-
|
|
354
381
|
/**
|
|
355
382
|
* Step 4: appendChangelog — 归档 test-matrix.md
|
|
356
383
|
*/
|
|
357
|
-
function appendChangelog(ledgerDir, changeName, matrixContent) {
|
|
384
|
+
export function appendChangelog(ledgerDir, changeName, matrixContent, dryRun) {
|
|
358
385
|
const changelogDir = join(ledgerDir, 'changelog');
|
|
359
|
-
if (!existsSync(changelogDir)) {
|
|
386
|
+
if (!existsSync(changelogDir) && !dryRun) {
|
|
360
387
|
mkdirSync(changelogDir, { recursive: true });
|
|
361
388
|
}
|
|
362
389
|
const destPath = join(changelogDir, `${changeName}.md`);
|
|
363
|
-
writeFileSync(destPath, matrixContent, 'utf-8');
|
|
390
|
+
if (!dryRun) writeFileSync(destPath, matrixContent, 'utf-8');
|
|
364
391
|
return destPath;
|
|
365
392
|
}
|
|
366
393
|
|
|
367
394
|
/**
|
|
368
395
|
* Step 5: rewriteIndex — 重写 INDEX.md
|
|
369
396
|
*/
|
|
370
|
-
function rewriteIndex(ledgerDir) {
|
|
397
|
+
export function rewriteIndex(ledgerDir, dryRun) {
|
|
371
398
|
const baselinesDir = join(ledgerDir, 'baselines');
|
|
372
399
|
const indexPath = join(ledgerDir, 'INDEX.md');
|
|
373
400
|
const today = new Date().toISOString().slice(0, 10);
|
|
@@ -399,11 +426,12 @@ function rewriteIndex(ledgerDir) {
|
|
|
399
426
|
if (deferredCount > 0) status = '⚠️ partial';
|
|
400
427
|
if (caseCount === 0) status = '⏭️ not_applicable';
|
|
401
428
|
|
|
402
|
-
// test_tier_breakdown
|
|
403
|
-
const unitCount = (content.match(/
|
|
404
|
-
const intCount = (content.match(/
|
|
429
|
+
// test_tier_breakdown(v0.38.0:补 e2e 层级;\b 词边界防误匹配 community 等)
|
|
430
|
+
const unitCount = (content.match(/\bunit\b/gi) || []).length;
|
|
431
|
+
const intCount = (content.match(/\bintegration\b/gi) || []).length;
|
|
432
|
+
const e2eCount = (content.match(/\be2e\b/gi) || []).length;
|
|
405
433
|
|
|
406
|
-
modules.push({ moduleName, caseCount, deferredCount, lastChange, lastDate, status, unitCount, intCount });
|
|
434
|
+
modules.push({ moduleName, caseCount, deferredCount, lastChange, lastDate, status, unitCount, intCount, e2eCount });
|
|
407
435
|
}
|
|
408
436
|
}
|
|
409
437
|
|
|
@@ -421,10 +449,10 @@ function rewriteIndex(ledgerDir) {
|
|
|
421
449
|
lines.push('|---|---|---|---|---|---|---|');
|
|
422
450
|
|
|
423
451
|
for (const m of modules) {
|
|
424
|
-
lines.push(`| ${m.moduleName} | unit:${m.unitCount}, integration:${m.intCount} | ${m.caseCount} | ${m.deferredCount} | ${m.lastChange} | ${m.lastDate} | ${m.status} |`);
|
|
452
|
+
lines.push(`| ${m.moduleName} | unit:${m.unitCount}, integration:${m.intCount}, e2e:${m.e2eCount} | ${m.caseCount} | ${m.deferredCount} | ${m.lastChange} | ${m.lastDate} | ${m.status} |`);
|
|
425
453
|
}
|
|
426
454
|
|
|
427
|
-
writeFileSync(indexPath, lines.join('\n') + '\n', 'utf-8');
|
|
455
|
+
if (!dryRun) writeFileSync(indexPath, lines.join('\n') + '\n', 'utf-8');
|
|
428
456
|
return { modules: modules.length, totalCases, totalDeferred };
|
|
429
457
|
}
|
|
430
458
|
|
|
@@ -494,16 +522,21 @@ async function main(argv, projectRoot) {
|
|
|
494
522
|
const candidateLedger = extractCandidateLedger(matrixContent);
|
|
495
523
|
const deferredItems = extractDeferredItems(matrixContent);
|
|
496
524
|
|
|
497
|
-
console.log(` Summary: ${summary.totalCases} cases, ${summary.modules} modules, ${summary.deferred} deferred`);
|
|
525
|
+
console.log(` Summary: ${summary.totalCases} cases (unit ${summary.unitCases} + integration ${summary.integrationCases} + e2e ${summary.e2eCases}), ${summary.modules} modules, ${summary.deferred} deferred`);
|
|
498
526
|
|
|
499
|
-
// Ensure ledger directory exists
|
|
500
|
-
if (!existsSync(ledgerDir)) {
|
|
527
|
+
// Ensure ledger directory exists(dry-run 不建目录)
|
|
528
|
+
if (!existsSync(ledgerDir) && !dryRun) {
|
|
501
529
|
mkdirSync(ledgerDir, { recursive: true });
|
|
502
530
|
}
|
|
503
531
|
|
|
532
|
+
const mark = dryRun ? '[DRY-RUN]' : '✅';
|
|
533
|
+
|
|
504
534
|
// Step 2: mergeBaselines
|
|
505
|
-
const mergeResults = mergeBaselines(ledgerDir, changeName, moduleSections, candidateLedger, deferredItems);
|
|
506
|
-
|
|
535
|
+
const mergeResults = mergeBaselines(ledgerDir, changeName, moduleSections, candidateLedger, deferredItems, dryRun);
|
|
536
|
+
const mergeStates = Object.values(mergeResults);
|
|
537
|
+
const created = mergeStates.filter(s => s === 'created' || s === 'would-create').length;
|
|
538
|
+
const updated = mergeStates.filter(s => s === 'updated' || s === 'would-update').length;
|
|
539
|
+
console.log(`${mark} Step 2: mergeBaselines — ${Object.keys(mergeResults).length} modules processed (${created} created, ${updated} updated)`);
|
|
507
540
|
|
|
508
541
|
// Step 3: resolveDeferred
|
|
509
542
|
const newCaseIds = [];
|
|
@@ -511,27 +544,31 @@ async function main(argv, projectRoot) {
|
|
|
511
544
|
const lines = section.split('\n');
|
|
512
545
|
for (const line of lines) {
|
|
513
546
|
if (line.startsWith('|') && !line.match(/^\|\s*[-:]+/) && !line.match(/\|\s*case_id/)) {
|
|
514
|
-
const cells = line.split('|').map(c => c.trim())
|
|
547
|
+
const cells = line.split('|').slice(1, -1).map(c => c.trim());
|
|
515
548
|
if (cells[0]) newCaseIds.push(cells[0]);
|
|
516
549
|
}
|
|
517
550
|
}
|
|
518
551
|
}
|
|
519
|
-
const deferredResult = resolveDeferred(ledgerDir, changeName, newCaseIds);
|
|
520
|
-
console.log(
|
|
552
|
+
const deferredResult = resolveDeferred(ledgerDir, changeName, newCaseIds, dryRun);
|
|
553
|
+
console.log(`${mark} Step 3: resolveDeferred — ${deferredResult.resolved} items resolved`);
|
|
521
554
|
|
|
522
555
|
// Step 4: appendChangelog
|
|
523
|
-
const changelogPath = appendChangelog(ledgerDir, changeName, matrixContent);
|
|
524
|
-
console.log(
|
|
556
|
+
const changelogPath = appendChangelog(ledgerDir, changeName, matrixContent, dryRun);
|
|
557
|
+
console.log(`${mark} Step 4: appendChangelog — ${relative(projectRoot, changelogPath)}`);
|
|
525
558
|
|
|
526
559
|
// Step 5: rewriteIndex
|
|
527
|
-
const indexResult = rewriteIndex(ledgerDir);
|
|
528
|
-
console.log(
|
|
560
|
+
const indexResult = rewriteIndex(ledgerDir, dryRun);
|
|
561
|
+
console.log(`${mark} Step 5: rewriteIndex — ${indexResult.modules} modules, ${indexResult.totalCases} total cases`);
|
|
529
562
|
|
|
530
563
|
// Step 6: gitCommit
|
|
531
564
|
gitCommit(projectRoot, changeName, dryRun);
|
|
532
|
-
console.log(
|
|
565
|
+
console.log(`${mark} Step 6: gitCommit`);
|
|
533
566
|
|
|
534
|
-
|
|
567
|
+
if (dryRun) {
|
|
568
|
+
console.log(`\n🧪 test-merge DRY-RUN complete for ${changeName} — 未写入任何文件`);
|
|
569
|
+
} else {
|
|
570
|
+
console.log(`\n🎉 test-merge complete for ${changeName}`);
|
|
571
|
+
}
|
|
535
572
|
}
|
|
536
573
|
|
|
537
574
|
// 直接运行(v0.35.0 修复:main() 无参自调用崩溃 → 调用 run() 走正确参数解析)
|
package/scripts/team-flow.mjs
CHANGED
|
@@ -34,6 +34,8 @@ const COMMANDS = {
|
|
|
34
34
|
'install-qoder': () => import('./lib/cmd-install-qoder.mjs'),
|
|
35
35
|
'install-zcode': () => import('./lib/cmd-install-zcode.mjs'),
|
|
36
36
|
'prototype-sync': () => import('./lib/prototype-sync.mjs'),
|
|
37
|
+
prototype: () => import('./lib/cmd-prototype.mjs'),
|
|
38
|
+
publish: () => import('./lib/cmd-publish.mjs'),
|
|
37
39
|
'arch-merge': () => import('./lib/arch-merge.mjs'),
|
|
38
40
|
arch: () => import('./lib/cmd-arch.mjs'),
|
|
39
41
|
'test-merge': () => import('./lib/test-merge.mjs'),
|
|
@@ -53,6 +55,12 @@ Commands:
|
|
|
53
55
|
sync <change-dir> Merge delta specs into main specs
|
|
54
56
|
prototype-sync <change-dir> [--source <path>] [--prototype-dir <path>]
|
|
55
57
|
Merge UX delta into global prototype/ + design-system.md
|
|
58
|
+
publish <--prd|--arch|--changes <dir>|--all> [--push] [--dry-run]
|
|
59
|
+
Commit stage artifacts (whitelist) + optional push (v0.37.0 §68.3)
|
|
60
|
+
prototype branch <prd-vN>
|
|
61
|
+
Create/reuse prototype version worktree (v0.37.0 §68.4)
|
|
62
|
+
prototype deisolate <prd-vN> [--merge] [--clean]
|
|
63
|
+
Version wrap-up: merge prototype branch back + clean worktree
|
|
56
64
|
arch init [--mode reconstruction|design] [--baseline-ref <prd/vN/>]
|
|
57
65
|
Stamp project-level arch_baseline into .team-flow/arch-state.json (v0.35.0 §59.4)
|
|
58
66
|
arch show Show current project architecture baseline state
|
|
@@ -72,6 +72,15 @@
|
|
|
72
72
|
|
|
73
73
|
逆向重建工具:`workflow-bootstrap` 的 recon-probe.sh(--ddl-out,路径 `${CLAUDE_PLUGIN_ROOT}/skills/workflow-bootstrap/scripts/recon-probe.sh`)+ codebase-recon-analyst + 新增 `arch-reverse-analyst`(v0.14 §63.2)。
|
|
74
74
|
|
|
75
|
+
## 阶段产物同步门禁点(v0.37.0 §68.2 G2)
|
|
76
|
+
|
|
77
|
+
ARCH 完成(评审 PASS 或 skip 已物化)后,**阻塞确认**(AskUserQuestion)是否同步阶段产物(团队协作:架构决策是实施依据):
|
|
78
|
+
- **A 提交并推送**:`tf publish --arch --push`
|
|
79
|
+
- **B 仅提交不推送**:`tf publish --arch`
|
|
80
|
+
- **C 暂不同步**:继续 S4,后续可补
|
|
81
|
+
|
|
82
|
+
同步对象:`docs/architecture/iterations/vN/`(架构快照)+ 评审 verdict(含首轮 `tf arch init` 打戳产物 `.team-flow/arch-state.json`)。
|
|
83
|
+
|
|
75
84
|
## 完成条件
|
|
76
85
|
|
|
77
86
|
- `docs/architecture/iterations/vN/architecture.md` 已产出(6 产物,provenance 标注)
|
|
@@ -43,6 +43,13 @@ Branch/worktree preflight before ANY implementation edit (mandatory — do not s
|
|
|
43
43
|
aggregated `<workspace>/.worktrees/<change>/` directory — and make all implementation
|
|
44
44
|
edits there.
|
|
45
45
|
|
|
46
|
+
### UI Contract Prototype Location (v0.37.0 §68.5)
|
|
47
|
+
|
|
48
|
+
When implementing UI tasks, the UI contract source is the **prototype version worktree**, not the change worktree's prototype copy:
|
|
49
|
+
- **定位**:读 `change-brief.md` 的 `upstream_plan_ref: prd/vN/plan.md`(或 change 目录名 `v{N}-` 前缀)→ PRD 版本 vN → 原型 worktree = `<workspace>/.worktrees/prd-vN/prototype/`
|
|
50
|
+
- **回退**:该 worktree 不存在时用 `<workspace>/prototype/`,并提示 `tf prototype branch <prd-vN>` 创建
|
|
51
|
+
- **引用**:execution-contract / design.md 中引用的原型页面(`prototype/pages/<page>.html` 等)以此为基准定位;**禁止**引用主干上其他版本的旧原型
|
|
52
|
+
|
|
46
53
|
## Core Laws
|
|
47
54
|
|
|
48
55
|
### Law 0: State Field Boundary (v0.30.0)
|
|
@@ -52,6 +52,8 @@ Subagent (general-purpose):
|
|
|
52
52
|
If `test-matrix.md` exists, read the module section relevant to your task.
|
|
53
53
|
For each case in the matrix, follow the protocol matching its `work_mode`:
|
|
54
54
|
|
|
55
|
+
**test_tier=e2e 豁免(v0.38.0)**: `test_tier=e2e` 的 case 不在 implementer 职责内——由 e2e skill / release-archivist Step 5b 覆盖,跳过(不计入模块 coverage,不按 work_mode 走 TDD)。
|
|
56
|
+
|
|
55
57
|
**work_mode=TDD** (new behavior):
|
|
56
58
|
1. RED: Write failing test matching `case_id` + `test_method_name`, confirm failure
|
|
57
59
|
2. GREEN: Implement minimum code to pass
|
|
@@ -68,7 +70,7 @@ Subagent (general-purpose):
|
|
|
68
70
|
2. Fix production code, confirm green
|
|
69
71
|
3. Report: defect reproduction + fix evidence
|
|
70
72
|
|
|
71
|
-
After all cases: self-check matrix coverage = passed cases / total cases in matrix for your module
|
|
73
|
+
After all cases: self-check matrix coverage = passed cases / total cases in matrix for your module(分母排除 test_tier=e2e)。
|
|
72
74
|
|
|
73
75
|
**两不原则 (Two Prohibitions)**:
|
|
74
76
|
- ⛔ DO NOT write production code and test code simultaneously — RED first, then GREEN
|
|
@@ -39,10 +39,10 @@ PRD 文档写入后、Handoff 之前,执行原型内循环。原型是 PRD 的
|
|
|
39
39
|
|
|
40
40
|
## 3.5.5 PRD 完整性评审(冻结前门禁)
|
|
41
41
|
|
|
42
|
-
原型审查通过后、冻结前,派发 `prd-completeness-reviewer`
|
|
42
|
+
原型审查通过后、冻结前,派发 `prd-completeness-reviewer` 子代理(独立上下文),评审 PRD「是否完整到能支撑后续 plan/spec 实施」(区别于 Phase 2.6 claim verifier——后者管"说得对不对",本评审管"说得全不全")。
|
|
43
43
|
|
|
44
44
|
- 派发:按名派发插件 agent `prd-completeness-reviewer`(定义见插件 `agents/prd-completeness-reviewer.md`),传入 PRD 路径 + CONCEPTS.md 路径。
|
|
45
|
-
-
|
|
45
|
+
- agent 直接写审查报告到 `prd/{ITERATION_VERSION}/prd-completeness-review.md`。
|
|
46
46
|
- 判定(柔性):PASS / PASS_WITH_WARNINGS → 进入冻结;**FAIL(Critical>0)→ 回 Phase 1.3 补充**后重审。
|
|
47
47
|
- 5 维度:用户故事完整性(Critical)/验收标准(Critical)/边界与非功能(Important)/术语一致性(Minor)/范围闭环(Important)。
|
|
48
48
|
|
|
@@ -144,7 +144,7 @@ If `test-matrix.md` exists in the change directory, audit the implementation aga
|
|
|
144
144
|
- Verify `design_method` matches actual test approach (a `boundary` case must use real boundary values, not a happy-path disguised as boundary)
|
|
145
145
|
|
|
146
146
|
2. **Coverage calculation**:
|
|
147
|
-
- Matrix coverage = implemented cases / total cases in matrix
|
|
147
|
+
- Matrix coverage = implemented cases / total cases in matrix(**分母排除 test_tier=e2e**——E2E case 由 release-archivist Step 5b 对账,不在实施层判定)
|
|
148
148
|
- Matrix coverage < 100% → **Critical finding** (test-matrix-gap)
|
|
149
149
|
- List missing case_ids
|
|
150
150
|
|
|
@@ -153,8 +153,9 @@ If `test-matrix.md` exists in the change directory, audit the implementation aga
|
|
|
153
153
|
- Entries with `decision=covered` actually have corresponding tests?
|
|
154
154
|
|
|
155
155
|
4. **Pyramid ratio check**:
|
|
156
|
-
- unit
|
|
157
|
-
-
|
|
156
|
+
- 分母 = unit + integration(e2e 不计入金字塔配比,v0.38.0)
|
|
157
|
+
- unit cases: 70-80% of (unit+integration)
|
|
158
|
+
- integration cases: ≤30% of (unit+integration)
|
|
158
159
|
- Significant deviation → **Important finding** (test-pyramid-imbalance)
|
|
159
160
|
|
|
160
161
|
If `test-matrix.md` does NOT exist, skip this step silently (legacy change compatibility).
|
|
@@ -73,9 +73,9 @@ Subagent (general-purpose):
|
|
|
73
73
|
- Each case has a corresponding test implementation (test_file + test_method_name match)?
|
|
74
74
|
- Assertions match the matrix's `expected` output?
|
|
75
75
|
- `design_method` matches actual test approach (boundary case uses real boundary values)?
|
|
76
|
-
- Matrix coverage < 100% → Critical finding (test-matrix-gap)
|
|
76
|
+
- Matrix coverage < 100% → Critical finding (test-matrix-gap)(分母排除 test_tier=e2e,E2E case 由 release-archivist Step 5b 对账)
|
|
77
77
|
- Candidate Coverage Ledger: deferred items have reasonable reasons?
|
|
78
|
-
- Pyramid ratio: unit 70-80%, integration ≤30
|
|
78
|
+
- Pyramid ratio: unit 70-80%, integration ≤30%?(分母 = unit+integration,e2e 不计入)
|
|
79
79
|
If `test-matrix.md` does NOT exist, skip this section silently.
|
|
80
80
|
|
|
81
81
|
**Production readiness:**
|
|
@@ -11,7 +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/` 存在,读取 `INDEX.md`(模块覆盖概览 coverage_status)+ `baselines/{module}.md`(已有 case 避免重复设计;Deferred Items 评估本次是否可解决),作为 test-matrix.md
|
|
14
|
+
**Test Ledger Injection (v0.12 §43.5)**: 若 `docs/test-ledger/` 存在,读取 `INDEX.md`(模块覆盖概览 coverage_status)+ `baselines/{module}.md`(已有 case 避免重复设计;Deferred Items 评估本次是否可解决),作为 test-matrix.md 生成的增量输入。不存在时跳过(首次使用或无历史数据)。**tier 语义(v0.38.0)**:baselines 中 `test_tier=e2e` 的行表示"端到端已覆盖",不参与 unit/integration 去重与金字塔配比——不得把它误判为单测已覆盖而省略低层级 case 设计。
|
|
15
15
|
|
|
16
16
|
## Artifact Mapping
|
|
17
17
|
|
|
@@ -36,6 +36,19 @@ Before finalizing:
|
|
|
36
36
|
|
|
37
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.
|
|
38
38
|
|
|
39
|
+
## UI UX Delta Prototype Reference (v0.37.0 §68.5)
|
|
40
|
+
|
|
41
|
+
When the change involves UI (design.md has a `## UI Contract` section), the execution-contract's `## UX 增量` section (consumed by `tf prototype-sync`) must **state the prototype version worktree path** in its first line:
|
|
42
|
+
|
|
43
|
+
```markdown
|
|
44
|
+
## UX 增量
|
|
45
|
+
原型目标:<workspace>/.worktrees/prd-vN/prototype/(PRD 版本 vN,从 change-brief `upstream_plan_ref` 推导)
|
|
46
|
+
[ADD] pages/xxx.html
|
|
47
|
+
[MODIFY] pages/yyy.html
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`[ADD]/[MODIFY]` relative paths stay prototype-root-relative; the stated worktree is what `prototype-sync` resolves to when `--prototype-dir` is absent (v0.37.0 §68.5 制品链路径版本化).
|
|
51
|
+
|
|
39
52
|
## Test Matrix Generation (v0.12 §42, v0.13 §52 B1 强制化)
|
|
40
53
|
|
|
41
54
|
`test-matrix.md` 是 `execution-contract.md` 的**附属产物**(不是独立第 6 核心产物),在 contract 的 `## Test Matrix` 段引用。
|
|
@@ -44,19 +57,20 @@ Must make obvious: approved behavior, out-of-scope, constraints, batches, test o
|
|
|
44
57
|
|
|
45
58
|
### Generation Protocol
|
|
46
59
|
|
|
47
|
-
1. **输入来源**:specs/(Scenario + Unit/Integration 标签)、tasks.md(batch + file structure)、test-strategy skill(design_method 规则)、test-ledger baselines(增量输入)
|
|
60
|
+
1. **输入来源**:specs/(Scenario + Unit/Integration/E2E 标签)、tasks.md(batch + file structure)、test-strategy skill(design_method 规则)、test-ledger baselines(增量输入)
|
|
48
61
|
2. **按模块分组**:为每个有业务逻辑的模块(Service/Controller/Repository 等)生成 case 列表
|
|
49
62
|
3. **复杂度分级**:每个模块标注 trivial/medium/complex,作为用例数下限判据(test-strategy §2)
|
|
50
63
|
4. **design_method 覆盖**:每个模块至少 1 个 `{boundary, equivalence}` case + 1 个 `{error, exception, reject}` case(test-strategy §6 schema 强制覆盖)
|
|
51
64
|
5. **对抗验证**:矩阵生成后执行三招回检(test-strategy §4),缺失则追加 adversarial case
|
|
52
65
|
6. **候选覆盖台账**:列出所有被测候选(方法/类),标注 decision(covered/deferred/not_applicable)
|
|
66
|
+
7. **E2E 层评估(v0.38.0)**:对 specs 中含 **UI 交互/跨层链路**的 Scenario(AC 涉及前端交互、跨服务/跨层数据流)显式评估 E2E 必要性——纳入 `test_tier=e2e` 的 case(标准 12 列,case_id `{Module}-e2e-{scene}-{NNN}`,`work_mode=E2E`(不参与 build-executor 的 TDD 路由),mock 列 `-`,test_kind 用 `playwright_prototype`/`playwright_integration`)或记录"E2E 排除 + 理由"(如无 UI/纯后端)。不得静默跳过。
|
|
53
67
|
|
|
54
68
|
### 12-Column Format
|
|
55
69
|
|
|
56
70
|
| case_id | behavior | design_method | input | expected | test_kind | test_tier | mock | work_mode | test_file | test_method_name | run_command |
|
|
57
71
|
|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
58
72
|
|
|
59
|
-
字段说明见 test-strategy skill 的 `references/design-methods-detail.md`。`test_tier` 从 `test_kind`
|
|
73
|
+
字段说明见 test-strategy skill 的 `references/design-methods-detail.md`。`test_tier` 从 `test_kind` 派生,可取 **unit / integration / e2e** 三值(v0.38.0:E2E case 的 test_kind 用 `playwright_prototype` / `playwright_integration`)。
|
|
60
74
|
|
|
61
75
|
### Structure
|
|
62
76
|
|
|
@@ -68,6 +82,7 @@ Must make obvious: approved behavior, out-of-scope, constraints, batches, test o
|
|
|
68
82
|
- Modules covered: {N} (complexity: trivial×{n}, medium×{n}, complex×{n})
|
|
69
83
|
- Unit cases: {N} ({percent}%)
|
|
70
84
|
- Integration cases: {N} ({percent}%)
|
|
85
|
+
- E2E cases: {N} ({percent}%)
|
|
71
86
|
- Deferred items: {N}
|
|
72
87
|
- Matrix revision: 1
|
|
73
88
|
|
|
@@ -91,7 +106,7 @@ Must make obvious: approved behavior, out-of-scope, constraints, batches, test o
|
|
|
91
106
|
```markdown
|
|
92
107
|
## Test Matrix
|
|
93
108
|
See `test-matrix.md` for the full test case matrix (附属产物, 独立 hash).
|
|
94
|
-
- Total cases: {N}, Unit: {N}, Integration: {N}, Deferred: {N}
|
|
109
|
+
- Total cases: {N}, Unit: {N}, Integration: {N}, E2E: {N}, Deferred: {N}
|
|
95
110
|
```
|
|
96
111
|
|
|
97
112
|
然后执行 `tf state rebuild <change-dir>` 更新 `test_matrix_hash`。
|
package/skills/e2e/SKILL.md
CHANGED
|
@@ -52,5 +52,7 @@ user-invocable: true
|
|
|
52
52
|
## 复用机制
|
|
53
53
|
单一 `e2e/` + `playwright.config.ts` `projects`(baseURL):`prototype` project → http-server 托管 HTML;`integration` project → 真实应用。脚本一致,仅切地址 + 少量选择器/断言适配(高重叠复用,非零触碰;原型绿 ≠ 集成绿)。
|
|
54
54
|
|
|
55
|
+
**原型期托管(v0.37.0 §68.5)**:`prototype` project 托管的原型 = 对应 PRD 版本 worktree 的 `prototype/`(`<workspace>/.worktrees/prd-vN/prototype/`,版本从 change-brief `upstream_plan_ref` 推导);baseURL 指向该 worktree 的 http-server,**不指向主干旧版本原型**。该 worktree 缺失时回退 `<workspace>/prototype/` 并提示 `tf prototype branch <prd-vN>` 创建(与 prototype-sync 回退语义一致)。
|
|
56
|
+
|
|
55
57
|
## 与 release-archivist 衔接
|
|
56
58
|
e2e 报告 + 4 级制品比对(L1 存在 / L2 实质 / L3 接线 / L4 数据流)作为 closing 验收 gate 的验证维度,由 `release-archivist` 接入(`ce-proof` 是 Proof 编辑器,与此无关)。
|
|
@@ -23,7 +23,7 @@ description: 本地 HTML 原型设计与维护 skill(零外部依赖、可离
|
|
|
23
23
|
① 环境探查 + 方案设计 → sub: prototype-env-scout(只读,产出环境简报+2-3 个差异化方向,v0.18.0)
|
|
24
24
|
② 方案评审 + 人工评审 → 主代理(方向选定+方案确认 → AskUserQuestion)
|
|
25
25
|
③ 原型绘制 → sub: prototype-builder(有 Write,种子优先:template.html+layouts.md 组合,v0.18.0)
|
|
26
|
-
④ 原型评审 → sub: prototype-reviewer
|
|
26
|
+
④ 原型评审 → sub: prototype-reviewer(PRD 6 维度+P0 grep+craft 4 席 rubric,v0.18.0,直接写审查报告)
|
|
27
27
|
⑤ 循环编排 → 主代理:FAIL→回③修正(≤3轮,收敛检测);PASS/PASS_WITH_WARNINGS→⑥
|
|
28
28
|
⑥ 人工评审路由 → 主代理 AskUserQuestion:
|
|
29
29
|
- PRD 有问题 → 回 orchestrator S2 修订 PRD(vN 内修订,非升版)→ 再更新原型
|
|
@@ -38,8 +38,8 @@ description: 本地 HTML 原型设计与维护 skill(零外部依赖、可离
|
|
|
38
38
|
| ①环境探查+方案设计 | sub `prototype-env-scout`(插件 agent,只读) | 无(简报+方向写 response) |
|
|
39
39
|
| ②方案/人工评审 | 主代理(含方向选定,v0.18.0) | 无 |
|
|
40
40
|
| ③绘制 | sub `prototype-builder`(插件 agent,种子优先) | prototype/ 全部 |
|
|
41
|
-
| ④评审 | sub `prototype-reviewer`(插件 agent
|
|
42
|
-
| ⑤循环编排 | 主代理 |
|
|
41
|
+
| ④评审 | sub `prototype-reviewer`(插件 agent,含 craft rubric) | 审查报告(直接写文件) |
|
|
42
|
+
| ⑤循环编排 | 主代理 | 无 |
|
|
43
43
|
| ⑥人工路由 | 主代理 | 无 |
|
|
44
44
|
|
|
45
45
|
## 内核(零外部依赖)
|
|
@@ -87,6 +87,7 @@ prototype/
|
|
|
87
87
|
## 分支约定
|
|
88
88
|
- 原型随 **PRD 当前版本分支**维护(如 `prd-v2` 分支的 `prototype/` = v2 产品原型)。
|
|
89
89
|
- change 引用其所在 PRD 版本分支的 `prototype/`。
|
|
90
|
+
- **版本 worktree 管理(v0.37.0 §68.4)**:原型为独立仓库(有远程),版本分支的 worktree 由 `tf prototype branch <prd-vN>` 创建/复用(隔离 + 团队拉取入口);版本收尾(change_dag 该版本全部 closing)由 `tf prototype deisolate <prd-vN> --merge --clean` merge 回主干 + 清理(方案 A:merge 时机 = 版本收尾)。原型 worktree 布局 `<workspace>/.worktrees/prd-vN/prototype/`(与代码实施 `.worktrees/` 统一隔离区)。
|
|
90
91
|
|
|
91
92
|
## prototype-sync(change 完成回写,release-archivist 自动触发,v0.24.0 升级)
|
|
92
93
|
|
|
@@ -83,12 +83,12 @@ test -f <prototype_path>/index.html && test -s <prototype_path>/index.html && ec
|
|
|
83
83
|
|
|
84
84
|
## 步骤 ④ 原型评审(子代理)
|
|
85
85
|
|
|
86
|
-
派发插件 agent `prototype-reviewer
|
|
86
|
+
派发插件 agent `prototype-reviewer`(独立上下文),**传入:`prd_path`、`prototype_path`(及当前轮次 N)**,执行:
|
|
87
87
|
- PRD 6 维度(D1-D6,D6 含五状态契约)
|
|
88
88
|
- **P0 工艺机械检查(v0.18.0)**:裸 hex / 靛蓝黑名单 / emoji / 填充文案 / scrollIntoView / data-testid / accent 超限——P0 违规 = Critical
|
|
89
89
|
- **Craft Quality 4 席 rubric(v0.18.0)**:工艺(×0.4)+品牌(×0.2)+无障碍(×0.2)+文案(×0.2) 加权合成
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
agent 直接写审查报告到 `prd/vN/prototype-auto-review.md`。
|
|
92
92
|
|
|
93
93
|
判定(v0.18.0 升级):
|
|
94
94
|
- **PASS**:Critical=0 且 Important=0 且 craft 合成 ≥6.0
|