| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586 |
- #!/usr/bin/env node
- const fs = require('fs');
- const os = require('os');
- const path = require('path');
- const { spawnSync } = require('child_process');
- const ROOT = path.resolve(__dirname, '..');
- function main() {
- const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-experience-transcript-index-'));
- const transcriptsDir = path.join(temp, 'transcripts');
- const output = path.join(temp, 'index');
- fs.mkdirSync(transcriptsDir, { recursive: true });
- fs.writeFileSync(path.join(transcriptsDir, '黄圆-AI提号经验逐字稿.md'), [
- '# 黄圆',
- '参考账号要拆类型和调性。',
- '小红书和蒲公英可以通过相似推荐链路继续找。',
- '抖音星图标签不准时,用招募和关键词兜底。',
- '看主页近10篇、20篇封面首图判断风格。',
- '先用预算、CPE、CP1、CPM、粉丝、女粉和城市做第一轮过滤。',
- 'Brief 之外还要看产品、参考账号和客户聊天记录,客户说的隐藏要求要并入。'
- ].join('\n'), 'utf8');
- fs.writeFileSync(path.join(transcriptsDir, '邓发-AI提号经验逐字稿.md'), [
- '# 邓发',
- '美容仪和脱毛仪要理解女性受众,不能推荐男性账号。',
- '内容质感看画面清晰、像素、排版,不要下沉。',
- '报价 300 元以上要看封面统一、背景干净和画面精致。',
- '近一个月停更、平均点赞百赞以下、重复评论和同一个人评论都要复核数据真实性。',
- '时尚穿搭要拆港风、成熟风,美妆护肤要拆妆教、测评和产品合集。',
- '封面不确定再点进去看视频内容,可以继续解析每一帧。',
- '最终要看客户选中率、降本增效和补号变化。',
- ['Authorization', 'Bearer abcdefghijklmnopqrstuvwxyz'].join(': ')
- ].join('\n'), 'utf8');
- const result = spawnSync(process.execPath, [
- 'scripts/experience-transcript-index.js',
- '--transcripts-dir',
- transcriptsDir,
- '--output',
- output,
- '--strict'
- ], {
- cwd: ROOT,
- encoding: 'utf8',
- shell: false
- });
- if (result.stdout) process.stdout.write(result.stdout);
- if (result.stderr) process.stderr.write(result.stderr);
- assert(result.status === 0, 'experience transcript index should pass');
- const summaryPath = path.join(output, 'experience-transcript-index-summary.json');
- const reportPath = path.join(output, 'experience-transcript-index.md');
- const csvPath = path.join(output, 'experience-transcript-index.csv');
- const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8'));
- const report = fs.readFileSync(reportPath, 'utf8');
- const csv = fs.readFileSync(csvPath, 'utf8');
- assert(summary.proofLevel === 'not_business_proof', 'summary should not be business proof');
- assert(summary.materialType === 'experience_seed', 'summary should classify as experience seed');
- assert(summary.directCustomerProof === false, 'summary should not be direct customer proof');
- assert(summary.canCloseProofGap === false, 'summary should not close proof gap');
- assert(summary.complete === false, 'summary should not claim completion');
- assert(summary.transcriptCount === 2, 'summary should index two transcripts');
- assert(summary.coveredRuleCount === summary.ruleCount, 'fixture should cover all rules');
- assert(summary.ruleCoverage.every(item => item.directCustomerProof === false), 'rules should keep proof boundary');
- assert(summary.ruleCoverage.some(item => item.id === 'reference-type-tone' && item.status === 'covered'), 'reference rule should be covered');
- assert(summary.ruleCoverage.some(item => item.id === 'customer-effect-goal' && item.status === 'covered'), 'customer effect boundary rule should be covered');
- assert(report.includes('提号经验逐字稿索引'), 'report should render title');
- assert(report.includes('canCloseProofGap:false'), 'report should render proof boundary');
- assert(csv.includes('experience-rule'), 'csv should include rule rows');
- assert(!JSON.stringify(summary).includes('Authorization'), 'summary should not expose secret-like transcript text');
- assert(!report.includes('Authorization'), 'report should not expose secret-like transcript text');
- assert(!csv.includes('Authorization'), 'csv should not expose secret-like transcript text');
- console.log(JSON.stringify({
- ok: true,
- transcriptCount: summary.transcriptCount,
- coveredRuleCount: summary.coveredRuleCount
- }, null, 2));
- }
- function assert(condition, message) {
- if (!condition) throw new Error(message);
- }
- main();
|