#!/usr/bin/env node const fs = require('fs'); const os = require('os'); const path = require('path'); const { spawnSync } = require('child_process'); const { sanitizeCell, sanitizePublicUrl } = require('./local-seed-material-index'); const ROOT = path.resolve(__dirname, '..'); function main() { const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-local-seed-index-')); const workspaceRoot = path.join(temp, 'workspace'); const outputs = path.join(temp, 'outputs'); const output = path.join(temp, 'index'); fs.mkdirSync(workspaceRoot, { recursive: true }); fs.mkdirSync(outputs, { recursive: true }); writeJson(path.join(outputs, 'optimization-pipeline-latest', 'optimization-pipeline-summary.json'), { readyForClaim: false, counts: { pass: 6, fail: 3 }, inputs: { currentManualSupplementCount: '0' }, proofGapClosure: { complete: false, openCount: 5, closedCount: 0 } }); writeJson(path.join(outputs, 'optimization-pipeline-latest', 'history-audit', 'historical-dataset-audit.json'), { briefCount: 1, acceptance: { readyForCustomerEffectProof: false, readyForLongRun: false, categoryCoverageMet: false } }); writeJson(path.join(outputs, 'optimization-pipeline-latest', 'customer-effect-audit', 'customer-effect-summary.json'), { acceptance: { overallPass: false, customerSelectedRateMeasured: false, referenceCustomerSelectedRateMeasured: false, historyReadyForCustomerEffectProof: false }, missingEvidence: ['缺少客户最终选择字段'] }); writeJson(path.join(outputs, 'overnight-quality-live', 'aggregate-summary.json'), { manifest: { liveEnabled: true }, acceptance: { overallPass: true, failedGateCount: 0 }, failureCount: 0, runCount: 2, fixtureCount: 1, variantCount: 2 }); const result = spawnSync(process.execPath, [ 'scripts/local-seed-material-index.js', '--workspace-root', workspaceRoot, '--outputs', outputs, '--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, 'local seed material index should pass without local workbooks'); const summary = readJson(path.join(output, 'local-seed-material-index-summary.json')); const report = fs.readFileSync(path.join(output, 'local-seed-material-index.md'), 'utf8'); const csv = fs.readFileSync(path.join(output, 'local-seed-material-index.csv'), 'utf8'); assert(summary.proofLevel === 'not_business_proof', 'summary proof level should be not_business_proof'); assert(summary.directCustomerProof === false, 'summary should not be direct customer proof'); assert(summary.canCloseProofGap === false, 'summary should not close proof gaps'); assert(summary.existingMaterialCount === 0, 'empty smoke workspace should have zero local materials'); assert(summary.pipelineArtifacts.pipeline.readyForClaim === false, 'pipeline boundary should be retained'); assert(summary.pipelineArtifacts.historyAudit.readyForCustomerEffectProof === false, 'history audit should remain not ready'); assert(summary.pipelineArtifacts.customerEffect.overallPass === false, 'customer effect should remain not passed'); assert(summary.liveAutomationCandidates.length === 1, 'live automation candidate should be indexed'); assert(summary.liveAutomationCandidates[0].liveAutomationPass === true, 'live automation candidate should be detected'); assert(summary.liveAutomationCandidates[0].canCloseProofGapNow === false, 'live automation candidate should not close proof gap directly'); const dirtyShareUrl = 'https://www.xiaohongshu.com/discovery/item/abc123?app_version=9.26.0&xsec_token=secret-token&author_share=1#frag'; const cleanShareUrl = sanitizePublicUrl(dirtyShareUrl); assert(cleanShareUrl === 'https://www.xiaohongshu.com/discovery/item/abc123', 'share URL should drop query and hash parameters'); assert(!sanitizeCell(dirtyShareUrl).includes('xsec_token'), 'display cells should not expose share token parameters'); const bearerValue = ['Authorization: Bearer', 'abcdefghijklmnopqrstuvwxyz'].join(' '); assert(sanitizeCell(bearerValue) === '[REDACTED_SECRET_LIKE_VALUE]', 'secret-like values should be redacted'); assert(report.includes('本地种子材料索引'), 'report should render title'); assert(report.includes('canCloseProofGap:false'), 'report should render proof boundary'); assert(csv.includes('local-material'), 'csv should include local material rows'); console.log(JSON.stringify({ ok: true, materialCount: summary.materialCount, existingMaterialCount: summary.existingMaterialCount, liveAutomationCandidates: summary.liveAutomationCandidates.length }, null, 2)); } function writeJson(file, value) { fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file, JSON.stringify(value, null, 2), 'utf8'); } function readJson(file) { return JSON.parse(fs.readFileSync(file, 'utf8')); } function assert(condition, message) { if (!condition) throw new Error(message); } main();