sdd-flow-kit 1.3.14 → 1.3.18
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/dist/cli.js +97 -10
- package/dist/core/apiFieldContract.js +90 -0
- package/dist/core/demoImplementationCoverage.js +242 -0
- package/dist/core/e2eAssertionDepth.js +233 -0
- package/dist/core/prdArtifacts.js +12 -1
- package/dist/core/prdCoverage.js +46 -9
- package/dist/core/prdSemanticDiff.js +7 -4
- package/dist/core/syncSourcePrd.js +181 -0
- package/dist/core/techDocQuality.js +87 -0
- package/dist/core/testAssertionAnalysis.js +213 -0
- package/dist/core/versionIntent.js +65 -0
- package/dist/core/writeNext.js +1 -1
- package/dist/index.js +6 -1
- package/dist/steps/invokeFlow.js +5 -19
- package/dist/steps/runGate.js +49 -5
- package/dist/steps/runGuard.js +44 -0
- package/dist/steps/runSyncSourcePrd.js +38 -0
- package/dist/steps/runValidate.js +4 -2
- package/dist/steps/setupProject.js +31 -0
- package/dist/steps/startFlow.js +7 -2
- package/package.json +2 -1
- package/scripts/assert-active-openspec-change.sh +114 -0
- package/scripts/assess-api-field-contract.mjs +119 -0
- package/scripts/assess-demo-implementation-coverage.mjs +188 -0
- package/scripts/assess-e2e-assertion-depth.mjs +146 -0
- package/scripts/assess-playwright-e2e-coverage.mjs +244 -0
- package/scripts/assess-tech-doc-quality.mjs +91 -0
- package/scripts/install-git-hooks.sh +27 -0
- package/scripts/lib/test-assertion-analysis.mjs +134 -0
- package/scripts/resolve-playwright-e2e-scope.mjs +248 -0
- package/src/templates/artifacts/01-adi-doc-skill-/346/214/207/345/274/225.template.md +6 -2
- package/src/templates/artifacts/02-/351/234/200/346/261/202/345/210/206/346/236/220/346/217/220/347/244/272/350/257/215.template.md +5 -0
- package/src/templates/artifacts/04-/346/212/200/346/234/257/346/226/207/346/241/243.template.md +14 -0
- package/src/templates/artifacts/05-/351/252/214/346/224/266/346/270/205/345/215/225.template.md +2 -2
- package/src/templates/artifacts/06-agent-skill.template.md +3 -3
- package/src/templates/artifacts/06-openspec-/346/217/220/346/241/210/350/215/211/347/250/277.template.md +2 -2
- package/src/templates/artifacts/07-opsx-auto-chain.template.md +2 -2
- package/src/templates/artifacts/08-PRD/344/270/200/350/207/264/346/200/247/345/244/215/346/237/245/346/212/245/345/221/212.template.md +2 -0
- package/src/templates/artifacts/design-demo-reference.template.md +24 -0
- package/src/templates/prompts/02-sdd-loop-prompt.md +25 -11
- package/src/templates/prompts/04-tech-doc-fill-prompt.md +71 -0
- package/src/templates/rules/sdd-version-trigger.mdc.template +56 -0
- package/src/templates/skills/confluence-doc.py.template +64 -25
- package/src/templates/skills/demo-reference-mandatory-workflow.md +77 -0
- package/src/templates/skills/doc-skill.SKILL.md.template +13 -6
- package/src/templates/skills/layered-testing-strategy.md +13 -6
- package/src/templates/skills/version-trigger.SKILL.md.template +56 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 04-技术文档草稿 结构质量门禁:与 artifacts/04-技术文档.template.md 对齐,禁止摘要版替换完整模板。
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.REQUIRED_TECH_DOC_SECTIONS = void 0;
|
|
7
|
+
exports.checkTechDocStructure = checkTechDocStructure;
|
|
8
|
+
/** 与模板一致的必备二级章节(允许「## 1. 改造范围」类带后缀标题) */
|
|
9
|
+
exports.REQUIRED_TECH_DOC_SECTIONS = [
|
|
10
|
+
{ id: "meta", pattern: /##\s*文档元信息/m, label: "文档元信息" },
|
|
11
|
+
{ id: "scope", pattern: /##\s*1\.\s*改造范围/m, label: "1. 改造范围" },
|
|
12
|
+
{ id: "ui", pattern: /##\s*2\.\s*页面与交互设计/m, label: "2. 页面与交互设计" },
|
|
13
|
+
{ id: "demo", pattern: /###\s*2\.4\s*演示代码参照/m, label: "2.4 演示代码参照" },
|
|
14
|
+
{ id: "state", pattern: /##\s*3\.\s*前端状态与数据模型设计/m, label: "3. 前端状态与数据模型设计" },
|
|
15
|
+
{ id: "api", pattern: /##\s*4\.\s*接口与数据交互设计/m, label: "4. 接口与数据交互设计" },
|
|
16
|
+
{ id: "form", pattern: /##\s*5\.\s*表单与校验设计/m, label: "5. 表单与校验设计" },
|
|
17
|
+
{ id: "edge", pattern: /##\s*6\.\s*异常、边界与降级方案/m, label: "6. 异常、边界与降级方案" },
|
|
18
|
+
{ id: "files", pattern: /##\s*7\.\s*改造清单/m, label: "7. 改造清单" },
|
|
19
|
+
{ id: "risk", pattern: /##\s*8\.\s*风险点与应对策略/m, label: "8. 风险点与应对策略" },
|
|
20
|
+
{ id: "release", pattern: /##\s*9\.\s*发布、灰度与回滚/m, label: "9. 发布、灰度与回滚" },
|
|
21
|
+
{ id: "test", pattern: /##\s*10\.\s*测试与验收要点/m, label: "10. 测试与验收要点" },
|
|
22
|
+
{ id: "pending", pattern: /##\s*11\.\s*待确认事项/m, label: "11. 待确认事项" },
|
|
23
|
+
];
|
|
24
|
+
const MIN_LINES = 150;
|
|
25
|
+
const MIN_H3_UNDER_SCOPE = 2;
|
|
26
|
+
const API_TABLE_RE = /\|\s*接口\s*\|\s*Method\s*\|/m;
|
|
27
|
+
const DEMO_TABLE_RE = /\|\s*演示模块\s*\|\s*演示文件\/组件\s*\|/m;
|
|
28
|
+
const AC_REF_RE = /\[AC-\d{2,}\]/g;
|
|
29
|
+
/** 典型「摘要版」反模式:仅若干条顶层 [AC-xx] bullet,无三级标题 */
|
|
30
|
+
const SUMMARY_ANTIPATTERN_RE = /^#\s*04-技术文档草稿\s*\n+##\s*文档元信息[\s\S]{0,800}##\s*7\.\s*权限/m;
|
|
31
|
+
/**
|
|
32
|
+
* 校验 04 技术文档是否按模板填满(非摘要替换版)。
|
|
33
|
+
*/
|
|
34
|
+
function checkTechDocStructure(content) {
|
|
35
|
+
var _a, _b, _c, _d;
|
|
36
|
+
const missingSections = [];
|
|
37
|
+
const warnings = [];
|
|
38
|
+
if (!content || content.trim().length < 80) {
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
detail: "04-技术文档草稿.md 缺失或过短",
|
|
42
|
+
missingSections: exports.REQUIRED_TECH_DOC_SECTIONS.map((s) => s.label),
|
|
43
|
+
warnings: [],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const lines = content.split("\n");
|
|
47
|
+
if (lines.length < MIN_LINES) {
|
|
48
|
+
warnings.push(`行数 ${lines.length} < ${MIN_LINES}(模板级文档通常 ≥150 行)`);
|
|
49
|
+
}
|
|
50
|
+
for (const sec of exports.REQUIRED_TECH_DOC_SECTIONS) {
|
|
51
|
+
if (!sec.pattern.test(content)) {
|
|
52
|
+
missingSections.push(sec.label);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const h3InScope = ((_b = (_a = content.match(/##\s*1\.\s*改造范围[\s\S]*?(?=##\s*2\.)/m)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : "").match(/^###\s+/gm);
|
|
56
|
+
if (!h3InScope || h3InScope.length < MIN_H3_UNDER_SCOPE) {
|
|
57
|
+
missingSections.push("1.x 改造目标/In Scope 等三级小节");
|
|
58
|
+
}
|
|
59
|
+
if (!API_TABLE_RE.test(content)) {
|
|
60
|
+
missingSections.push("4.1 接口清单表格(| 接口 | Method |)");
|
|
61
|
+
}
|
|
62
|
+
if (!DEMO_TABLE_RE.test(content)) {
|
|
63
|
+
missingSections.push("2.4 演示模块映射表格(| 演示模块 | 演示文件/组件 |)");
|
|
64
|
+
}
|
|
65
|
+
const acRefs = (_c = content.match(AC_REF_RE)) !== null && _c !== void 0 ? _c : [];
|
|
66
|
+
if (acRefs.length < 3) {
|
|
67
|
+
warnings.push("文中 [AC-XX] 引用过少(建议在各章节分散标注)");
|
|
68
|
+
}
|
|
69
|
+
if (SUMMARY_ANTIPATTERN_RE.test(content) && lines.length < 80) {
|
|
70
|
+
missingSections.push("禁止摘要版:须保留模板全部 11 章并逐节填写");
|
|
71
|
+
}
|
|
72
|
+
const placeholderHeavy = ((_d = content.match(/(填写)|待填写|TBD|示例:/g)) !== null && _d !== void 0 ? _d : []).length > 25 &&
|
|
73
|
+
lines.length < MIN_LINES;
|
|
74
|
+
if (placeholderHeavy) {
|
|
75
|
+
warnings.push("占位符过多且篇幅过短,疑似未按 PRD/接口/演示填充");
|
|
76
|
+
}
|
|
77
|
+
const ok = missingSections.length === 0 && lines.length >= MIN_LINES;
|
|
78
|
+
const detail = ok
|
|
79
|
+
? `04 技术文档结构合格(${lines.length} 行,${exports.REQUIRED_TECH_DOC_SECTIONS.length} 章齐全)`
|
|
80
|
+
: `04 技术文档不符合模板: 缺 ${missingSections.join("、")}${warnings.length ? `;${warnings.join(";")}` : ""}`;
|
|
81
|
+
return {
|
|
82
|
+
ok,
|
|
83
|
+
detail,
|
|
84
|
+
missingSections,
|
|
85
|
+
warnings,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 测试断言深度分析:用于 PRD 语义覆盖、AC 映射与 E2E 深度门禁。
|
|
4
|
+
* 禁止仅靠注释登记 PRD/AC;要求 test/it 块内存在 expect 等有效断言。
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.stripComments = stripComments;
|
|
8
|
+
exports.parseTestBlocks = parseTestBlocks;
|
|
9
|
+
exports.isRegistryStubFile = isRegistryStubFile;
|
|
10
|
+
exports.testBlockHasExpect = testBlockHasExpect;
|
|
11
|
+
exports.testBlockReferencesAc = testBlockReferencesAc;
|
|
12
|
+
exports.testBlockReferencesAtom = testBlockReferencesAtom;
|
|
13
|
+
exports.hasMeaningfulAtomCoverageInFile = hasMeaningfulAtomCoverageInFile;
|
|
14
|
+
exports.hasMeaningfulAcCoverageInFile = hasMeaningfulAcCoverageInFile;
|
|
15
|
+
exports.testBlockIsPlaceholderOnly = testBlockIsPlaceholderOnly;
|
|
16
|
+
exports.analyzeE2eSpecDepth = analyzeE2eSpecDepth;
|
|
17
|
+
const TEST_TITLE_RE = /(?:^|\n)\s*(?:test|it)(?:\.(?:only|skip))?\s*\(\s*(?:async\s*)?[`'"]([^`'"]*)[`'"]/g;
|
|
18
|
+
const EXPECT_RE = /\bexpect\s*\(/;
|
|
19
|
+
const E2E_DIR_RE = /(^|\/)(e2e|playwright)(\/|$)/i;
|
|
20
|
+
/** 仅用于登记 PRD 原子、无真实 UI 断言的伪覆盖文件 */
|
|
21
|
+
const REGISTRY_STUB_FILE_RE = /prd-atoms-coverage|prd-atoms-registry|semantic-registry/i;
|
|
22
|
+
/** 浅层 E2E:仅有占位符可见性断言 */
|
|
23
|
+
const PLACEHOLDER_ONLY_RE = /getByPlaceholder|getByLabel|getByTestId/i;
|
|
24
|
+
const DATA_ASSERTION_RE = /getByRole\s*\(\s*['"]cell|getByRole\s*\(\s*['"]row|toContainText|waitForResponse|\.n-data-table|data-table-tbody|data-table-body/i;
|
|
25
|
+
const HOOK_TITLE_RE = /^(?:beforeEach|afterEach|beforeAll|afterAll)$/i;
|
|
26
|
+
/** 移除行注释与块注释,避免注释中的 PRD 或 AC 标识误计为覆盖 */
|
|
27
|
+
function stripComments(content) {
|
|
28
|
+
let out = "";
|
|
29
|
+
let i = 0;
|
|
30
|
+
while (i < content.length) {
|
|
31
|
+
if (content[i] === "/" && content[i + 1] === "/") {
|
|
32
|
+
while (i < content.length && content[i] !== "\n")
|
|
33
|
+
i += 1;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (content[i] === "/" && content[i + 1] === "*") {
|
|
37
|
+
i += 2;
|
|
38
|
+
while (i < content.length && !(content[i] === "*" && content[i + 1] === "/"))
|
|
39
|
+
i += 1;
|
|
40
|
+
i += 2;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
out += content[i];
|
|
44
|
+
i += 1;
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
/** 解析 test/it 块(标题 + 回调体近似区间) */
|
|
49
|
+
function parseTestBlocks(content) {
|
|
50
|
+
var _a, _b, _c;
|
|
51
|
+
const stripped = stripComments(content);
|
|
52
|
+
const blocks = [];
|
|
53
|
+
const re = new RegExp(TEST_TITLE_RE.source, "g");
|
|
54
|
+
let m;
|
|
55
|
+
const indices = [];
|
|
56
|
+
let idx = 0;
|
|
57
|
+
while ((m = re.exec(stripped)) !== null) {
|
|
58
|
+
indices.push({ title: (_a = m[1]) !== null && _a !== void 0 ? _a : "", start: m.index, index: idx });
|
|
59
|
+
idx += 1;
|
|
60
|
+
}
|
|
61
|
+
for (let i = 0; i < indices.length; i += 1) {
|
|
62
|
+
const cur = indices[i];
|
|
63
|
+
const nextStart = (_c = (_b = indices[i + 1]) === null || _b === void 0 ? void 0 : _b.start) !== null && _c !== void 0 ? _c : stripped.length;
|
|
64
|
+
const slice = stripped.slice(cur.start, Math.min(cur.start + 4000, nextStart));
|
|
65
|
+
blocks.push({ title: cur.title, body: slice, index: cur.index });
|
|
66
|
+
}
|
|
67
|
+
return blocks;
|
|
68
|
+
}
|
|
69
|
+
/** 是否为 PRD 原子登记类伪覆盖文件 */
|
|
70
|
+
function isRegistryStubFile(relPath, content) {
|
|
71
|
+
const norm = relPath.replace(/\\/g, "/");
|
|
72
|
+
if (REGISTRY_STUB_FILE_RE.test(norm))
|
|
73
|
+
return true;
|
|
74
|
+
if (!E2E_DIR_RE.test(norm))
|
|
75
|
+
return false;
|
|
76
|
+
const blocks = parseTestBlocks(content);
|
|
77
|
+
if (blocks.length === 0)
|
|
78
|
+
return false;
|
|
79
|
+
const allShallow = blocks.every((b) => {
|
|
80
|
+
const hasExpect = EXPECT_RE.test(b.body);
|
|
81
|
+
const onlyCount = /expect\s*\([^)]*\)\s*\.toBeGreaterThan\s*\(\s*0\s*\)/.test(b.body) &&
|
|
82
|
+
!DATA_ASSERTION_RE.test(b.body) &&
|
|
83
|
+
!/\bpage\.goto\b/.test(b.body);
|
|
84
|
+
return !hasExpect || onlyCount;
|
|
85
|
+
});
|
|
86
|
+
return allShallow && blocks.length >= 3;
|
|
87
|
+
}
|
|
88
|
+
function testBlockHasExpect(block) {
|
|
89
|
+
return EXPECT_RE.test(block.body);
|
|
90
|
+
}
|
|
91
|
+
/** test 块是否引用 AC(标题或块内显式标注) */
|
|
92
|
+
function testBlockReferencesAc(block, acId) {
|
|
93
|
+
const bracket = `[${acId}]`;
|
|
94
|
+
const upper = acId.toUpperCase();
|
|
95
|
+
return (block.title.includes(bracket) ||
|
|
96
|
+
block.title.toUpperCase().includes(upper) ||
|
|
97
|
+
block.body.includes(bracket));
|
|
98
|
+
}
|
|
99
|
+
/** test 块是否引用 PRD 原子或可核对信号 */
|
|
100
|
+
function testBlockReferencesAtom(block, atomId, signals) {
|
|
101
|
+
const hasId = block.title.includes(atomId) ||
|
|
102
|
+
block.title.includes(`[${atomId}]`) ||
|
|
103
|
+
block.body.includes(atomId);
|
|
104
|
+
if (hasId)
|
|
105
|
+
return true;
|
|
106
|
+
return signals.some((s) => s.length >= 4 && (block.title.includes(s) || block.body.includes(s)));
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* 判断单条测试文件是否对 PRD 原子有「有效」自动化覆盖(非注释、非 stub、含 expect)。
|
|
110
|
+
*/
|
|
111
|
+
function hasMeaningfulAtomCoverageInFile(relPath, content, atomId, signals) {
|
|
112
|
+
if (isRegistryStubFile(relPath, content)) {
|
|
113
|
+
return { covered: false, hasE2e: false };
|
|
114
|
+
}
|
|
115
|
+
const isE2e = E2E_DIR_RE.test(relPath);
|
|
116
|
+
const blocks = parseTestBlocks(content);
|
|
117
|
+
for (const block of blocks) {
|
|
118
|
+
if (!testBlockHasExpect(block))
|
|
119
|
+
continue;
|
|
120
|
+
if (!testBlockReferencesAtom(block, atomId, signals))
|
|
121
|
+
continue;
|
|
122
|
+
return { covered: true, hasE2e: isE2e };
|
|
123
|
+
}
|
|
124
|
+
return { covered: false, hasE2e: false };
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* 判断单条测试文件是否对 AC 有有效覆盖(test 块内 [AC-XX] + expect)。
|
|
128
|
+
*/
|
|
129
|
+
function hasMeaningfulAcCoverageInFile(relPath, content, acId) {
|
|
130
|
+
if (isRegistryStubFile(relPath, content)) {
|
|
131
|
+
return { covered: false, isE2e: false };
|
|
132
|
+
}
|
|
133
|
+
const isE2e = E2E_DIR_RE.test(relPath);
|
|
134
|
+
const blocks = parseTestBlocks(content);
|
|
135
|
+
for (const block of blocks) {
|
|
136
|
+
if (!testBlockReferencesAc(block, acId))
|
|
137
|
+
continue;
|
|
138
|
+
if (!testBlockHasExpect(block))
|
|
139
|
+
continue;
|
|
140
|
+
return { covered: true, isE2e };
|
|
141
|
+
}
|
|
142
|
+
return { covered: false, isE2e: false };
|
|
143
|
+
}
|
|
144
|
+
function testBlockIsPlaceholderOnly(block) {
|
|
145
|
+
if (HOOK_TITLE_RE.test(block.title.trim()))
|
|
146
|
+
return false;
|
|
147
|
+
if (!EXPECT_RE.test(block.body))
|
|
148
|
+
return false;
|
|
149
|
+
const hasPlaceholder = PLACEHOLDER_ONLY_RE.test(block.body);
|
|
150
|
+
const hasData = DATA_ASSERTION_RE.test(block.body);
|
|
151
|
+
const hasInteraction = /\b(?:click|dblclick|fill|press|selectOption)\b/.test(block.body);
|
|
152
|
+
return hasPlaceholder && !hasData && !hasInteraction;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* 评估单个 E2E spec 的断言深度(L3 最低要求)。
|
|
156
|
+
* 每个非 hook 的 test 块不得仅断言 placeholder;至少一个 test 块须含数据或交互断言。
|
|
157
|
+
*/
|
|
158
|
+
function analyzeE2eSpecDepth(relPath, content) {
|
|
159
|
+
const issues = [];
|
|
160
|
+
if (isRegistryStubFile(relPath, content)) {
|
|
161
|
+
return {
|
|
162
|
+
ok: false,
|
|
163
|
+
issues: ["伪覆盖登记文件,不能作为 change 级 E2E 验收"],
|
|
164
|
+
stats: {
|
|
165
|
+
testCount: 0,
|
|
166
|
+
testsWithExpect: 0,
|
|
167
|
+
hasGoto: false,
|
|
168
|
+
hasDataAssertion: false,
|
|
169
|
+
hasInteraction: false,
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const stripped = stripComments(content);
|
|
174
|
+
const blocks = parseTestBlocks(content);
|
|
175
|
+
const hookBlocks = blocks.filter((b) => HOOK_TITLE_RE.test(b.title.trim()));
|
|
176
|
+
const testBlocks = blocks.filter((b) => !HOOK_TITLE_RE.test(b.title.trim()));
|
|
177
|
+
const testsWithExpect = testBlocks.filter((b) => testBlockHasExpect(b)).length;
|
|
178
|
+
const hasGoto = /\bpage\.goto\b/.test(stripped);
|
|
179
|
+
const hasDataInTests = testBlocks.some((b) => DATA_ASSERTION_RE.test(b.body));
|
|
180
|
+
const hasInteractionInTests = testBlocks.some((b) => /\b(?:click|dblclick|fill|press|selectOption)\b/.test(b.body));
|
|
181
|
+
const placeholderOnlyTests = testBlocks.filter((b) => testBlockIsPlaceholderOnly(b));
|
|
182
|
+
if (testBlocks.length === 0) {
|
|
183
|
+
issues.push("未解析到 test/it 用例(不含 hook)");
|
|
184
|
+
}
|
|
185
|
+
if (testsWithExpect === 0) {
|
|
186
|
+
issues.push("无含 expect( 的 test/it 块");
|
|
187
|
+
}
|
|
188
|
+
if (!hasGoto && !hookBlocks.some((b) => /\bpage\.goto\b/.test(b.body))) {
|
|
189
|
+
issues.push("缺少 page.goto(页面可达性未验证)");
|
|
190
|
+
}
|
|
191
|
+
if (placeholderOnlyTests.length > 0) {
|
|
192
|
+
issues.push(`以下用例仅断言 placeholder/label,缺少数据或交互: ${placeholderOnlyTests.map((t) => t.title).join(", ")}`);
|
|
193
|
+
}
|
|
194
|
+
if (!hasDataInTests && !hasInteractionInTests) {
|
|
195
|
+
issues.push("所有 test 块均缺少数据展示断言(cell/row/toContainText/waitForResponse)或交互操作");
|
|
196
|
+
}
|
|
197
|
+
const ok = testBlocks.length > 0 &&
|
|
198
|
+
testsWithExpect > 0 &&
|
|
199
|
+
(hasGoto || hookBlocks.some((b) => /\bpage\.goto\b/.test(b.body))) &&
|
|
200
|
+
placeholderOnlyTests.length === 0 &&
|
|
201
|
+
(hasDataInTests || hasInteractionInTests);
|
|
202
|
+
return {
|
|
203
|
+
ok,
|
|
204
|
+
issues,
|
|
205
|
+
stats: {
|
|
206
|
+
testCount: testBlocks.length,
|
|
207
|
+
testsWithExpect,
|
|
208
|
+
hasGoto,
|
|
209
|
+
hasDataAssertion: hasDataInTests,
|
|
210
|
+
hasInteraction: hasInteractionInTests,
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseVersionIntent = parseVersionIntent;
|
|
4
|
+
exports.normalizeProductName = normalizeProductName;
|
|
5
|
+
exports.isVersionDevelopmentIntent = isVersionDevelopmentIntent;
|
|
6
|
+
exports.getVersionIntentInvokeCommand = getVersionIntentInvokeCommand;
|
|
7
|
+
/**
|
|
8
|
+
* 解析“开发某产品某版本”的自然语言意图。
|
|
9
|
+
* 该解析器只负责提取产品与版本,不承担后续流程执行。
|
|
10
|
+
*/
|
|
11
|
+
function parseVersionIntent(text) {
|
|
12
|
+
const normalized = text.trim();
|
|
13
|
+
if (!normalized)
|
|
14
|
+
return null;
|
|
15
|
+
const normalizedText = normalized
|
|
16
|
+
.replace(/[::,,。;;!!??]/g, " ")
|
|
17
|
+
.replace(/[_-]/g, " ")
|
|
18
|
+
.replace(/\s+/g, " ")
|
|
19
|
+
.trim();
|
|
20
|
+
const patterns = [
|
|
21
|
+
/(?:开发|进行|实现|处理|做)\s*([\u4e00-\u9fa5A-Za-z][\u4e00-\u9fa5A-Za-z\s]*?)\s*[Vv]?\s*([0-9]+(?:\.[0-9]+){1,3})\s*(?:版本|需求|PRD)?/i,
|
|
22
|
+
/([\u4e00-\u9fa5A-Za-z][\u4e00-\u9fa5A-Za-z\s]*?)\s*[Vv]?\s*([0-9]+(?:\.[0-9]+){1,3})\s*(?:版本|需求)?\s*(?:开发|进行|实现|处理|做)?/i,
|
|
23
|
+
];
|
|
24
|
+
for (const re of patterns) {
|
|
25
|
+
const match = normalizedText.match(re);
|
|
26
|
+
if (!match)
|
|
27
|
+
continue;
|
|
28
|
+
const product = normalizeProductName(match[1]);
|
|
29
|
+
const version = match[2];
|
|
30
|
+
if (!product || !version)
|
|
31
|
+
continue;
|
|
32
|
+
return { product, version };
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
function normalizeProductName(raw) {
|
|
37
|
+
const value = raw
|
|
38
|
+
.replace(/\b(PRD|需求|版本|开发|进行|实现|处理|做)\b/gi, " ")
|
|
39
|
+
.replace(/需求|版本|开发|进行|实现|处理|做/g, " ")
|
|
40
|
+
.replace(/\s+/g, " ")
|
|
41
|
+
.trim();
|
|
42
|
+
if (!value)
|
|
43
|
+
return "";
|
|
44
|
+
const upper = value.toUpperCase();
|
|
45
|
+
if (upper === "ADI" || /ADINSIGHT/i.test(value))
|
|
46
|
+
return "ADI";
|
|
47
|
+
if (upper === "OMS" || /OPERATION/i.test(value))
|
|
48
|
+
return "OMS";
|
|
49
|
+
if (/BREAK\s*X/i.test(value) || /BREAKX/i.test(value))
|
|
50
|
+
return "BreakX";
|
|
51
|
+
if (/欢盟|HUANMENG|HM/i.test(value))
|
|
52
|
+
return "欢盟";
|
|
53
|
+
if (/AD\s*TOOLS/i.test(value) || /ADTOOLS/i.test(value))
|
|
54
|
+
return "AD Tools";
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
function isVersionDevelopmentIntent(text) {
|
|
58
|
+
return parseVersionIntent(text) !== null;
|
|
59
|
+
}
|
|
60
|
+
function getVersionIntentInvokeCommand(options) {
|
|
61
|
+
const parsed = parseVersionIntent(options.rawText);
|
|
62
|
+
if (!parsed)
|
|
63
|
+
return null;
|
|
64
|
+
return `npx sdd-flow-kit invoke "开发 ${parsed.product} ${parsed.version} 需求" -y --agent ${options.agent}`;
|
|
65
|
+
}
|
package/dist/core/writeNext.js
CHANGED
|
@@ -35,7 +35,7 @@ async function writeInitialNext(outputRoot, projectRoot, runId) {
|
|
|
35
35
|
"| `tasks.md` | AC 的可执行映射(`[AC-XX]`) |",
|
|
36
36
|
"",
|
|
37
37
|
"## 1. Step1 — 拉取 PRD",
|
|
38
|
-
"按 `01-获取需求文档指引.md` 执行 confluence
|
|
38
|
+
"按 `01-获取需求文档指引.md` 执行 confluence 脚本(设置 `SDD_SYNC_SOURCE_DIR` 后自动同步到 `source/PRD.md` + `source/image/`);脚本和 gate 会拒绝 `技术文档`/`后端`/`详细设计` 等非 PRD 页面,然后:",
|
|
39
39
|
"```bash",
|
|
40
40
|
`npx sdd-flow-kit gate --project-root "${pr}" --run-id ${runId} --expect prd-fetched`,
|
|
41
41
|
`npx sdd-flow-kit phase advance --project-root "${pr}" --run-id ${runId} --to after-prd`,
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.loadRunSession = exports.resolveRunDir = exports.runDeliver = exports.runPropose = exports.runPhaseAdvance = exports.printGateResult = exports.runGate = exports.toRunOptions = exports.parseInvokeText = exports.runDoctor = exports.setupProject = exports.runFlow = exports.startFlowScaffold = void 0;
|
|
3
|
+
exports.loadRunSession = exports.resolveRunDir = exports.tryAutoSyncSourcePrd = exports.syncSourcePrdFromDocs = exports.runSyncSourcePrd = exports.runDeliver = exports.runPropose = exports.runPhaseAdvance = exports.printGateResult = exports.runGate = exports.toRunOptions = exports.parseInvokeText = exports.runDoctor = exports.setupProject = exports.runFlow = exports.startFlowScaffold = void 0;
|
|
4
4
|
var startFlow_1 = require("./steps/startFlow");
|
|
5
5
|
Object.defineProperty(exports, "startFlowScaffold", { enumerable: true, get: function () { return startFlow_1.startFlowScaffold; } });
|
|
6
6
|
var runFlow_1 = require("./steps/runFlow");
|
|
@@ -21,6 +21,11 @@ var runPropose_1 = require("./steps/runPropose");
|
|
|
21
21
|
Object.defineProperty(exports, "runPropose", { enumerable: true, get: function () { return runPropose_1.runPropose; } });
|
|
22
22
|
var runDeliver_1 = require("./steps/runDeliver");
|
|
23
23
|
Object.defineProperty(exports, "runDeliver", { enumerable: true, get: function () { return runDeliver_1.runDeliver; } });
|
|
24
|
+
var runSyncSourcePrd_1 = require("./steps/runSyncSourcePrd");
|
|
25
|
+
Object.defineProperty(exports, "runSyncSourcePrd", { enumerable: true, get: function () { return runSyncSourcePrd_1.runSyncSourcePrd; } });
|
|
26
|
+
var syncSourcePrd_1 = require("./core/syncSourcePrd");
|
|
27
|
+
Object.defineProperty(exports, "syncSourcePrdFromDocs", { enumerable: true, get: function () { return syncSourcePrd_1.syncSourcePrdFromDocs; } });
|
|
28
|
+
Object.defineProperty(exports, "tryAutoSyncSourcePrd", { enumerable: true, get: function () { return syncSourcePrd_1.tryAutoSyncSourcePrd; } });
|
|
24
29
|
var resolveRun_1 = require("./steps/resolveRun");
|
|
25
30
|
Object.defineProperty(exports, "resolveRunDir", { enumerable: true, get: function () { return resolveRun_1.resolveRunDir; } });
|
|
26
31
|
Object.defineProperty(exports, "loadRunSession", { enumerable: true, get: function () { return resolveRun_1.loadRunSession; } });
|
package/dist/steps/invokeFlow.js
CHANGED
|
@@ -2,26 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.parseInvokeText = parseInvokeText;
|
|
4
4
|
exports.toRunOptions = toRunOptions;
|
|
5
|
+
const versionIntent_1 = require("../core/versionIntent");
|
|
5
6
|
function parseInvokeText(text) {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
/([A-Za-z]+)[_\s-]([0-9]+(?:\.[0-9]+){2,3})(?:\s|$)/i,
|
|
11
|
-
];
|
|
12
|
-
for (const re of patterns) {
|
|
13
|
-
const match = normalized.match(re);
|
|
14
|
-
if (!match)
|
|
15
|
-
continue;
|
|
16
|
-
let product = match[1].replace(/[-_\s]+$/g, "").trim();
|
|
17
|
-
product = product.replace(/^进行/, "").trim();
|
|
18
|
-
if (/^adi$/i.test(product) || product.toUpperCase() === "ADI")
|
|
19
|
-
product = "ADI";
|
|
20
|
-
if (/^oms$/i.test(product))
|
|
21
|
-
product = "OMS";
|
|
22
|
-
return { product: product.toUpperCase(), version: match[2] };
|
|
23
|
-
}
|
|
24
|
-
return null;
|
|
7
|
+
const parsed = (0, versionIntent_1.parseVersionIntent)(text);
|
|
8
|
+
if (!parsed)
|
|
9
|
+
return null;
|
|
10
|
+
return { product: parsed.product, version: parsed.version };
|
|
25
11
|
}
|
|
26
12
|
function toRunOptions(args) {
|
|
27
13
|
var _a, _b;
|
package/dist/steps/runGate.js
CHANGED
|
@@ -7,11 +7,14 @@ exports.runGate = runGate;
|
|
|
7
7
|
exports.printGateResult = printGateResult;
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const prdArtifacts_1 = require("../core/prdArtifacts");
|
|
10
|
+
const syncSourcePrd_1 = require("../core/syncSourcePrd");
|
|
10
11
|
const prdCoverage_1 = require("../core/prdCoverage");
|
|
11
12
|
const gitDiffGuard_1 = require("../core/gitDiffGuard");
|
|
12
13
|
const acMatrix_1 = require("../core/acMatrix");
|
|
13
14
|
const projectRoots_1 = require("../core/projectRoots");
|
|
14
15
|
const openspecGate_1 = require("../core/openspecGate");
|
|
16
|
+
const demoImplementationCoverage_1 = require("../core/demoImplementationCoverage");
|
|
17
|
+
const techDocQuality_1 = require("../core/techDocQuality");
|
|
15
18
|
const resolveRun_1 = require("./resolveRun");
|
|
16
19
|
const fs_1 = require("../core/fs");
|
|
17
20
|
function fail(checks, expect, runId) {
|
|
@@ -73,7 +76,7 @@ async function resolveImplRoots(projectRoot) {
|
|
|
73
76
|
* 脚本门禁:失败时 process.exitCode=1,成功 exit 0。
|
|
74
77
|
*/
|
|
75
78
|
async function runGate(options) {
|
|
76
|
-
var _a, _b, _c;
|
|
79
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
77
80
|
const loaded = await (0, resolveRun_1.loadRunSession)(options.projectRoot, options.runId);
|
|
78
81
|
const checks = [];
|
|
79
82
|
if (!loaded || !loaded.resolved) {
|
|
@@ -85,7 +88,27 @@ async function runGate(options) {
|
|
|
85
88
|
const runId = resolved.runId;
|
|
86
89
|
const implRoots = await resolveImplRoots(options.projectRoot);
|
|
87
90
|
if (options.expect === "prd-fetched") {
|
|
88
|
-
|
|
91
|
+
let prd = await (0, prdArtifacts_1.isPrdFetched)(outputRoot);
|
|
92
|
+
if (!prd.ok) {
|
|
93
|
+
const product = (_b = (_a = session === null || session === void 0 ? void 0 : session.product) !== null && _a !== void 0 ? _a : runId.split("-")[1]) !== null && _b !== void 0 ? _b : "ADI";
|
|
94
|
+
const version = (_d = (_c = session === null || session === void 0 ? void 0 : session.version) !== null && _c !== void 0 ? _c : runId.split("-").slice(2).join("-")) !== null && _d !== void 0 ? _d : "";
|
|
95
|
+
const sync = await (0, syncSourcePrd_1.tryAutoSyncSourcePrd)({
|
|
96
|
+
projectRoot: options.projectRoot,
|
|
97
|
+
outputRoot,
|
|
98
|
+
product,
|
|
99
|
+
version,
|
|
100
|
+
});
|
|
101
|
+
if (sync.attempted) {
|
|
102
|
+
checks.push({
|
|
103
|
+
name: "source-prd-sync",
|
|
104
|
+
ok: sync.ok,
|
|
105
|
+
detail: sync.detail,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
if (sync.ok) {
|
|
109
|
+
prd = await (0, prdArtifacts_1.isPrdFetched)(outputRoot);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
89
112
|
checks.push({ name: "prd-fetched", ...prd });
|
|
90
113
|
return pass(checks, options.expect, runId);
|
|
91
114
|
}
|
|
@@ -105,6 +128,14 @@ async function runGate(options) {
|
|
|
105
128
|
checks.push({ name: "questions-closed", ...closed });
|
|
106
129
|
const docs = await (0, prdArtifacts_1.areAnalysisDocsReady)(outputRoot);
|
|
107
130
|
checks.push({ name: "analysis-docs", ...docs });
|
|
131
|
+
const techRaw = await (0, fs_1.readFileIfExists)(path_1.default.join(outputRoot, "04-技术文档草稿.md"));
|
|
132
|
+
if (process.env.OPSX_SKIP_TECH_DOC_QUALITY !== "1") {
|
|
133
|
+
const techStruct = (0, techDocQuality_1.checkTechDocStructure)(techRaw !== null && techRaw !== void 0 ? techRaw : "");
|
|
134
|
+
checks.push({ name: "tech-doc-structure", ok: techStruct.ok, detail: techStruct.detail });
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
checks.push({ name: "tech-doc-structure", ok: true, detail: "OPSX_SKIP_TECH_DOC_QUALITY=1 已跳过" });
|
|
138
|
+
}
|
|
108
139
|
const acDefined = await (0, prdArtifacts_1.isAcceptanceChecklistDefined)(outputRoot);
|
|
109
140
|
checks.push({ name: "acceptance-checklist-defined", ...acDefined });
|
|
110
141
|
if (session && session.docModeOnly === false) {
|
|
@@ -173,6 +204,11 @@ async function runGate(options) {
|
|
|
173
204
|
detail: `openspec/changes/${changeName} 不存在,无法校验 AC 映射`,
|
|
174
205
|
});
|
|
175
206
|
}
|
|
207
|
+
const pkgRoot = await (0, projectRoots_1.resolvePackageRoot)(installRoot);
|
|
208
|
+
const demoArtifactChecks = await (0, demoImplementationCoverage_1.checkDemoReferenceArtifacts)(pkgRoot, changeName);
|
|
209
|
+
for (const c of demoArtifactChecks) {
|
|
210
|
+
checks.push({ name: c.name, ok: c.ok, detail: c.detail });
|
|
211
|
+
}
|
|
176
212
|
return pass(checks, options.expect, runId);
|
|
177
213
|
}
|
|
178
214
|
if (options.expect === "ac-signed") {
|
|
@@ -183,7 +219,10 @@ async function runGate(options) {
|
|
|
183
219
|
if (options.expect === "prd-coverage") {
|
|
184
220
|
const installRoot = (0, projectRoots_1.resolveInstallRoot)(options.projectRoot);
|
|
185
221
|
const pkgRoot = await (0, projectRoots_1.resolvePackageRoot)(installRoot);
|
|
186
|
-
const
|
|
222
|
+
const changeName = await readActiveChange(options.projectRoot, session, options.change);
|
|
223
|
+
const coverage = await (0, prdCoverage_1.checkPrdCoverage)(outputRoot, pkgRoot, {
|
|
224
|
+
changeName: changeName !== null && changeName !== void 0 ? changeName : undefined,
|
|
225
|
+
});
|
|
187
226
|
for (const c of coverage.checks) {
|
|
188
227
|
checks.push({ name: c.name, ok: c.ok, detail: c.detail });
|
|
189
228
|
}
|
|
@@ -211,6 +250,11 @@ async function runGate(options) {
|
|
|
211
250
|
found = true;
|
|
212
251
|
const st = (0, openspecGate_1.checkOpenspecApplyReady)(root, changeName);
|
|
213
252
|
checks.push({ name: "openspec-apply-ready", ok: st.applyReady, detail: st.detail });
|
|
253
|
+
const pkgRoot = await (0, projectRoots_1.resolvePackageRoot)(installRoot);
|
|
254
|
+
const demoArtifactChecks = await (0, demoImplementationCoverage_1.checkDemoReferenceArtifacts)(pkgRoot, changeName);
|
|
255
|
+
for (const c of demoArtifactChecks) {
|
|
256
|
+
checks.push({ name: c.name, ok: c.ok, detail: c.detail });
|
|
257
|
+
}
|
|
214
258
|
break;
|
|
215
259
|
}
|
|
216
260
|
}
|
|
@@ -330,8 +374,8 @@ async function runGate(options) {
|
|
|
330
374
|
name: "validate-report",
|
|
331
375
|
ok,
|
|
332
376
|
detail: ok
|
|
333
|
-
? `验收通过 (pipeline=${(
|
|
334
|
-
: `验收结果: ${(
|
|
377
|
+
? `验收通过 (pipeline=${(_e = j.pipelineShortExitCode) !== null && _e !== void 0 ? _e : "?"}, e2e=${(_f = j.e2eExitCode) !== null && _f !== void 0 ? _f : "?"})`
|
|
378
|
+
: `验收结果: ${(_g = j.verdict) !== null && _g !== void 0 ? _g : "unknown"}`,
|
|
335
379
|
});
|
|
336
380
|
}
|
|
337
381
|
catch {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runGuard = runGuard;
|
|
4
|
+
const runFlow_1 = require("./runFlow");
|
|
5
|
+
const invokeFlow_1 = require("./invokeFlow");
|
|
6
|
+
const runSessionMode_1 = require("./runSessionMode");
|
|
7
|
+
/**
|
|
8
|
+
* 识别“开发产品版本”的自然语言入口,并在命中时直接启动 SDD 流程。
|
|
9
|
+
* 该守卫用于压过普通 OpenSpec/OPSX 入口,避免版本需求误走自由提案流程。
|
|
10
|
+
*/
|
|
11
|
+
async function runGuard(options) {
|
|
12
|
+
const text = options.text.trim();
|
|
13
|
+
const parsed = (0, invokeFlow_1.parseInvokeText)(text);
|
|
14
|
+
if (!parsed) {
|
|
15
|
+
return {
|
|
16
|
+
ok: false,
|
|
17
|
+
action: "noop",
|
|
18
|
+
input: text,
|
|
19
|
+
detail: "未识别到版本开发意图,无需启动 sdd-flow-kit invoke。",
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const runOptions = (0, invokeFlow_1.toRunOptions)({
|
|
23
|
+
parsed,
|
|
24
|
+
projectRoot: options.projectRoot,
|
|
25
|
+
branch: options.branch,
|
|
26
|
+
yes: options.yes,
|
|
27
|
+
dryRun: options.dryRun,
|
|
28
|
+
agent: options.agent,
|
|
29
|
+
});
|
|
30
|
+
const result = await (0, runFlow_1.runFlow)(runOptions);
|
|
31
|
+
if (!options.dryRun) {
|
|
32
|
+
await (0, runSessionMode_1.applyInvokeTextToSession)(options.projectRoot, result.runId, text);
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
ok: true,
|
|
36
|
+
action: "invoke",
|
|
37
|
+
input: text,
|
|
38
|
+
product: parsed.product,
|
|
39
|
+
version: parsed.version,
|
|
40
|
+
runId: result.runId,
|
|
41
|
+
nextPath: `openspec/PRD/${result.runId}/NEXT.md`,
|
|
42
|
+
detail: `已识别版本开发意图并启动 SDD 流程:${parsed.product} ${parsed.version} -> runId=${result.runId}`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runSyncSourcePrd = runSyncSourcePrd;
|
|
4
|
+
const syncSourcePrd_1 = require("../core/syncSourcePrd");
|
|
5
|
+
const resolveRun_1 = require("./resolveRun");
|
|
6
|
+
/**
|
|
7
|
+
* CLI:将 docs 版本目录 PRD 同步到 openspec run 的 source/。
|
|
8
|
+
*/
|
|
9
|
+
async function runSyncSourcePrd(options) {
|
|
10
|
+
var _a, _b, _c, _d;
|
|
11
|
+
const loaded = await (0, resolveRun_1.loadRunSession)(options.projectRoot, options.runId);
|
|
12
|
+
if (!(loaded === null || loaded === void 0 ? void 0 : loaded.resolved)) {
|
|
13
|
+
return { ok: false, detail: "未找到 openspec/PRD/<runId> 目录", runId: null };
|
|
14
|
+
}
|
|
15
|
+
const { resolved, session } = loaded;
|
|
16
|
+
const parts = resolved.runId.split("-");
|
|
17
|
+
const product = (_b = (_a = session === null || session === void 0 ? void 0 : session.product) !== null && _a !== void 0 ? _a : parts[1]) !== null && _b !== void 0 ? _b : "ADI";
|
|
18
|
+
const version = (_d = (_c = session === null || session === void 0 ? void 0 : session.version) !== null && _c !== void 0 ? _c : parts.slice(2).join("-")) !== null && _d !== void 0 ? _d : "";
|
|
19
|
+
if (options.dryRun) {
|
|
20
|
+
return {
|
|
21
|
+
ok: true,
|
|
22
|
+
detail: `dry-run: 将把 docs 版本 PRD 同步到 ${resolved.outputRoot}/source/`,
|
|
23
|
+
runId: resolved.runId,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const result = await (0, syncSourcePrd_1.syncSourcePrdFromDocs)({
|
|
27
|
+
projectRoot: options.projectRoot,
|
|
28
|
+
outputRoot: resolved.outputRoot,
|
|
29
|
+
product,
|
|
30
|
+
version,
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
ok: result.ok,
|
|
34
|
+
detail: result.detail,
|
|
35
|
+
runId: resolved.runId,
|
|
36
|
+
imageCount: result.imageCount,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -114,7 +114,9 @@ async function runValidate(options) {
|
|
|
114
114
|
if (remediateLoop.escalated) {
|
|
115
115
|
needUserConfirmFromRemediate = true;
|
|
116
116
|
}
|
|
117
|
-
let prdCoverage = await (0, prdCoverage_1.checkPrdCoverage)(loaded.resolved.outputRoot, pkgRoot
|
|
117
|
+
let prdCoverage = await (0, prdCoverage_1.checkPrdCoverage)(loaded.resolved.outputRoot, pkgRoot, {
|
|
118
|
+
changeName: options.change,
|
|
119
|
+
});
|
|
118
120
|
if (!prdCoverage.ok) {
|
|
119
121
|
const failed = prdCoverage.checks.filter((c) => !c.ok).map((c) => `${c.name}: ${c.detail}`);
|
|
120
122
|
const payload = {
|
|
@@ -210,7 +212,7 @@ async function runValidate(options) {
|
|
|
210
212
|
}
|
|
211
213
|
return {
|
|
212
214
|
ok: true,
|
|
213
|
-
detail: "验收通过(prd-coverage + lint+type+unit + 强制 e2e),可进入交付",
|
|
215
|
+
detail: "验收通过(prd-coverage 含 E2E 深度与 API 契约 + lint+type+unit + 强制 e2e),可进入交付",
|
|
214
216
|
needUserConfirm: false,
|
|
215
217
|
};
|
|
216
218
|
}
|