#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const ROOT = path.resolve(__dirname, '..'); const DEFAULT_OUTPUT = path.join(ROOT, 'outputs', 'latest-form-index-latest'); function main() { const args = parseArgs(process.argv.slice(2)); const outputsDir = path.resolve(args.outputs || path.join(ROOT, 'outputs')); const outputDir = path.resolve(args.output || DEFAULT_OUTPUT); const summary = buildLatestFormIndex({ root: ROOT, outputsDir }); fs.mkdirSync(outputDir, { recursive: true }); const summaryPath = path.join(outputDir, 'latest-form-index-summary.json'); const reportPath = path.join(outputDir, 'latest-form-index.md'); fs.writeFileSync(summaryPath, JSON.stringify(summary, null, 2), 'utf8'); fs.writeFileSync(reportPath, withBom(renderReport(summary)), 'utf8'); console.log(JSON.stringify({ outputDir, summary: summaryPath, report: reportPath, passed: summary.passed, primaryForm: summary.primaryForm?.csv || null, clientList: summary.clientList?.csv || null }, null, 2)); if (args.strict && !summary.passed) process.exitCode = 1; } function buildLatestFormIndex({ root, outputsDir }) { const proofFormDir = latestOutputDir(outputsDir, /^tihao-proof-gap-software-form-/) || path.join(outputsDir, 'tihao-proof-gap-software-form-latest'); const clientListDir = latestOutputDir(outputsDir, /^software-client-/) || path.join(outputsDir, 'software-client-latest'); const nextActionsDir = latestOutputDir(outputsDir, /^business-proof-next-actions-/) || path.join(outputsDir, 'business-proof-next-actions-latest'); const handoffDir = latestOutputDir(outputsDir, /^optimization-handoff-/) || path.join(outputsDir, 'optimization-handoff-latest'); const intakeReadinessDir = latestOutputDir(outputsDir, /^intake-readiness-/) || path.join(outputsDir, 'intake-readiness-latest'); const videoResourceReadinessDir = latestOutputDir(outputsDir, /^video-resource-readiness-/) || path.join(outputsDir, 'video-resource-readiness-latest'); const homepageEvidenceReadinessDir = latestOutputDir(outputsDir, /^homepage-evidence-readiness-/) || path.join(outputsDir, 'homepage-evidence-readiness-latest'); const longRunReadinessDir = latestOutputDir(outputsDir, /^long-run-readiness-/) || path.join(outputsDir, 'long-run-readiness-latest'); const intakeFieldChecklistDir = latestOutputDir(outputsDir, /^intake-field-checklist-/) || path.join(outputsDir, 'intake-field-checklist-latest'); const manualReviewLabelGuideDir = latestOutputDir(outputsDir, /^manual-review-label-guide-/) || path.join(outputsDir, 'manual-review-label-guide-latest'); const businessExecutionIndexDir = latestOutputDir(outputsDir, /^business-execution-index-/) || path.join(outputsDir, 'business-execution-index-latest'); const planExecutionStatusDir = latestOutputDir(outputsDir, /^plan-execution-status-/) || path.join(outputsDir, 'plan-execution-status-latest'); const proofGapRequestDir = latestOutputDir(outputsDir, /^proof-gap-request-/) || path.join(outputsDir, 'proof-gap-request-latest'); const proofGapClosureDir = latestOutputDir(outputsDir, /^proof-gap-closure-/) || path.join(outputsDir, 'proof-gap-closure-latest'); const proofGapOperatorPackDir = latestOutputDir(outputsDir, /^proof-gap-operator-pack-/) || path.join(outputsDir, 'proof-gap-operator-pack-latest'); const realProofIntakeBundleDir = latestOutputDir(outputsDir, /^real-proof-intake-bundle-/) || path.join(outputsDir, 'real-proof-intake-bundle-latest'); const realProofClosureWorkOrderDir = latestOutputDir(outputsDir, /^real-proof-closure-work-order-/) || path.join(outputsDir, 'real-proof-closure-work-order-latest'); const optimizationCompletionDir = latestOutputDir(outputsDir, /^optimization-completion-/) || path.join(outputsDir, 'optimization-completion-latest'); const localSeedToIntakeWorklistDir = latestOutputDir(outputsDir, /^local-seed-to-intake-worklist-/) || path.join(outputsDir, 'local-seed-to-intake-worklist-latest'); const realGapMaterialAuditDir = latestOutputDir(outputsDir, /^real-gap-material-audit-/) || path.join(outputsDir, 'real-gap-material-audit-latest'); const proofSummary = readJsonIfExists(path.join(proofFormDir, 'tihao-proof-gap-software-form-summary.json')); const clientSummary = readJsonIfExists(path.join(clientListDir, 'software-client-list.final.summary.json')); const nextActionsSummary = readJsonIfExists(path.join(nextActionsDir, 'business-proof-next-actions-summary.json')); const handoffSummary = readJsonIfExists(path.join(handoffDir, 'optimization-handoff-summary.json')); const intakeReadinessSummary = readJsonIfExists(path.join(intakeReadinessDir, 'intake-readiness-summary.json')); const videoResourceReadinessSummary = readJsonIfExists(path.join(videoResourceReadinessDir, 'video-resource-readiness-summary.json')); const homepageEvidenceReadinessSummary = readJsonIfExists(path.join(homepageEvidenceReadinessDir, 'homepage-evidence-readiness-summary.json')); const longRunReadinessSummary = readJsonIfExists(path.join(longRunReadinessDir, 'long-run-readiness-summary.json')); const intakeFieldChecklistSummary = readJsonIfExists(path.join(intakeFieldChecklistDir, 'intake-field-checklist-summary.json')); const manualReviewLabelGuideSummary = readJsonIfExists(path.join(manualReviewLabelGuideDir, 'manual-review-label-guide-summary.json')); const businessExecutionIndexSummary = readJsonIfExists(path.join(businessExecutionIndexDir, 'business-execution-index-summary.json')); const planExecutionStatusSummary = readJsonIfExists(path.join(planExecutionStatusDir, 'plan-execution-status-summary.json')); const proofGapRequestSummary = readJsonIfExists(path.join(proofGapRequestDir, 'proof-gap-request-summary.json')); const proofGapClosureSummary = readJsonIfExists(path.join(proofGapClosureDir, 'proof-gap-closure-summary.json')); const proofGapOperatorPackSummary = readJsonIfExists(path.join(proofGapOperatorPackDir, 'proof-gap-operator-pack-summary.json')); const realProofIntakeBundleSummary = readJsonIfExists(path.join(realProofIntakeBundleDir, 'real-proof-intake-bundle-summary.json')); const realProofClosureWorkOrderSummary = readJsonIfExists(path.join(realProofClosureWorkOrderDir, 'real-proof-closure-work-order-summary.json')); const optimizationCompletionSummary = readJsonIfExists(path.join(optimizationCompletionDir, 'optimization-completion-summary.json')); const localSeedToIntakeWorklistSummary = readJsonIfExists(path.join(localSeedToIntakeWorklistDir, 'local-seed-to-intake-worklist-summary.json')); const realGapMaterialAuditSummary = readJsonIfExists(path.join(realGapMaterialAuditDir, 'real-gap-material-audit-summary.json')); const primaryForm = fileGroup(root, proofFormDir, { csv: 'tihao-proof-gap-software-form.csv', markdown: 'tihao-proof-gap-software-form.md', summary: 'tihao-proof-gap-software-form-summary.json' }); const clientList = fileGroup(root, clientListDir, { csv: 'software-client-list.final.csv', markdown: 'software-client-list.final.md', summary: 'software-client-list.final.summary.json' }); const intakeFieldChecklist = fileGroup(root, intakeFieldChecklistDir, { csv: 'intake-field-checklist.csv', markdown: 'intake-field-checklist.md', summary: 'intake-field-checklist-summary.json' }); const manualReviewLabelGuide = fileGroup(root, manualReviewLabelGuideDir, { csv: 'manual-review-label-guide.csv', markdown: 'manual-review-label-guide.md', summary: 'manual-review-label-guide-summary.json' }); const businessExecutionIndex = fileGroup(root, businessExecutionIndexDir, { csv: 'business-execution-index.csv', markdown: 'business-execution-index.md', summary: 'business-execution-index-summary.json' }); const planExecutionStatus = fileGroup(root, planExecutionStatusDir, { csv: 'plan-execution-status.csv', markdown: 'plan-execution-status.md', summary: 'plan-execution-status-summary.json' }); const proofGapRequest = fileGroup(root, proofGapRequestDir, { csv: 'proof-gap-request-table.csv', markdown: 'proof-gap-request-report.md', summary: 'proof-gap-request-summary.json' }); const proofGapClosure = fileGroup(root, proofGapClosureDir, { markdown: 'proof-gap-closure-report.md', summary: 'proof-gap-closure-summary.json' }); const proofGapOperatorPack = fileGroup(root, proofGapOperatorPackDir, { csv: 'proof-gap-operator-pack.csv', markdown: 'proof-gap-operator-pack.md', summary: 'proof-gap-operator-pack-summary.json' }); const realProofIntakeBundle = fileGroup(root, realProofIntakeBundleDir, { csv: 'real-proof-intake-bundle.csv', markdown: 'real-proof-intake-bundle.md', summary: 'real-proof-intake-bundle-summary.json' }); const realProofClosureWorkOrder = fileGroup(root, realProofClosureWorkOrderDir, { csv: 'real-proof-closure-work-order.csv', markdown: 'real-proof-closure-work-order.md', summary: 'real-proof-closure-work-order-summary.json' }); const optimizationCompletion = fileGroup(root, optimizationCompletionDir, { markdown: 'optimization-completion-report.md', summary: 'optimization-completion-summary.json' }); const localSeedToIntakeWorklist = fileGroup(root, localSeedToIntakeWorklistDir, { markdown: 'local-seed-to-intake-worklist.md', csv: 'local-seed-to-intake-worklist.csv', summary: 'local-seed-to-intake-worklist-summary.json', historyDraft: 'history-intake-draft.csv', videoWorklist: 'video-resource-worklist.csv', candidateWorklist: 'candidate-seed-worklist.csv' }); const realGapMaterialAudit = fileGroup(root, realGapMaterialAuditDir, { markdown: 'real-gap-material-audit.md', csv: 'real-gap-material-audit.csv', summary: 'real-gap-material-audit-summary.json' }); const longRunReadiness = fileGroup(root, longRunReadinessDir, { report: 'long-run-readiness-report.md', summary: 'long-run-readiness-summary.json' }); const ownerArtifacts = normalizeOwnerArtifacts(root, nextActionsDir, nextActionsSummary); const proofGapOperatorOwnerArtifacts = normalizeOwnerArtifacts(root, proofGapOperatorPackDir, proofGapOperatorPackSummary); const realProofOperatorPackOwnerArtifacts = normalizePassThroughOwnerArtifacts(root, realProofIntakeBundleSummary?.operatorPackOwnerArtifacts || []); const businessExecutionOperatorPackOwnerArtifacts = normalizePassThroughOwnerArtifacts(root, businessExecutionIndexSummary?.operatorPackOwnerArtifacts || []); const planExecutionOperatorPackOwnerArtifacts = normalizePassThroughOwnerArtifacts(root, planExecutionStatusSummary?.operatorPackOwnerArtifacts || []); const intakeOwnerGroups = normalizeIntakeOwnerGroups(root, intakeFieldChecklistSummary?.ownerGroups || []); const intakeRecheckCommands = normalizeRecheckCommands(intakeFieldChecklistSummary?.recheckCommands || []); const businessFillGuide = buildBusinessFillGuide({ ownerGroups: intakeOwnerGroups, recheckCommands: intakeRecheckCommands }); const proofOpenCount = Number(nextActionsSummary?.sourceState?.proofGapOpenCount ?? handoffSummary?.businessProofNextActions?.actionCount ?? 0); const proofGapClosureCounts = normalizeProofGapClosureCounts(proofGapClosureSummary); const actionCount = Number(nextActionsSummary?.actionCount ?? (Array.isArray(nextActionsSummary?.actions) ? nextActionsSummary.actions.length : 0)); const nextActionSourceState = normalizeBusinessProofNextActionSourceState(nextActionsSummary?.sourceState); const nextActionTopActions = normalizeBusinessProofNextTopActions( Array.isArray(nextActionsSummary?.topActions) && nextActionsSummary.topActions.length ? nextActionsSummary.topActions : nextActionsSummary?.actions ); const primaryPassed = Boolean(proofSummary?.passed); const clientExists = fs.existsSync(path.join(clientListDir, 'software-client-list.final.csv')); const passed = primaryPassed && Boolean(primaryForm.csv) && actionCount >= 1; const supportDocs = [ { title: '长期优化计划主文件', path: relIfExists(root, path.join(root, 'docs', 'tihao-experience-optimization-plan.md')), usage: '当前目标文件;用于执行长期优化计划、同步每轮进度、确认验收标准和完成边界。' }, { title: '长期优化计划中文执行版', path: relIfExists(root, path.join(root, 'docs', 'tihao-experience-optimization-plan.zh-CN.md')), usage: '用于下一轮 AI 或项目负责人理解长期目标、策略矩阵、验收标准和完成边界。' }, { title: '补证操作顺序中文执行版', path: relIfExists(root, path.join(root, 'docs', 'business-proof-action-order.zh-CN.md')), usage: '用于商务、投放、技术/AI 按 15 步顺序补齐真实材料和验收证据。' } , { title: '提号任务交接入口', path: relIfExists(root, path.join(root, 'docs', 'tihao-handoff-index.md')), usage: '下一轮 AI、商务或投放接手时优先打开;按顺序定位主计划、补证顺序、最新表单索引、真实材料总包、proof-gap 请求包、商务执行总表和交接摘要。' } ]; return { generatedAt: new Date().toISOString(), root, outputsDir, passed, purpose: '给商务和下一轮 AI 快速定位最新提号补证表单、候选名单、负责人行动文件和验收边界。', primaryForm: { title: '最新提号补证表单', usage: '用于分派真实历史 Brief、真实视频资源、视频 A/B、live 长跑和客户效果审计补证任务。', directCustomerProof: false, csv: primaryForm.csv, markdown: primaryForm.markdown, summary: primaryForm.summary, generatedAt: proofSummary?.generatedAt || null, passed: primaryPassed, rowCount: Number(proofSummary?.rowCount || 0), duplicateIdCount: Number(proofSummary?.duplicateIdCount || 0), headerMatches: Boolean(proofSummary?.headerMatches), rowWidthOk: Boolean(proofSummary?.rowWidthOk) }, clientList: { title: '最新软件端候选名单', usage: '用于商务复核候选博主;当前仍需结合风险提示和人工复核标签,不等同于客户效果证明。', directCustomerProof: false, exists: clientExists, csv: clientList.csv, markdown: clientList.markdown, summary: clientList.summary, generatedAt: clientSummary?.generatedAt || null, rowCount: Number(clientSummary?.rowCount || clientSummary?.finalRowCount || clientSummary?.outputRows || 0), duplicateGroups: clientSummary?.duplicateGroups || clientSummary?.duplicateSummary || null, duplicateCount: Number(clientSummary?.duplicateCount || 0), duplicateCountMeaning: clientSummary?.duplicateCountMeaning || 'source_duplicate_removed_count', sourceDuplicateRemovedCount: Number(clientSummary?.sourceDuplicateRemovedCount ?? clientSummary?.duplicateCount ?? 0), finalDuplicateGroupCount: Number(clientSummary?.finalDuplicateGroupCount ?? finalDuplicateGroupCount(clientSummary?.duplicateAudit) ?? 0), rankContinuous: Boolean(clientSummary?.rankContinuous), dedupeRule: clientSummary?.dedupeRule || '' }, intakeFieldChecklist: { title: '真实材料字段级补齐核对表', usage: '用于商务、投放按行号和字段补齐真实历史 Brief、人工复核字段和真实视频资源;不等同于客户效果证明。', directCustomerProof: false, exists: Boolean(intakeFieldChecklist.markdown || intakeFieldChecklist.csv), csv: intakeFieldChecklist.csv, markdown: intakeFieldChecklist.markdown, summary: intakeFieldChecklist.summary, generatedAt: intakeFieldChecklistSummary?.generatedAt || null, itemCount: Number(intakeFieldChecklistSummary?.itemCount || 0), missingRequiredCount: Number(intakeFieldChecklistSummary?.missingRequiredCount || 0), placeholderCount: Number(intakeFieldChecklistSummary?.placeholderCount || 0), ownerGroups: intakeOwnerGroups, recheckCommands: intakeRecheckCommands, passed: Boolean(intakeFieldChecklistSummary?.passed) }, manualReviewLabelGuide: { title: '人工复核标签与归因指南', usage: '用于商务填写人工复核标签、负样本归因、反馈原因和客户选择;它只规范复核口径,不等同于客户效果证明。', directCustomerProof: false, proofLevel: manualReviewLabelGuideSummary?.proofLevel || 'smoke_or_local', exists: Boolean(manualReviewLabelGuide.markdown || manualReviewLabelGuide.csv), csv: manualReviewLabelGuide.csv, markdown: manualReviewLabelGuide.markdown, summary: manualReviewLabelGuide.summary, generatedAt: manualReviewLabelGuideSummary?.generatedAt || null, passed: Boolean(manualReviewLabelGuideSummary?.passed), labelCount: Number(manualReviewLabelGuideSummary?.labelCount || 0), positiveLabelCount: Number(manualReviewLabelGuideSummary?.positiveLabelCount || 0), negativeLabelCount: Number(manualReviewLabelGuideSummary?.negativeLabelCount || 0), attributionTypeCount: Number(manualReviewLabelGuideSummary?.attributionTypeCount || 0), negativeLabelsRequireAttribution: Boolean(manualReviewLabelGuideSummary?.negativeLabelsRequireAttribution) }, businessFillGuide, businessExecutionIndex: { title: '商务补证执行总表', usage: '用于商务、投放和技术/AI 在一张表里查看负责人行动、修复清单、操作包负责人附件、字段补齐负责人、补齐后复验命令和关键文件;不等同于客户效果证明。', directCustomerProof: false, exists: Boolean(businessExecutionIndex.markdown || businessExecutionIndex.csv), csv: businessExecutionIndex.csv, markdown: businessExecutionIndex.markdown, summary: businessExecutionIndex.summary, generatedAt: businessExecutionIndexSummary?.generatedAt || null, passed: Boolean(businessExecutionIndexSummary?.passed), rowCount: Number(businessExecutionIndexSummary?.rowCount || 0), actionRowCount: Number(businessExecutionIndexSummary?.actionRowCount || 0), repairRowCount: Number(businessExecutionIndexSummary?.repairRowCount || 0), operatorPackOwnerArtifactRowCount: Number(businessExecutionIndexSummary?.operatorPackOwnerArtifactRowCount || 0), operatorPackOwnerArtifactCount: Number(businessExecutionIndexSummary?.operatorPackOwnerArtifactCount || businessExecutionOperatorPackOwnerArtifacts.length || 0), operatorPackOwnerArtifacts: businessExecutionOperatorPackOwnerArtifacts, fieldOwnerRowCount: Number(businessExecutionIndexSummary?.fieldOwnerRowCount || 0), recheckCommandRowCount: Number(businessExecutionIndexSummary?.recheckCommandRowCount || 0), proofOpenCount: Number(businessExecutionIndexSummary?.proofOpenCount || 0) }, planExecutionStatus: { title: '长期优化计划执行状态面板', usage: '用于商务和下一轮 AI 在一张表里查看长期优化计划 8 个章节的执行状态、当前证据、下一步、验收标准和证明边界;不等同于客户效果证明。', directCustomerProof: false, exists: Boolean(planExecutionStatus.markdown || planExecutionStatus.csv), csv: planExecutionStatus.csv, markdown: planExecutionStatus.markdown, summary: planExecutionStatus.summary, generatedAt: planExecutionStatusSummary?.generatedAt || null, passed: Boolean(planExecutionStatusSummary?.passed), complete: Boolean(planExecutionStatusSummary?.complete), rowCount: Number(planExecutionStatusSummary?.rowCount || 0), proofOpenCount: Number(planExecutionStatusSummary?.proofOpenCount || 0), operatorPackOwnerArtifactCount: Number(planExecutionStatusSummary?.operatorPackOwnerArtifactCount || planExecutionOperatorPackOwnerArtifacts.length || 0), operatorPackOwnerArtifacts: planExecutionOperatorPackOwnerArtifacts, businessExecutionOperatorPackOwnerArtifactCount: Number(planExecutionStatusSummary?.businessExecutionOperatorPackOwnerArtifactCount || 0), businessExecutionOperatorPackOwnerArtifactRowCount: Number(planExecutionStatusSummary?.businessExecutionOperatorPackOwnerArtifactRowCount || 0), roundPassed: Boolean(planExecutionStatusSummary?.roundPassed), guardrailCount: Array.isArray(planExecutionStatusSummary?.guardrails) ? planExecutionStatusSummary.guardrails.length : 0 }, proofGapRequest: { title: '真实证明缺口请求包', usage: '用于把仍未关闭的真实证明缺口整理成对商务、投放和技术/AI 的请求清单;只请求补证,不证明客户效果。', directCustomerProof: false, proofLevel: 'smoke_or_local', exists: Boolean(proofGapRequest.markdown || proofGapRequest.csv), csv: proofGapRequest.csv, markdown: proofGapRequest.markdown, summary: proofGapRequest.summary, generatedAt: proofGapRequestSummary?.generatedAt || null, complete: Boolean(proofGapRequestSummary?.complete), unresolvedCount: Number(proofGapRequestSummary?.unresolvedCount || 0), rowCount: Array.isArray(proofGapRequestSummary?.rows) ? proofGapRequestSummary.rows.length : 0, proofGapClosureOpenCount: Number(proofGapRequestSummary?.observed?.proofGapClosureOpenCount || 0) }, proofGapClosure: { title: '提号真实证明缺口关闭审计', usage: '用于判断真实业务证明缺口是否关闭;openCount/businessOpenCount 只统计业务缺口,intake 预审行用 precheckOpenCount 单独记录。', directCustomerProof: false, proofLevel: proofGapClosureSummary?.complete ? 'real_evidence' : 'not_business_proof', exists: Boolean(proofGapClosure.markdown || proofGapClosure.summary), markdown: proofGapClosure.markdown, summary: proofGapClosure.summary, generatedAt: proofGapClosureSummary?.generatedAt || null, complete: Boolean(proofGapClosureSummary?.complete), intakeReady: Boolean(proofGapClosureSummary?.intakeReady), openCount: Number(proofGapClosureSummary?.openCount || 0), closedCount: Number(proofGapClosureSummary?.closedCount || 0), businessOpenCount: proofGapClosureCounts.businessOpenCount, businessClosedCount: proofGapClosureCounts.businessClosedCount, businessGapCount: proofGapClosureCounts.businessGapCount, totalRows: proofGapClosureCounts.totalRows, precheckRowCount: proofGapClosureCounts.precheckRowCount, precheckOpenCount: proofGapClosureCounts.precheckOpenCount, precheckClosedCount: proofGapClosureCounts.precheckClosedCount, countSemantics: proofGapClosureCounts.countSemantics }, proofGapOperatorPack: { title: '提号补证操作包', usage: '用于把 5 个真实证明缺口转成负责人、填写文件、待补字段、复验命令和通过标准;只安排补证,不证明客户效果。', directCustomerProof: false, proofLevel: proofGapOperatorPackSummary?.proofLevel || 'not_business_proof', exists: Boolean(proofGapOperatorPack.markdown || proofGapOperatorPack.csv), csv: proofGapOperatorPack.csv, markdown: proofGapOperatorPack.markdown, summary: proofGapOperatorPack.summary, generatedAt: proofGapOperatorPackSummary?.generatedAt || null, passed: Boolean(proofGapOperatorPackSummary?.passed), complete: Boolean(proofGapOperatorPackSummary?.complete), rowCount: Number(proofGapOperatorPackSummary?.rowCount || 0), openCount: Number(proofGapOperatorPackSummary?.openCount || 0), gapIds: Array.isArray(proofGapOperatorPackSummary?.gapIds) ? proofGapOperatorPackSummary.gapIds : (Array.isArray(proofGapOperatorPackSummary?.rows) ? proofGapOperatorPackSummary.rows.map(row => row.gapId).filter(Boolean) : []), fillFileCount: Number(proofGapOperatorPackSummary?.fillFileCount || 0), recheckCommandCount: Number(proofGapOperatorPackSummary?.recheckCommandCount || 0), ownerGroupCount: Number(proofGapOperatorPackSummary?.ownerGroupCount || (Array.isArray(proofGapOperatorPackSummary?.ownerGroups) ? proofGapOperatorPackSummary.ownerGroups.length : 0)), ownerArtifactCount: Number(proofGapOperatorPackSummary?.ownerArtifactCount || proofGapOperatorOwnerArtifacts.length || 0), ownerArtifacts: proofGapOperatorOwnerArtifacts, evidenceRowCount: Number(proofGapOperatorPackSummary?.evidenceRowCount || 0), nextActionCount: Number(proofGapOperatorPackSummary?.nextActionCount || 0), missingProofRequirementCount: Number(proofGapOperatorPackSummary?.missingProofRequirementCount || 0) }, realProofIntakeBundle: { title: '真实验收材料收集总包', usage: '用于把真实材料模板、字段缺口、修复清单、证明缺口和复验命令集中成一个商务可打开的入口;不等同于客户效果证明。', directCustomerProof: false, proofLevel: realProofIntakeBundleSummary?.proofLevel || 'not_business_proof', exists: Boolean(realProofIntakeBundle.markdown || realProofIntakeBundle.csv), csv: realProofIntakeBundle.csv, markdown: realProofIntakeBundle.markdown, summary: realProofIntakeBundle.summary, generatedAt: realProofIntakeBundleSummary?.generatedAt || null, passed: Boolean(realProofIntakeBundleSummary?.passed), complete: Boolean(realProofIntakeBundleSummary?.complete), proofOpenCount: Number(realProofIntakeBundleSummary?.proofOpenCount || 0), fillRowCount: Number(realProofIntakeBundleSummary?.fillRowCount || 0), proofGapRowCount: Number(realProofIntakeBundleSummary?.proofGapRowCount || 0), recheckCommandCount: Number(realProofIntakeBundleSummary?.recheckCommandCount || 0), exportRowCount: Number(realProofIntakeBundleSummary?.exportRowCount || 0), operatorPackEvidenceRowCount: Number(realProofIntakeBundleSummary?.operatorPackEvidenceRowCount || realProofIntakeBundleSummary?.sourceState?.operatorPackEvidenceRowCount || 0), operatorPackNextActionCount: Number(realProofIntakeBundleSummary?.operatorPackNextActionCount || realProofIntakeBundleSummary?.sourceState?.operatorPackNextActionCount || 0), operatorPackMissingProofRequirementCount: Number(realProofIntakeBundleSummary?.operatorPackMissingProofRequirementCount || realProofIntakeBundleSummary?.sourceState?.operatorPackMissingProofRequirementCount || 0), operatorPackOwnerArtifactCount: Number(realProofIntakeBundleSummary?.operatorPackOwnerArtifactCount || realProofIntakeBundleSummary?.sourceState?.operatorPackOwnerArtifactCount || realProofOperatorPackOwnerArtifacts.length || 0), operatorPackOwnerArtifacts: realProofOperatorPackOwnerArtifacts }, realProofClosureWorkOrder: { title: '真实证明闭环工单', usage: '用于把 intake/video/proof-gap/completion 的未关闭项拆成负责人、目标模板、字段、行范围、种子线索和复验命令;不写入正式模板,不等同于客户效果证明。', directCustomerProof: false, proofLevel: realProofClosureWorkOrderSummary?.proofLevel || 'not_business_proof', materialType: realProofClosureWorkOrderSummary?.materialType || 'real_proof_closure_work_order', exists: Boolean(realProofClosureWorkOrder.markdown || realProofClosureWorkOrder.csv), csv: realProofClosureWorkOrder.csv, markdown: realProofClosureWorkOrder.markdown, summary: realProofClosureWorkOrder.summary, generatedAt: realProofClosureWorkOrderSummary?.generatedAt || null, passed: Boolean(realProofClosureWorkOrderSummary?.passed), complete: Boolean(realProofClosureWorkOrderSummary?.complete), canCloseProofGap: Boolean(realProofClosureWorkOrderSummary?.canCloseProofGap), workOrderCount: Number(realProofClosureWorkOrderSummary?.workOrderCount || 0), ownerGroupCount: Number(realProofClosureWorkOrderSummary?.ownerGroupCount || 0), seedSuggestionCount: Number(realProofClosureWorkOrderSummary?.seedSuggestionCount || 0), proofGapOpenCount: Number(realProofClosureWorkOrderSummary?.sourceState?.proofGapOpenCount || 0), intakeFailureCount: Number(realProofClosureWorkOrderSummary?.sourceState?.intakeFailureCount || 0), videoFailureCount: Number(realProofClosureWorkOrderSummary?.sourceState?.videoFailureCount || 0), realCandidateRows: Number(realProofClosureWorkOrderSummary?.sourceState?.realCandidateRows || 0), ownerArtifacts: normalizeWorkOrderOwnerArtifacts(root, realProofClosureWorkOrderSummary?.ownerArtifacts || []) }, localSeedToIntakeWorklist: { title: '本地种子转 intake 补表 worklist', usage: '用于把本地 DHA Brief、PoC 候选和参考账号解析转成可转写的历史数据草稿、视频资源补表清单和候选复核清单;不写入正式 intake 模板,不等同于客户效果证明。', directCustomerProof: false, proofLevel: localSeedToIntakeWorklistSummary?.proofLevel || 'not_business_proof', materialType: localSeedToIntakeWorklistSummary?.materialType || '', exists: Boolean(localSeedToIntakeWorklist.markdown || localSeedToIntakeWorklist.csv), markdown: localSeedToIntakeWorklist.markdown, csv: localSeedToIntakeWorklist.csv, summary: localSeedToIntakeWorklist.summary, historyDraft: localSeedToIntakeWorklist.historyDraft, videoWorklist: localSeedToIntakeWorklist.videoWorklist, candidateWorklist: localSeedToIntakeWorklist.candidateWorklist, generatedAt: localSeedToIntakeWorklistSummary?.generatedAt || null, complete: Boolean(localSeedToIntakeWorklistSummary?.complete), canCloseProofGap: Boolean(localSeedToIntakeWorklistSummary?.canCloseProofGap), historyDraftRows: Number(localSeedToIntakeWorklistSummary?.counts?.historyDraftRows || 0), referenceSeedRows: Number(localSeedToIntakeWorklistSummary?.counts?.referenceSeedRows || 0), candidateSeedRows: Number(localSeedToIntakeWorklistSummary?.counts?.candidateSeedRows || 0), videoWorklistRows: Number(localSeedToIntakeWorklistSummary?.counts?.videoWorklistRows || 0), videoCandidateNeedsResourceRows: Number(localSeedToIntakeWorklistSummary?.counts?.videoCandidateNeedsResourceRows || 0), missingProofRequirementCount: Array.isArray(localSeedToIntakeWorklistSummary?.missingProofRequirements) ? localSeedToIntakeWorklistSummary.missingProofRequirements.length : 0, targetFiles: Array.isArray(localSeedToIntakeWorklistSummary?.targetFiles) ? localSeedToIntakeWorklistSummary.targetFiles : [], nextActionCount: Array.isArray(localSeedToIntakeWorklistSummary?.nextActions) ? localSeedToIntakeWorklistSummary.nextActions.length : 0 }, realGapMaterialAudit: { title: 'three real gap material audit', usage: 'Shows that project materials exist for the three still-open proof gaps; it is not customer-effect, video A/B, or manual-supplement proof.', directCustomerProof: false, proofLevel: realGapMaterialAuditSummary?.proofLevel || 'not_business_proof', materialType: realGapMaterialAuditSummary?.materialType || 'three_real_gap_material_audit', exists: Boolean(realGapMaterialAudit.markdown || realGapMaterialAudit.csv), markdown: realGapMaterialAudit.markdown, csv: realGapMaterialAudit.csv, summary: realGapMaterialAudit.summary, generatedAt: realGapMaterialAuditSummary?.generatedAt || null, complete: Boolean(realGapMaterialAuditSummary?.complete), canCloseProofGap: Boolean(realGapMaterialAuditSummary?.canCloseProofGap), gapCount: Number(realGapMaterialAuditSummary?.gapCount || 0), materialFoundCount: Number(realGapMaterialAuditSummary?.materialFoundCount || 0), openGapCount: Number(realGapMaterialAuditSummary?.openGapCount || 0), sourceState: realGapMaterialAuditSummary?.sourceState || {}, gapIds: Array.isArray(realGapMaterialAuditSummary?.gaps) ? realGapMaterialAuditSummary.gaps.map(item => item.id).filter(Boolean) : [] }, optimizationCompletion: { title: '优化完成度审计', usage: '用于汇总长期优化是否具备完成声明条件;readyForClaim=false 时不得宣称提号率提升、客户效果达标或人工补号量下降。', directCustomerProof: false, proofLevel: optimizationCompletionSummary?.readyForClaim ? 'real_evidence' : 'not_business_proof', exists: Boolean(optimizationCompletion.markdown || optimizationCompletion.summary), markdown: optimizationCompletion.markdown, summary: optimizationCompletion.summary, generatedAt: optimizationCompletionSummary?.generatedAt || null, complete: Boolean(optimizationCompletionSummary?.complete), readyForClaim: Boolean(optimizationCompletionSummary?.readyForClaim), proofGapOpenCount: Number(optimizationCompletionSummary?.proofGapOpenCount || 0), blockingReasonCount: Number(optimizationCompletionSummary?.blockingReasonCount ?? (Array.isArray(optimizationCompletionSummary?.blockingReasons) ? optimizationCompletionSummary.blockingReasons.length : 0)), blockingReasonsWithMissingProofRequirements: Number(optimizationCompletionSummary?.blockingReasonsWithMissingProofRequirements ?? countRowsWithMissingProofRequirements(optimizationCompletionSummary?.blockingReasons)), missingProofRequirementCount: Number(optimizationCompletionSummary?.missingProofRequirementCount ?? uniqueMissingProofRequirementCount(optimizationCompletionSummary?.blockingReasons)), videoAbPreflightReadyForVideoAb: Boolean(optimizationCompletionSummary?.videoAbPreflightReadyForVideoAb), videoAbPreflightFailureCount: Number.isFinite(Number(optimizationCompletionSummary?.videoAbPreflightFailureCount)) ? Number(optimizationCompletionSummary.videoAbPreflightFailureCount) : null, canClaim: { tihaoRateImproved: Boolean(optimizationCompletionSummary?.canClaim?.tihaoRateImproved), customerEffectAchieved: Boolean(optimizationCompletionSummary?.canClaim?.customerEffectAchieved), manualSupplementReduced: Boolean(optimizationCompletionSummary?.canClaim?.manualSupplementReduced) } }, longRunReadiness: { title: '长跑前置门禁', usage: '用于判断 full-matrix/live 长跑是否可启动;ready=false 时只能继续补真实材料,不证明客户效果。', directCustomerProof: false, proofLevel: longRunReadinessSummary?.ready ? 'smoke_or_local' : 'not_business_proof', exists: Boolean(longRunReadiness.report || longRunReadiness.summary), report: longRunReadiness.report, summary: longRunReadiness.summary, generatedAt: longRunReadinessSummary?.generatedAt || null, mode: longRunReadinessSummary?.mode || null, ready: Boolean(longRunReadinessSummary?.ready), passCount: Number(longRunReadinessSummary?.counts?.pass || 0), failCount: Number(longRunReadinessSummary?.counts?.fail || 0), warnCount: Number(longRunReadinessSummary?.counts?.warn || 0), nextCommandCount: Array.isArray(longRunReadinessSummary?.nextCommands) ? longRunReadinessSummary.nextCommands.length : 0, boundaryCount: Array.isArray(longRunReadinessSummary?.boundaries) ? longRunReadinessSummary.boundaries.length : 0 }, nextActions: { complete: Boolean(nextActionsSummary?.complete), actionCount, proofOpenCount, ownerGroupCount: Array.isArray(nextActionsSummary?.ownerGroups) ? nextActionsSummary.ownerGroups.length : 0, ownerArtifactCount: ownerArtifacts.length, sourceState: nextActionSourceState, topActions: nextActionTopActions, ownerGroups: nextActionsSummary?.ownerGroups || [], ownerArtifacts }, supportDocs, repairActions: { dataIntake: { title: '历史数据/商务复核修复清单', csv: relIfExists(root, path.join(intakeReadinessDir, 'intake-readiness-repair-actions.csv')), report: relIfExists(root, path.join(intakeReadinessDir, 'intake-readiness-report.md')), summary: relIfExists(root, path.join(intakeReadinessDir, 'intake-readiness-summary.json')), failureCount: Number(intakeReadinessSummary?.failureCount || 0), repairActionCount: Number(intakeReadinessSummary?.repairActions?.length || 0), ready: Boolean(intakeReadinessSummary?.acceptance?.overallReady) }, videoResource: { title: '视频资源修复清单', csv: relIfExists(root, path.join(videoResourceReadinessDir, 'video-resource-repair-actions.csv')), report: relIfExists(root, path.join(videoResourceReadinessDir, 'video-resource-readiness-report.md')), summary: relIfExists(root, path.join(videoResourceReadinessDir, 'video-resource-readiness-summary.json')), failureCount: Number(videoResourceReadinessSummary?.failureCount || 0), repairActionCount: Number(videoResourceReadinessSummary?.repairActions?.length || 0), readyForVideoAbPreflight: Boolean(videoResourceReadinessSummary?.acceptance?.readyForVideoAbPreflight) }, homepageEvidence: { title: '主页近期内容证据修复清单', csv: relIfExists(root, path.join(homepageEvidenceReadinessDir, 'homepage-evidence-repair-actions.csv')), report: relIfExists(root, path.join(homepageEvidenceReadinessDir, 'homepage-evidence-readiness-report.md')), summary: relIfExists(root, path.join(homepageEvidenceReadinessDir, 'homepage-evidence-readiness-summary.json')), failureCount: Number(homepageEvidenceReadinessSummary?.failureCount || 0), repairActionCount: Number(homepageEvidenceReadinessSummary?.repairActions?.length || 0), ready: Boolean(homepageEvidenceReadinessSummary?.ready), directCustomerProof: Boolean(homepageEvidenceReadinessSummary?.directCustomerProof) } }, handoff: { complete: Boolean(handoffSummary?.complete), summary: relIfExists(root, path.join(handoffDir, 'optimization-handoff-summary.json')), report: relIfExists(root, path.join(handoffDir, 'optimization-handoff-report.md')) }, guardrails: [ '最新提号补证表单只安排补证任务,不证明客户效果完成。', '最新软件端候选名单需要商务复核和客户反馈后才能进入客户效果审计。', '不能把 sample、smoke、模板、provider fallback 或接口 200 当成业务证明。', '本索引不得包含 sessionToken、鉴权头、模型 token 或 npm token。' ], recommendedNextCommand: 'npm run round:refresh -- --output-root outputs --strict' }; } function renderReport(summary) { const lines = [ '# 最新提号表单索引', '', `- 生成时间:${summary.generatedAt}`, `- 是否通过:${summary.passed ? '是' : '否'}`, `- 用途:${summary.purpose}`, '', '## 当前可用文件', '', '| 类型 | 路径 | 状态 | 用法 |', '| --- | --- | --- | --- |', tableRow([ summary.primaryForm.title, summary.primaryForm.csv || '缺失', summary.primaryForm.passed ? `通过,${summary.primaryForm.rowCount} 行,无重复 ID` : '未通过或缺失', summary.primaryForm.usage ]), tableRow([ summary.clientList.title, summary.clientList.csv || '缺失', summary.clientList.exists ? `存在,${summary.clientList.rowCount || '行数待摘要确认'} 行;最终重复组 ${summary.clientList.finalDuplicateGroupCount ?? 'unknown'};排名连续 ${summary.clientList.rankContinuous ? '是' : '待确认'};源表去重丢弃 ${summary.clientList.sourceDuplicateRemovedCount ?? 0} 行` : '缺失', summary.clientList.usage ]), tableRow([ summary.businessExecutionIndex.title, summary.businessExecutionIndex.markdown || summary.businessExecutionIndex.csv || '缺失', summary.businessExecutionIndex.exists ? `通过=${summary.businessExecutionIndex.passed},${summary.businessExecutionIndex.rowCount} 行,操作包负责人附件 ${summary.businessExecutionIndex.operatorPackOwnerArtifactCount},字段负责人 ${summary.businessExecutionIndex.fieldOwnerRowCount},复验命令 ${summary.businessExecutionIndex.recheckCommandRowCount}` : '缺失', summary.businessExecutionIndex.usage ]), tableRow([ summary.planExecutionStatus.title, summary.planExecutionStatus.markdown || summary.planExecutionStatus.csv || '缺失', summary.planExecutionStatus.exists ? `complete=${summary.planExecutionStatus.complete},rowCount=${summary.planExecutionStatus.rowCount},proofOpenCount=${summary.planExecutionStatus.proofOpenCount},operatorPackOwnerArtifactCount=${summary.planExecutionStatus.operatorPackOwnerArtifactCount}` : '缺失', summary.planExecutionStatus.usage ]), tableRow([ summary.proofGapRequest.title, summary.proofGapRequest.markdown || summary.proofGapRequest.csv || '缺失', summary.proofGapRequest.exists ? `complete=${summary.proofGapRequest.complete},unresolved=${summary.proofGapRequest.unresolvedCount},rows=${summary.proofGapRequest.rowCount}` : '缺失', summary.proofGapRequest.usage ]), tableRow([ summary.proofGapClosure.title, summary.proofGapClosure.markdown || summary.proofGapClosure.summary || '缺失', summary.proofGapClosure.exists ? `complete=${summary.proofGapClosure.complete},openCount=${summary.proofGapClosure.openCount},businessOpenCount=${summary.proofGapClosure.businessOpenCount},precheckOpenCount=${summary.proofGapClosure.precheckOpenCount},totalRows=${summary.proofGapClosure.totalRows}` : '缺失', summary.proofGapClosure.usage ]), tableRow([ summary.proofGapOperatorPack.title, summary.proofGapOperatorPack.markdown || summary.proofGapOperatorPack.csv || '缺失', summary.proofGapOperatorPack.exists ? `passed=${summary.proofGapOperatorPack.passed},openCount=${summary.proofGapOperatorPack.openCount},rowCount=${summary.proofGapOperatorPack.rowCount},ownerGroupCount=${summary.proofGapOperatorPack.ownerGroupCount},ownerArtifactCount=${summary.proofGapOperatorPack.ownerArtifactCount},recheckCommandCount=${summary.proofGapOperatorPack.recheckCommandCount},evidenceRowCount=${summary.proofGapOperatorPack.evidenceRowCount},nextActionCount=${summary.proofGapOperatorPack.nextActionCount},gapIds=${summary.proofGapOperatorPack.gapIds.join(';')}` : '缺失', summary.proofGapOperatorPack.usage ]), tableRow([ summary.realProofIntakeBundle.title, summary.realProofIntakeBundle.markdown || summary.realProofIntakeBundle.csv || '缺失', summary.realProofIntakeBundle.exists ? `passed=${summary.realProofIntakeBundle.passed},proofOpenCount=${summary.realProofIntakeBundle.proofOpenCount},填表 ${summary.realProofIntakeBundle.fillRowCount},复验命令 ${summary.realProofIntakeBundle.recheckCommandCount},operatorPackEvidenceRowCount=${summary.realProofIntakeBundle.operatorPackEvidenceRowCount},operatorPackNextActionCount=${summary.realProofIntakeBundle.operatorPackNextActionCount},operatorPackOwnerArtifactCount=${summary.realProofIntakeBundle.operatorPackOwnerArtifactCount}` : '缺失', summary.realProofIntakeBundle.usage ]), tableRow([ summary.realProofClosureWorkOrder.title, summary.realProofClosureWorkOrder.markdown || summary.realProofClosureWorkOrder.csv || '缺失', summary.realProofClosureWorkOrder.exists ? `passed=${summary.realProofClosureWorkOrder.passed},complete=${summary.realProofClosureWorkOrder.complete},workOrderCount=${summary.realProofClosureWorkOrder.workOrderCount},ownerGroupCount=${summary.realProofClosureWorkOrder.ownerGroupCount},proofGapOpenCount=${summary.realProofClosureWorkOrder.proofGapOpenCount},intakeFailureCount=${summary.realProofClosureWorkOrder.intakeFailureCount},realCandidateRows=${summary.realProofClosureWorkOrder.realCandidateRows},canCloseProofGap=${summary.realProofClosureWorkOrder.canCloseProofGap}` : '缺失', summary.realProofClosureWorkOrder.usage ]), tableRow([ summary.localSeedToIntakeWorklist.title, summary.localSeedToIntakeWorklist.markdown || summary.localSeedToIntakeWorklist.csv || '缺失', summary.localSeedToIntakeWorklist.exists ? `proofLevel=${summary.localSeedToIntakeWorklist.proofLevel},historyDraftRows=${summary.localSeedToIntakeWorklist.historyDraftRows},candidateSeedRows=${summary.localSeedToIntakeWorklist.candidateSeedRows},videoWorklistRows=${summary.localSeedToIntakeWorklist.videoWorklistRows},videoCandidateNeedsResourceRows=${summary.localSeedToIntakeWorklist.videoCandidateNeedsResourceRows}` : '缺失', summary.localSeedToIntakeWorklist.usage ]), tableRow([ summary.realGapMaterialAudit.title, summary.realGapMaterialAudit.markdown || summary.realGapMaterialAudit.csv || 'missing', summary.realGapMaterialAudit.exists ? `proofLevel=${summary.realGapMaterialAudit.proofLevel}, materialFound=${summary.realGapMaterialAudit.materialFoundCount}/${summary.realGapMaterialAudit.gapCount}, openGapCount=${summary.realGapMaterialAudit.openGapCount}, canCloseProofGap=${summary.realGapMaterialAudit.canCloseProofGap}` : 'missing', summary.realGapMaterialAudit.usage ]), tableRow([ summary.optimizationCompletion.title, summary.optimizationCompletion.markdown || summary.optimizationCompletion.summary || '缺失', summary.optimizationCompletion.exists ? `complete=${summary.optimizationCompletion.complete},readyForClaim=${summary.optimizationCompletion.readyForClaim},proofGapOpenCount=${summary.optimizationCompletion.proofGapOpenCount},blockingReasons=${summary.optimizationCompletion.blockingReasonCount},missingProofRequirementCount=${summary.optimizationCompletion.missingProofRequirementCount},videoAbPreflightReadyForVideoAb=${summary.optimizationCompletion.videoAbPreflightReadyForVideoAb},videoAbPreflightFailureCount=${summary.optimizationCompletion.videoAbPreflightFailureCount ?? 'unknown'}` : '缺失', summary.optimizationCompletion.usage ]), tableRow([ summary.longRunReadiness.title, summary.longRunReadiness.report || summary.longRunReadiness.summary || '缺失', summary.longRunReadiness.exists ? `mode=${summary.longRunReadiness.mode || 'unknown'},ready=${summary.longRunReadiness.ready},pass=${summary.longRunReadiness.passCount},fail=${summary.longRunReadiness.failCount},warn=${summary.longRunReadiness.warnCount}` : '缺失', summary.longRunReadiness.usage ]), '', '## 下一步行动队列摘要', '', `- complete:${summary.nextActions.complete ? 'true' : 'false'}`, `- actionCount:${summary.nextActions.actionCount}`, `- proofOpenCount:${summary.nextActions.proofOpenCount}`, `- ownerGroupCount:${summary.nextActions.ownerGroupCount || 0}`, `- ownerArtifactCount:${summary.nextActions.ownerArtifactCount || 0}`, `- intakeFailureCount:${summary.nextActions.sourceState?.intakeFailureCount || 0}`, `- videoFailureCount:${summary.nextActions.sourceState?.videoFailureCount || 0}`, `- pipelineReadyForClaim:${summary.nextActions.sourceState?.pipelineReadyForClaim ? 'true' : 'false'}`, `- pipelineStale:${summary.nextActions.sourceState?.pipelineStale ? 'true' : 'false'}`, `- pipelineSuppressedNextActionCount:${summary.nextActions.sourceState?.pipelineSuppressedNextActionCount || 0}`, summary.nextActions.sourceState?.pipelineStaleReason ? `- pipelineStaleReason:${summary.nextActions.sourceState.pipelineStaleReason}` : '', '- 边界:该队列只用于补证派工和复验;proofOpenCount 不为 0 时不得宣称提号率、客户效果或人工补号量改善。', '', '| ID | 优先级 | 负责人 | 动作 | 状态 | 命令 | 通过标准 | 缺证明项 |', '| --- | ---: | --- | --- | --- | --- | --- | ---: |', ...summary.nextActions.topActions.map(item => tableRow([ item.id || '缺失', item.priority || item.order || 0, item.owner || '缺失', item.title || '缺失', item.status || '缺失', item.command || '缺失', item.acceptance || '缺失', item.missingProofRequirementCount || 0 ])), '', '## 负责人行动文件', '', '| 负责人 | 动作数 | Markdown | CSV | 修复附件 |', '| --- | ---: | --- | --- | --- |', ...summary.nextActions.ownerArtifacts.map(item => tableRow([ item.owner, item.actionCount, item.markdown || '缺失', item.csv || '缺失', renderRepairArtifactSummary(item.repairArtifacts || []) ])), '', '## proof-gap 操作包负责人文件', '', '| 负责人 | 动作数 | Markdown | CSV | 缺口 | 当前证据行 | 下一步动作 |', '| --- | ---: | --- | --- | --- | ---: | ---: |', ...summary.proofGapOperatorPack.ownerArtifacts.map(item => tableRow([ item.owner, item.actionCount, item.markdown || '缺失', item.csv || '缺失', (item.gapIds || []).join('; ') || '缺失', item.evidenceRowCount, item.nextActionCount ])), '', '## 商务执行总表操作包负责人附件', '', '| 负责人 | 动作数 | Markdown | CSV | 缺口 | 当前证据行 | 下一步动作 |', '| --- | ---: | --- | --- | --- | ---: | ---: |', ...summary.businessExecutionIndex.operatorPackOwnerArtifacts.map(item => tableRow([ item.owner, item.actionCount, item.markdown || '缺失', item.csv || '缺失', (item.gapIds || []).join('; ') || '缺失', item.evidenceRowCount, item.nextActionCount ])), '', '## 真实验收总包操作包负责人附件', '', '| 负责人 | 动作数 | Markdown | CSV | 缺口 | 当前证据行 | 下一步动作 |', '| --- | ---: | --- | --- | --- | ---: | ---: |', ...summary.realProofIntakeBundle.operatorPackOwnerArtifacts.map(item => tableRow([ item.owner, item.actionCount, item.markdown || '缺失', item.csv || '缺失', (item.gapIds || []).join('; ') || '缺失', item.evidenceRowCount, item.nextActionCount ])), '', '## 真实证明闭环工单负责人附件', '', '| 负责人 | 工单数 | Markdown | CSV | 缺口 | 目标文件 |', '| --- | ---: | --- | --- | --- | --- |', ...summary.realProofClosureWorkOrder.ownerArtifacts.map(item => tableRow([ item.owner, item.workOrderCount, item.markdown || '缺失', item.csv || '缺失', (item.gapIds || []).join('; ') || '缺失', (item.targetFiles || []).join('; ') || '缺失' ])), '', '## 本地种子转 intake 补表 worklist', '', renderLocalSeedToIntakeWorklist(summary.localSeedToIntakeWorklist), '', '## three real gap material audit', '', renderRealGapMaterialAudit(summary.realGapMaterialAudit), '', '## 修复清单入口', '', '| 类型 | CSV | 当前问题数 | 修复动作数 | 状态 |', '| --- | --- | ---: | ---: | --- |', tableRow([ summary.repairActions.dataIntake.title, summary.repairActions.dataIntake.csv || '缺失', summary.repairActions.dataIntake.failureCount, summary.repairActions.dataIntake.repairActionCount, summary.repairActions.dataIntake.ready ? '已就绪' : '待补齐' ]), tableRow([ summary.repairActions.homepageEvidence.title, summary.repairActions.homepageEvidence.csv || '缺失', summary.repairActions.homepageEvidence.failureCount, summary.repairActions.homepageEvidence.repairActionCount, summary.repairActions.homepageEvidence.ready ? '主页证据已就绪' : '待补主页近期内容证据' ]), tableRow([ summary.repairActions.videoResource.title, summary.repairActions.videoResource.csv || '缺失', summary.repairActions.videoResource.failureCount, summary.repairActions.videoResource.repairActionCount, summary.repairActions.videoResource.readyForVideoAbPreflight ? '可进入视频 A/B 前置' : '待补真实视频资源' ]), '', '## 当前边界', '', `- 补证行动数:${summary.nextActions.actionCount}`, `- 未关闭证明缺口:${summary.nextActions.proofOpenCount}`, `- proofGapClosure.businessOpenCount:${summary.proofGapClosure.businessOpenCount}`, `- proofGapClosure.precheckOpenCount:${summary.proofGapClosure.precheckOpenCount}`, `- optimizationCompletion.videoAbPreflightReadyForVideoAb:${summary.optimizationCompletion.videoAbPreflightReadyForVideoAb ? 'true' : 'false'}`, `- optimizationCompletion.videoAbPreflightFailureCount:${summary.optimizationCompletion.videoAbPreflightFailureCount ?? 'unknown'}`, `- 交接摘要 complete:${summary.handoff.complete}`, '', '## 下一步命令', '', '```powershell', summary.recommendedNextCommand, '```', '', '## 防误用边界', '', ...summary.guardrails.map(item => `- ${item}`) ]; const supportDocLines = [ '## 中文 SOP 入口', '', '| 文档 | 路径 | 用法 |', '| --- | --- | --- |', ...summary.supportDocs.map(item => tableRow([ item.title, item.path || '缺失', item.usage ])), '' ]; const insertAt = lines.findIndex((line, index) => index > 10 && String(line).startsWith('## ')); const currentFilesEnd = lines.findIndex((line, index) => index > 10 && line === '' && String(lines[index + 1] || '').startsWith('## ')); if (currentFilesEnd > 0) { lines.splice(currentFilesEnd, 0, tableRow([ summary.intakeFieldChecklist.title, summary.intakeFieldChecklist.markdown || summary.intakeFieldChecklist.csv || '缺失', summary.intakeFieldChecklist.exists ? `存在,${summary.intakeFieldChecklist.itemCount} 项,缺必填 ${summary.intakeFieldChecklist.missingRequiredCount},占位 ${summary.intakeFieldChecklist.placeholderCount}` : '缺失', summary.intakeFieldChecklist.usage ]), tableRow([ summary.manualReviewLabelGuide.title, summary.manualReviewLabelGuide.markdown || summary.manualReviewLabelGuide.csv || '缺失', summary.manualReviewLabelGuide.exists ? `通过=${summary.manualReviewLabelGuide.passed},标签 ${summary.manualReviewLabelGuide.labelCount},归因 ${summary.manualReviewLabelGuide.attributionTypeCount}` : '缺失', summary.manualReviewLabelGuide.usage ]), ''); } if (summary.intakeFieldChecklist.ownerGroups.length) { lines.splice(currentFilesEnd + 2, 0, ...renderIntakeOwnerGroups(summary.intakeFieldChecklist.ownerGroups)); } if (summary.businessFillGuide?.rows?.length) { const ownerGroupInsertAt = lines.findIndex(line => line === '## 负责人行动文件'); lines.splice(ownerGroupInsertAt > 0 ? ownerGroupInsertAt : currentFilesEnd + 2, 0, ...renderBusinessFillGuide(summary.businessFillGuide)); } if (summary.intakeFieldChecklist.recheckCommands.length) { const ownerGroupInsertAt = lines.findIndex(line => line === '## 负责人行动文件'); lines.splice(ownerGroupInsertAt > 0 ? ownerGroupInsertAt : currentFilesEnd + 2, 0, ...renderRecheckCommands(summary.intakeFieldChecklist.recheckCommands)); } lines.splice(insertAt > 0 ? insertAt : 0, 0, ...supportDocLines); return lines.join('\n'); } function renderLocalSeedToIntakeWorklist(worklist) { if (!worklist || !worklist.exists) { return '- 尚未找到本地种子转 intake 补表 worklist,请运行 `npm run local-seed-to-intake:worklist -- --outputs outputs --output outputs\\local-seed-to-intake-worklist-latest`。'; } return [ `- 报告:${worklist.markdown || '缺失'}`, `- 汇总 CSV:${worklist.csv || '缺失'}`, `- Summary:${worklist.summary || '缺失'}`, `- history-intake-draft:${worklist.historyDraft || '缺失'}`, `- video-resource-worklist:${worklist.videoWorklist || '缺失'}`, `- candidate-seed-worklist:${worklist.candidateWorklist || '缺失'}`, `- proofLevel:${worklist.proofLevel || 'not_business_proof'}`, `- materialType:${worklist.materialType || 'seed_to_intake_worklist'}`, `- directCustomerProof:${worklist.directCustomerProof ? 'true' : 'false'}`, `- canCloseProofGap:${worklist.canCloseProofGap ? 'true' : 'false'}`, `- historyDraftRows:${worklist.historyDraftRows || 0}`, `- referenceSeedRows:${worklist.referenceSeedRows || 0}`, `- candidateSeedRows:${worklist.candidateSeedRows || 0}`, `- videoWorklistRows:${worklist.videoWorklistRows || 0}`, `- videoCandidateNeedsResourceRows:${worklist.videoCandidateNeedsResourceRows || 0}`, `- missingProofRequirementCount:${worklist.missingProofRequirementCount || 0}`, `- nextActionCount:${worklist.nextActionCount || 0}`, `- 目标模板:${(worklist.targetFiles || []).join(';') || '缺失'}`, '- 边界:该 worklist 只把本地种子转成补表草稿;空字段仍需商务/投放补真实材料,不能关闭 proof-gap。' ].join('\n'); } function renderRealGapMaterialAudit(audit) { if (!audit || !audit.exists) { return '- missing; run `npm run real-gap:materials -- --outputs outputs --output outputs\\real-gap-material-audit-latest --strict`.'; } return [ `- report: ${audit.markdown || 'missing'}`, `- csv: ${audit.csv || 'missing'}`, `- summary: ${audit.summary || 'missing'}`, `- proofLevel: ${audit.proofLevel || 'not_business_proof'}`, `- materialType: ${audit.materialType || 'three_real_gap_material_audit'}`, `- directCustomerProof: ${audit.directCustomerProof ? 'true' : 'false'}`, `- canCloseProofGap: ${audit.canCloseProofGap ? 'true' : 'false'}`, `- complete: ${audit.complete ? 'true' : 'false'}`, `- materialFound: ${audit.materialFoundCount || 0}/${audit.gapCount || 0}`, `- openGapCount: ${audit.openGapCount || 0}`, `- gapIds: ${(audit.gapIds || []).join('; ') || 'missing'}`, '- boundary: material coverage does not prove tihao-rate improvement, customer effect, video A/B lift, or manual supplement reduction.' ].join('\n'); } function renderIntakeOwnerGroups(ownerGroups) { return [ '## 字段补齐负责人顺序', '', '| 负责人 | 范围 | 字段项 | 缺必填 | 占位 | 文件 | 下一步 | 通过标准 | 边界 |', '| --- | --- | ---: | ---: | ---: | --- | --- | --- | --- |', ...ownerGroups.map(item => tableRow([ item.owner, item.section, item.itemCount, item.missingRequiredCount, item.placeholderCount, (item.files || []).join(';') || '缺失', item.nextAction, item.acceptance, item.boundary ])), '' ]; } function renderRecheckCommands(recheckCommands) { return [ '## 补齐后复验命令', '', '| 阶段 | 负责人 | 命令 | 通过标准 | 边界 |', '| --- | --- | --- | --- | --- |', ...recheckCommands.map(item => tableRow([ item.stage, item.owner, item.command, item.acceptance, item.boundary ])), '' ]; } function buildBusinessFillGuide({ ownerGroups, recheckCommands }) { const rows = (ownerGroups || []).map((group, index) => { const recheck = recheckForSection(group.section, recheckCommands); return { order: index + 1, owner: group.owner || '', section: group.section || '', file: (group.files || [])[0] || '', firstItemIds: group.firstItemIds || [], issueCount: Number(group.itemCount || 0), missingRequiredCount: Number(group.missingRequiredCount || 0), placeholderCount: Number(group.placeholderCount || 0), fillAction: group.nextAction || '', recheckCommand: recheck?.command || '', acceptance: group.acceptance || recheck?.acceptance || '', boundary: group.boundary || '补齐材料,不证明客户效果' }; }); return { title: '商务填表执行视图', directCustomerProof: false, proofLevel: 'smoke_or_local', rowCount: rows.length, rows, guardrail: '本视图只帮助商务按表补齐真实材料,不代表客户效果已达标。' }; } function recheckForSection(section, recheckCommands) { const commands = Array.isArray(recheckCommands) ? recheckCommands : []; if (section === 'video') { return commands.find(item => /video:resource-readiness/.test(item.command)) || commands.find(item => /acceptance:video-ab/.test(item.command)) || null; } if (section === 'history' || section === 'review') { return commands.find(item => /intake:readiness/.test(item.command)) || commands.find(item => /customer-effect:audit/.test(item.command)) || null; } return commands[0] || null; } function renderBusinessFillGuide(guide) { return [ `## ${guide.title}`, '', '| 顺序 | 负责人 | 表单区域 | 填表文件 | 优先项 | 问题数 | 填表动作 | 补齐后复验 | 验收 | 边界 |', '| ---: | --- | --- | --- | --- | ---: | --- | --- | --- | --- |', ...guide.rows.map(item => tableRow([ item.order, item.owner, item.section, item.file || '缺失', (item.firstItemIds || []).join('、') || '无', item.issueCount, item.fillAction, item.recheckCommand || '待补齐后运行 round:refresh', item.acceptance, item.boundary ])), '', `- ${guide.guardrail}`, '' ]; } function normalizeIntakeOwnerGroups(root, ownerGroups) { if (!Array.isArray(ownerGroups)) return []; return ownerGroups.map(item => ({ owner: item.owner || '', section: item.section || '', itemCount: Number(item.itemCount || 0), missingRequiredCount: Number(item.missingRequiredCount || 0), placeholderCount: Number(item.placeholderCount || 0), files: Array.isArray(item.files) ? item.files.map(file => relIfPossible(root, file)) : [], firstItemIds: Array.isArray(item.firstItemIds) ? item.firstItemIds : [], nextAction: item.nextAction || '', acceptance: item.acceptance || '', boundary: item.boundary || '' })); } function normalizeRecheckCommands(recheckCommands) { if (!Array.isArray(recheckCommands)) return []; return recheckCommands.map(item => ({ stage: item.stage || '', owner: item.owner || '', command: item.command || '', acceptance: item.acceptance || '', boundary: item.boundary || '' })); } function normalizeProofGapClosureCounts(summary) { const rows = Array.isArray(summary?.rows) ? summary.rows : []; const precheckRows = rows.filter(row => row.id === 'intake-readiness'); const businessRows = rows.filter(row => row.id !== 'intake-readiness'); const fallbackBusinessOpen = businessRows.filter(row => row.status === 'open').length; const fallbackBusinessClosed = businessRows.filter(row => row.status === 'closed').length; const fallbackPrecheckOpen = precheckRows.filter(row => row.status === 'open').length; const fallbackPrecheckClosed = precheckRows.filter(row => row.status === 'closed').length; return { businessOpenCount: numberOr(summary?.businessOpenCount, numberOr(summary?.openCount, fallbackBusinessOpen)), businessClosedCount: numberOr(summary?.businessClosedCount, numberOr(summary?.closedCount, fallbackBusinessClosed)), businessGapCount: numberOr(summary?.businessGapCount, businessRows.length), totalRows: numberOr(summary?.totalRows, rows.length), precheckRowCount: numberOr(summary?.precheckRowCount, precheckRows.length), precheckOpenCount: numberOr(summary?.precheckOpenCount, fallbackPrecheckOpen), precheckClosedCount: numberOr(summary?.precheckClosedCount, fallbackPrecheckClosed), countSemantics: summary?.countSemantics || { openCount: 'business_gap_open_count_excludes_precheck_rows', closedCount: 'business_gap_closed_count_excludes_precheck_rows', businessOpenCount: 'same_as_openCount', businessClosedCount: 'same_as_closedCount', precheckOpenCount: 'precheck_rows_reported_separately' } }; } function numberOr(value, fallback) { const number = Number(value); return Number.isFinite(number) ? number : fallback; } function normalizeBusinessProofNextActionSourceState(sourceState) { if (!sourceState || typeof sourceState !== 'object') return {}; return { progressComplete: Boolean(sourceState.progressComplete), progressCounts: sourceState.progressCounts || {}, proofGapOpenCount: Number(sourceState.proofGapOpenCount || 0), intakeFailureCount: Number(sourceState.intakeFailureCount || 0), videoFailureCount: Number(sourceState.videoFailureCount || 0), homepageFailureCount: Number(sourceState.homepageFailureCount || 0), pipelineReadyForClaim: Boolean(sourceState.pipelineReadyForClaim), pipelineFailCount: Number(sourceState.pipelineFailCount || 0), pipelineNextActionCount: Number(sourceState.pipelineNextActionCount || 0), pipelineRawNextActionCount: Number(sourceState.pipelineRawNextActionCount || 0), pipelineSuppressedNextActionCount: Number(sourceState.pipelineSuppressedNextActionCount || 0), pipelineStale: Boolean(sourceState.pipelineStale), pipelineStaleReason: sourceState.pipelineStaleReason || '', pipelineStaleMissingProofRequirements: Array.isArray(sourceState.pipelineStaleMissingProofRequirements) ? sourceState.pipelineStaleMissingProofRequirements : [] }; } function normalizeBusinessProofNextTopActions(actions, limit = 5) { if (!Array.isArray(actions)) return []; return actions .slice() .sort((left, right) => actionSortValue(left) - actionSortValue(right)) .slice(0, limit) .map(item => ({ id: item.id || '', source: item.source || '', order: Number(item.order || 0), priority: Number(item.priority || 0), owner: item.owner || '', title: item.title || '', status: item.status || '', evidence: item.evidence || '', command: item.command || '', expectedArtifact: item.expectedArtifact || '', acceptance: item.acceptance || '', missingProofRequirementCount: Array.isArray(item.missingProofRequirements) ? item.missingProofRequirements.length : 0, missingProofRequirements: Array.isArray(item.missingProofRequirements) ? item.missingProofRequirements : [] })); } function actionSortValue(item) { const priority = Number(item?.priority); const order = Number(item?.order); const primary = Number.isFinite(priority) && priority > 0 ? priority : 999999; const secondary = Number.isFinite(order) && order > 0 ? order / 100000 : 0; return primary + secondary; } function normalizeOwnerArtifacts(root, dir, summary) { const artifacts = Array.isArray(summary?.ownerArtifacts) && summary.ownerArtifacts.length ? summary.ownerArtifacts : (summary?.ownerGroups || []).map(group => { const slug = ownerSlug(group.owner); return { owner: group.owner, actionCount: group.actionCount, topPriority: group.topPriority, markdown: path.join('by-owner', `${slug}.md`).replace(/\\/g, '/'), csv: path.join('by-owner', `${slug}.csv`).replace(/\\/g, '/'), gapIds: group.gapIds || [], evidenceRowCount: 0, nextActionCount: 0, repairArtifacts: [] }; }); return artifacts.map(item => { const markdown = path.join(dir, item.markdown || ''); const csv = path.join(dir, item.csv || ''); return { owner: item.owner, actionCount: Number(item.actionCount || 0), topPriority: Number(item.topPriority || 0), markdown: relIfExists(root, markdown), csv: relIfExists(root, csv), gapIds: Array.isArray(item.gapIds) ? item.gapIds : [], evidenceRowCount: Number(item.evidenceRowCount || 0), nextActionCount: Number(item.nextActionCount || 0), repairArtifacts: normalizeRepairArtifacts(item.repairArtifacts || []) }; }); } function normalizePassThroughOwnerArtifacts(root, ownerArtifacts) { if (!Array.isArray(ownerArtifacts)) return []; return ownerArtifacts.map(item => ({ owner: item.owner || '', actionCount: Number(item.actionCount || 0), topPriority: Number(item.topPriority || 0), markdown: relIfPossible(root, item.markdown || ''), csv: relIfPossible(root, item.csv || ''), gapIds: Array.isArray(item.gapIds) ? item.gapIds : [], evidenceRowCount: Number(item.evidenceRowCount || 0), nextActionCount: Number(item.nextActionCount || 0), repairArtifacts: normalizeRepairArtifacts(item.repairArtifacts || []) })); } function normalizeWorkOrderOwnerArtifacts(root, ownerArtifacts) { if (!Array.isArray(ownerArtifacts)) return []; return ownerArtifacts.map(item => ({ owner: item.owner || '', workOrderCount: Number(item.workOrderCount || item.actionCount || 0), topPriority: Number(item.topPriority || 0), markdown: relIfPossible(root, item.markdown || ''), csv: relIfPossible(root, item.csv || ''), targetFiles: Array.isArray(item.targetFiles) ? item.targetFiles : [], gapIds: Array.isArray(item.gapIds) ? item.gapIds : [] })); } function normalizeRepairArtifacts(repairArtifacts) { return repairArtifacts.map(item => ({ title: item.title || '', csv: item.csv || '', actionCount: Number(item.actionCount || 0), topPriority: Number(item.topPriority || 0), fields: Array.isArray(item.fields) ? item.fields : [] })); } function renderRepairArtifactSummary(repairArtifacts) { if (!repairArtifacts.length) return '无'; return repairArtifacts .map(item => `${item.title || '修复清单'}:${item.csv || '缺失'}(${item.actionCount || 0})`) .join(';'); } function ownerSlug(owner) { if (owner === '商务') return 'business'; if (owner === '商务/投放') return 'business-media'; if (owner === '技术/AI') return 'tech-ai'; return String(owner || 'owner').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'owner'; } function fileGroup(root, dir, files) { return Object.fromEntries(Object.entries(files).map(([key, file]) => [ key, relIfExists(root, path.join(dir, file)) ])); } function latestOutputDir(outputsDir, pattern) { if (!fs.existsSync(outputsDir)) return null; const dirs = fs.readdirSync(outputsDir, { withFileTypes: true }) .filter(entry => entry.isDirectory() && pattern.test(entry.name)) .map(entry => path.join(outputsDir, entry.name)) .sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs); return dirs[0] || null; } function readJsonIfExists(file) { if (!fs.existsSync(file)) return null; return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, '')); } function relIfExists(root, file) { return fs.existsSync(file) ? path.relative(root, file).replace(/\\/g, '/') : null; } function relIfPossible(root, file) { if (!file) return ''; const resolvedRoot = path.resolve(root); const resolvedFile = path.resolve(file); const relative = path.relative(resolvedRoot, resolvedFile); if (relative && (relative.startsWith('..') || path.isAbsolute(relative))) return String(file).replace(/\\/g, '/'); return relative.replace(/\\/g, '/'); } function finalDuplicateGroupCount(duplicateAudit) { if (!duplicateAudit) return null; return Object.values(duplicateAudit) .reduce((sum, groups) => sum + (Array.isArray(groups) ? groups.length : 0), 0); } function countRowsWithMissingProofRequirements(rows) { if (!Array.isArray(rows)) return 0; return rows.filter(row => Array.isArray(row.missingProofRequirements) && row.missingProofRequirements.length >= 1 ).length; } function uniqueMissingProofRequirementCount(rows) { if (!Array.isArray(rows)) return 0; const values = new Set(); for (const row of rows) { const requirements = Array.isArray(row.missingProofRequirements) ? row.missingProofRequirements : []; for (const requirement of requirements) { if (requirement) values.add(String(requirement)); } } return values.size; } function tableRow(cells) { return `| ${cells.map(cell => String(cell ?? '').replace(/\|/g, '/').replace(/\r?\n/g, ' ')).join(' | ')} |`; } 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 withBom(text) { return `\uFEFF${text}`; } if (require.main === module) main(); module.exports = { buildLatestFormIndex };