Files
Genarrative/scripts/check-visual-novel-vn12-acceptance.mjs
2026-05-08 11:44:42 +08:00

267 lines
9.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)}`);
}