#!/usr/bin/env node const fs = require('fs'); const path = require('path'); function main() { const root = path.resolve(__dirname, '..'); const args = parseArgs(process.argv.slice(2)); const outputDir = path.resolve(args.output || process.env.TIHAO_STATUS_OUTPUT || path.join(root, 'outputs', `optimization-status-${Date.now()}`)); const summary = buildStatusSummary(root); fs.mkdirSync(outputDir, { recursive: true }); const jsonPath = path.join(outputDir, 'optimization-status-summary.json'); const mdPath = path.join(outputDir, 'optimization-status-report.md'); fs.writeFileSync(jsonPath, JSON.stringify(summary, null, 2), 'utf8'); fs.writeFileSync(mdPath, withBom(renderReport(summary)), 'utf8'); console.log(JSON.stringify({ outputDir, json: jsonPath, report: mdPath, counts: summary.counts, complete: summary.complete }, null, 2)); } function parseArgs(argv) { const result = {}; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; if (!arg.startsWith('--')) continue; const key = arg.slice(2).replace(/-([a-z])/g, (_, char) => char.toUpperCase()); const next = argv[index + 1]; if (!next || next.startsWith('--')) { result[key] = true; } else { result[key] = next; index += 1; } } return result; } function buildStatusSummary(root) { const state = collectState(root); const items = buildStatusItems(state); return { generatedAt: new Date().toISOString(), root, counts: countStatuses(items), complete: items.every(item => item.status === 'passed'), items }; } function collectState(root) { const packageJson = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')); const scripts = packageJson.scripts || {}; const files = fileSet(root, [ 'scripts/live-preflight.js', 'scripts/long-run-readiness-audit.js', 'scripts/long-run-readiness-smoke.js', 'scripts/optimization-pipeline.js', 'scripts/optimization-pipeline-smoke.js', 'scripts/optimization-completion-audit.js', 'scripts/optimization-completion-smoke.js', 'scripts/generate-optimization-handoff.js', 'scripts/generate-optimization-handoff-smoke.js', 'scripts/generate-proof-gap-request.js', 'scripts/generate-proof-gap-request-smoke.js', 'scripts/proof-gap-closure-audit.js', 'scripts/proof-gap-closure-smoke.js', 'scripts/business-proof-progress.js', 'scripts/business-proof-progress-smoke.js', 'scripts/business-proof-next-actions.js', 'scripts/business-proof-next-actions-smoke.js', 'scripts/latest-form-index.js', 'scripts/latest-form-index-smoke.js', 'scripts/round-deposition-audit.js', 'scripts/round-deposition-smoke.js', 'scripts/evidence-index.js', 'scripts/evidence-index-smoke.js', 'scripts/acceptance.js', 'scripts/smoke-package.js', 'scripts/plan-consistency-smoke.js', 'scripts/hard-rules-smoke.js', 'scripts/reference-rules-smoke.js', 'scripts/platform-sourcing-strategy-smoke.js', 'scripts/overnight-quality-campaign.js', 'scripts/overnight-manual-review-smoke.js', 'scripts/leak-mojibake-audit.js', 'scripts/video-hit-rate-audit.js', 'scripts/video-hit-rate-audit-smoke.js', 'scripts/review-metrics.js', 'scripts/customer-effect-audit.js', 'scripts/customer-effect-audit-smoke.js', 'scripts/create-data-intake-pack.js', 'scripts/create-data-intake-pack-smoke.js', 'scripts/intake-readiness-audit.js', 'scripts/intake-readiness-smoke.js', 'scripts/create-video-intake-pack.js', 'scripts/create-video-intake-pack-smoke.js', 'scripts/video-resource-readiness-audit.js', 'scripts/video-resource-readiness-smoke.js', 'scripts/homepage-evidence-readiness-audit.js', 'scripts/homepage-evidence-readiness-smoke.js', 'scripts/history-dataset-from-csv.js', 'scripts/history-dataset-from-csv-smoke.js', 'scripts/historical-dataset-audit.js', 'scripts/candidate-pool-smoke.js', 'scripts/feedback-roundtrip-smoke.js', 'scripts/feedback-loop-closure-audit.js', 'scripts/feedback-loop-closure-smoke.js', 'docs/historical-dataset-guide.md', 'docs/ai-overnight-optimization-task.md', 'docs/ai-optimization-job-dedup-software-format.md', 'docs/overnight-quality-runbook.md', 'docs/manual-review-handoff.md', 'mcp/src/features/tihao-sourcing/homepage-evidence-provider.js', 'mcp/src/features/tihao-sourcing/preference-memory.js', 'mcp/src/features/tihao-sourcing/report.js' ]); const packageText = JSON.stringify(packageJson); const overnightText = readOptional(path.join(root, 'scripts/overnight-quality-campaign.js')); const overnightReviewSmokeText = readOptional(path.join(root, 'scripts/overnight-manual-review-smoke.js')); const reviewMetricsText = readOptional(path.join(root, 'scripts/review-metrics.js')); const reviewMetricsSmokeText = readOptional(path.join(root, 'scripts/review-metrics-smoke.js')); const customerEffectAuditText = readOptional(path.join(root, 'scripts/customer-effect-audit.js')); const customerEffectSmokeText = readOptional(path.join(root, 'scripts/customer-effect-audit-smoke.js')); const dataIntakePackText = readOptional(path.join(root, 'scripts/create-data-intake-pack.js')); const dataIntakePackSmokeText = readOptional(path.join(root, 'scripts/create-data-intake-pack-smoke.js')); const intakeReadinessText = readOptional(path.join(root, 'scripts/intake-readiness-audit.js')); const intakeReadinessSmokeText = readOptional(path.join(root, 'scripts/intake-readiness-smoke.js')); const videoIntakePackText = readOptional(path.join(root, 'scripts/create-video-intake-pack.js')); const videoIntakePackSmokeText = readOptional(path.join(root, 'scripts/create-video-intake-pack-smoke.js')); const videoResourceReadinessText = readOptional(path.join(root, 'scripts/video-resource-readiness-audit.js')); const videoResourceReadinessSmokeText = readOptional(path.join(root, 'scripts/video-resource-readiness-smoke.js')); const historyFromCsvText = readOptional(path.join(root, 'scripts/history-dataset-from-csv.js')); const historyFromCsvSmokeText = readOptional(path.join(root, 'scripts/history-dataset-from-csv-smoke.js')); const memoryText = readOptional(path.join(root, 'mcp/src/features/tihao-sourcing/preference-memory.js')); const reportText = readOptional(path.join(root, 'mcp/src/features/tihao-sourcing/report.js')); const smokePackageText = readOptional(path.join(root, 'scripts/smoke-package.js')); const planConsistencyText = readOptional(path.join(root, 'scripts/plan-consistency-smoke.js')); const candidatePoolSmokeText = readOptional(path.join(root, 'scripts/candidate-pool-smoke.js')); const hygieneSmokeText = readOptional(path.join(root, 'scripts/leak-mojibake-audit.js')); const hardRulesSmokeText = readOptional(path.join(root, 'scripts/hard-rules-smoke.js')); const homepageSmokeText = readOptional(path.join(root, 'scripts/homepage-evidence-provider-smoke.js')); const homepageReadinessText = readOptional(path.join(root, 'scripts/homepage-evidence-readiness-audit.js')); const homepageReadinessSmokeText = readOptional(path.join(root, 'scripts/homepage-evidence-readiness-smoke.js')); const referenceRulesSmokeText = readOptional(path.join(root, 'scripts/reference-rules-smoke.js')); const platformStrategySmokeText = readOptional(path.join(root, 'scripts/platform-sourcing-strategy-smoke.js')); const feedbackRoundtripSmokeText = readOptional(path.join(root, 'scripts/feedback-roundtrip-smoke.js')); const feedbackLoopClosureText = readOptional(path.join(root, 'scripts/feedback-loop-closure-audit.js')); const feedbackLoopClosureSmokeText = readOptional(path.join(root, 'scripts/feedback-loop-closure-smoke.js')); const providerContractText = readOptional(path.join(root, 'scripts/provider-contract-audit.js')); const longRunReadinessText = readOptional(path.join(root, 'scripts/long-run-readiness-audit.js')); const longRunReadinessSmokeText = readOptional(path.join(root, 'scripts/long-run-readiness-smoke.js')); const optimizationPipelineText = readOptional(path.join(root, 'scripts/optimization-pipeline.js')); const optimizationPipelineSmokeText = readOptional(path.join(root, 'scripts/optimization-pipeline-smoke.js')); const optimizationCompletionText = readOptional(path.join(root, 'scripts/optimization-completion-audit.js')); const optimizationCompletionSmokeText = readOptional(path.join(root, 'scripts/optimization-completion-smoke.js')); const handoffText = readOptional(path.join(root, 'scripts/generate-optimization-handoff.js')); const handoffSmokeText = readOptional(path.join(root, 'scripts/generate-optimization-handoff-smoke.js')); const proofGapText = readOptional(path.join(root, 'scripts/generate-proof-gap-request.js')); const proofGapSmokeText = readOptional(path.join(root, 'scripts/generate-proof-gap-request-smoke.js')); const proofGapClosureText = readOptional(path.join(root, 'scripts/proof-gap-closure-audit.js')); const proofGapClosureSmokeText = readOptional(path.join(root, 'scripts/proof-gap-closure-smoke.js')); const businessProofProgressText = readOptional(path.join(root, 'scripts/business-proof-progress.js')); const businessProofProgressSmokeText = readOptional(path.join(root, 'scripts/business-proof-progress-smoke.js')); const businessProofNextActionsText = readOptional(path.join(root, 'scripts/business-proof-next-actions.js')); const businessProofNextActionsSmokeText = readOptional(path.join(root, 'scripts/business-proof-next-actions-smoke.js')); const latestFormIndexText = readOptional(path.join(root, 'scripts/latest-form-index.js')); const latestFormIndexSmokeText = readOptional(path.join(root, 'scripts/latest-form-index-smoke.js')); const roundDepositionText = readOptional(path.join(root, 'scripts/round-deposition-audit.js')); const roundDepositionSmokeText = readOptional(path.join(root, 'scripts/round-deposition-smoke.js')); const evidenceIndexText = readOptional(path.join(root, 'scripts/evidence-index.js')); const evidenceIndexSmokeText = readOptional(path.join(root, 'scripts/evidence-index-smoke.js')); const videoHitRateAuditText = readOptional(path.join(root, 'scripts/video-hit-rate-audit.js')); const videoHitRateAuditSmokeText = readOptional(path.join(root, 'scripts/video-hit-rate-audit-smoke.js')); const historyAuditText = readOptional(path.join(root, 'scripts/historical-dataset-audit.js')); const historyAuditSmokeText = readOptional(path.join(root, 'scripts/historical-dataset-audit-smoke.js')); const docsText = [ 'docs/tihao-experience-implementation-log.md', 'docs/ai-overnight-optimization-task.md', 'docs/overnight-quality-runbook.md', 'docs/historical-dataset-guide.md', 'docs/manual-review-handoff.md', 'docs/capability-map.md', 'README.md' ].map(file => readOptional(path.join(root, file))).join('\n'); return { root, scripts, files, packageText, overnightText, overnightReviewSmokeText, reviewMetricsText, reviewMetricsSmokeText, customerEffectAuditText, customerEffectSmokeText, dataIntakePackText, dataIntakePackSmokeText, intakeReadinessText, intakeReadinessSmokeText, videoIntakePackText, videoIntakePackSmokeText, videoResourceReadinessText, videoResourceReadinessSmokeText, historyFromCsvText, historyFromCsvSmokeText, memoryText, reportText, smokePackageText, planConsistencyText, candidatePoolSmokeText, hygieneSmokeText, hardRulesSmokeText, homepageSmokeText, homepageReadinessText, homepageReadinessSmokeText, referenceRulesSmokeText, platformStrategySmokeText, feedbackRoundtripSmokeText, feedbackLoopClosureText, feedbackLoopClosureSmokeText, providerContractText, longRunReadinessText, longRunReadinessSmokeText, optimizationPipelineText, optimizationPipelineSmokeText, optimizationCompletionText, optimizationCompletionSmokeText, handoffText, handoffSmokeText, proofGapText, proofGapSmokeText, proofGapClosureText, proofGapClosureSmokeText, businessProofProgressText, businessProofProgressSmokeText, businessProofNextActionsText, businessProofNextActionsSmokeText, latestFormIndexText, latestFormIndexSmokeText, roundDepositionText, roundDepositionSmokeText, evidenceIndexText, evidenceIndexSmokeText, videoHitRateAuditText, videoHitRateAuditSmokeText, historyAuditText, historyAuditSmokeText, docsText, outputs: findOutputEvidence(root) }; } function buildStatusItems(state) { const liveProof = hasLiveProof(state.outputs); const customerEffectProof = hasCustomerEffectProof(state.outputs); return [ item({ id: 'release-chain', requirement: '基础发布链路', status: hasScripts(state, ['acceptance', 'smoke:package', 'mcp:smoke', 'acceptance:providers:mock']) && state.files['scripts/acceptance.js'] && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("acceptance:providers:mock") && state.smokePackageText.includes('assertFriendlyAuthState') && state.smokePackageText.includes('should not expose raw 401/403') && state.providerContractText.includes('assertProviderFailurePublicOutputSafe') && state.providerContractText.includes('provider failure public output should not expose raw 401/403') ? 'passed' : 'missing', evidence: 'package.json scripts include acceptance/smoke/mcp/provider mock gates; acceptance.js calls provider mock; smoke-package and provider mock cover friendly auth states', next: '继续用 npm run acceptance 做发布前验收。' }), item({ id: 'software-table', requirement: '软件端表格固定表头、去重和连续排名', status: state.reportText.includes('SOFTWARE_TABLE_HEADER') && state.reportText.includes('dedupeCandidates') && state.reportText.includes('getReviewPoolTarget') && state.reportText.includes('1.5 倍候选池') && state.scripts['candidate-pool:smoke'] && state.files['scripts/candidate-pool-smoke.js'] && state.candidatePoolSmokeText.includes('duplicate software row key') && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("candidate-pool:smoke") ? 'passed' : 'missing', evidence: 'report.js contains fixed software header, candidate dedupe, 1.5x review-pool target, and candidate-pool smoke is in default acceptance', next: '真实交付前用 review:metrics 和 CSV 抽检验证重复键、排名连续和候选池规模。' }), item({ id: 'output-hygiene', requirement: '输出泄密和中文乱码自动化门禁', status: state.scripts['hygiene:smoke'] && state.files['scripts/leak-mojibake-audit.js'] && state.hygieneSmokeText.includes('parse-session-token') && state.hygieneSmokeText.includes('common-mojibake') && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("hygiene:smoke") ? 'passed' : 'missing', evidence: 'hygiene:smoke scans docs, README, scripts, mcp/src, skills, and fixtures for token-like secrets and common mojibake patterns; default acceptance runs it', next: '每次发布前继续运行 npm run acceptance,确保输出不含 token 和乱码。' }), item({ id: 'plan-consistency', requirement: '长期计划、AI 任务书和 skill 输出规范口径一致', status: state.scripts['plan:consistency:smoke'] && state.files['scripts/plan-consistency-smoke.js'] && state.files['docs/ai-optimization-job-dedup-software-format.md'] && state.planConsistencyText.includes('legacy-dedupe-phrase') && state.planConsistencyText.includes('softwareTableHeader') && state.planConsistencyText.includes('unexpected-task-count') && state.planConsistencyText.includes('全局博主唯一') && state.planConsistencyText.includes('docs/tihao-experience-optimization-plan.md') && state.planConsistencyText.includes('docs/ai-optimization-job-dedup-software-format.md') && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("plan:consistency:smoke") ? 'passed' : 'missing', evidence: 'plan:consistency:smoke verifies current plan, software table doc, AI task table, dedup software-format task table, and skill output-format all use global creator dedupe; it also checks fixed 17-column software headers, 9 unique deduped OPT rows, and blocks legacy brief-scoped dedupe phrases', next: '后续改 SOP 或输出格式时继续让 acceptance 守住计划口径一致性。' }), item({ id: 'hard-rules', requirement: '硬性规则和剔除账号本地验收', status: state.scripts['hard-rules:smoke'] && state.files['scripts/hard-rules-smoke.js'] && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("hard-rules:smoke") && state.hardRulesSmokeText.includes('male generic account should not be strong recommendation') && state.hardRulesSmokeText.includes('low homepage quality creator should not be strong recommendation') && state.hardRulesSmokeText.includes('主页质感风险') && state.hardRulesSmokeText.includes('excluded competitor should not enter software CSV') ? 'passed' : 'missing', evidence: 'hard-rules smoke verifies hard exclusions, implicit category rules, over-budget downgrade, low homepage quality downgrade, platform filtering, and software CSV exclusion', next: '继续把新增品类规则加入 hard-rules smoke。' }), item({ id: 'homepage-provider-entry', requirement: '主页最近内容 provider 接入入口', status: state.files['mcp/src/features/tihao-sourcing/homepage-evidence-provider.js'] && state.scripts['homepage:smoke'] && state.homepageSmokeText.includes('assertMockHomepageContract') && state.homepageSmokeText.includes('recent_posts') && state.homepageSmokeText.includes('assertUnauthorizedFallback') && state.homepageSmokeText.includes('public output should not expose raw 401/403') ? 'passed' : 'missing', evidence: 'homepage evidence provider module and homepage:smoke cover provider request contract, successful evidence attachment, unauthorized fallback, and public-output safety', next: '真实 provider 配置后打开 TIHAO_GATE_REQUIRE_HOMEPAGE_PROVIDER。' }), item({ id: 'homepage-evidence-readiness', requirement: '主页近期内容证据就绪审计', status: state.scripts['homepage:readiness'] && state.scripts['homepage:readiness:smoke'] && state.files['scripts/homepage-evidence-readiness-audit.js'] && state.files['scripts/homepage-evidence-readiness-smoke.js'] && state.homepageReadinessText.includes('buildHomepageEvidenceReadiness') && state.homepageReadinessText.includes('recentContentWindow') && state.homepageReadinessText.includes('directCustomerProof') && state.homepageReadinessSmokeText.includes('strict weak evidence should fail readiness gate') && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("homepage:readiness:smoke") ? 'passed' : 'missing', evidence: 'homepage readiness audit and smoke verify provider/path evidence, recent 10 posts, cover/title/publish/interactions/risk gates, strict failure, and no customer-proof claim', next: '真实 provider 或本地主页证据补齐后运行 npm run homepage:readiness -- --strict。' }), item({ id: 'feedback-roundtrip', requirement: '反馈导入后二轮闭环', status: state.scripts['feedback:roundtrip'] && state.scripts['feedback:closure'] && state.scripts['feedback:closure:smoke'] && state.memoryText.includes('blockedCreators') && state.feedbackRoundtripSmokeText.includes('调性不符') && state.feedbackRoundtripSmokeText.includes('主页质感不符') && state.feedbackRoundtripSmokeText.includes('参考账号不像') && state.feedbackRoundtripSmokeText.includes('teamRuleSuggestions') && state.feedbackRoundtripSmokeText.includes('should not enter client-ready candidates') && state.feedbackLoopClosureText.includes('directCustomerProof') && state.feedbackLoopClosureText.includes('leakedCount') && state.feedbackLoopClosureSmokeText.includes('leakedCount === 0') && state.feedbackLoopClosureSmokeText.includes('directCustomerProof === false') ? 'passed' : 'missing', evidence: 'feedback:roundtrip and feedback:closure cover negative feedback import, blocked creator memory, second-round exclusion, leakedCount audit, proof boundary, blocked style memory, and repeated team-rule suggestions', next: '导入真实客户反馈 CSV 后复测二轮负样本率。' }), item({ id: 'team-rule-suggestions', requirement: '团队规则升级建议', status: state.memoryText.includes('teamRuleSuggestions') && state.reportText.includes('反馈记忆与团队规则建议') ? 'passed' : 'missing', evidence: 'teamRuleSuggestions and report section exist', next: '团队确认后再把建议固化为正式规则。' }), item({ id: 'video-ab-proof', requirement: '视频 A/B 严格验收', status: state.scripts['acceptance:video-ab'] && state.scripts['acceptance:video-ab:smoke'] && state.files['scripts/video-hit-rate-audit.js'] && state.files['scripts/video-hit-rate-audit-smoke.js'] && state.videoHitRateAuditText.includes('computeVideoAcceptance') && state.videoHitRateAuditText.includes('strongNotDegraded') && state.videoHitRateAuditText.includes('top10EvidenceImproved') && state.videoHitRateAuditSmokeText.includes('provider failure should fail') && state.videoHitRateAuditSmokeText.includes('strong count degradation should fail') ? 'ready_not_proven' : 'missing', evidence: 'acceptance:video-ab live script and acceptance:video-ab:smoke cover real video resources, provider ok, evidence cards, top10 non-degradation, and strong-count non-degradation', next: '需要真实 token/company/provider 运行 npm run acceptance:video-ab。' }), item({ id: 'long-run-readiness', requirement: '真实长跑启动前就绪度审计', status: state.scripts['longrun:readiness'] && state.scripts['longrun:readiness:smoke'] && state.files['scripts/long-run-readiness-audit.js'] && state.files['scripts/long-run-readiness-smoke.js'] && state.longRunReadinessText.includes('readyForLiveAcceptance') && state.longRunReadinessText.includes('readyForCustomerEffectProof') && state.longRunReadinessText.includes('intake-readiness-overall-ready') && state.longRunReadinessText.includes('doubao-seed-2-0-pro') && state.longRunReadinessText.includes('video-resource-readiness-present') && state.longRunReadinessText.includes('proof-gap-unresolved-zero') && state.longRunReadinessSmokeText.includes('customer effect proof should require history audit') && state.longRunReadinessSmokeText.includes('failing intake readiness should block full matrix readiness') && state.longRunReadinessSmokeText.includes('unresolved proof gaps should block full matrix readiness') && state.longRunReadinessSmokeText.includes('video A/B should require video resource readiness summary') && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("longrun:readiness:smoke") ? 'passed' : 'missing', evidence: 'longrun:readiness audits live preflight, intake readiness, history audit, video resource readiness, proof-gap status, video A/B readiness, and customer-effect proof prerequisites before starting controlled or full live matrix runs', next: '真实长跑前把 live-preflight-summary.json、historical-dataset-audit.json、video-resource-readiness-summary.json 和 proof-gap-request-summary.json 传给 npm run longrun:readiness。' }), item({ id: 'optimization-pipeline', requirement: '端到端优化链路编排', status: state.scripts['optimization:pipeline'] && state.scripts['optimization:pipeline:smoke'] && state.files['scripts/optimization-pipeline.js'] && state.files['scripts/optimization-pipeline-smoke.js'] && state.optimizationPipelineText.includes('history:from-csv') && state.optimizationPipelineText.includes('customer-effect:audit') && state.optimizationPipelineText.includes('evidence:index') && state.optimizationPipelineSmokeText.includes('pipeline should run customer-effect-audit') && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("optimization:pipeline:smoke") ? 'passed' : 'missing', evidence: 'optimization:pipeline can compose history CSV import, history audit, readiness, review metrics, customer effect audit, and evidence index in one local workflow', next: '真实数据到位后用 optimization:pipeline 串起审计链路;真实 live 长跑仍需 preflight/readiness 控制。' }), item({ id: 'optimization-completion', requirement: '长期优化完成度审计', status: state.scripts['optimization:completion'] && state.scripts['optimization:completion:smoke'] && state.files['scripts/optimization-completion-audit.js'] && state.files['scripts/optimization-completion-smoke.js'] && state.optimizationCompletionText.includes('readyForClaim') && state.optimizationCompletionText.includes('proofGapOpenCount') && state.optimizationCompletionText.includes('canClaim') && state.optimizationCompletionSmokeText.includes('should not allow tihao-rate claim') && state.optimizationCompletionSmokeText.includes('closed fixture should be ready for claim') && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("optimization:completion:smoke") ? 'passed' : 'missing', evidence: 'optimization:completion aggregates intake/video/proof-gap/video A/B/live-provider/customer-effect/pipeline/handoff/progress gates and keeps readyForClaim=false until all real proof gates pass', next: '每轮结束后运行 npm run round:refresh -- --output-root outputs --strict;只需单独判断是否可声明完成时再运行 npm run optimization:completion -- --output outputs\\optimization-completion-latest。' }), item({ id: 'optimization-handoff', requirement: '长期优化交接摘要', status: state.scripts['handoff:summary'] && state.scripts['handoff:summary:smoke'] && state.files['scripts/generate-optimization-handoff.js'] && state.files['scripts/generate-optimization-handoff-smoke.js'] && state.handoffText.includes('buildHandoffSummary') && state.handoffText.includes('customer-effect:audit') && state.handoffText.includes('不能把 sample、smoke、dry-run') && state.handoffSmokeText.includes('handoff must not mark long-term goal complete without real proof') && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("handoff:summary:smoke") ? 'passed' : 'missing', evidence: 'handoff:summary combines optimization status, evidence index, data intake pack paths, next commands, and proof guardrails; smoke verifies it stays incomplete without real proof', next: '常规每轮结束后优先运行 npm run round:refresh -- --output-root outputs --strict;只需单独刷新交接摘要时再运行 npm run handoff:summary。' }), item({ id: 'proof-gap-request', requirement: '真实证明缺口请求包', status: state.scripts['proof-gap:request'] && state.scripts['proof-gap:request:smoke'] && state.files['scripts/generate-proof-gap-request.js'] && state.files['scripts/generate-proof-gap-request-smoke.js'] && state.proofGapText.includes('buildProofGapRequest') && state.proofGapText.includes('video-real-candidate') && state.proofGapText.includes('historical-dataset') && state.proofGapText.includes('manual-review-and-customer-effect') && state.proofGapText.includes('不要在 CSV、报告或聊天中写入 sessionToken') && state.proofGapSmokeText.includes('should request real candidate video resources') && state.evidenceIndexText.includes("type: 'proof-gap-request'") && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("proof-gap:request:smoke") ? 'passed' : 'missing', evidence: 'proof-gap:request generates JSON/Markdown/CSV request material for historical data, real video resources, live/provider proof, manual review, customer choices, and manual supplement baseline; smoke verifies boundary and no secret-like values', next: '每轮结束后运行 npm run proof-gap:request -- --output outputs\\proof-gap-request-latest,交给商务补齐真实证明数据。' }), item({ id: 'proof-gap-closure', requirement: '真实证明缺口关闭审计', status: state.scripts['proof-gap:closure'] && state.scripts['proof-gap:closure:smoke'] && state.files['scripts/proof-gap-closure-audit.js'] && state.files['scripts/proof-gap-closure-smoke.js'] && state.proofGapClosureText.includes('proof-gap-closure-summary.json') && state.proofGapClosureText.includes('intake-readiness') && state.proofGapClosureText.includes('intakeReady') && state.proofGapClosureText.includes('historical-dataset') && state.proofGapClosureText.includes('video-ab-live-proof') && state.proofGapClosureText.includes('manual-review-and-customer-effect') && state.proofGapClosureSmokeText.includes('openCount') && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("proof-gap:closure:smoke") ? 'passed' : 'missing', evidence: 'proof-gap:closure audits intake readiness plus whether the real historical dataset, video resource readiness, video A/B live proof, live/provider overnight proof, and customer-effect proof are actually closed; acceptance runs its smoke gate', next: '真实数据补齐后运行 npm run proof-gap:closure -- --output outputs\\proof-gap-closure-latest,只有 openCount=0 才能进入长期完成判断。' }), item({ id: 'round-deposition', requirement: '每轮沉淀完整性审计', status: state.scripts['round:deposition'] && state.scripts['round:deposition:smoke'] && state.files['scripts/round-deposition-audit.js'] && state.files['scripts/round-deposition-smoke.js'] && state.roundDepositionText.includes('buildRoundDepositionSummary') && state.roundDepositionText.includes('implementation-log') && state.roundDepositionText.includes('failureCount') && state.roundDepositionText.includes('software-table') && state.roundDepositionText.includes('video-ab-proof') && state.roundDepositionText.includes('customer-effect-proof') && state.roundDepositionText.includes('proof-gap-unresolved-recorded') && state.roundDepositionText.includes('proofGapUnresolvedCount') && state.roundDepositionSmokeText.includes('round-deposition-audit should pass') && state.roundDepositionSmokeText.includes('summary should include proof-gap unresolved check') && state.evidenceIndexText.includes('classifyRoundDeposition') && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("round:deposition:smoke") ? 'passed' : 'missing', evidence: 'round:deposition audits implementation log, status report, evidence index, handoff summary, proof boundaries, live/video/provider indicators, software duplicate/rank status, and review/customer metrics; acceptance runs its smoke gate', next: '常规每轮结束后优先运行 npm run round:refresh -- --output-root outputs --strict,确保状态、证据台账、交接摘要和沉淀审计按顺序刷新。' }), item({ id: 'business-proof-progress', requirement: '15 步补证进度实时同步', status: state.scripts['business-proof:progress'] && state.scripts['business-proof:progress:smoke'] && state.files['scripts/business-proof-progress.js'] && state.files['scripts/business-proof-progress-smoke.js'] && state.businessProofProgressText.includes('buildBusinessProofProgress') && state.businessProofProgressText.includes('business-proof-progress-summary.json') && state.businessProofProgressText.includes('blocked_by_external_data') && state.businessProofProgressText.includes('不能把 sample、smoke') && state.businessProofProgressSmokeText.includes('progress should include 15 steps') && state.roundDepositionText.includes('business-proof-progress-summary') && state.roundDepositionText.includes('businessProofProgressCounts') && state.evidenceIndexText.includes("type: 'business-proof-progress'") && state.handoffText.includes('businessProofProgress') && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("business-proof:progress:smoke") ? 'passed' : 'missing', evidence: 'business-proof:progress outputs a 15-step pass/pending/fail/blocked_by_external_data progress board; round:deposition requires it, evidence:index classifies it as smoke_or_local, and handoff:summary links it for the next round', next: '每轮结束后运行 npm run business-proof:progress -- --output outputs\\business-proof-progress-latest;它只同步补证进度,不证明客户效果。' }), item({ id: 'business-proof-next-actions', requirement: '下一步补证行动队列', status: state.scripts['business-proof:next-actions'] && state.scripts['business-proof:next-actions:smoke'] && state.files['scripts/business-proof-next-actions.js'] && state.files['scripts/business-proof-next-actions-smoke.js'] && state.businessProofNextActionsText.includes('buildNextActions') && state.businessProofNextActionsText.includes('business-proof-next-actions-summary.json') && state.businessProofNextActionsText.includes('ownerGroups') && state.businessProofNextActionsText.includes('ownerArtifacts') && state.businessProofNextActionsText.includes('writeOwnerArtifacts') && state.businessProofNextActionsText.includes('行动队列只用于安排下一步补证') && state.businessProofNextActionsSmokeText.includes('history blocker should be first') && state.businessProofNextActionsSmokeText.includes('owner groups should include business owner') && state.businessProofNextActionsSmokeText.includes('owner artifact files should exist') && state.roundDepositionText.includes('business-proof-next-actions-summary') && state.handoffText.includes('businessProofNextActions') && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("business-proof:next-actions:smoke") ? 'passed' : 'missing', evidence: 'business-proof:next-actions converts progress/proof-gap/intake/video blockers into owner, command, expected artifact, and acceptance rows; handoff links it and round:deposition requires it', next: '每轮结束后运行 npm run business-proof:next-actions -- --output outputs\\business-proof-next-actions-latest;它只安排补证任务,不证明客户效果。' }), item({ id: 'latest-form-index', requirement: '最新提号表单统一入口', status: state.scripts['latest-form:index'] && state.scripts['latest-form:index:smoke'] && state.files['scripts/latest-form-index.js'] && state.files['scripts/latest-form-index-smoke.js'] && state.latestFormIndexText.includes('buildLatestFormIndex') && state.latestFormIndexText.includes('directCustomerProof') && state.latestFormIndexText.includes('latest-form-index-summary.json') && state.latestFormIndexText.includes('latest-form-index.md') && state.latestFormIndexSmokeText.includes('client list must still require review/customer proof') && state.roundDepositionText.includes('latest-form-index-summary') && state.handoffText.includes('latestFormIndex') && state.evidenceIndexText.includes("type: 'latest-form-index'") && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("latest-form:index:smoke") ? 'passed' : 'missing', evidence: 'latest-form:index exposes the current proof-gap form, software client list, owner action files, and proof guardrails as one handoff entry; smoke verifies it is not treated as customer-effect proof', next: '常规每轮结束后优先运行 npm run round:refresh -- --output-root outputs --strict;只需单独定位最新表单时再运行 npm run latest-form:index。' }), item({ id: 'evidence-index', requirement: '长跑产物证据台账', status: state.scripts['evidence:index'] && state.scripts['evidence:index:smoke'] && state.files['scripts/evidence-index.js'] && state.files['scripts/evidence-index-smoke.js'] && state.evidenceIndexText.includes('real_evidence') && state.evidenceIndexText.includes('not_business_proof') && state.evidenceIndexText.includes('live-preflight-summary.json') && state.evidenceIndexText.includes("type: 'live-preflight'") && state.evidenceIndexText.includes('video-hit-rate-summary.json') && state.evidenceIndexText.includes('customer-effect-summary.json') && state.evidenceIndexText.includes("type: 'customer-effect'") && state.evidenceIndexText.includes('optimization-pipeline-summary.json') && state.evidenceIndexText.includes("type: 'optimization-pipeline'") && state.evidenceIndexText.includes('optimization-completion-summary.json') && state.evidenceIndexText.includes("type: 'optimization-completion'") && state.evidenceIndexText.includes('optimization-handoff-summary.json') && state.evidenceIndexText.includes("type: 'optimization-handoff'") && state.evidenceIndexText.includes('proof-gap-request-summary.json') && state.evidenceIndexText.includes("type: 'proof-gap-request'") && state.evidenceIndexText.includes('proof-gap-closure-summary.json') && state.evidenceIndexText.includes("type: 'proof-gap-closure'") && state.evidenceIndexText.includes('round-deposition-summary.json') && state.evidenceIndexText.includes("type: 'round-deposition'") && state.evidenceIndexText.includes("type: 'data-intake-pack'") && state.evidenceIndexText.includes("type: 'video-intake-pack'") && state.evidenceIndexText.includes('intake-readiness-summary.json') && state.evidenceIndexText.includes("type: 'intake-readiness'") && state.evidenceIndexText.includes('video-resource-readiness-summary.json') && state.evidenceIndexText.includes("type: 'video-resource-readiness'") && state.evidenceIndexSmokeText.includes('closed proof-gap closure should be real evidence') && state.evidenceIndexSmokeText.includes('handoff summary') && state.evidenceIndexSmokeText.includes('optimization pipeline orchestration') && state.evidenceIndexSmokeText.includes('optimization completion audits') && state.evidenceIndexSmokeText.includes('optimization handoff summary') && state.evidenceIndexSmokeText.includes('proof gap request summary') && state.evidenceIndexSmokeText.includes('proof gap closure summaries') && state.evidenceIndexSmokeText.includes('round deposition summary') && state.evidenceIndexSmokeText.includes('intake readiness audits') && state.evidenceIndexSmokeText.includes('data intake pack readiness') && state.evidenceIndexSmokeText.includes('video intake pack readiness') && state.evidenceIndexSmokeText.includes('video resource readiness audits') && state.evidenceIndexSmokeText.includes('incomplete customer effect') && state.evidenceIndexSmokeText.includes('missing preflight') && state.evidenceIndexSmokeText.includes('failed live') && state.evidenceIndexSmokeText.includes('weak video') && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("evidence:index:smoke") ? 'passed' : 'missing', evidence: 'evidence:index scans outputs and classifies overnight, status, readiness, live preflight, review metrics, customer effect, historical dataset, video A/B, proof-gap request, and proof-gap closure artifacts into real_evidence, smoke_or_local, and not_business_proof', next: '每次长跑或复核后优先运行 npm run round:refresh -- --output-root outputs --strict;只需单独刷新证明等级时再运行 npm run evidence:index。' }), item({ id: 'overnight-matrix', requirement: 'overnight 完整策略矩阵', status: ['reference-account', 'homepage-evidence', 'video-enhanced', 'result-first-broad'].every(term => state.overnightText.includes(term)) ? 'passed' : 'missing', evidence: 'overnight script contains baseline/reference/homepage/video/result-first variants', next: '真实 live 长跑时使用新版 ai-overnight-optimization-task.md。' }), item({ id: 'strict-provider-gates', requirement: '严格 provider 门禁', status: state.overnightText.includes('TIHAO_GATE_REQUIRE_REFERENCE_PROVIDER') && state.overnightText.includes('TIHAO_GATE_REQUIRE_HOMEPAGE_PROVIDER') ? 'passed' : 'missing', evidence: 'overnight strict provider gates exist', next: '真实 provider 联调时打开 strict gates。' }), item({ id: 'reference-rules', requirement: '参考账号冲突和强推荐解释本地验收', status: state.scripts['reference-rules:smoke'] && state.scripts['platform:strategy:smoke'] && state.files['scripts/reference-rules-smoke.js'] && state.files['scripts/platform-sourcing-strategy-smoke.js'] && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("reference-rules:smoke") && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("platform:strategy:smoke") && state.referenceRulesSmokeText.includes('reference_conflict') && state.referenceRulesSmokeText.includes('不能强行合并') && state.referenceRulesSmokeText.includes('strong candidate reasons should explain reference/style evidence') && state.referenceRulesSmokeText.includes('strong candidates should have reference style or homepage evidence') && state.referenceRulesSmokeText.includes('strong reference style hit points should not contain placeholder evidence') && state.referenceRulesSmokeText.includes('weak placeholder reference evidence should not be enough for strong recommendation') && state.referenceRulesSmokeText.includes('strong candidate reasons should explain homepage evidence') && state.platformStrategySmokeText.includes('reference_similarity') && state.platformStrategySmokeText.includes('keyword_or_recruitment') && state.platformStrategySmokeText.includes('不作为适配证明') ? 'passed' : 'missing', evidence: 'reference-rules and platform strategy smokes verify conflicting references, strong recommendation brief hits, reference/homepage evidence explanation, xiaohongshu reference-similarity path, and douyin keyword/recruitment fallback', next: '真实参考账号数据接入后继续用 review:metrics 验证参考链路通过率。' }), item({ id: 'review-metrics', requirement: '人工复核/业务指标统计', status: state.scripts['review:metrics'] && state.scripts['review:metrics:smoke'] && state.scripts['overnight:review:smoke'] && state.files['scripts/overnight-manual-review-smoke.js'] && state.overnightText.includes('归因类型') && state.overnightText.includes('客户选择') && state.overnightReviewSmokeText.includes('manual review sample should include') && state.reviewMetricsText.includes('compareReferenceAndKeywordStrategies') && state.reviewMetricsText.includes('referencePassRateHigherThanKeyword') && state.reviewMetricsSmokeText.includes('reference strategies should outperform keyword baseline') && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("overnight:review:smoke") ? 'passed' : 'missing', evidence: 'review metrics command/smoke exist; overnight manual-review sample includes customer decision, attribution, and feedback fields; review metrics compares reference/homepage/video strategies against keyword baseline; default acceptance runs overnight review smoke', next: '商务标注 CSV 后运行 npm run review:metrics,负样本必须填写归因类型。' }), item({ id: 'customer-effect-audit-entry', requirement: '客户效果证明审计入口', status: state.scripts['customer-effect:audit'] && state.scripts['customer-effect:smoke'] && state.files['scripts/customer-effect-audit.js'] && state.files['scripts/customer-effect-audit-smoke.js'] && state.customerEffectAuditText.includes('manualSupplementReduction50Pass') && state.customerEffectAuditText.includes('customerSelectedRate50Pass') && state.customerEffectAuditText.includes('referenceCustomerSelectedRate40Pass') && state.customerEffectSmokeText.includes('missing current manual supplement count should block overall pass') && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("customer-effect:smoke") ? 'passed' : 'missing', evidence: 'customer-effect:audit combines review metrics, historical manual supplement baseline, current manual supplement count, customer selected-rate gates, and a smoke that blocks missing proof', next: '拿到真实客户选择和人工补号量后运行 customer-effect:audit 生成客户效果证明。' }), item({ id: 'history-csv-import', requirement: '真实历史数据 CSV 导入', status: state.scripts['history:from-csv'] && state.scripts['history:from-csv:smoke'] && state.files['scripts/history-dataset-from-csv.js'] && state.files['scripts/history-dataset-from-csv-smoke.js'] && state.historyFromCsvText.includes('manualFinalList') && state.historyFromCsvText.includes('customerFeedback') && state.historyFromCsvText.includes('manualSupplementBaseline') && state.historyFromCsvSmokeText.includes('converted dataset should be ready for customer effect proof') && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("history:from-csv:smoke") ? 'passed' : 'missing', evidence: 'history:from-csv converts business CSV rows into per-brief JSON history records and smoke verifies history:audit readiness', next: '商务可先按 CSV 模板整理真实历史数据,再运行 history:from-csv 和 history:audit。' }), item({ id: 'data-intake-pack', requirement: '真实数据收集包', status: state.scripts['data:intake-template'] && state.scripts['data:intake-template:smoke'] && state.files['scripts/create-data-intake-pack.js'] && state.files['scripts/create-data-intake-pack-smoke.js'] && state.dataIntakePackText.includes('history-data-template.csv') && state.dataIntakePackText.includes('manual-review-template.csv') && state.dataIntakePackText.includes('历史人工补号量基线') && state.dataIntakePackText.includes('本轮人工补号量') && state.dataIntakePackSmokeText.includes('history template header should match history:from-csv fields') && state.dataIntakePackSmokeText.includes('review template header should match review/customer-effect fields') && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("data:intake-template:smoke") ? 'passed' : 'missing', evidence: 'data:intake-template generates history and manual-review CSV templates plus a README; smoke verifies headers, proof fields, and no secret-like values', next: '把生成的数据收集包交给商务填写真实历史 Brief、客户选择和本轮人工补号量。' }), item({ id: 'intake-readiness', requirement: '真实材料半成品预审', status: state.scripts['intake:readiness'] && state.scripts['intake:readiness:smoke'] && state.files['scripts/intake-readiness-audit.js'] && state.files['scripts/intake-readiness-smoke.js'] && state.intakeReadinessText.includes('overallReady') && state.intakeReadinessText.includes('example.com') && state.intakeReadinessText.includes('readyForCustomerEffectAudit') && state.intakeReadinessText.includes('真实候选视频') && state.intakeReadinessSmokeText.includes('fresh templates should not be treated as real intake data') && state.intakeReadinessSmokeText.includes('complete intake fixtures should pass readiness') && state.evidenceIndexText.includes("type: 'intake-readiness'") && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("intake:readiness:smoke") ? 'passed' : 'missing', evidence: 'intake:readiness audits filled data/video intake packs before formal history/video/review/customer-effect audits; smoke verifies fresh templates fail and complete fixtures pass; evidence:index classifies readiness as startup evidence or not_business_proof', next: '商务/投放填完模板后先运行 intake:readiness,确认没有示例占位和半成品,再进入 history:audit、video:resource-readiness、review:metrics 和 customer-effect:audit。' }), item({ id: 'video-intake-pack', requirement: '真实视频资源收集包', status: state.scripts['video:intake-template'] && state.scripts['video:intake-template:smoke'] && state.files['scripts/create-video-intake-pack.js'] && state.files['scripts/create-video-intake-pack-smoke.js'] && state.videoIntakePackText.includes('video-resource-template.csv') && state.videoIntakePackText.includes('视频链接') && state.videoIntakePackText.includes('封面链接') && state.videoIntakePackText.includes('字幕或ASR文本') && state.videoIntakePackText.includes('帧图链接') && state.videoIntakePackText.includes('是否真实资源') && state.videoIntakePackText.includes('不得声明视频分析完成') && state.videoIntakePackSmokeText.includes('video resource header should match expected fields') && state.evidenceIndexText.includes("type: 'video-intake-pack'") && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("video:intake-template:smoke") ? 'passed' : 'missing', evidence: 'video:intake-template generates a CSV/README for real reference and candidate video URLs, covers, ASR/subtitles, frame URLs, real-resource flag, and proof boundaries; evidence:index classifies it as smoke_or_local', next: '交给商务补齐真实视频资源后,再运行 npm run acceptance:video-ab;模板本身不证明视频分析提升命中率。' }), item({ id: 'video-resource-readiness', requirement: '视频 A/B 前置资源就绪审计', status: state.scripts['video:resource-readiness'] && state.scripts['video:resource-readiness:smoke'] && state.files['scripts/video-resource-readiness-audit.js'] && state.files['scripts/video-resource-readiness-smoke.js'] && state.videoResourceReadinessText.includes('readyForVideoAbPreflight') && state.videoResourceReadinessText.includes('hasRealReferenceResource') && state.videoResourceReadinessText.includes('hasRealCandidateResource') && state.videoResourceReadinessText.includes('hasRealVideoUrl') && state.videoResourceReadinessText.includes('hasCoverOrAsrOrFrameOrText') && state.videoResourceReadinessText.includes('provider ok、证据卡、Top 10 非退化') && state.videoResourceReadinessSmokeText.includes('complete video resources should pass preflight readiness') && state.videoResourceReadinessSmokeText.includes('missing video resources should fail readiness') && state.evidenceIndexText.includes("type: 'video-resource-readiness'") && readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("video:resource-readiness:smoke") ? 'passed' : 'missing', evidence: 'video:resource-readiness audits filled video-resource CSVs for real reference video, real candidate video, real video URL, cover/ASR/frame/text evidence, no fake-real rows, and no secret-like values; evidence:index classifies it as smoke_or_local when ready', next: '商务填完 video-resource-template.csv 后先运行 video:resource-readiness;通过后再配置 provider 运行 acceptance:video-ab。' }), item({ id: 'historical-dataset', requirement: '真实历史数据准备验收', status: state.scripts['history:audit'] && state.files['docs/historical-dataset-guide.md'] && state.historyAuditText.includes('readyForCustomerEffectProof') && state.historyAuditText.includes('allHaveManualSupplementBaseline') && state.historyAuditSmokeText.includes('missing manual supplement baseline should block customer effect proof') ? 'ready_not_proven' : 'missing', evidence: 'history:audit, historical dataset guide, customer decision gates, rejection reason gates, and manual supplement baseline gates exist', next: '先运行 data:intake-template 生成收集包;真实历史 Brief/人工名单/客户选择/人工补号量基线数据填完后通过 history:audit。' }), item({ id: 'live-provider-overnight-proof', requirement: '真实 live/provider 长跑效果', status: liveProof ? 'passed' : 'blocked_by_external_data', evidence: liveProof ? 'outputs include closable live/provider aggregate: liveEnabled=true, overallPass=true, failureCount=0, failedGateCount=0, tokenLeak=false' : 'no current verified live/provider/customer dataset proof in local outputs', next: liveProof ? '该单项只证明真实 live/provider overnight 通过;仍不能替代客户效果、历史数据或视频 A/B proof。' : '需要有效 sessionToken/company/provider 和真实历史数据运行完整 live 矩阵。' }), item({ id: 'customer-effect-proof', requirement: '客户选中率 30%/40%/50% 和人工补号量减少 50%', status: customerEffectProof ? 'passed' : 'blocked_by_external_data', evidence: customerEffectProof ? 'review metrics output includes customer selection evidence' : 'no real customer selection dataset has been audited', next: '需要客户最终选择字段和人工补号量基线。' }) ]; } function item(value) { return { ...value, title: value.title || value.requirement }; } function hasScripts(state, names) { return names.every(name => Boolean(state.scripts[name])); } function findOutputEvidence(root) { const outputs = path.join(root, 'outputs'); if (!fs.existsSync(outputs)) return []; return fs.readdirSync(outputs, { withFileTypes: true }) .filter(entry => entry.isDirectory()) .map(entry => path.join(outputs, entry.name)) .sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs) .map(dir => ({ dir, aggregate: readJsonOptional(path.join(dir, 'aggregate-summary.json')), review: readJsonOptional(path.join(dir, 'review-metrics-summary.json')), history: readJsonOptional(path.join(dir, 'historical-dataset-audit.json')), customerEffect: readJsonOptional(path.join(dir, 'customer-effect-summary.json')) || readJsonOptional(path.join(dir, 'customer-effect-audit', 'customer-effect-summary.json')) })); } function hasLiveProof(outputs) { return outputs.some(item => isClosableLiveAggregate(item.aggregate)); } function hasCustomerEffectProof(outputs) { return outputs.some(item => item.customerEffect?.acceptance?.overallPass === true); } function isClosableLiveAggregate(aggregate) { return Boolean( aggregate?.manifest?.liveEnabled === true && aggregate?.acceptance?.overallPass === true && Number(aggregate?.failureCount || 0) === 0 && Number(aggregate?.acceptance?.failedGateCount ?? aggregate?.failedGateCount ?? 0) === 0 && aggregate?.acceptance?.tokenLeak !== true ); } function countStatuses(items) { const result = {}; for (const item of items) result[item.status] = (result[item.status] || 0) + 1; return result; } function renderReport(summary) { const lines = [ '# 提号优化计划状态审计', '', `- 生成时间:${summary.generatedAt}`, `- 是否全部完成:${summary.complete ? '是' : '否'}`, '', '## 状态汇总', '', ...Object.entries(summary.counts).map(([key, value]) => `- ${key}: ${value}`), '', '## 要求明细', '', '| ID | 状态 | 要求 | 当前证据 | 下一步 |', '| --- | --- | --- | --- | --- |', ...summary.items.map(item => `| ${escapeCell(item.id)} | ${item.status} | ${escapeCell(item.title || item.requirement)} | ${escapeCell(item.evidence)} | ${escapeCell(item.next)} |`), '', '## 说明', '', '- `passed` 表示当前代码、脚本或本地验收已覆盖该能力。', '- `ready_not_proven` 表示验收入口已具备,但需要真实 token/provider/数据运行后才能证明业务效果。', '- `blocked_by_external_data` 表示当前不能靠代码继续证明,需要真实 live 权限、provider 或客户选择数据。' ]; return lines.join('\n'); } function fileSet(root, files) { return Object.fromEntries(files.map(file => [file, fs.existsSync(path.join(root, file))])); } function readOptional(file) { return fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : ''; } function readJsonOptional(file) { if (!fs.existsSync(file)) return null; try { return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, '')); } catch { return null; } } function escapeCell(value) { return String(value || '').replace(/\|/g, '/').replace(/\n/g, ' '); } function withBom(text) { return `\uFEFF${text}`; } if (require.main === module) { try { main(); } catch (error) { console.error(error && error.stack ? error.stack : String(error)); process.exit(1); } } module.exports = { buildStatusItems, buildStatusSummary, collectState, findOutputEvidence, hasLiveProof };