import { readFile } from 'node:fs/promises' import { fileURLToPath } from 'node:url' import path from 'node:path' const scriptDir = path.dirname(fileURLToPath(import.meta.url)) const rootDir = path.resolve(scriptDir, '..') const candidatePath = path.join( rootDir, 'docs', 'content-candidates', 'aug1-life-advisor-content-candidates-v0.1.json', ) const content = JSON.parse(await readFile(candidatePath, 'utf8')) const failures = [] const check = (condition, message) => { if (!condition) failures.push(message) } const requiredDimensions = new Set([ 'task_completion', 'standardization', 'emotion', 'response_timeliness', 'marketing', ]) const requiredCategories = new Map([ ['NORMAL', 6], ['SYNONYM', 6], ['CONFLICT', 6], ['NO_EVIDENCE', 6], ['UNAUTHORIZED', 6], ]) const placeholderPattern = /待补|待提供|待完善|待定|占位内容|占位文本|占位符|placeholder|示例来源|示例内容|待确认|\b(?:todo|tbd)\b/i const phonePattern = /(?= 2, `${label}: at least two source references are required`) check((scenario.requiredReviewers ?? []).length >= 2, `${label}: at least two reviewer roles are required`) check((scenario.reviewChecklist ?? []).length >= 4, `${label}: review checklist is incomplete`) check(requiredText.every((value) => typeof value === 'string' && value.trim()), `${label}: required scenario fields are incomplete`) check(Array.isArray(draft.rounds) && draft.rounds.length >= 2, `${label}: at least two rounds are required`) for (const [index, round] of (draft.rounds ?? []).entries()) { check(Boolean(round?.customer?.trim()), `${label}: round ${index + 1} customer text is missing`) check(Boolean(round?.coachHint?.trim()), `${label}: round ${index + 1} coach hint is missing`) } const dimensions = rubric.dimensions ?? [] check(dimensions.length === 5, `${label}: rubric must contain exactly five dimensions`) check( new Set(dimensions.map((item) => item.code)).size === 5 && dimensions.every((item) => requiredDimensions.has(item.code)), `${label}: rubric dimension codes are incomplete or duplicated`, ) const weight = dimensions.reduce((sum, item) => sum + Number(item.weight ?? 0), 0) check(Math.abs(weight - 1) < 0.0001, `${label}: rubric weights must total 1`) check( dimensions.every((item) => Boolean(item.name?.trim()) && Boolean(item.description?.trim())), `${label}: rubric name or description is missing`, ) } const categoryCounts = new Map() for (const question of content.policyQuestionCandidates ?? []) { const label = question.id ?? 'unknown question' categoryCounts.set(question.category, (categoryCounts.get(question.category) ?? 0) + 1) check(question.status === 'PENDING_REVIEW', `${label}: status must remain pending review`) check(Boolean(question.question?.trim()), `${label}: question is missing`) check(Boolean(question.sourceNeed?.trim()), `${label}: source requirement is missing`) check(Boolean(question.expectedBehavior?.trim()), `${label}: expected boundary behavior is missing`) check(Boolean(question.reviewerRole?.trim()), `${label}: reviewer role is missing`) check(!Object.hasOwn(question, 'answer'), `${label}: answer must not exist before formal sourcing`) check(!Object.hasOwn(question, 'standardAnswer'), `${label}: standardAnswer must not exist before formal sourcing`) } for (const [category, expected] of requiredCategories) { check(categoryCounts.get(category) === expected, `${category}: expected ${expected} questions`) } const serialized = JSON.stringify(content) check(!phonePattern.test(serialized), 'candidate pack contains a mobile phone number') check(!preciseRoomPattern.test(serialized), 'candidate pack contains a precise building or room locator') check(!credentialPattern.test(serialized), 'candidate pack contains access-credential wording') check(!/"(?:reviewStatus|answer|standardAnswer)"\s*:/.test(serialized), 'candidate pack contains a publish state or answer field') if (failures.length > 0) { console.error(`Aug 1 content candidate verification failed (${failures.length}):`) for (const failure of failures) console.error(`- ${failure}`) process.exit(1) } console.log('Aug 1 content candidate verification passed') console.log(`- contentVersion: ${content.contentVersion}`) console.log(`- scenarios: ${content.scenarios.length} (all disabled and pending review)`) console.log(`- questions: ${content.policyQuestionCandidates.length} (6 per category)`) console.log('- formal publishable scenarios: 0') console.log('- formally sourced standard answers: 0')