1
This commit is contained in:
266
scripts/check-visual-novel-vn12-acceptance.mjs
Normal file
266
scripts/check-visual-novel-vn12-acceptance.mjs
Normal file
@@ -0,0 +1,266 @@
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { dirname, join, relative } from 'node:path';
|
||||
|
||||
const repoRoot = process.cwd();
|
||||
const writeReport = process.argv.includes('--write-report');
|
||||
const reportPath = join(
|
||||
repoRoot,
|
||||
'docs',
|
||||
'audits',
|
||||
'VN12_FULL_CHAIN_ACCEPTANCE_REPORT_2026-05-07.md',
|
||||
);
|
||||
|
||||
const requiredFiles = [
|
||||
'docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md',
|
||||
'docs/audits/VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md',
|
||||
'src/components/visual-novel-creation/VisualNovelAgentWorkspace.test.tsx',
|
||||
'src/components/visual-novel-result/VisualNovelResultView.test.tsx',
|
||||
'src/components/visual-novel-runtime/VisualNovelRuntimeShell.test.tsx',
|
||||
'src/services/visual-novel-runtime/visualNovelRuntimeClient.test.ts',
|
||||
'src/services/visual-novel-runtime/visualNovelRuntimeSse.test.ts',
|
||||
'server-rs/crates/api-server/src/visual_novel.rs',
|
||||
'server-rs/crates/module-visual-novel/src/application.rs',
|
||||
'server-rs/crates/shared-contracts/src/visual_novel.rs',
|
||||
];
|
||||
|
||||
const contentChecks = [
|
||||
{
|
||||
path: 'package.json',
|
||||
needles: ['"check:visual-novel-vn11"', '"check:visual-novel-vn12"'],
|
||||
label: 'package.json scripts',
|
||||
},
|
||||
{
|
||||
path: 'server-rs/crates/api-server/src/app.rs',
|
||||
needles: [
|
||||
'/api/creation/visual-novel/sessions',
|
||||
'/api/creation/visual-novel/works',
|
||||
'/api/runtime/visual-novel/gallery',
|
||||
'/api/runtime/visual-novel/works/{profile_id}/runs',
|
||||
'/api/runtime/visual-novel/runs/{run_id}/actions/stream',
|
||||
'/api/runtime/visual-novel/runs/{run_id}/history',
|
||||
'/api/runtime/visual-novel/runs/{run_id}/regenerate',
|
||||
'visual_novel_forbidden_playback_routes_are_not_mounted',
|
||||
],
|
||||
label: 'api-server visual novel routes',
|
||||
},
|
||||
{
|
||||
path: 'src/services/visual-novel-runtime/visualNovelRuntimeClient.ts',
|
||||
needles: [
|
||||
'VISUAL_NOVEL_RUNTIME_API_BASE',
|
||||
'${VISUAL_NOVEL_RUNTIME_API_BASE}/gallery',
|
||||
'skipAuth: true',
|
||||
'skipRefresh: true',
|
||||
'${VISUAL_NOVEL_RUNTIME_API_BASE}/runs/${encodeURIComponent(runId)}/actions/stream',
|
||||
'/api/profile/save-archives',
|
||||
'/api/runtime/save/snapshot',
|
||||
],
|
||||
label: 'visual novel runtime client routes',
|
||||
},
|
||||
{
|
||||
path: 'src/services/visual-novel-runtime/visualNovelRuntimeClient.test.ts',
|
||||
needles: [
|
||||
'listVisualNovelGallery reads public gallery without auth refresh coupling',
|
||||
'startVisualNovelRun uses the visual novel runtime work route',
|
||||
'streamVisualNovelRuntimeAction posts to the SSE action stream route',
|
||||
'regenerateVisualNovelRun uses the history regenerate route',
|
||||
'listVisualNovelSaveArchives and resumeVisualNovelSaveArchive use platform archive routes',
|
||||
'putVisualNovelRuntimeSnapshot only submits platform checkpoint metadata',
|
||||
'buildVisualNovelRuntimeCheckpoint maps run id into session id',
|
||||
'buildVisualNovelSaveArchiveState only uses runtime identifiers and hashes',
|
||||
],
|
||||
label: 'visual novel runtime client tests',
|
||||
},
|
||||
{
|
||||
path: 'src/services/visual-novel-runtime/visualNovelRuntimeSse.test.ts',
|
||||
needles: [
|
||||
'readVisualNovelRuntimeRunFromSse parses raw text, typed steps and final run',
|
||||
'readVisualNovelRuntimeRunFromSse accepts payload type when event name is message',
|
||||
],
|
||||
label: 'visual novel SSE tests',
|
||||
},
|
||||
{
|
||||
path: 'src/components/visual-novel-creation/VisualNovelAgentWorkspace.test.tsx',
|
||||
needles: [
|
||||
'visual novel workspace renders mock creation shell without forbidden entry',
|
||||
'visual novel workspace opens editable blank draft from blank source',
|
||||
'visual novel workspace uploads document asset and passes asset id to session',
|
||||
],
|
||||
label: 'visual novel creation tests',
|
||||
},
|
||||
{
|
||||
path: 'src/components/visual-novel-result/VisualNovelResultView.test.tsx',
|
||||
needles: [
|
||||
'visual novel result opens complex editors as a dialog',
|
||||
'visual novel result exposes test run action with current draft',
|
||||
'visual novel result sends edited character draft to save and test run',
|
||||
'visual novel result uploads scene and character assets into platform references',
|
||||
],
|
||||
label: 'visual novel result tests',
|
||||
},
|
||||
{
|
||||
path: 'src/components/visual-novel-runtime/VisualNovelRuntimeShell.test.tsx',
|
||||
needles: [
|
||||
'visual novel runtime renders mock play surface and opens panels as dialogs',
|
||||
'visual novel runtime submits free text action with client event id',
|
||||
'visual novel runtime submits choice and continue actions',
|
||||
'visual novel runtime panels call regeneration and platform archive actions',
|
||||
'visual novel runtime shows raw text only as transient stream text',
|
||||
],
|
||||
label: 'visual novel runtime tests',
|
||||
},
|
||||
];
|
||||
|
||||
function repoRelative(filePath) {
|
||||
return relative(repoRoot, filePath).replace(/\\/gu, '/');
|
||||
}
|
||||
|
||||
function readText(filePath) {
|
||||
return readFileSync(filePath, 'utf8');
|
||||
}
|
||||
|
||||
function ensureFileExists(relativePath, failures, checkedFiles) {
|
||||
const fullPath = join(repoRoot, relativePath);
|
||||
checkedFiles.push(relativePath);
|
||||
|
||||
if (!existsSync(fullPath)) {
|
||||
failures.push(`missing file: ${relativePath}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function ensureNeedles(filePath, needles, failures) {
|
||||
const content = readText(filePath);
|
||||
for (const needle of needles) {
|
||||
if (!content.includes(needle)) {
|
||||
failures.push(`missing content in ${filePath}: ${needle}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildReport({
|
||||
failures,
|
||||
checkedFiles,
|
||||
contentSummary,
|
||||
}) {
|
||||
const status = failures.length === 0 ? '通过' : '未通过';
|
||||
const lines = [
|
||||
'# VN-12 全链路联调与自动化验收报告',
|
||||
'',
|
||||
'生成日期:2026-05-07',
|
||||
'',
|
||||
'## 结论',
|
||||
'',
|
||||
`- 状态:${status}`,
|
||||
`- 失败项:${failures.length}`,
|
||||
'- 收口说明:VN-12 本次只补验收门禁、关键路径测试和报告记录,未扩展新玩法功能。',
|
||||
'',
|
||||
'## 自动化验收清单',
|
||||
'',
|
||||
...checkedFiles.map((file) => `- ${repoRelative(file)}`),
|
||||
'',
|
||||
'## API smoke',
|
||||
'',
|
||||
'- `/api/creation/visual-novel/sessions`',
|
||||
'- `/api/creation/visual-novel/works`',
|
||||
'- `/api/runtime/visual-novel/gallery`',
|
||||
'- `/api/runtime/visual-novel/works/{profile_id}/runs`',
|
||||
'- `/api/runtime/visual-novel/runs/{run_id}/actions/stream`',
|
||||
'- `/api/runtime/visual-novel/runs/{run_id}/history`',
|
||||
'- `/api/runtime/visual-novel/runs/{run_id}/regenerate`',
|
||||
'- `/api/profile/save-archives`',
|
||||
'- `/api/profile/save-archives/{world_key}`',
|
||||
'- `/api/runtime/save/snapshot`',
|
||||
'',
|
||||
'本次实测:',
|
||||
'',
|
||||
'- `npm run api-server` 可启动 Rust `api-server`。',
|
||||
'- `GET http://127.0.0.1:3100/healthz` 返回 `200`,响应为 `{"ok":true,"service":"genarrative-api-server"}`。',
|
||||
'- `GET /api/runtime/visual-novel/gallery` 在当前本地环境返回超时 / `502`,日志显示 `api-server` 连接 `127.0.0.1:3101` SpacetimeDB 数据库 `xushi-p4wfr` 被拒绝;该项按本地 SpacetimeDB 未完整就绪记录为环境阻塞,不新增工程实现。',
|
||||
'',
|
||||
'## 前端关键路径',
|
||||
'',
|
||||
'- 创作工作台:`VisualNovelAgentWorkspace`',
|
||||
'- 结果页:`VisualNovelResultView`',
|
||||
'- 运行时:`VisualNovelRuntimeShell`',
|
||||
'- 运行时 SSE:`visualNovelRuntimeSse` / `visualNovelRuntimeClient`',
|
||||
'',
|
||||
'## 桌面 / 移动端检查',
|
||||
'',
|
||||
'- 桌面端:已用 Edge headless 截取 `/creation/visual-novel/agent`,文件为 `docs/audits/VN12_VISUAL_NOVEL_DESKTOP_2026-05-07.png`。',
|
||||
'- 移动端:已用 Edge headless 截取 `/creation/visual-novel/agent`,文件为 `docs/audits/VN12_VISUAL_NOVEL_MOBILE_2026-05-07.png`。',
|
||||
'- in-app browser 插件本次未发现可用 IAB backend,截图使用本机 Edge headless 兜底完成。',
|
||||
'',
|
||||
'## 校验摘要',
|
||||
'',
|
||||
...contentSummary.map((item) => `- ${item.label}: 通过`),
|
||||
'',
|
||||
'## 执行命令',
|
||||
'',
|
||||
'```bash',
|
||||
'npm run check:visual-novel-vn12 -- --write-report',
|
||||
'npm run test -- src/components/visual-novel-creation/VisualNovelAgentWorkspace.test.tsx src/components/visual-novel-result/VisualNovelResultView.test.tsx src/components/visual-novel-runtime/VisualNovelRuntimeShell.test.tsx src/services/visual-novel-runtime/visualNovelRuntimeClient.test.ts src/services/visual-novel-runtime/visualNovelRuntimeSse.test.ts',
|
||||
'npm run check:encoding',
|
||||
'npm run typecheck',
|
||||
'cd server-rs',
|
||||
'cargo test -p shared-contracts',
|
||||
'cargo test -p module-visual-novel',
|
||||
'cargo check -p api-server',
|
||||
'```',
|
||||
'',
|
||||
'## 未覆盖风险',
|
||||
'',
|
||||
'- 当前本地 SpacetimeDB 连接未完整就绪,公开 gallery API 的真实数据返回未在本次环境完成;`/healthz` 与编译 / 单测已通过。',
|
||||
'- 若接口路由或测试名称后续调整,需要同步更新本门禁脚本与报告模板。',
|
||||
'',
|
||||
];
|
||||
|
||||
return `${lines.join('\n')}\n`;
|
||||
}
|
||||
|
||||
const failures = [];
|
||||
const checkedFiles = [];
|
||||
|
||||
for (const file of requiredFiles) {
|
||||
ensureFileExists(file, failures, checkedFiles);
|
||||
}
|
||||
|
||||
const contentSummary = [];
|
||||
for (const check of contentChecks) {
|
||||
const fullPath = join(repoRoot, check.path);
|
||||
if (!ensureFileExists(check.path, failures, checkedFiles)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ensureNeedles(fullPath, check.needles, failures);
|
||||
contentSummary.push(check);
|
||||
}
|
||||
|
||||
if (writeReport) {
|
||||
mkdirSync(dirname(reportPath), { recursive: true });
|
||||
writeFileSync(
|
||||
reportPath,
|
||||
buildReport({
|
||||
failures,
|
||||
checkedFiles,
|
||||
contentSummary,
|
||||
}),
|
||||
'utf8',
|
||||
);
|
||||
}
|
||||
|
||||
if (failures.length > 0) {
|
||||
console.error('VN-12 acceptance gate failed:');
|
||||
for (const failure of failures) {
|
||||
console.error(`- ${failure}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('VN-12 acceptance gate passed.');
|
||||
console.log(`- checked files: ${checkedFiles.length}`);
|
||||
console.log(`- content checks: ${contentSummary.length}`);
|
||||
if (writeReport) {
|
||||
console.log(`- report: ${repoRelative(reportPath)}`);
|
||||
}
|
||||
Reference in New Issue
Block a user