speccore 8.3.349 → 8.3.366
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/.agents/skills/spec-unitize/SKILL.md +5 -1
- package/.agents/skills/spec-verify/SKILL.md +22 -1
- package/dist/cli.js +19 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/about.d.ts.map +1 -1
- package/dist/commands/about.js +36 -1
- package/dist/commands/about.js.map +1 -1
- package/dist/commands/analyze/add-platform.d.ts +3 -0
- package/dist/commands/analyze/add-platform.d.ts.map +1 -0
- package/dist/commands/analyze/add-platform.js +82 -0
- package/dist/commands/analyze/add-platform.js.map +1 -0
- package/dist/commands/analyze/audit-fix.d.ts +3 -0
- package/dist/commands/analyze/audit-fix.d.ts.map +1 -0
- package/dist/commands/analyze/audit-fix.js +73 -0
- package/dist/commands/analyze/audit-fix.js.map +1 -0
- package/dist/commands/analyze/context-guard.d.ts +3 -0
- package/dist/commands/analyze/context-guard.d.ts.map +1 -0
- package/dist/commands/analyze/context-guard.js +75 -0
- package/dist/commands/analyze/context-guard.js.map +1 -0
- package/dist/commands/analyze/doc.d.ts +3 -0
- package/dist/commands/analyze/doc.d.ts.map +1 -0
- package/dist/commands/analyze/doc.js +77 -0
- package/dist/commands/analyze/doc.js.map +1 -0
- package/dist/commands/analyze/feature.d.ts +3 -0
- package/dist/commands/analyze/feature.d.ts.map +1 -0
- package/dist/commands/analyze/feature.js +75 -0
- package/dist/commands/analyze/feature.js.map +1 -0
- package/dist/commands/analyze/from-code.d.ts +3 -0
- package/dist/commands/analyze/from-code.d.ts.map +1 -0
- package/dist/commands/analyze/from-code.js +94 -0
- package/dist/commands/analyze/from-code.js.map +1 -0
- package/dist/commands/analyze/incremental.d.ts +3 -0
- package/dist/commands/analyze/incremental.d.ts.map +1 -0
- package/dist/commands/analyze/incremental.js +90 -0
- package/dist/commands/analyze/incremental.js.map +1 -0
- package/dist/commands/analyze/modes.d.ts +13 -0
- package/dist/commands/analyze/modes.d.ts.map +1 -0
- package/dist/commands/analyze/modes.js +147 -0
- package/dist/commands/analyze/modes.js.map +1 -0
- package/dist/commands/analyze/module.d.ts +3 -0
- package/dist/commands/analyze/module.d.ts.map +1 -0
- package/dist/commands/analyze/module.js +95 -0
- package/dist/commands/analyze/module.js.map +1 -0
- package/dist/commands/analyze/overview.d.ts +3 -0
- package/dist/commands/analyze/overview.d.ts.map +1 -0
- package/dist/commands/analyze/overview.js +125 -0
- package/dist/commands/analyze/overview.js.map +1 -0
- package/dist/commands/analyze/shared.d.ts +53 -0
- package/dist/commands/analyze/shared.d.ts.map +1 -0
- package/dist/commands/analyze/shared.js +34 -0
- package/dist/commands/analyze/shared.js.map +1 -0
- package/dist/commands/analyze/units.d.ts +46 -0
- package/dist/commands/analyze/units.d.ts.map +1 -0
- package/dist/commands/analyze/units.js +950 -0
- package/dist/commands/analyze/units.js.map +1 -0
- package/dist/commands/analyze.d.ts +6 -88
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +167 -1419
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/ask.d.ts.map +1 -1
- package/dist/commands/ask.js +4 -2
- package/dist/commands/ask.js.map +1 -1
- package/dist/commands/dashboard.d.ts.map +1 -1
- package/dist/commands/dashboard.js +36 -1
- package/dist/commands/dashboard.js.map +1 -1
- package/dist/commands/deploy.d.ts +6 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +153 -0
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/doc2spec.d.ts +1 -1
- package/dist/commands/doc2spec.d.ts.map +1 -1
- package/dist/commands/doc2spec.js +39 -24
- package/dist/commands/doc2spec.js.map +1 -1
- package/dist/commands/doctor.js +60 -13
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/execute.d.ts.map +1 -1
- package/dist/commands/execute.js +22 -0
- package/dist/commands/execute.js.map +1 -1
- package/dist/commands/help.js +36 -1
- package/dist/commands/help.js.map +1 -1
- package/dist/commands/init.js +1 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/pipeline.d.ts.map +1 -1
- package/dist/commands/pipeline.js +15 -0
- package/dist/commands/pipeline.js.map +1 -1
- package/dist/commands/plan.js +2 -0
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/pr.d.ts.map +1 -1
- package/dist/commands/pr.js +14 -3
- package/dist/commands/pr.js.map +1 -1
- package/dist/commands/prompts.d.ts +11 -1
- package/dist/commands/prompts.d.ts.map +1 -1
- package/dist/commands/prompts.js +197 -15
- package/dist/commands/prompts.js.map +1 -1
- package/dist/commands/reindex.d.ts.map +1 -1
- package/dist/commands/reindex.js +23 -0
- package/dist/commands/reindex.js.map +1 -1
- package/dist/commands/retro.d.ts.map +1 -1
- package/dist/commands/retro.js +3 -1
- package/dist/commands/retro.js.map +1 -1
- package/dist/commands/status-panel.d.ts.map +1 -1
- package/dist/commands/status-panel.js +9 -1
- package/dist/commands/status-panel.js.map +1 -1
- package/dist/commands/unitize.d.ts +3 -0
- package/dist/commands/unitize.d.ts.map +1 -1
- package/dist/commands/unitize.js +183 -6
- package/dist/commands/unitize.js.map +1 -1
- package/dist/commands/update-env-configs.d.ts.map +1 -1
- package/dist/commands/update-env-configs.js +10 -0
- package/dist/commands/update-env-configs.js.map +1 -1
- package/dist/commands/verify.d.ts +8 -0
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +383 -16
- package/dist/commands/verify.js.map +1 -1
- package/dist/commands/welcome.d.ts.map +1 -1
- package/dist/commands/welcome.js +3 -1
- package/dist/commands/welcome.js.map +1 -1
- package/dist/core/ai-context-generator.d.ts +21 -0
- package/dist/core/ai-context-generator.d.ts.map +1 -1
- package/dist/core/ai-context-generator.js +54 -1
- package/dist/core/ai-context-generator.js.map +1 -1
- package/dist/core/ai-verify/plan-generator.d.ts +94 -0
- package/dist/core/ai-verify/plan-generator.d.ts.map +1 -0
- package/dist/core/ai-verify/plan-generator.js +598 -0
- package/dist/core/ai-verify/plan-generator.js.map +1 -0
- package/dist/core/ai-verify/result-parser.d.ts +27 -0
- package/dist/core/ai-verify/result-parser.d.ts.map +1 -0
- package/dist/core/ai-verify/result-parser.js +212 -0
- package/dist/core/ai-verify/result-parser.js.map +1 -0
- package/dist/core/ai-verify/scope-resolver.d.ts +34 -0
- package/dist/core/ai-verify/scope-resolver.d.ts.map +1 -0
- package/dist/core/ai-verify/scope-resolver.js +183 -0
- package/dist/core/ai-verify/scope-resolver.js.map +1 -0
- package/dist/core/ai-verify/types.d.ts +171 -0
- package/dist/core/ai-verify/types.d.ts.map +1 -0
- package/dist/core/ai-verify/types.js +9 -0
- package/dist/core/ai-verify/types.js.map +1 -0
- package/dist/core/ai-verify/visual-judge.d.ts +33 -0
- package/dist/core/ai-verify/visual-judge.d.ts.map +1 -0
- package/dist/core/ai-verify/visual-judge.js +123 -0
- package/dist/core/ai-verify/visual-judge.js.map +1 -0
- package/dist/core/analyze-engine.d.ts.map +1 -1
- package/dist/core/analyze-engine.js +2 -1
- package/dist/core/analyze-engine.js.map +1 -1
- package/dist/core/ask-engine.d.ts +6 -0
- package/dist/core/ask-engine.d.ts.map +1 -1
- package/dist/core/ask-engine.js +83 -3
- package/dist/core/ask-engine.js.map +1 -1
- package/dist/core/code-graph/builder.d.ts.map +1 -1
- package/dist/core/code-graph/builder.js +93 -75
- package/dist/core/code-graph/builder.js.map +1 -1
- package/dist/core/code-graph/incremental-build.d.ts.map +1 -1
- package/dist/core/code-graph/incremental-build.js +13 -19
- package/dist/core/code-graph/incremental-build.js.map +1 -1
- package/dist/core/code-graph/index.d.ts +1 -1
- package/dist/core/code-graph/index.d.ts.map +1 -1
- package/dist/core/code-graph/index.js +15 -12
- package/dist/core/code-graph/index.js.map +1 -1
- package/dist/core/code-graph/multimodal.js +1 -1
- package/dist/core/code-graph/parser.js +1 -1
- package/dist/core/code-graph/query.d.ts +1 -1
- package/dist/core/code-graph/reporter.d.ts +1 -1
- package/dist/core/code-graph/reporter.d.ts.map +1 -1
- package/dist/core/code-graph/reporter.js +18 -10
- package/dist/core/code-graph/reporter.js.map +1 -1
- package/dist/core/code-graph/requirement-linker.d.ts.map +1 -1
- package/dist/core/code-graph/requirement-linker.js +24 -14
- package/dist/core/code-graph/requirement-linker.js.map +1 -1
- package/dist/core/code-graph/types.d.ts +1 -1
- package/dist/core/code-graph/types.js +1 -1
- package/dist/core/code-graph/visualizer.d.ts +1 -7
- package/dist/core/code-graph/visualizer.d.ts.map +1 -1
- package/dist/core/code-graph/visualizer.js +476 -49
- package/dist/core/code-graph/visualizer.js.map +1 -1
- package/dist/core/code-scanner.d.ts +2 -0
- package/dist/core/code-scanner.d.ts.map +1 -1
- package/dist/core/code-scanner.js +13 -1
- package/dist/core/code-scanner.js.map +1 -1
- package/dist/core/deploy/engine.d.ts +21 -0
- package/dist/core/deploy/engine.d.ts.map +1 -1
- package/dist/core/deploy/engine.js +276 -0
- package/dist/core/deploy/engine.js.map +1 -1
- package/dist/core/deploy-ledger.d.ts +12 -0
- package/dist/core/deploy-ledger.d.ts.map +1 -0
- package/dist/core/deploy-ledger.js +35 -0
- package/dist/core/deploy-ledger.js.map +1 -0
- package/dist/core/environment-config.d.ts +11 -0
- package/dist/core/environment-config.d.ts.map +1 -1
- package/dist/core/environment-config.js +4 -0
- package/dist/core/environment-config.js.map +1 -1
- package/dist/core/intent-cache.d.ts.map +1 -1
- package/dist/core/intent-cache.js +6 -0
- package/dist/core/intent-cache.js.map +1 -1
- package/dist/core/knowledge-visualizer.js +1 -1
- package/dist/core/page-bundle.d.ts +2 -0
- package/dist/core/page-bundle.d.ts.map +1 -1
- package/dist/core/page-bundle.js +18 -1
- package/dist/core/page-bundle.js.map +1 -1
- package/dist/core/plan-html.js +1 -1
- package/dist/core/platform-inference.d.ts +35 -0
- package/dist/core/platform-inference.d.ts.map +1 -1
- package/dist/core/platform-inference.js +92 -0
- package/dist/core/platform-inference.js.map +1 -1
- package/dist/core/prompt-plugins/analyze-graph-plugin.d.ts.map +1 -1
- package/dist/core/prompt-plugins/analyze-graph-plugin.js +16 -1
- package/dist/core/prompt-plugins/analyze-graph-plugin.js.map +1 -1
- package/dist/core/prompt-plugins/execute-graph-plugin.d.ts +2 -0
- package/dist/core/prompt-plugins/execute-graph-plugin.d.ts.map +1 -1
- package/dist/core/prompt-plugins/execute-graph-plugin.js +127 -27
- package/dist/core/prompt-plugins/execute-graph-plugin.js.map +1 -1
- package/dist/core/quality-audit.d.ts.map +1 -1
- package/dist/core/quality-audit.js +18 -0
- package/dist/core/quality-audit.js.map +1 -1
- package/dist/core/rag-engine.d.ts.map +1 -1
- package/dist/core/rag-engine.js +12 -243
- package/dist/core/rag-engine.js.map +1 -1
- package/dist/core/reindex-engine.d.ts +20 -0
- package/dist/core/reindex-engine.d.ts.map +1 -1
- package/dist/core/reindex-engine.js +142 -0
- package/dist/core/reindex-engine.js.map +1 -1
- package/dist/core/requirement-clarifier.d.ts.map +1 -1
- package/dist/core/requirement-clarifier.js +22 -6
- package/dist/core/requirement-clarifier.js.map +1 -1
- package/dist/core/reverse-requirement.d.ts +37 -0
- package/dist/core/reverse-requirement.d.ts.map +1 -1
- package/dist/core/reverse-requirement.js +84 -9
- package/dist/core/reverse-requirement.js.map +1 -1
- package/dist/core/spec-paths.d.ts +5 -0
- package/dist/core/spec-paths.d.ts.map +1 -1
- package/dist/core/spec-paths.js +1 -0
- package/dist/core/spec-paths.js.map +1 -1
- package/dist/core/test-config.d.ts +7 -1
- package/dist/core/test-config.d.ts.map +1 -1
- package/dist/core/test-config.js +59 -11
- package/dist/core/test-config.js.map +1 -1
- package/dist/core/ui-verify/index.d.ts +1 -1
- package/dist/core/ui-verify/index.d.ts.map +1 -1
- package/dist/core/ui-verify/index.js +2 -1
- package/dist/core/ui-verify/index.js.map +1 -1
- package/dist/core/ui-verify/smoke-engine.js +71 -15
- package/dist/core/ui-verify/smoke-engine.js.map +1 -1
- package/dist/core/ui-verify/spec-generator.d.ts +5 -9
- package/dist/core/ui-verify/spec-generator.d.ts.map +1 -1
- package/dist/core/ui-verify/spec-generator.js +66 -3
- package/dist/core/ui-verify/spec-generator.js.map +1 -1
- package/dist/core/ui-verify/types.d.ts +25 -0
- package/dist/core/ui-verify/types.d.ts.map +1 -1
- package/dist/core/ui-verify/visual-model-client.d.ts +2 -0
- package/dist/core/ui-verify/visual-model-client.d.ts.map +1 -1
- package/dist/core/ui-verify/visual-model-client.js +4 -4
- package/dist/core/ui-verify/visual-model-client.js.map +1 -1
- package/dist/core/unified-config.d.ts +27 -0
- package/dist/core/unified-config.d.ts.map +1 -1
- package/dist/core/unified-config.js +37 -0
- package/dist/core/unified-config.js.map +1 -1
- package/dist/core/unified-retrieval.d.ts +8 -0
- package/dist/core/unified-retrieval.d.ts.map +1 -1
- package/dist/core/unified-retrieval.js +18 -0
- package/dist/core/unified-retrieval.js.map +1 -1
- package/dist/utils/synonyms.d.ts.map +1 -1
- package/dist/utils/synonyms.js +4 -0
- package/dist/utils/synonyms.js.map +1 -1
- package/package.json +1 -1
- package/templates/accounts-example.yaml +49 -0
- package/templates/html/fonts/JetBrainsMono.woff2 +0 -0
- package/templates/html/fonts/Orbitron.woff2 +0 -0
- package/templates/html/speccore-ask-explain.html +1 -1
- package/templates/html/speccore-ask-guide.html +2 -2
- package/templates/html/speccore-ask-match.html +1 -1
- package/templates/html/speccore-ask-pipeline.html +1 -1
- package/templates/html/speccore-ask-result.html +2 -2
- package/templates/html/speccore-help.html +2 -2
- package/templates/html/speccore-setup-guide.html +16 -1
- package/templates/speccore-yml-example.yml +12 -0
package/dist/commands/verify.js
CHANGED
|
@@ -78,6 +78,30 @@ const task_paths_1 = require("../core/task-paths");
|
|
|
78
78
|
async function verifyCommand(options) {
|
|
79
79
|
// v8.3.101+: 向上查找项目根目录(支持在子目录执行)
|
|
80
80
|
const projectRoot = (0, task_utils_1.findProjectRoot)() || process.cwd();
|
|
81
|
+
// v8.3.353+: CLI 参数归一化(commander 默认值是字符串,不转数字会让 Playwright 报 expected float)
|
|
82
|
+
if (typeof options.timeout === 'string')
|
|
83
|
+
options.timeout = Number(options.timeout);
|
|
84
|
+
// v8.3.351+: 测试双线开关(一次读取,AI 验证/冒烟线/剧本分层共用;CLI 显式参数优先于开关)
|
|
85
|
+
const unifiedConfig = await (0, unified_config_1.loadConfig)();
|
|
86
|
+
const testsCfg = unifiedConfig.settings?.tests;
|
|
87
|
+
const smokeOff = testsCfg?.smoke?.enabled === false;
|
|
88
|
+
// v8.3.350+ AI 模式:生成验证剧本 / 回写 HTML 报告 / 视觉判定兜底(不进入代码验证流程)
|
|
89
|
+
if (options.ai) {
|
|
90
|
+
await runAiVerifyMode(options, testsCfg);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (options.aiReport) {
|
|
94
|
+
await runAiReportMode(options.aiReport);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (options.aiJudge) {
|
|
98
|
+
await runAiJudgeMode(options.aiJudge, options);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// 冒烟线已关闭但用户显式要求执行 → 显式参数优先,仅提示配置状态
|
|
102
|
+
if (smokeOff && (options.ui || options.smokeOnly || options.visualOnly || options.config || options.stage)) {
|
|
103
|
+
logger_1.logger.warn('⚠️ 冒烟线已在 settings.tests.smoke.enabled 关闭,本次按显式参数执行');
|
|
104
|
+
}
|
|
81
105
|
// v8.3.60+: 分层测试阶段映射
|
|
82
106
|
if (options.stage) {
|
|
83
107
|
applyStageDefaults(options);
|
|
@@ -124,7 +148,8 @@ async function verifyCommand(options) {
|
|
|
124
148
|
return;
|
|
125
149
|
}
|
|
126
150
|
const iterDir = await (0, context_1.getIterationDir)(iteration);
|
|
127
|
-
|
|
151
|
+
// v8.3.351+: 复用函数开头读取的配置(含 tests 双线开关)
|
|
152
|
+
const config = unifiedConfig;
|
|
128
153
|
const projectConfig = await (0, unified_config_1.loadProjectConfig)();
|
|
129
154
|
// v8.3.105+: 加载平台列表用于推断平台名
|
|
130
155
|
const platformList = await (0, spec_paths_1.parsePlatformList)();
|
|
@@ -177,15 +202,22 @@ async function verifyCommand(options) {
|
|
|
177
202
|
});
|
|
178
203
|
// UI 验证(任务绑定,自动检测配置)
|
|
179
204
|
if ((0, quality_gate_1.shouldRunUIVerify)(resolvedQG)) {
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
if (
|
|
183
|
-
logger_1.logger.info('');
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
205
|
+
// v8.3.351+: 冒烟线开关——配置关闭且非命令行显式时跳过自动触发的 UI 验证
|
|
206
|
+
const cliExplicitUi = Boolean(options.ui || options.smokeOnly || options.visualOnly);
|
|
207
|
+
if (smokeOff && !cliExplicitUi) {
|
|
208
|
+
logger_1.logger.info('⏭️ 冒烟线已在 settings.tests.smoke.enabled 关闭,跳过自动 UI 验证(可用 --ui 显式执行)');
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
logger_1.logger.info(`🔒 质量门禁: UI 验证已启用(项目级${config.quality_gates?.verify_ui?.enabled ? '✅' : '⏭️'} → 任务级${taskQG?.verify_ui?.enabled ? '✅' : '⏭️'} → 命令行${options.ui ? '✅' : '⏭️'})`);
|
|
212
|
+
const uiReport = await runUIVerification(task.id, iterDir, options, resolvedQG);
|
|
213
|
+
if (uiReport) {
|
|
214
|
+
logger_1.logger.info('');
|
|
215
|
+
logUIReport(uiReport);
|
|
216
|
+
// 如果 UI 验证失败且阈值 strict,阻断后续流程
|
|
217
|
+
if (!uiReport.summary.passed && resolvedQG.verify_ui.threshold === 'strict') {
|
|
218
|
+
logger_1.logger.error('❌ UI 验证失败(strict 模式),阻断执行');
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
189
221
|
}
|
|
190
222
|
}
|
|
191
223
|
}
|
|
@@ -308,6 +340,8 @@ function logReport(report, reportPath) {
|
|
|
308
340
|
logger_1.logger.info(` 通过率: ${passRate}% (${summary.passed}/${summary.total})`);
|
|
309
341
|
if (summary.failed > 0) {
|
|
310
342
|
logger_1.logger.warn(` ❌ ${summary.failed} 项检查失败`);
|
|
343
|
+
// v8.3.353+: 失败必须非零退出(CI 无法感知失败即假通过)
|
|
344
|
+
process.exitCode = 1;
|
|
311
345
|
}
|
|
312
346
|
if (summary.warnings > 0) {
|
|
313
347
|
logger_1.logger.info(` ⚠️ ${summary.warnings} 项警告`);
|
|
@@ -576,6 +610,12 @@ function logUIReport(report) {
|
|
|
576
610
|
logger_1.logger.info(` 通过率: ${passRate}% (${summary.passed}/${summary.total})`);
|
|
577
611
|
if (summary.failed > 0) {
|
|
578
612
|
logger_1.logger.warn(` ❌ ${summary.failed} 项检查失败`);
|
|
613
|
+
// v8.3.353+: 失败必须非零退出(CI 无法感知失败即假通过)
|
|
614
|
+
process.exitCode = 1;
|
|
615
|
+
}
|
|
616
|
+
// v8.3.352+: 跳过项显式展示——降级跳过不再无声蒸发
|
|
617
|
+
if (summary.skipped && summary.skipped > 0) {
|
|
618
|
+
logger_1.logger.warn(` ⏭️ ${summary.skipped} 项检查被跳过(未覆盖,详情见跳过清单)`);
|
|
579
619
|
}
|
|
580
620
|
}
|
|
581
621
|
// ============================================================
|
|
@@ -650,6 +690,11 @@ async function runTestConfigMode(options) {
|
|
|
650
690
|
logger_1.logger.info(`🌍 已合并环境配置: ${options.envFile}`);
|
|
651
691
|
}
|
|
652
692
|
}
|
|
693
|
+
// v8.3.352+: 跳过项显式统计——契约缺失/账号未就绪/URL 无法确定等降级不再无声蒸发,
|
|
694
|
+
// 汇总报告单独列出,配合 --require-contract 可将契约缺失升级为失败。
|
|
695
|
+
let skippedCount = 0;
|
|
696
|
+
const skipReasons = [];
|
|
697
|
+
let contractFailure = false;
|
|
653
698
|
// v8.3.216+: 冒烟前自动装配登录账号(开发完成、即将冒烟的时间点,内存装配 + 透明告知)
|
|
654
699
|
if (mergedConfig.auth) {
|
|
655
700
|
const hint = await extractTestAccountHint((0, task_utils_1.findProjectRoot)() || process.cwd());
|
|
@@ -662,12 +707,26 @@ async function runTestConfigMode(options) {
|
|
|
662
707
|
logger_1.logger.info(`\n🔐 冒烟登录账号装配:`);
|
|
663
708
|
logger_1.logger.info(` - 账号: ${resolution.username || '(未确定)'}(来源:${resolution.usernameSource})`);
|
|
664
709
|
logger_1.logger.info(` - 密码: ${resolution.password ? '已就绪(' + resolution.password.length + ' 位)' : '(未确定)'}(来源:${resolution.passwordSource})`);
|
|
710
|
+
// v8.3.354+: 验证码装配状态透明展示(就绪/降级原因);未就绪计入 skipped,不假通过
|
|
711
|
+
if (resolution.verificationNote) {
|
|
712
|
+
logger_1.logger.info(` - 验证码: ${resolution.verificationNote}`);
|
|
713
|
+
}
|
|
665
714
|
if (resolution.warnings.length > 0) {
|
|
666
715
|
for (const w of resolution.warnings) {
|
|
667
716
|
logger_1.logger.warn(` ⚠️ ${w}`);
|
|
668
717
|
}
|
|
669
718
|
logger_1.logger.warn(` 💡 冒烟时若账号未就绪,登录将自动降级跳过(不影响页面可达性检查)`);
|
|
670
719
|
}
|
|
720
|
+
// 账号未就绪 → 登录场景降级跳过:计入 skipped,报告里可见「哪些场景因账号缺失没测」
|
|
721
|
+
if (!resolution.username || !resolution.password) {
|
|
722
|
+
skippedCount += 1;
|
|
723
|
+
skipReasons.push(`登录场景降级跳过(账号未就绪:${!resolution.username ? '用户名缺失' : ''}${!resolution.username && !resolution.password ? '、' : ''}${!resolution.password ? '密码缺失' : ''})`);
|
|
724
|
+
}
|
|
725
|
+
else if (resolution.verificationReady === false) {
|
|
726
|
+
// 账号密码就绪但验证码未就绪:登录可能卡在验证码环节,同样计入 skipped 显式声明
|
|
727
|
+
skippedCount += 1;
|
|
728
|
+
skipReasons.push('登录二次验证降级跳过(验证码码源未就绪:code_env 未设置或 fixed_code/code_selector 缺失)');
|
|
729
|
+
}
|
|
671
730
|
}
|
|
672
731
|
const outputDir = options.output || mergedConfig.output || (0, path_1.join)((0, task_utils_1.findProjectRoot)() || process.cwd(), 'reports');
|
|
673
732
|
const screenshotDir = (0, path_1.join)(outputDir, 'screenshots');
|
|
@@ -682,6 +741,8 @@ async function runTestConfigMode(options) {
|
|
|
682
741
|
const targetUrl = resolveTestUrl(testCase, mergedConfig, options.url);
|
|
683
742
|
if (!targetUrl) {
|
|
684
743
|
logger_1.logger.warn(`⚠️ 无法确定目标 URL,跳过: ${testCase.name}`);
|
|
744
|
+
skippedCount += 1;
|
|
745
|
+
skipReasons.push(`[${testCase.name}] 无法确定目标 URL`);
|
|
685
746
|
continue;
|
|
686
747
|
}
|
|
687
748
|
if (testCase.type === 'smoke' || testCase.type === 'visual') {
|
|
@@ -748,19 +809,37 @@ async function runTestConfigMode(options) {
|
|
|
748
809
|
const apiReport = await (0, api_verify_1.runApiContractTest)(apiSpec);
|
|
749
810
|
if (apiReport) {
|
|
750
811
|
logger_1.logger.info(` API 测试完成: ${apiReport.summary.passed}/${apiReport.summary.total} 通过`);
|
|
812
|
+
if (apiReport.summary.failed > 0) {
|
|
813
|
+
contractFailure = true;
|
|
814
|
+
}
|
|
751
815
|
}
|
|
752
816
|
}
|
|
753
817
|
else {
|
|
754
|
-
|
|
818
|
+
// v8.3.352+: 解析失败不再静默——计入 skipped;--require-contract 时升级为失败
|
|
819
|
+
skippedCount += 1;
|
|
820
|
+
skipReasons.push(`[${testCase.name}] API 规格解析失败: ${parseResult.error}`);
|
|
821
|
+
if (options.requireContract)
|
|
822
|
+
contractFailure = true;
|
|
823
|
+
logger_1.logger.warn(` ⚠️ API 规格解析失败,已计入跳过项: ${parseResult.error}`);
|
|
755
824
|
}
|
|
756
825
|
}
|
|
757
826
|
else {
|
|
758
|
-
|
|
827
|
+
// v8.3.352+: 契约缺失显式计入 skipped(不再只 warn 后当无事发生);
|
|
828
|
+
// --require-contract 时将「契约缺失」升级为验证失败,杜绝假通过。
|
|
829
|
+
skippedCount += 1;
|
|
830
|
+
skipReasons.push(`[${testCase.name}] 未找到 API_CONTRACT.yaml`);
|
|
831
|
+
if (options.requireContract) {
|
|
832
|
+
contractFailure = true;
|
|
833
|
+
logger_1.logger.error(` ❌ 未找到 API_CONTRACT.yaml(--require-contract 已开启,契约缺失按失败处理)`);
|
|
834
|
+
}
|
|
835
|
+
else {
|
|
836
|
+
logger_1.logger.warn(` ⚠️ 未找到 API_CONTRACT.yaml,跳过 API 测试(已计入跳过项;--require-contract 可升级为失败)`);
|
|
837
|
+
}
|
|
759
838
|
}
|
|
760
839
|
}
|
|
761
840
|
}
|
|
762
841
|
// 汇总并输出报告
|
|
763
|
-
if (allReports.length > 0) {
|
|
842
|
+
if (allReports.length > 0 || skippedCount > 0) {
|
|
764
843
|
const combinedReport = {
|
|
765
844
|
taskId: mergedConfig.name || 'test-config-run',
|
|
766
845
|
timestamp: new Date().toISOString(),
|
|
@@ -775,9 +854,23 @@ async function runTestConfigMode(options) {
|
|
|
775
854
|
passed: allReports.reduce((s, r) => s + r.summary.passed, 0),
|
|
776
855
|
failed: allReports.reduce((s, r) => s + r.summary.failed, 0),
|
|
777
856
|
duration: 0,
|
|
857
|
+
skipped: skippedCount,
|
|
778
858
|
},
|
|
779
859
|
};
|
|
780
860
|
logUIReport(combinedReport);
|
|
861
|
+
// v8.3.352+: 跳过项清单单独列出——降级跳过的场景不再无声蒸发
|
|
862
|
+
if (skippedCount > 0) {
|
|
863
|
+
logger_1.logger.warn('');
|
|
864
|
+
logger_1.logger.warn(`⏭️ 本次共跳过 ${skippedCount} 项检查(非失败,但未覆盖):`);
|
|
865
|
+
for (const reason of skipReasons) {
|
|
866
|
+
logger_1.logger.warn(` - ${reason}`);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
// v8.3.352+: 契约缺失/解析失败升级为失败时,验证非零退出
|
|
870
|
+
if (contractFailure) {
|
|
871
|
+
logger_1.logger.error('❌ API 契约缺失/解析失败(--require-contract),验证不通过');
|
|
872
|
+
process.exitCode = 1;
|
|
873
|
+
}
|
|
781
874
|
const htmlPath = await (0, ui_verify_1.generateHtmlReport)(combinedReport, outputDir);
|
|
782
875
|
logger_1.logger.info(`\n📄 测试报告: ${htmlPath}`);
|
|
783
876
|
}
|
|
@@ -795,16 +888,34 @@ function resolveTestUrl(testCase, config, cliUrl) {
|
|
|
795
888
|
}
|
|
796
889
|
return null;
|
|
797
890
|
}
|
|
798
|
-
/** 将 TestCase 转换为 VerifySpec */
|
|
891
|
+
/** 将 TestCase 转换为 VerifySpec(v8.3.353+: 优先透传精细场景 verify_scenarios,否则 routes 可达性三件套) */
|
|
799
892
|
function buildVerifySpecFromTestCase(testCase, baseUrl) {
|
|
893
|
+
// 精细场景优先:完整执行 22 种动作 + 9 种断言,不再降级为纯可达性
|
|
894
|
+
if (testCase.verify_scenarios && testCase.verify_scenarios.length > 0) {
|
|
895
|
+
const scenarios = testCase.verify_scenarios.map((sc) => ({
|
|
896
|
+
...sc,
|
|
897
|
+
// navigate 相对路径归一化:非 http(s):// 开头的 URL 拼上 baseUrl
|
|
898
|
+
actions: sc.actions.map((a) => a.type === 'navigate' && typeof a.value === 'string' && !/^https?:\/\//i.test(a.value)
|
|
899
|
+
? { ...a, value: baseUrl.replace(/\/$/, '') + a.value }
|
|
900
|
+
: a),
|
|
901
|
+
}));
|
|
902
|
+
return {
|
|
903
|
+
name: testCase.name,
|
|
904
|
+
platform: 'test-config',
|
|
905
|
+
url: baseUrl,
|
|
906
|
+
scenarios,
|
|
907
|
+
};
|
|
908
|
+
}
|
|
800
909
|
const routes = testCase.routes || ['/'];
|
|
801
910
|
const scenarios = routes.map((route) => ({
|
|
802
911
|
name: `navigate-${route}`,
|
|
803
912
|
actions: [
|
|
804
|
-
|
|
913
|
+
// 完整 URL 不再重复拼接(修复 VERIFY_SPEC 转换后双重前缀隐患)
|
|
914
|
+
{ type: 'navigate', value: /^https?:\/\//i.test(route) ? route : baseUrl + route },
|
|
805
915
|
{ type: 'screenshot' },
|
|
806
916
|
],
|
|
807
|
-
|
|
917
|
+
// v8.3.353+: 断言 body 可见(无 selector 的 visible 断言必抛错,属历史 bug)
|
|
918
|
+
assertions: [{ type: 'visible', selector: 'body' }],
|
|
808
919
|
}));
|
|
809
920
|
return {
|
|
810
921
|
name: testCase.name,
|
|
@@ -1330,4 +1441,260 @@ function inferPlatformNameFromCodePath(codePath, platforms) {
|
|
|
1330
1441
|
}
|
|
1331
1442
|
return undefined;
|
|
1332
1443
|
}
|
|
1444
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
1445
|
+
// v8.3.350+ AI 浏览器验证模式(--ai 生成剧本 / --ai-report 回写报告)
|
|
1446
|
+
//
|
|
1447
|
+
// 分工原则:CLI 只切片不解析语义——从 020-specs 的 UI_SPEC.md/TEST.md
|
|
1448
|
+
// 按标题抓取原文段落组装剧本,语义理解与页面操作交给宿主 AI。
|
|
1449
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
1450
|
+
/** 列出项目下已配置的环境名(排除 .example) */
|
|
1451
|
+
async function listEnvironmentNames(projectRoot) {
|
|
1452
|
+
const envDir = (0, path_1.join)(projectRoot, '.speccore', 'environments');
|
|
1453
|
+
let entries;
|
|
1454
|
+
try {
|
|
1455
|
+
entries = await (0, fs_extra_1.readdir)(envDir);
|
|
1456
|
+
}
|
|
1457
|
+
catch {
|
|
1458
|
+
return [];
|
|
1459
|
+
}
|
|
1460
|
+
return entries
|
|
1461
|
+
.filter((f) => f.endsWith('.yaml') || f.endsWith('.yml'))
|
|
1462
|
+
.map((f) => f.replace(/\.(yaml|yml)$/, ''))
|
|
1463
|
+
.filter((n) => n && !n.includes('.example'));
|
|
1464
|
+
}
|
|
1465
|
+
/** 环境选择:--env 显式 > 交互选择(TTY)> 默认 staging */
|
|
1466
|
+
async function promptForEnv(projectRoot, explicit) {
|
|
1467
|
+
if (explicit)
|
|
1468
|
+
return explicit;
|
|
1469
|
+
const names = await listEnvironmentNames(projectRoot);
|
|
1470
|
+
if (names.length === 0) {
|
|
1471
|
+
logger_1.logger.warn('⚠️ 未找到任何环境配置(.speccore/environments/*.yaml),默认使用环境 "staging"');
|
|
1472
|
+
return 'staging';
|
|
1473
|
+
}
|
|
1474
|
+
if (!process.stdin.isTTY) {
|
|
1475
|
+
const fallback = names.includes('staging') ? 'staging' : names[0];
|
|
1476
|
+
logger_1.logger.info(`ℹ️ 非交互环境,默认使用环境 "${fallback}"(可用 --env 指定)`);
|
|
1477
|
+
return fallback;
|
|
1478
|
+
}
|
|
1479
|
+
logger_1.logger.info('可用环境:');
|
|
1480
|
+
names.forEach((n, i) => logger_1.logger.info(` [${i + 1}] ${n}`));
|
|
1481
|
+
const answer = await promptUser(`请选择环境(1-${names.length},回车默认 staging):`);
|
|
1482
|
+
const idx = parseInt(answer, 10);
|
|
1483
|
+
if (!Number.isNaN(idx) && idx >= 1 && idx <= names.length)
|
|
1484
|
+
return names[idx - 1];
|
|
1485
|
+
if (names.includes(answer))
|
|
1486
|
+
return answer;
|
|
1487
|
+
return names.includes('staging') ? 'staging' : names[0];
|
|
1488
|
+
}
|
|
1489
|
+
/** --ai:生成验证剧本 + 输出宿主 AI 接管标记 */
|
|
1490
|
+
async function runAiVerifyMode(options, testsCfg) {
|
|
1491
|
+
const { resolveScopeUnits, parsePlatformFilter } = await Promise.resolve().then(() => __importStar(require('../core/ai-verify/scope-resolver')));
|
|
1492
|
+
const { generateAiVerifyPlan } = await Promise.resolve().then(() => __importStar(require('../core/ai-verify/plan-generator')));
|
|
1493
|
+
const { loadEnvironmentByNameOrPath } = await Promise.resolve().then(() => __importStar(require('../core/environment-config')));
|
|
1494
|
+
// v8.3.351+: AI 验证线开关——显式 --ai 优先,关闭时仅提示
|
|
1495
|
+
if (testsCfg?.ai_verify?.enabled === false) {
|
|
1496
|
+
logger_1.logger.warn('⚠️ AI 验证线已在 settings.tests.ai_verify.enabled 关闭,本次按显式参数执行');
|
|
1497
|
+
}
|
|
1498
|
+
const projectRoot = (0, task_utils_1.findProjectRoot)() || process.cwd();
|
|
1499
|
+
const iteration = await (0, context_1.getDefaultIteration)(options.iteration);
|
|
1500
|
+
const iterDir = await (0, context_1.getIterationDir)(iteration);
|
|
1501
|
+
const env = await promptForEnv(projectRoot, options.env);
|
|
1502
|
+
const platformFilter = parsePlatformFilter(options.platform);
|
|
1503
|
+
// 环境配置(environments/{env}.yaml,提供 tests.base_urls)
|
|
1504
|
+
const envConfig = await loadEnvironmentByNameOrPath(env);
|
|
1505
|
+
if (!envConfig) {
|
|
1506
|
+
logger_1.logger.error(`❌ 无法加载环境配置 "${env}"(.speccore/environments/${env}.yaml)`);
|
|
1507
|
+
process.exitCode = 1;
|
|
1508
|
+
return;
|
|
1509
|
+
}
|
|
1510
|
+
// 范围解析:all / deployed / modules:xxx
|
|
1511
|
+
const resolved = await resolveScopeUnits({
|
|
1512
|
+
iterDir,
|
|
1513
|
+
scope: options.scope || 'all',
|
|
1514
|
+
platformFilter: options.platform,
|
|
1515
|
+
projectRoot,
|
|
1516
|
+
}).catch((err) => {
|
|
1517
|
+
logger_1.logger.error(`❌ ${err.message}`);
|
|
1518
|
+
return null;
|
|
1519
|
+
});
|
|
1520
|
+
if (!resolved) {
|
|
1521
|
+
process.exitCode = 1;
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
for (const w of resolved.warnings)
|
|
1525
|
+
logger_1.logger.warn(`⚠️ ${w}`);
|
|
1526
|
+
// v8.3.351+: 无功能单元时——公共能力层开启则仅生成公共层,否则才视为无剧本可生成
|
|
1527
|
+
const layeredCfg = testsCfg?.layered;
|
|
1528
|
+
const commonOn = layeredCfg?.common !== false;
|
|
1529
|
+
if (resolved.units.length === 0 && !commonOn) {
|
|
1530
|
+
logger_1.logger.warn('⚠️ 范围内没有可验证的功能单元(020-specs 为空或过滤后无匹配),且公共能力层已关闭');
|
|
1531
|
+
process.exitCode = 1;
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
if (resolved.units.length === 0) {
|
|
1535
|
+
logger_1.logger.warn('⚠️ 范围内没有功能单元(020-specs 为空或过滤后无匹配),仅生成公共能力层场景');
|
|
1536
|
+
}
|
|
1537
|
+
// 账号配置(v8.3.351+ 账号收敛):环境内嵌 tests.accounts 为权威源;
|
|
1538
|
+
// 环境文件已声明账号时不生成 accounts.yaml 占位模板(兑底源非必需,避免噪音)。
|
|
1539
|
+
// 环境文件无账号且 accounts.yaml 不存在时才从模板生成示例并提醒填写(已存在不覆盖)。
|
|
1540
|
+
const accountsPath = (0, path_1.join)(projectRoot, '.speccore', 'tests', 'accounts.yaml');
|
|
1541
|
+
if (!envConfig.tests?.accounts && !(await (0, fs_extra_1.pathExists)(accountsPath))) {
|
|
1542
|
+
const tplPath = (0, path_1.join)(__dirname, '..', '..', 'templates', 'accounts-example.yaml');
|
|
1543
|
+
if (await (0, fs_extra_1.pathExists)(tplPath)) {
|
|
1544
|
+
await (0, fs_extra_1.ensureDir)((0, path_1.dirname)(accountsPath));
|
|
1545
|
+
await (0, fs_extra_1.writeFile)(accountsPath, await (0, fs_extra_1.readFile)(tplPath, 'utf-8'), 'utf-8');
|
|
1546
|
+
logger_1.logger.warn('⚠️ 已生成账号模板 .speccore/tests/accounts.yaml(占位密码 change-me),请填写真实账号后再执行验证;或直接在 environments/{env}.yaml 的 tests.accounts 声明环境内嵌账号');
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
// 剧本生成(layered 分层开关透传,缺省全开)
|
|
1550
|
+
const output = await generateAiVerifyPlan({
|
|
1551
|
+
iteration,
|
|
1552
|
+
iterDir,
|
|
1553
|
+
env,
|
|
1554
|
+
envConfig,
|
|
1555
|
+
units: resolved.units,
|
|
1556
|
+
platformFilter,
|
|
1557
|
+
projectRoot,
|
|
1558
|
+
accountsPath,
|
|
1559
|
+
layered: layeredCfg,
|
|
1560
|
+
});
|
|
1561
|
+
// 按层统计(公共能力层 → 模块层)
|
|
1562
|
+
const groupStats = output.plan.groups
|
|
1563
|
+
.map((g) => `${g.label}${g.enabled ? ` ${g.scenarios.length} 场景` : '(⏭️ 已关闭)'}`)
|
|
1564
|
+
.join(' | ');
|
|
1565
|
+
logger_1.logger.success(`✅ AI 验证剧本已生成:${output.plan.scenarios.length} 个场景(${groupStats})`);
|
|
1566
|
+
logger_1.logger.info(` 剧本: ${output.planMdPath}`);
|
|
1567
|
+
if (output.plan.skipped.length > 0) {
|
|
1568
|
+
logger_1.logger.warn(`⚠️ ${output.plan.skipped.length} 个单元被跳过(缺地址/空模板等),详见剧本「跳过项」`);
|
|
1569
|
+
}
|
|
1570
|
+
// 账号密码/用户名使用 ${VAR} 引用但环境变量未设置 → 提醒(不阻断,执行 AI 可按提示从环境读取)
|
|
1571
|
+
for (const h of output.plan.accountEnvVars || []) {
|
|
1572
|
+
if (!h.set) {
|
|
1573
|
+
logger_1.logger.warn(`⚠️ 账号 ${h.ref} 的 ${h.field} 引用环境变量 ${h.varName},但当前未设置`);
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
logger_1.logger.info('');
|
|
1577
|
+
logger_1.logger.info('🤖 执行协议(宿主 AI):');
|
|
1578
|
+
logger_1.logger.info(` 1. 读剧本 ${output.planJsonPath}(accountSources 指明账号来源)取账号明文`);
|
|
1579
|
+
logger_1.logger.info(' 2. 按层推进:先公共能力层(入口可达性/登录态/全局导航),全部通过后进模块层;enabled=false 的层跳过');
|
|
1580
|
+
logger_1.logger.info(' 3. 层内按端分批,用 Browser 子代理逐场景视觉执行,结果写 ' + (0, path_1.join)(output.plan.outDir, 'results.json'));
|
|
1581
|
+
logger_1.logger.info(' 4. 执行 speccore verify --ai-report ' + (0, path_1.join)(output.plan.outDir, 'results.json') + ' 生成 HTML 报告');
|
|
1582
|
+
logger_1.logger.info('');
|
|
1583
|
+
// 机器可读标记(spec-verify skill 据此接管执行);账号标记按实际来源输出(v8.3.351+ 账号收敛:
|
|
1584
|
+
// 权威源环境文件 tests.accounts 优先,accounts.yaml 兑底;均无时回退指向兑底路径)
|
|
1585
|
+
console.log(`[SPECCORE_AI_VERIFY: ${output.planJsonPath}]`);
|
|
1586
|
+
for (const src of output.plan.accountSources || []) {
|
|
1587
|
+
console.log(`[SPECCORE_AI_VERIFY_ACCOUNTS: ${src}]`);
|
|
1588
|
+
}
|
|
1589
|
+
if (!output.plan.accountSources || output.plan.accountSources.length === 0) {
|
|
1590
|
+
console.log(`[SPECCORE_AI_VERIFY_ACCOUNTS: ${accountsPath}]`);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
/** --ai-report:读 results.json + 同目录 plan.json → 生成 HTML 报告 */
|
|
1594
|
+
async function runAiReportMode(resultsPath) {
|
|
1595
|
+
const { parseAiVerifyResults, buildReportContext, generateAiVerifyReport, summarizeResults } = await Promise.resolve().then(() => __importStar(require('../core/ai-verify/result-parser')));
|
|
1596
|
+
const results = await parseAiVerifyResults(resultsPath);
|
|
1597
|
+
if (!results) {
|
|
1598
|
+
logger_1.logger.error(`❌ 无法解析结果文件: ${resultsPath}(需为含 results 数组的 JSON)`);
|
|
1599
|
+
process.exitCode = 1;
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
// plan.json 与 results.json 同目录(均为剧本输出目录)
|
|
1603
|
+
const planPath = (0, path_1.join)((0, path_1.dirname)(resultsPath), 'plan.json');
|
|
1604
|
+
if (!(await (0, fs_extra_1.pathExists)(planPath))) {
|
|
1605
|
+
logger_1.logger.error(`❌ 未找到剧本 plan.json(${planPath}),无法对齐场景`);
|
|
1606
|
+
process.exitCode = 1;
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
let plan;
|
|
1610
|
+
try {
|
|
1611
|
+
plan = JSON.parse(await (0, fs_extra_1.readFile)(planPath, 'utf-8'));
|
|
1612
|
+
}
|
|
1613
|
+
catch {
|
|
1614
|
+
logger_1.logger.error(`❌ 剧本 plan.json 解析失败(${planPath})`);
|
|
1615
|
+
process.exitCode = 1;
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
// 以 results.json 实际位置为准落盘报告(支持相对路径调用)
|
|
1619
|
+
plan.outDir = (0, path_1.dirname)(resultsPath);
|
|
1620
|
+
const ctx = buildReportContext(plan, results);
|
|
1621
|
+
const reportPath = await generateAiVerifyReport(ctx);
|
|
1622
|
+
const summary = summarizeResults(ctx);
|
|
1623
|
+
logger_1.logger.info('');
|
|
1624
|
+
logger_1.logger.info(`📊 AI 验证结果摘要(${plan.env} · ${plan.iteration}):`);
|
|
1625
|
+
logger_1.logger.info(` 总场景 ${summary.total} | ✅ 通过 ${summary.passed} | ❌ 失败 ${summary.failed} | ⏭️ 跳过 ${summary.skipped} | ⬜ 未执行 ${summary.notRun}`);
|
|
1626
|
+
if (summary.notRun > 0) {
|
|
1627
|
+
logger_1.logger.warn(`⚠️ ${summary.notRun} 个场景未执行(结果中缺失),报告已标注`);
|
|
1628
|
+
}
|
|
1629
|
+
if (summary.failed > 0) {
|
|
1630
|
+
logger_1.logger.error(`❌ ${summary.failed} 个场景失败,详情见报告: ${reportPath}`);
|
|
1631
|
+
process.exitCode = 1;
|
|
1632
|
+
}
|
|
1633
|
+
else {
|
|
1634
|
+
logger_1.logger.success(`✅ 全部场景通过(或跳过),报告: ${reportPath}`);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
/** --ai-judge:视觉判定兜底——对 judged:false 的截图用视觉模型判定,回写后生成报告 */
|
|
1638
|
+
async function runAiJudgeMode(resultsPath, options) {
|
|
1639
|
+
const { parseAiVerifyResults, buildReportContext, generateAiVerifyReport, summarizeResults } = await Promise.resolve().then(() => __importStar(require('../core/ai-verify/result-parser')));
|
|
1640
|
+
const { runAiJudge } = await Promise.resolve().then(() => __importStar(require('../core/ai-verify/visual-judge')));
|
|
1641
|
+
const results = await parseAiVerifyResults(resultsPath);
|
|
1642
|
+
if (!results) {
|
|
1643
|
+
logger_1.logger.error(`❌ 无法解析结果文件: ${resultsPath}(需为含 results 数组的 JSON)`);
|
|
1644
|
+
process.exitCode = 1;
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
// plan.json 与 results.json 同目录
|
|
1648
|
+
const planPath = (0, path_1.join)((0, path_1.dirname)(resultsPath), 'plan.json');
|
|
1649
|
+
if (!(await (0, fs_extra_1.pathExists)(planPath))) {
|
|
1650
|
+
logger_1.logger.error(`❌ 未找到剧本 plan.json(${planPath}),无法获取场景预期结果`);
|
|
1651
|
+
process.exitCode = 1;
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
let plan;
|
|
1655
|
+
try {
|
|
1656
|
+
plan = JSON.parse(await (0, fs_extra_1.readFile)(planPath, 'utf-8'));
|
|
1657
|
+
}
|
|
1658
|
+
catch {
|
|
1659
|
+
logger_1.logger.error(`❌ 剧本 plan.json 解析失败(${planPath})`);
|
|
1660
|
+
process.exitCode = 1;
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
// 视觉模型配置:CLI --visual-model 优先,其次 .speccore.yml quality_gates.verify_ui.visual_model
|
|
1664
|
+
let visualModel;
|
|
1665
|
+
if (options.visualModel) {
|
|
1666
|
+
const parsed = parseVisualModelCli(options.visualModel);
|
|
1667
|
+
// 字符串形式为 provider 名(如 qwen-vl),包装为配置对象
|
|
1668
|
+
visualModel = typeof parsed === 'string'
|
|
1669
|
+
? { provider: parsed }
|
|
1670
|
+
: parsed;
|
|
1671
|
+
}
|
|
1672
|
+
else {
|
|
1673
|
+
const cfg = await (0, unified_config_1.loadConfig)().catch(() => null);
|
|
1674
|
+
visualModel = cfg?.quality_gates?.verify_ui?.visual_model;
|
|
1675
|
+
}
|
|
1676
|
+
try {
|
|
1677
|
+
const judgedCount = await runAiJudge(resultsPath, plan, visualModel);
|
|
1678
|
+
if (judgedCount > 0)
|
|
1679
|
+
logger_1.logger.success(`✅ 视觉判定兜底完成:${judgedCount} 个场景已判定并回写`);
|
|
1680
|
+
}
|
|
1681
|
+
catch (error) {
|
|
1682
|
+
logger_1.logger.error(`❌ 视觉判定兜底失败: ${error.message}`);
|
|
1683
|
+
process.exitCode = 1;
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
// 判定后重新生成报告
|
|
1687
|
+
const freshResults = await parseAiVerifyResults(resultsPath);
|
|
1688
|
+
if (!freshResults) {
|
|
1689
|
+
process.exitCode = 1;
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
plan.outDir = (0, path_1.dirname)(resultsPath);
|
|
1693
|
+
const ctx = buildReportContext(plan, freshResults);
|
|
1694
|
+
const reportPath = await generateAiVerifyReport(ctx);
|
|
1695
|
+
const summary = summarizeResults(ctx);
|
|
1696
|
+
logger_1.logger.info(`📊 报告: ${reportPath}(总 ${summary.total} | 通过 ${summary.passed} | 失败 ${summary.failed} | 跳过 ${summary.skipped} | 未执行 ${summary.notRun})`);
|
|
1697
|
+
if (summary.failed > 0)
|
|
1698
|
+
process.exitCode = 1;
|
|
1699
|
+
}
|
|
1333
1700
|
//# sourceMappingURL=verify.js.map
|