#!/usr/bin/env node const assert = require('assert'); const path = require('path'); const { buildRealGapMaterialAudit, extractCandidateVideoNotes, findTranscriptHits } = require('./real-gap-material-audit'); const root = path.resolve(__dirname, '..'); const workspaceRoot = path.resolve(root, '..'); const outputsDir = path.join(root, 'outputs'); const transcriptsDir = path.join(workspaceRoot, 'docs', '提号经验'); const summary = buildRealGapMaterialAudit({ root, workspaceRoot, outputsDir, transcriptsDir }); assert.strictEqual(summary.materialType, 'three_real_gap_material_audit'); assert.strictEqual(summary.proofLevel, 'not_business_proof'); assert.strictEqual(summary.directCustomerProof, false); assert.strictEqual(summary.canCloseProofGap, false); assert.strictEqual(summary.gapCount, 3); assert.strictEqual(summary.gaps.length, 3); assert(summary.gaps.every(gap => gap.projectMaterialFound), 'all three real gaps should have project material connected'); assert(summary.gaps.every(gap => gap.canCloseNow === false), 'material audit must not close business proof gaps'); const byId = Object.fromEntries(summary.gaps.map(gap => [gap.id, gap])); assert(byId['historical-dataset'], 'historical-dataset gap should be present'); assert(byId['video-ab-live-proof'], 'video-ab-live-proof gap should be present'); assert(byId['manual-review-and-customer-effect'], 'manual-review-and-customer-effect gap should be present'); assert(byId['historical-dataset'].blockingRequirements.some(item => /briefCount|Brief|客户|补号/.test(item)), 'historical gap should keep real proof blockers'); assert.strictEqual(byId['video-ab-live-proof'].materialSignals.videoResourceReady, true, 'video resources should be connected before live A/B'); assert.strictEqual(byId['video-ab-live-proof'].materialSignals.preflightReadyForVideoAb, false, 'preflight should still require runtime/provider proof'); assert(byId['manual-review-and-customer-effect'].materialSignals.reviewDraftRows >= 1, 'manual review draft rows should be connected'); const candidateVideoNotes = extractCandidateVideoNotes(path.join(workspaceRoot, 'output', 'dha-tihao-poc', 'candidate_details_top18.json')); assert(candidateVideoNotes.length >= 1, 'candidate video notes should be discovered from project material'); assert(candidateVideoNotes.every(item => /^https:\/\/www\.xiaohongshu\.com\/discovery\/item\//.test(item.videoUrl)), 'candidate video urls should be sanitized public note urls'); const transcriptHits = findTranscriptHits(transcriptsDir, [/选中率/, /没有给我反馈/]); assert(transcriptHits.length >= 1, 'experience transcript evidence should be discoverable'); const serialized = JSON.stringify(summary); assert(!/npm_[A-Za-z0-9]{10,}/.test(serialized), 'summary must not include npm tokens'); assert(!/sk-[A-Za-z0-9_-]{10,}/.test(serialized), 'summary must not include model tokens'); assert(!/sessionToken\s*[:=]/i.test(serialized), 'summary must not include session tokens'); console.log('real-gap-material-audit smoke ok');