generate-optimization-handoff-smoke.js 64 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const os = require('os');
  4. const path = require('path');
  5. const { spawnSync } = require('child_process');
  6. const root = path.resolve(__dirname, '..');
  7. const outputDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-handoff-'));
  8. function main() {
  9. const outputsDir = path.join(root, 'outputs');
  10. const operatorPackResult = spawnSync(process.execPath, [
  11. path.join(root, 'scripts', 'proof-gap-operator-pack.js'),
  12. '--outputs',
  13. outputsDir,
  14. '--output',
  15. path.join(outputsDir, 'proof-gap-operator-pack-latest'),
  16. '--strict'
  17. ], { cwd: root, encoding: 'utf8' });
  18. if (operatorPackResult.status !== 0) {
  19. process.stderr.write(operatorPackResult.stderr || operatorPackResult.stdout || '');
  20. throw new Error('proof-gap-operator-pack 执行失败');
  21. }
  22. const bundleResult = spawnSync(process.execPath, [
  23. path.join(root, 'scripts', 'real-proof-intake-bundle.js'),
  24. '--output',
  25. path.join(outputsDir, 'real-proof-intake-bundle-latest'),
  26. '--strict'
  27. ], { cwd: root, encoding: 'utf8' });
  28. if (bundleResult.status !== 0) {
  29. process.stderr.write(bundleResult.stderr || bundleResult.stdout || '');
  30. throw new Error('real-proof-intake-bundle 执行失败');
  31. }
  32. const completionResult = spawnSync(process.execPath, [
  33. path.join(root, 'scripts', 'optimization-completion-audit.js'),
  34. '--outputs',
  35. outputsDir,
  36. '--output',
  37. path.join(outputsDir, 'optimization-completion-latest')
  38. ], { cwd: root, encoding: 'utf8' });
  39. if (completionResult.status !== 0) {
  40. process.stderr.write(completionResult.stderr || completionResult.stdout || '');
  41. throw new Error('optimization-completion-audit 执行失败');
  42. }
  43. const businessIndexResult = spawnSync(process.execPath, [
  44. path.join(root, 'scripts', 'business-execution-index.js'),
  45. '--outputs',
  46. outputsDir,
  47. '--output',
  48. path.join(outputsDir, 'business-execution-index-latest'),
  49. '--strict'
  50. ], { cwd: root, encoding: 'utf8' });
  51. if (businessIndexResult.status !== 0) {
  52. process.stderr.write(businessIndexResult.stderr || businessIndexResult.stdout || '');
  53. throw new Error('business-execution-index 执行失败');
  54. }
  55. const progressResult = spawnSync(process.execPath, [
  56. path.join(root, 'scripts', 'business-proof-progress.js'),
  57. '--outputs',
  58. outputsDir,
  59. '--output',
  60. path.join(outputsDir, 'business-proof-progress-latest')
  61. ], { cwd: root, encoding: 'utf8' });
  62. if (progressResult.status !== 0) {
  63. process.stderr.write(progressResult.stderr || progressResult.stdout || '');
  64. throw new Error('business-proof-progress 执行失败');
  65. }
  66. const nextActionsResult = spawnSync(process.execPath, [
  67. path.join(root, 'scripts', 'business-proof-next-actions.js'),
  68. '--outputs',
  69. outputsDir,
  70. '--output',
  71. path.join(outputsDir, 'business-proof-next-actions-latest')
  72. ], { cwd: root, encoding: 'utf8' });
  73. if (nextActionsResult.status !== 0) {
  74. process.stderr.write(nextActionsResult.stderr || nextActionsResult.stdout || '');
  75. throw new Error('business-proof-next-actions 执行失败');
  76. }
  77. const planExecutionResult = spawnSync(process.execPath, [
  78. path.join(root, 'scripts', 'plan-execution-status.js'),
  79. '--outputs',
  80. outputsDir,
  81. '--output',
  82. path.join(outputsDir, 'plan-execution-status-latest'),
  83. '--strict'
  84. ], { cwd: root, encoding: 'utf8' });
  85. if (planExecutionResult.status !== 0) {
  86. process.stderr.write(planExecutionResult.stderr || planExecutionResult.stdout || '');
  87. throw new Error('plan-execution-status 执行失败');
  88. }
  89. const localSeedWorklistResult = spawnSync(process.execPath, [
  90. path.join(root, 'scripts', 'local-seed-to-intake-worklist.js'),
  91. '--outputs',
  92. outputsDir,
  93. '--output',
  94. path.join(outputsDir, 'local-seed-to-intake-worklist-latest')
  95. ], { cwd: root, encoding: 'utf8' });
  96. if (localSeedWorklistResult.status !== 0) {
  97. process.stderr.write(localSeedWorklistResult.stderr || localSeedWorklistResult.stdout || '');
  98. throw new Error('local-seed-to-intake-worklist 执行失败');
  99. }
  100. const realProofClosureWorkOrderResult = spawnSync(process.execPath, [
  101. path.join(root, 'scripts', 'real-proof-closure-work-order.js'),
  102. '--outputs',
  103. outputsDir,
  104. '--output',
  105. path.join(outputsDir, 'real-proof-closure-work-order-latest'),
  106. '--strict'
  107. ], { cwd: root, encoding: 'utf8' });
  108. if (realProofClosureWorkOrderResult.status !== 0) {
  109. process.stderr.write(realProofClosureWorkOrderResult.stderr || realProofClosureWorkOrderResult.stdout || '');
  110. throw new Error('real-proof-closure-work-order 执行失败');
  111. }
  112. const latestFormResult = spawnSync(process.execPath, [
  113. path.join(root, 'scripts', 'latest-form-index.js'),
  114. '--outputs',
  115. outputsDir,
  116. '--output',
  117. path.join(outputsDir, 'latest-form-index-latest'),
  118. '--strict'
  119. ], { cwd: root, encoding: 'utf8' });
  120. if (latestFormResult.status !== 0) {
  121. process.stderr.write(latestFormResult.stderr || latestFormResult.stdout || '');
  122. throw new Error('latest-form-index 执行失败');
  123. }
  124. const result = spawnSync(process.execPath, [
  125. path.join(root, 'scripts', 'generate-optimization-handoff.js'),
  126. '--output',
  127. outputDir
  128. ], { cwd: root, encoding: 'utf8' });
  129. if (result.status !== 0) {
  130. process.stderr.write(result.stderr || result.stdout || '');
  131. throw new Error('generate-optimization-handoff 执行失败');
  132. }
  133. const jsonPath = path.join(outputDir, 'optimization-handoff-summary.json');
  134. const reportPath = path.join(outputDir, 'optimization-handoff-report.md');
  135. assert(fs.existsSync(jsonPath), 'handoff summary json should exist');
  136. assert(fs.existsSync(reportPath), 'handoff report should exist');
  137. const summary = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
  138. const report = fs.readFileSync(reportPath, 'utf8').replace(/^\uFEFF/, '');
  139. const operatorOwnerArtifactCount = Number(summary.proofGapOperatorPack?.ownerArtifactCount || 0);
  140. const nextActionCount = Number(summary.businessProofNextActions?.actionCount || 0);
  141. const nextActionSourceState = summary.businessProofNextActions?.sourceState || {};
  142. const latestFormNextActionSourceState = summary.latestFormIndex?.sourceState || {};
  143. const pipelineStaleSummary = summary.pipelineStaleSummary || {};
  144. const workOrderCount = Number(summary.realProofClosureWorkOrder?.workOrderCount || 0);
  145. assert(summary.complete === false, 'handoff must not mark long-term goal complete without real proof');
  146. assert(summary.readyCapabilities.length >= 1, 'handoff should list ready local capabilities');
  147. assert(summary.readyNotProven.length >= 1, 'handoff should list ready-not-proven work');
  148. assert(summary.externalBlockers.length >= 1, 'handoff should list external-data blockers');
  149. assert(summary.nextCommands.some(item => item.command.includes('customer-effect:audit')), 'handoff should include customer-effect audit command');
  150. assert(summary.nextCommands.some(item => item.command.includes('intake:readiness')), 'handoff should include intake readiness audit command');
  151. assert(summary.nextCommands.some(item => item.command.includes('longrun:readiness')), 'handoff should include long-run readiness command');
  152. assert(summary.nextCommands.some(item => item.command.includes('intake:field-checklist')), 'handoff should include intake field checklist command');
  153. assert(summary.nextCommands.some(item => item.command.includes('video:intake-template')), 'handoff should include video intake command');
  154. assert(summary.nextCommands.some(item => item.command.includes('video:resource-readiness')), 'handoff should include video resource readiness command');
  155. assert(summary.nextCommands.some(item => item.command.includes('acceptance:video-ab')), 'handoff should include video A/B command');
  156. assert(summary.nextCommands.some(item => item.command.includes('proof-gap:request')), 'handoff should include proof gap request command');
  157. assert(summary.nextCommands.some(item => item.command.includes('proof-gap:closure')), 'handoff should include proof gap closure command');
  158. assert(summary.nextCommands.some(item => item.command.includes('feedback:closure')), 'handoff should include feedback loop closure command');
  159. assert(summary.nextCommands.some(item => item.command.includes('business-proof:progress')), 'handoff should include business proof progress command');
  160. assert(summary.nextCommands.some(item => item.command.includes('business-proof:next-actions')), 'handoff should include business proof next actions command');
  161. assert(summary.nextCommands.some(item => item.command.includes('business-execution:index')), 'handoff should include business execution index command');
  162. assert(summary.nextCommands.some(item => item.command.includes('proof-gap:software-form')), 'handoff should include proof gap software form command');
  163. assert(summary.nextCommands.some(item => item.command.includes('latest-form:index')), 'handoff should include latest form index command');
  164. assert(summary.nextCommands.some(item => item.command.includes('local-seed-to-intake:worklist')), 'handoff should include seed-to-intake worklist command');
  165. assert(summary.nextCommands.some(item => item.command.includes('proof-gap:intake-bundle')), 'handoff should include real proof intake bundle command');
  166. assert(summary.nextCommands.some(item => item.command.includes('real-proof:work-order')), 'handoff should include real proof closure work order command');
  167. assert(summary.nextCommands.some(item => item.command.includes('round:refresh') && item.command.includes('--strict')), 'handoff should include ordered round refresh command');
  168. assert(summary.nextCommands.some(item => item.command.includes('optimization:pipeline') && item.command.includes('--data-pack') && item.command.includes('--video-pack')), 'handoff should include data/video pack optimization pipeline command');
  169. assert(summary.proofActionOrder && summary.proofActionOrder.doc.includes('business-proof-action-order'), 'handoff should include business proof action order document');
  170. assert(summary.proofActionOrder.doc.includes('business-proof-action-order.zh-CN.md'), 'handoff should prefer Chinese business proof action order document');
  171. assert(Array.isArray(summary.supportDocs) && summary.supportDocs.length >= 2, 'handoff should include Chinese SOP support docs');
  172. assert(summary.supportDocs.some(item => item.doc.includes('tihao-experience-optimization-plan.zh-CN.md')), 'handoff should include Chinese optimization plan document');
  173. assert(summary.supportDocs.some(item => item.doc.includes('tihao-experience-optimization-plan.md')), 'handoff should include main optimization plan document');
  174. assert(summary.supportDocs.some(item => item.doc.includes('business-proof-action-order.zh-CN.md')), 'handoff should include Chinese action order document');
  175. assert(summary.supportDocs.some(item => item.doc.includes('tihao-handoff-index.md')), 'handoff should include task handoff index document');
  176. assert(summary.businessProofProgress?.summary?.includes('business-proof-progress-summary.json'), 'handoff should include business proof progress summary');
  177. assert(summary.businessProofProgress?.report?.includes('business-proof-progress-report.md'), 'handoff should include business proof progress report');
  178. assert(summary.businessProofProgress?.complete === false, 'handoff business proof progress should keep incomplete state');
  179. assert(summary.businessProofProgress?.counts?.blocked_by_external_data >= 1, 'handoff business proof progress should expose external blockers');
  180. assert(summary.businessProofProgress?.operatorPackOwnerArtifactCount === operatorOwnerArtifactCount, 'handoff business proof progress should expose operator owner attachment count');
  181. assert(summary.businessProofProgress?.businessExecutionOperatorPackOwnerArtifactRowCount === operatorOwnerArtifactCount, 'handoff business proof progress should expose business execution operator attachment rows');
  182. assert(summary.businessProofProgress?.operatorPackOwnerArtifacts?.some(item => item.owner === '商务' && item.csv?.includes('by-owner/business.csv')), 'handoff business proof progress should expose business owner operator csv');
  183. assert(summary.businessProofNextActions && summary.businessProofNextActions.ownerArtifacts.length >= 1, 'handoff should include owner action artifacts');
  184. assert(summary.businessProofNextActions.complete === false, 'handoff should keep next action queue incomplete while proof gaps remain');
  185. assert(summary.businessProofNextActions.actionCount === nextActionCount, 'handoff should expose next action count');
  186. assert(summary.businessProofNextActions.sourceState?.proofGapOpenCount >= 1, 'handoff should expose next action proof gap open count');
  187. assert(nextActionSourceState.pipelineStale === true, 'handoff should expose stale pipeline state for next actions');
  188. assert(Number(nextActionSourceState.pipelineNextActionCount || 0) === 0, 'handoff should expose zero current pipeline actions when stale');
  189. assert(Number(nextActionSourceState.pipelineRawNextActionCount || 0) >= 1, 'handoff should retain raw stale pipeline action count');
  190. assert(Number(nextActionSourceState.pipelineSuppressedNextActionCount || 0) === Number(nextActionSourceState.pipelineRawNextActionCount || 0), 'handoff should suppress every stale pipeline action');
  191. assert(String(nextActionSourceState.pipelineStaleReason || '').includes('stale'), 'handoff should expose stale pipeline reason');
  192. assert(Array.isArray(nextActionSourceState.pipelineStaleMissingProofRequirements) && nextActionSourceState.pipelineStaleMissingProofRequirements.includes('optimizationPipeline.readyForClaim=true'), 'handoff should expose stale pipeline missing proof requirements');
  193. assert(summary.businessProofNextActions.ownerGroupCount >= 1, 'handoff should expose next action owner groups');
  194. assert(summary.businessProofNextActions.ownerArtifactCount >= summary.businessProofNextActions.ownerGroupCount, 'handoff should expose next action owner artifact count');
  195. assert(summary.businessProofNextActions.topActions?.every(item => !String(item.source || '').includes('optimization-pipeline')), 'handoff should not expose stale optimization-pipeline top actions');
  196. assert(summary.businessProofNextActions.topActions?.some(item => item.id === 'step-03' && item.command?.includes('history-data-template.csv')), 'handoff should expose top historical data action');
  197. assert(summary.businessProofNextActions.topActions?.some(item => item.id === 'gap-video-ab-live-proof' && item.command?.includes('acceptance:video-ab')), 'handoff should expose top video A/B proof action');
  198. assert(summary.businessProofNextActions.ownerArtifacts.some(item => item.markdown.includes('by-owner/')), 'handoff should include owner markdown files');
  199. assert(summary.businessProofNextActions.ownerArtifacts.some(item => item.csv.includes('by-owner/')), 'handoff should include owner csv files');
  200. assert(summary.businessProofNextActions.ownerArtifacts.some(item => item.owner === '商务' && item.repairArtifacts?.some(artifact => artifact.csv.includes('intake-readiness-repair-actions.csv'))), 'handoff should include business owner data repair artifact');
  201. assert(summary.businessProofNextActions.ownerArtifacts.some(item => item.repairArtifacts?.some(artifact => artifact.csv.includes('homepage-evidence-repair-actions.csv') || artifact.csv.includes('video-resource-repair-actions.csv'))), 'handoff should include owner repair artifacts');
  202. assert(pipelineStaleSummary.pipelineStale === true, 'handoff should expose top-level pipeline stale summary');
  203. assert(pipelineStaleSummary.pipelineReadyForClaim === false, 'top-level pipeline stale summary should keep readyForClaim false');
  204. assert(Number(pipelineStaleSummary.pipelineNextActionCount || 0) === 0, 'top-level pipeline stale summary should expose zero current pipeline actions');
  205. assert(Number(pipelineStaleSummary.pipelineRawNextActionCount || 0) === Number(nextActionSourceState.pipelineRawNextActionCount || 0), 'top-level pipeline stale summary should mirror raw pipeline action count');
  206. assert(Number(pipelineStaleSummary.pipelineSuppressedNextActionCount || 0) === Number(nextActionSourceState.pipelineSuppressedNextActionCount || 0), 'top-level pipeline stale summary should mirror suppressed pipeline action count');
  207. assert(Number(pipelineStaleSummary.pipelineStaleMissingProofRequirementCount || 0) === nextActionSourceState.pipelineStaleMissingProofRequirements.length, 'top-level pipeline stale summary should expose missing proof requirement count');
  208. assert(String(pipelineStaleSummary.boundary || '').includes('not current dispatch'), 'top-level pipeline stale summary should expose dispatch boundary');
  209. assert(summary.businessExecutionIndex && summary.businessExecutionIndex.summary.includes('business-execution-index-summary.json'), 'handoff should include business execution index summary');
  210. assert(summary.businessExecutionIndex.report.includes('business-execution-index.md'), 'handoff should include business execution index report');
  211. assert(summary.businessExecutionIndex.csv.includes('business-execution-index.csv'), 'handoff should include business execution index csv');
  212. assert(summary.businessExecutionIndex.directCustomerProof === false, 'business execution index should not be direct customer proof');
  213. assert(summary.businessExecutionIndex.operatorPackOwnerArtifactCount === operatorOwnerArtifactCount, 'handoff business execution index should expose operator pack owner artifact count');
  214. assert(summary.businessExecutionIndex.operatorPackOwnerArtifactRowCount === operatorOwnerArtifactCount, 'handoff business execution index should expose operator pack owner artifact rows');
  215. assert(summary.businessExecutionIndex.operatorPackOwnerArtifacts?.some(item => item.owner === '商务' && item.csv?.includes('by-owner/business.csv')), 'handoff business execution index should expose business operator owner csv');
  216. assert(summary.businessExecutionIndex.longRunReadiness?.report?.includes('long-run-readiness-report.md'), 'handoff business execution index should expose long-run readiness report row');
  217. assert(summary.businessExecutionIndex.longRunReadiness?.summary?.includes('long-run-readiness-summary.json'), 'handoff business execution index should expose long-run readiness source summary');
  218. assert(summary.businessExecutionIndex.longRunReadiness?.status === 'blocked', 'handoff business execution index should keep long-run readiness blocked while ready=false');
  219. assert(summary.businessExecutionIndex.longRunReadiness?.ready === false, 'handoff business execution index should expose long-run ready=false');
  220. assert(summary.businessExecutionIndex.longRunReadiness?.failCount >= 1, 'handoff business execution index should expose long-run fail count');
  221. assert(summary.businessExecutionIndex.longRunReadiness?.directCustomerProof === false, 'handoff business execution long-run entry should not be direct customer proof');
  222. assert(summary.businessExecutionIndex.optimizationCompletion?.report?.includes('optimization-completion-report.md'), 'handoff business execution index should expose optimization completion report row');
  223. assert(summary.businessExecutionIndex.optimizationCompletion?.summary?.includes('optimization-completion-summary.json'), 'handoff business execution index should expose optimization completion source summary');
  224. assert(summary.businessExecutionIndex.optimizationCompletion?.status === 'blocked', 'handoff business execution index should keep optimization completion blocked while readyForClaim=false');
  225. assert(summary.businessExecutionIndex.optimizationCompletion?.readyForClaim === false, 'handoff business execution index should expose readyForClaim=false');
  226. assert(summary.businessExecutionIndex.optimizationCompletion?.proofGapOpenCount >= 1, 'handoff business execution index should expose completion proof gap open count');
  227. assert(summary.businessExecutionIndex.optimizationCompletion?.blockingReasonsWithMissingProofRequirements >= 1, 'handoff business execution index should expose completion blockers with missing proof requirements');
  228. assert(summary.businessExecutionIndex.optimizationCompletion?.missingProofRequirementCount >= 1, 'handoff business execution index should expose completion missing proof requirement count');
  229. assert(summary.businessExecutionIndex.optimizationCompletion?.videoAbPreflightReadyForVideoAb === false, 'handoff business execution index should expose video A/B preflight readiness');
  230. assert(Number.isFinite(Number(summary.businessExecutionIndex.optimizationCompletion?.videoAbPreflightFailureCount)), 'handoff business execution index should expose video A/B preflight failure count');
  231. assert(summary.businessExecutionIndex.optimizationCompletion?.directCustomerProof === false, 'handoff business execution completion entry should not be direct customer proof');
  232. assert(summary.proofGapClosure && summary.proofGapClosure.summary.includes('proof-gap-closure-summary.json'), 'handoff should include proof gap closure summary');
  233. assert(summary.proofGapClosure.report.includes('proof-gap-closure-report.md'), 'handoff should include proof gap closure report');
  234. assert(summary.proofGapClosure.openCount >= 1, 'handoff should expose proof gap closure open count');
  235. assert(summary.proofGapClosure.businessOpenCount === summary.proofGapClosure.openCount, 'handoff should keep openCount as businessOpenCount alias');
  236. assert(summary.proofGapClosure.businessClosedCount === summary.proofGapClosure.closedCount, 'handoff should keep closedCount as businessClosedCount alias');
  237. assert(summary.proofGapClosure.businessGapCount >= summary.proofGapClosure.businessOpenCount, 'handoff should expose business proof gap count');
  238. assert(summary.proofGapClosure.precheckRowCount >= 1, 'handoff should expose precheck row count separately');
  239. assert(summary.proofGapClosure.totalRows >= summary.proofGapClosure.businessGapCount + summary.proofGapClosure.precheckRowCount, 'handoff should expose total rows including prechecks');
  240. assert(summary.proofGapClosure.countSemantics?.openCount === 'business_gap_open_count_excludes_precheck_rows', 'handoff should expose proof gap closure count semantics');
  241. assert(summary.proofGapClosure.rows.some(row => row.id === 'video-ab-live-proof' && row.missingProofRequirements.includes('proofContext.mode=live')), 'handoff should expose video A/B missing live proof requirement');
  242. if (summary.feedbackLoopClosure) {
  243. assert(summary.feedbackLoopClosure.summary.includes('feedback-loop-closure-summary.json'), 'handoff should include feedback loop closure summary');
  244. assert(summary.feedbackLoopClosure.report.includes('feedback-loop-closure-report.md'), 'handoff should include feedback loop closure report');
  245. assert(summary.feedbackLoopClosure.csv.includes('feedback-loop-closure.csv'), 'handoff should include feedback loop closure csv');
  246. assert(summary.feedbackLoopClosure.directCustomerProof === false, 'feedback loop closure should not be direct customer proof by default');
  247. }
  248. if (summary.homepageEvidenceReadiness) {
  249. assert(summary.homepageEvidenceReadiness.summary.includes('homepage-evidence-readiness-summary.json'), 'handoff should include homepage evidence readiness summary');
  250. assert(summary.homepageEvidenceReadiness.report.includes('homepage-evidence-readiness-report.md'), 'handoff should include homepage evidence readiness report');
  251. assert(summary.homepageEvidenceReadiness.csv.includes('homepage-evidence-repair-actions.csv'), 'handoff should include homepage evidence repair csv');
  252. assert(summary.homepageEvidenceReadiness.directCustomerProof === false, 'homepage evidence readiness should not be direct customer proof');
  253. assert(['not_business_proof', 'smoke_or_local'].includes(summary.homepageEvidenceReadiness.proofLevel), 'homepage evidence readiness should classify proof level');
  254. }
  255. assert(summary.longRunReadiness?.summary?.includes('long-run-readiness-summary.json'), 'handoff should include long-run readiness summary');
  256. assert(summary.longRunReadiness?.report?.includes('long-run-readiness-report.md'), 'handoff should include long-run readiness report');
  257. assert(summary.longRunReadiness?.directCustomerProof === false, 'long-run readiness should not be direct customer proof');
  258. assert(summary.longRunReadiness?.proofLevel === 'not_business_proof', 'long-run readiness should keep not-business-proof level while blocked');
  259. assert(summary.longRunReadiness?.ready === false, 'long-run readiness should expose ready=false while proof gaps remain');
  260. assert(summary.longRunReadiness?.failCount >= 1, 'long-run readiness should expose failing preflight checks');
  261. assert(summary.optimizationCompletion?.summary?.includes('optimization-completion-summary.json'), 'handoff should include optimization completion summary');
  262. assert(summary.optimizationCompletion?.report?.includes('optimization-completion-report.md'), 'handoff should include optimization completion report');
  263. assert(summary.optimizationCompletion?.directCustomerProof === false, 'optimization completion should not be direct customer proof while blocked');
  264. assert(summary.optimizationCompletion?.proofLevel === 'not_business_proof', 'optimization completion should keep not-business-proof level while blocked');
  265. assert(summary.optimizationCompletion?.readyForClaim === false, 'optimization completion should expose readyForClaim=false while proof gaps remain');
  266. assert(summary.optimizationCompletion?.blockingReasonsWithMissingProofRequirements >= 1, 'optimization completion should expose blockers with missing proof requirements');
  267. assert(summary.optimizationCompletion?.missingProofRequirementCount >= 1, 'optimization completion should expose missing proof requirement count');
  268. assert(summary.optimizationCompletion?.videoAbPreflightReadyForVideoAb === false, 'optimization completion should expose video A/B preflight readiness');
  269. assert(Number.isFinite(Number(summary.optimizationCompletion?.videoAbPreflightFailureCount)), 'optimization completion should expose video A/B preflight failure count');
  270. assert(summary.optimizationCompletion?.canClaim?.tihaoRateImproved === false, 'optimization completion should forbid tihao-rate claim');
  271. assert(summary.optimizationCompletion?.canClaim?.customerEffectAchieved === false, 'optimization completion should forbid customer-effect claim');
  272. assert(summary.optimizationCompletion?.canClaim?.manualSupplementReduced === false, 'optimization completion should forbid manual-supplement claim');
  273. assert(summary.proofGapSoftwareForm && summary.proofGapSoftwareForm.summary.includes('tihao-proof-gap-software-form-summary.json'), 'handoff should include latest proof gap software form summary');
  274. assert(summary.proofGapSoftwareForm.csv.includes('tihao-proof-gap-software-form.csv'), 'handoff should include latest proof gap software form csv');
  275. assert(summary.proofGapSoftwareForm.markdown.includes('tihao-proof-gap-software-form.md'), 'handoff should include latest proof gap software form markdown');
  276. assert(summary.proofGapSoftwareForm.passed === true, 'handoff should record latest proof gap software form pass state');
  277. assert(summary.proofGapSoftwareForm.rowCount === 5, 'handoff should record latest proof gap software form row count');
  278. assert(summary.proofGapSoftwareForm.duplicateIdCount === 0, 'handoff should record latest proof gap software form duplicate id count');
  279. assert(summary.intakeFieldChecklist && summary.intakeFieldChecklist.summary.includes('intake-field-checklist-summary.json'), 'handoff should include intake field checklist summary');
  280. assert(summary.intakeFieldChecklist.report.includes('intake-field-checklist.md'), 'handoff should include intake field checklist report');
  281. assert(summary.intakeFieldChecklist.csv.includes('intake-field-checklist.csv'), 'handoff should include intake field checklist csv');
  282. assert(summary.intakeFieldChecklist.directCustomerProof === false, 'intake field checklist should not be direct customer proof');
  283. assert(Array.isArray(summary.intakeFieldChecklist.ownerGroups) && summary.intakeFieldChecklist.ownerGroups.length >= 1, 'handoff should include intake field checklist owner groups');
  284. assert(summary.intakeFieldChecklist.ownerGroups.some(item => item.owner === '商务' && item.files?.some(file => file.includes('history-data-template.csv'))), 'handoff should include business history owner group files');
  285. assert(Array.isArray(summary.intakeFieldChecklist.recheckCommands) && summary.intakeFieldChecklist.recheckCommands.length >= 1, 'handoff should include intake field checklist recheck commands');
  286. assert(summary.intakeFieldChecklist.recheckCommands.some(item => item.command.includes('intake:readiness')), 'handoff should include intake readiness recheck command');
  287. assert(summary.intakeFieldChecklist.recheckCommands.some(item => item.command.includes('acceptance:video-ab')), 'handoff should include video A/B recheck command');
  288. assert(summary.latestFormIndex && summary.latestFormIndex.summary.includes('latest-form-index-summary.json'), 'handoff should include latest form index summary');
  289. assert(summary.latestFormIndex.report.includes('latest-form-index.md'), 'handoff should include latest form index report');
  290. assert(summary.latestFormIndex.passed === true, 'handoff should record latest form index pass state');
  291. assert(latestFormNextActionSourceState.pipelineStale === true, 'handoff latest form index should mirror stale pipeline state');
  292. assert(Number(latestFormNextActionSourceState.pipelineNextActionCount || 0) === 0, 'handoff latest form index should mirror zero current pipeline actions when stale');
  293. assert(Number(latestFormNextActionSourceState.pipelineSuppressedNextActionCount || 0) === Number(nextActionSourceState.pipelineSuppressedNextActionCount || 0), 'handoff latest form index should mirror suppressed stale pipeline count');
  294. assert(summary.latestFormIndex.primaryFormCsv.includes('tihao-proof-gap-software-form.csv'), 'handoff should record latest indexed proof form csv');
  295. assert(summary.latestFormIndex.clientListCsv.includes('software-client-list.final.csv'), 'handoff should record latest indexed client list csv');
  296. assert(summary.latestFormIndex.intakeFieldChecklist?.ownerGroups?.length >= 1, 'handoff should record latest form index field owner groups');
  297. assert(summary.latestFormIndex.intakeFieldChecklist?.recheckCommands?.length >= 1, 'handoff should record latest form index recheck commands');
  298. assert(summary.latestFormIndex.manualReviewLabelGuide?.markdown?.includes('manual-review-label-guide.md'), 'handoff should record latest form manual review label guide');
  299. assert(summary.latestFormIndex.manualReviewLabelGuide?.directCustomerProof === false, 'handoff should carry manual review label guide proof boundary');
  300. assert(summary.latestFormIndex.manualReviewLabelGuide?.negativeLabelsRequireAttribution === true, 'handoff should carry manual review negative attribution rule');
  301. assert(summary.latestFormIndex.businessFillGuide?.directCustomerProof === false, 'handoff should carry latest form business fill guide proof boundary');
  302. assert(summary.latestFormIndex.businessFillGuide?.rowCount >= 2, 'handoff should carry latest form business fill guide rows');
  303. assert(summary.latestFormIndex.businessFillGuide.rows.some(item => item.section === 'history' && item.file.includes('history-data-template.csv')), 'handoff should carry history fill guide row');
  304. assert(summary.latestFormIndex.businessFillGuide.rows.some(item => item.section === 'video' && item.recheckCommand.includes('video:resource-readiness')), 'handoff should carry video fill guide recheck command');
  305. assert(summary.latestFormIndex.planExecutionStatus?.markdown?.includes('plan-execution-status.md'), 'handoff should record latest form plan execution status');
  306. assert(summary.latestFormIndex.planExecutionStatus?.directCustomerProof === false, 'handoff should carry plan execution proof boundary through latest form index');
  307. assert(summary.latestFormIndex.planExecutionStatus?.rowCount === 8, 'handoff should carry plan execution eight-section status');
  308. assert(summary.latestFormIndex.planExecutionStatus?.complete === false, 'handoff should carry plan execution incomplete state');
  309. assert(summary.latestFormIndex.planExecutionStatus?.operatorPackOwnerArtifactCount === operatorOwnerArtifactCount, 'handoff should carry latest form plan execution operator owner artifact count');
  310. assert(summary.latestFormIndex.planExecutionStatus?.operatorPackOwnerArtifacts?.some(item => item.csv?.includes('by-owner/business.csv')), 'handoff should carry latest form plan execution business owner csv');
  311. assert(summary.latestFormIndex.longRunReadiness?.report?.includes('long-run-readiness-report.md'), 'handoff should carry latest form long-run readiness report');
  312. assert(summary.latestFormIndex.longRunReadiness?.ready === false, 'handoff should carry latest form long-run ready=false state');
  313. assert(summary.latestFormIndex.longRunReadiness?.directCustomerProof === false, 'handoff should carry long-run readiness proof boundary through latest form index');
  314. assert(summary.latestFormIndex.optimizationCompletion?.report?.includes('optimization-completion-report.md'), 'handoff should carry latest form optimization completion report');
  315. assert(summary.latestFormIndex.optimizationCompletion?.summary?.includes('optimization-completion-summary.json'), 'handoff should carry latest form optimization completion summary');
  316. assert(summary.latestFormIndex.optimizationCompletion?.readyForClaim === false, 'handoff should carry latest form optimization readyForClaim=false state');
  317. assert(summary.latestFormIndex.optimizationCompletion?.videoAbPreflightReadyForVideoAb === false, 'handoff should carry latest form video A/B preflight readiness');
  318. assert(Number.isFinite(Number(summary.latestFormIndex.optimizationCompletion?.videoAbPreflightFailureCount)), 'handoff should carry latest form video A/B preflight failure count');
  319. assert(summary.latestFormIndex.optimizationCompletion?.directCustomerProof === false, 'handoff should carry optimization completion proof boundary through latest form index');
  320. assert(summary.localSeedToIntakeWorklist?.markdown?.includes('local-seed-to-intake-worklist.md'), 'handoff should include top-level seed-to-intake worklist report');
  321. assert(summary.localSeedToIntakeWorklist?.historyDraft?.includes('history-intake-draft.csv'), 'handoff should include top-level seed-to-intake history draft');
  322. assert(summary.localSeedToIntakeWorklist?.videoWorklist?.includes('video-resource-worklist.csv'), 'handoff should include top-level seed-to-intake video worklist');
  323. assert(summary.localSeedToIntakeWorklist?.candidateWorklist?.includes('candidate-seed-worklist.csv'), 'handoff should include top-level seed-to-intake candidate worklist');
  324. assert(summary.localSeedToIntakeWorklist?.directCustomerProof === false, 'handoff seed-to-intake worklist should not be direct customer proof');
  325. assert(summary.localSeedToIntakeWorklist?.proofLevel === 'not_business_proof', 'handoff seed-to-intake worklist should keep not-business-proof level');
  326. assert(summary.localSeedToIntakeWorklist?.materialType === 'seed_to_intake_worklist', 'handoff seed-to-intake worklist should keep material type');
  327. assert(summary.localSeedToIntakeWorklist?.canCloseProofGap === false, 'handoff seed-to-intake worklist should not close proof gaps');
  328. assert(summary.localSeedToIntakeWorklist?.candidateSeedRows >= 1, 'handoff seed-to-intake worklist should expose candidate seed rows');
  329. assert(summary.latestFormIndex.localSeedToIntakeWorklist?.historyDraft?.includes('history-intake-draft.csv'), 'handoff should carry latest form seed-to-intake history draft');
  330. assert(summary.latestFormIndex.localSeedToIntakeWorklist?.directCustomerProof === false, 'handoff latest form seed-to-intake worklist should keep proof boundary');
  331. assert(summary.realProofClosureWorkOrder?.markdown?.includes('real-proof-closure-work-order.md'), 'handoff should include real proof closure work order report');
  332. assert(summary.realProofClosureWorkOrder?.csv?.includes('real-proof-closure-work-order.csv'), 'handoff should include real proof closure work order csv');
  333. assert(summary.realProofClosureWorkOrder?.summary?.includes('real-proof-closure-work-order-summary.json'), 'handoff should include real proof closure work order summary');
  334. assert(summary.realProofClosureWorkOrder?.directCustomerProof === false, 'real proof closure work order should not be direct customer proof');
  335. assert(summary.realProofClosureWorkOrder?.proofLevel === 'not_business_proof', 'real proof closure work order should keep not-business-proof level');
  336. assert(summary.realProofClosureWorkOrder?.materialType === 'real_proof_closure_work_order', 'real proof closure work order should expose material type');
  337. assert(summary.realProofClosureWorkOrder?.complete === false, 'real proof closure work order should not claim completion');
  338. assert(summary.realProofClosureWorkOrder?.canCloseProofGap === false, 'real proof closure work order should not close proof gaps');
  339. assert(summary.realProofClosureWorkOrder?.workOrderCount >= 5, 'real proof closure work order should expose work orders');
  340. assert(summary.realProofClosureWorkOrder?.ownerGroupCount >= 1, 'real proof closure work order should expose owner groups');
  341. assert(summary.realProofClosureWorkOrder?.proofGapOpenCount >= 1, 'real proof closure work order should expose open proof gaps');
  342. assert(summary.realProofClosureWorkOrder?.intakeFailureCount >= 1, 'real proof closure work order should expose intake failures');
  343. assert(summary.realProofClosureWorkOrder?.realCandidateRows >= 0, 'real proof closure work order should expose real candidate rows');
  344. assert(summary.realProofClosureWorkOrder?.ownerArtifacts?.some(item => item.csv?.includes('by-owner/business.csv')), 'real proof closure work order should expose business owner csv');
  345. assert(summary.realProofClosureWorkOrder?.ownerArtifacts?.some(item => item.csv?.includes('by-owner/tech-ai.csv')), 'real proof closure work order should expose tech/AI owner csv');
  346. if (summary.realProofClosureWorkOrder?.ownerArtifacts?.some(item => item.owner === '商务/投放')) {
  347. assert(summary.realProofClosureWorkOrder.ownerArtifacts.some(item => item.markdown?.includes('by-owner/business-media.md')), 'real proof closure work order should expose business/media owner markdown when present');
  348. }
  349. assert(summary.latestFormIndex.realProofClosureWorkOrder?.markdown?.includes('real-proof-closure-work-order.md'), 'handoff should carry latest form real proof closure work order');
  350. assert(summary.latestFormIndex.realProofClosureWorkOrder?.workOrderCount === workOrderCount, 'handoff latest form real proof closure work order should expose work order count');
  351. assert(summary.latestFormIndex.realProofClosureWorkOrder?.canCloseProofGap === false, 'handoff latest form real proof closure work order should keep non-closure boundary');
  352. if (summary.latestFormIndex.realProofClosureWorkOrder?.ownerArtifacts?.some(item => item.owner === '商务/投放')) {
  353. assert(summary.latestFormIndex.realProofClosureWorkOrder.ownerArtifacts.some(item => item.csv?.includes('by-owner/business-media.csv')), 'handoff latest form real proof closure work order should expose business/media csv when present');
  354. }
  355. assert(summary.planExecutionStatus?.markdown?.includes('plan-execution-status.md'), 'handoff should include top-level plan execution status');
  356. assert(summary.planExecutionStatus?.csv?.includes('plan-execution-status.csv'), 'handoff should include plan execution status csv');
  357. assert(summary.planExecutionStatus?.summary?.includes('plan-execution-status-summary.json'), 'handoff should include plan execution status summary');
  358. assert(summary.planExecutionStatus?.directCustomerProof === false, 'plan execution status should not be direct customer proof');
  359. assert(summary.planExecutionStatus?.rowCount === 8, 'plan execution status should expose eight plan sections');
  360. assert(summary.planExecutionStatus?.complete === false, 'plan execution status should keep incomplete state while proof gaps remain');
  361. assert(summary.planExecutionStatus?.operatorPackOwnerArtifactCount === operatorOwnerArtifactCount, 'plan execution status should expose operator owner attachment count');
  362. assert(summary.planExecutionStatus?.businessExecutionOperatorPackOwnerArtifactRowCount === operatorOwnerArtifactCount, 'plan execution status should expose business execution operator attachment rows');
  363. if (summary.planExecutionStatus?.operatorPackOwnerArtifacts?.some(item => item.owner === '商务/投放')) {
  364. assert(summary.planExecutionStatus.operatorPackOwnerArtifacts.some(item => item.markdown?.includes('by-owner/business-media.md')), 'plan execution status should expose business/media owner markdown when present');
  365. }
  366. assert(summary.proofGapRequest?.markdown?.includes('proof-gap-request-report.md'), 'handoff should include proof gap request report');
  367. assert(summary.proofGapRequest?.csv?.includes('proof-gap-request-table.csv'), 'handoff should include proof gap request csv');
  368. assert(summary.proofGapRequest?.summary?.includes('proof-gap-request-summary.json'), 'handoff should include proof gap request summary');
  369. assert(summary.proofGapRequest?.directCustomerProof === false, 'proof gap request should not be direct customer proof');
  370. assert(summary.proofGapRequest?.proofLevel === 'smoke_or_local', 'proof gap request should keep smoke/local proof level');
  371. assert(summary.proofGapRequest?.unresolvedCount >= 1, 'proof gap request should expose unresolved gaps');
  372. assert(summary.latestFormIndex.proofGapRequest?.markdown?.includes('proof-gap-request-report.md'), 'handoff should carry latest form proof gap request report');
  373. assert(summary.latestFormIndex.proofGapRequest?.directCustomerProof === false, 'handoff should carry proof gap request boundary through latest form index');
  374. assert(summary.proofGapOperatorPack?.markdown?.includes('proof-gap-operator-pack.md'), 'handoff should include proof gap operator pack report');
  375. assert(summary.proofGapOperatorPack?.csv?.includes('proof-gap-operator-pack.csv'), 'handoff should include proof gap operator pack csv');
  376. assert(summary.proofGapOperatorPack?.summary?.includes('proof-gap-operator-pack-summary.json'), 'handoff should include proof gap operator pack summary');
  377. assert(summary.proofGapOperatorPack?.directCustomerProof === false, 'proof gap operator pack should not be direct customer proof');
  378. assert(summary.proofGapOperatorPack?.proofLevel === 'not_business_proof', 'proof gap operator pack should keep not-business-proof level');
  379. assert(summary.proofGapOperatorPack?.openCount >= 1, 'proof gap operator pack should expose open count');
  380. assert(summary.proofGapOperatorPack?.rowCount === 5, 'proof gap operator pack should expose five rows');
  381. assert(summary.proofGapOperatorPack?.ownerGroupCount >= operatorOwnerArtifactCount, 'proof gap operator pack should expose owner group count');
  382. assert(summary.proofGapOperatorPack?.ownerArtifactCount === operatorOwnerArtifactCount, 'proof gap operator pack should expose owner artifact count');
  383. assert(Array.isArray(summary.proofGapOperatorPack?.ownerArtifacts) && summary.proofGapOperatorPack.ownerArtifacts.length === operatorOwnerArtifactCount, 'proof gap operator pack should expose owner artifacts');
  384. assert(summary.proofGapOperatorPack.ownerArtifacts.some(item => item.owner === '商务' && item.markdown?.includes('by-owner/business.md') && item.csv?.includes('by-owner/business.csv')), 'proof gap operator pack should expose business owner files');
  385. assert(summary.proofGapOperatorPack.ownerArtifacts.some(item => item.owner === '技术/AI' && item.markdown?.includes('by-owner/tech-ai.md') && item.csv?.includes('by-owner/tech-ai.csv')), 'proof gap operator pack should expose tech/AI owner files');
  386. if (summary.proofGapOperatorPack.ownerArtifacts.some(item => item.owner === '商务/投放')) {
  387. assert(summary.proofGapOperatorPack.ownerArtifacts.some(item => item.markdown?.includes('by-owner/business-media.md') && item.csv?.includes('by-owner/business-media.csv')), 'proof gap operator pack should expose business/media owner files when present');
  388. }
  389. assert(summary.proofGapOperatorPack?.recheckCommandCount >= 5, 'proof gap operator pack should expose recheck command count');
  390. assert(summary.proofGapOperatorPack?.evidenceRowCount === 5, 'proof gap operator pack should expose evidence row count');
  391. assert(summary.proofGapOperatorPack?.nextActionCount === 5, 'proof gap operator pack should expose next action count');
  392. assert(summary.proofGapOperatorPack?.missingProofRequirementCount >= 1, 'proof gap operator pack should expose missing proof requirement count');
  393. assert(Array.isArray(summary.proofGapOperatorPack?.gapIds) && summary.proofGapOperatorPack.gapIds.includes('historical-dataset') && summary.proofGapOperatorPack.gapIds.includes('manual-review-and-customer-effect'), 'proof gap operator pack should expose stable gap ids');
  394. assert(summary.latestFormIndex.proofGapOperatorPack?.markdown?.includes('proof-gap-operator-pack.md'), 'handoff should carry latest form proof gap operator pack report');
  395. assert(summary.latestFormIndex.proofGapOperatorPack?.ownerArtifactCount === operatorOwnerArtifactCount, 'handoff should carry latest form proof gap operator owner artifact count');
  396. assert(summary.latestFormIndex.proofGapOperatorPack?.ownerArtifacts?.some(item => item.csv?.includes('by-owner/business.csv')), 'handoff should carry latest form business owner operator csv');
  397. assert(summary.latestFormIndex.proofGapOperatorPack?.recheckCommandCount >= 5, 'handoff should carry latest form proof gap operator recheck count');
  398. assert(summary.latestFormIndex.proofGapOperatorPack?.evidenceRowCount === 5, 'handoff should carry latest form proof gap operator evidence row count');
  399. assert(summary.latestFormIndex.proofGapOperatorPack?.nextActionCount === 5, 'handoff should carry latest form proof gap operator next action count');
  400. assert(summary.realProofIntakeBundle?.markdown?.includes('real-proof-intake-bundle.md'), 'handoff should include real proof intake bundle report');
  401. assert(summary.realProofIntakeBundle?.csv?.includes('real-proof-intake-bundle.csv'), 'handoff should include real proof intake bundle csv');
  402. assert(summary.realProofIntakeBundle?.directCustomerProof === false, 'real proof intake bundle should not be direct customer proof');
  403. assert(summary.realProofIntakeBundle?.proofLevel === 'not_business_proof', 'real proof intake bundle should keep not-business-proof level');
  404. assert(summary.realProofIntakeBundle?.proofOpenCount >= 1, 'real proof intake bundle should expose open proof count');
  405. assert(summary.realProofIntakeBundle?.fillRowCount >= 3, 'real proof intake bundle should expose fill rows');
  406. assert(summary.realProofIntakeBundle?.operatorPackEvidenceRowCount === 5, 'real proof intake bundle should expose operator pack evidence row count');
  407. assert(summary.realProofIntakeBundle?.operatorPackNextActionCount === 5, 'real proof intake bundle should expose operator pack next action count');
  408. assert(summary.realProofIntakeBundle?.operatorPackMissingProofRequirementCount >= 1, 'real proof intake bundle should expose operator pack missing proof requirement count');
  409. assert(summary.realProofIntakeBundle?.operatorPackOwnerArtifactCount === operatorOwnerArtifactCount, 'real proof intake bundle should expose operator pack owner artifact count');
  410. assert(summary.realProofIntakeBundle?.operatorPackOwnerArtifacts?.some(item => item.csv?.includes('by-owner/business.csv')), 'real proof intake bundle should expose business operator owner csv');
  411. assert(summary.latestFormIndex.realProofIntakeBundle?.operatorPackEvidenceRowCount === 5, 'handoff should carry latest form real proof bundle operator evidence row count');
  412. assert(summary.latestFormIndex.realProofIntakeBundle?.operatorPackNextActionCount === 5, 'handoff should carry latest form real proof bundle operator next action count');
  413. assert(summary.latestFormIndex.realProofIntakeBundle?.operatorPackOwnerArtifactCount === operatorOwnerArtifactCount, 'handoff should carry latest form real proof bundle operator owner artifact count');
  414. if (summary.latestFormIndex.realProofIntakeBundle?.operatorPackOwnerArtifacts?.some(item => item.owner === '商务/投放')) {
  415. assert(summary.latestFormIndex.realProofIntakeBundle.operatorPackOwnerArtifacts.some(item => item.markdown?.includes('by-owner/business-media.md')), 'handoff should carry latest form real proof bundle business/media owner markdown when present');
  416. }
  417. assert(summary.latestFormIndex.repairActions?.dataIntake?.csv?.includes('intake-readiness-repair-actions.csv'), 'handoff should record data intake repair actions csv');
  418. assert(summary.latestFormIndex.repairActions?.videoResource?.csv?.includes('video-resource-repair-actions.csv'), 'handoff should record video repair actions csv');
  419. assert(summary.optimizationPipeline && summary.optimizationPipeline.summary.includes('optimization-pipeline-summary.json'), 'handoff should include optimization pipeline summary');
  420. assert(Array.isArray(summary.optimizationPipeline.nextActions), 'handoff should expose optimization pipeline next actions');
  421. assert(report.includes('# 提号优化交接摘要'), 'handoff report should be Chinese');
  422. assert(report.includes('## 补证操作顺序'), 'handoff report should include proof action order section');
  423. assert(report.includes('## 补证进度表'), 'handoff report should include proof progress section');
  424. assert(report.includes('business-proof-progress-summary.json'), 'handoff report should include business proof progress summary');
  425. assert(report.includes(`operatorPackOwnerArtifactCount: ${operatorOwnerArtifactCount}`), 'handoff report should include business proof progress operator owner attachment count');
  426. assert(report.includes('blocked_by_external_data'), 'handoff report should include business proof progress blocker count');
  427. assert(report.includes('by-owner/business.csv'), 'handoff report should include business proof progress business owner csv');
  428. assert(report.includes('## 下一步行动队列'), 'handoff report should include next actions section');
  429. assert(report.includes(`actionCount: ${nextActionCount}`), 'handoff report should include next action count');
  430. assert(report.includes('proofGapOpenCount'), 'handoff report should include next action source proof gap open count');
  431. assert(report.includes('pipelineStale: true'), 'handoff report should include stale pipeline state');
  432. assert(report.includes(`pipelineSuppressedNextActionCount: ${nextActionSourceState.pipelineSuppressedNextActionCount}`), 'handoff report should include suppressed stale pipeline action count');
  433. assert(report.includes('pipelineStaleReason:'), 'handoff report should include stale pipeline reason');
  434. assert(report.includes('step-03'), 'handoff report should include top historical data action id');
  435. assert(report.includes('gap-video-ab-live-proof'), 'handoff report should include top video A/B action id');
  436. assert(report.includes('acceptance:video-ab'), 'handoff report should include top video A/B command');
  437. assert(report.includes('## Pipeline stale 摘要'), 'handoff report should include top-level pipeline stale summary section');
  438. assert(report.includes(`pipelineRawNextActionCount: ${pipelineStaleSummary.pipelineRawNextActionCount}`), 'handoff report should include raw stale pipeline action count');
  439. assert(report.includes(`pipelineStaleMissingProofRequirementCount: ${pipelineStaleSummary.pipelineStaleMissingProofRequirementCount}`), 'handoff report should include stale pipeline missing proof count');
  440. assert(report.includes('## 负责人行动文件'), 'handoff report should include owner action artifact section');
  441. assert(report.includes('## 商务执行总表'), 'handoff report should include business execution index section');
  442. assert(report.includes('business-execution-index.csv'), 'handoff report should include business execution index csv');
  443. assert(report.includes('businessExecutionOperatorPackOwnerArtifactRowCount'), 'handoff report should include plan execution operator owner attachment row count');
  444. assert(report.includes(`operatorPackOwnerArtifactCount:${operatorOwnerArtifactCount}`), 'handoff report should include business execution operator owner artifact count');
  445. assert(report.includes('## 真实证明缺口关闭状态'), 'handoff report should include proof gap closure status section');
  446. assert(report.includes('businessOpenCount'), 'handoff report should include business proof gap open count semantics');
  447. assert(report.includes('precheckOpenCount'), 'handoff report should include precheck proof gap count separately');
  448. assert(report.includes(`operatorPackOwnerArtifactCount: ${operatorOwnerArtifactCount}`), 'handoff report should include real proof bundle operator owner artifact count');
  449. assert(report.includes('by-owner/tech-ai.csv'), 'handoff report should include real proof bundle tech/AI owner csv');
  450. assert(report.includes('proofContext.mode=live'), 'handoff report should include video A/B missing proof requirement');
  451. assert(report.includes('## 反馈二轮闭环审计'), 'handoff report should include feedback loop closure section');
  452. if (summary.feedbackLoopClosure) {
  453. assert(report.includes('feedback-loop-closure-summary.json'), 'handoff report should include feedback loop closure summary');
  454. }
  455. assert(report.includes('## 主页近期内容证据就绪审计'), 'handoff report should include homepage evidence readiness section');
  456. if (summary.homepageEvidenceReadiness) {
  457. assert(report.includes('homepage-evidence-readiness-summary.json'), 'handoff report should include homepage evidence readiness summary');
  458. assert(report.includes('homepage-evidence-repair-actions.csv'), 'handoff report should include homepage evidence repair csv');
  459. }
  460. assert(report.includes('## 长跑前置门禁'), 'handoff report should include long-run readiness section');
  461. assert(report.includes('long-run-readiness-report.md'), 'handoff report should include long-run readiness report');
  462. assert(report.includes('ready=false'), 'handoff report should include long-run ready=false boundary');
  463. assert(report.includes('## 优化完成度审计'), 'handoff report should include optimization completion section');
  464. assert(report.includes('optimization-completion-report.md'), 'handoff report should include optimization completion report');
  465. assert(report.includes('readyForClaim: false'), 'handoff report should include readyForClaim=false boundary');
  466. assert(report.includes('missingProofRequirementCount:'), 'handoff report should include optimization completion missing proof requirement count');
  467. assert(report.includes('videoAbPreflightReadyForVideoAb:'), 'handoff report should include optimization completion video A/B preflight readiness');
  468. assert(report.includes('videoAbPreflightFailureCount:'), 'handoff report should include optimization completion video A/B preflight failure count');
  469. assert(report.includes('optimizationCompletion.readyForClaim'), 'handoff business execution section should include optimization completion readyForClaim');
  470. assert(report.includes('optimizationCompletion.videoAbPreflightFailureCount'), 'handoff business execution section should include optimization completion video A/B preflight failure count');
  471. assert(report.includes('### 优化完成度审计'), 'handoff latest form section should include optimization completion subsection');
  472. assert(report.includes('videoAbPreflightFailureCount:'), 'handoff report should show latest form optimization video A/B preflight failure count');
  473. assert(report.includes('directCustomerProof'), 'handoff report should show feedback loop proof boundary');
  474. assert(report.includes('## 最新提号补证表单'), 'handoff report should include latest proof gap software form section');
  475. assert(report.includes('## 真实证明缺口请求包'), 'handoff report should include proof gap request section');
  476. assert(report.includes('proof-gap-request-report.md'), 'handoff report should include proof gap request report');
  477. assert(report.includes('## 提号补证操作包'), 'handoff report should include proof gap operator pack section');
  478. assert(report.includes('proof-gap-operator-pack.md'), 'handoff report should include proof gap operator pack report');
  479. assert(report.includes('gapIds: historical-dataset'), 'handoff report should include proof gap operator stable ids');
  480. assert(report.includes(`ownerArtifactCount: ${operatorOwnerArtifactCount}`), 'handoff report should include proof gap operator owner artifact count');
  481. assert(report.includes('by-owner/business.csv'), 'handoff report should include business owner operator csv');
  482. if (summary.proofGapOperatorPack.ownerArtifacts.some(item => item.owner === '商务/投放')) {
  483. assert(report.includes('by-owner/business-media.md'), 'handoff report should include business/media owner operator markdown when present');
  484. }
  485. assert(report.includes('recheckCommandCount'), 'handoff report should include proof gap operator recheck count');
  486. assert(report.includes('evidenceRowCount: 5'), 'handoff report should include proof gap operator evidence row count');
  487. assert(report.includes('nextActionCount: 5'), 'handoff report should include proof gap operator next action count');
  488. assert(report.includes('tihao-proof-gap-software-form.csv'), 'handoff report should list latest proof gap software form csv');
  489. assert(report.includes('passed:true'), 'handoff report should show latest proof gap software form pass state');
  490. assert(report.includes('rowCount:5'), 'handoff report should show latest proof gap software form row count');
  491. assert(report.includes('## 真实材料字段级补齐核对表'), 'handoff report should include intake field checklist section');
  492. assert(report.includes('intake-field-checklist.csv'), 'handoff report should include intake field checklist csv');
  493. assert(report.includes('### 按负责人补齐顺序'), 'handoff report should include field checklist owner group section');
  494. assert(report.includes('history-data-template.csv'), 'handoff report should include history template from owner groups');
  495. assert(report.includes('### 补齐后复验命令'), 'handoff report should include field checklist recheck command section');
  496. assert(report.includes('npm run intake:readiness'), 'handoff report should include intake readiness recheck command');
  497. assert(report.includes('npm run acceptance:video-ab'), 'handoff report should include video A/B recheck command');
  498. assert(report.includes('## 最新提号表单索引'), 'handoff report should include latest form index section');
  499. assert(report.includes('latest-form-index.md'), 'handoff report should list latest form index report');
  500. assert(report.includes('software-client-list.final.csv'), 'handoff report should list indexed software client list');
  501. assert(report.includes('## 本地种子转 intake 补表 worklist'), 'handoff report should include top-level seed-to-intake worklist section');
  502. assert(report.includes('### 本地种子转 intake 补表 worklist'), 'handoff report should include latest form seed-to-intake worklist section');
  503. assert(report.includes('local-seed-to-intake-worklist.md'), 'handoff report should include seed-to-intake worklist report');
  504. assert(report.includes('history-intake-draft.csv'), 'handoff report should include seed-to-intake history draft');
  505. assert(report.includes('video-resource-worklist.csv'), 'handoff report should include seed-to-intake video worklist');
  506. assert(report.includes('candidate-seed-worklist.csv'), 'handoff report should include seed-to-intake candidate worklist');
  507. assert(report.includes('seed_to_intake_worklist'), 'handoff report should include seed-to-intake material type');
  508. assert(report.includes('## 真实证明闭环工单'), 'handoff report should include top-level real proof closure work order section');
  509. assert(report.includes('### 真实证明闭环工单'), 'handoff report should include latest form real proof closure work order section');
  510. assert(report.includes('real-proof-closure-work-order.md'), 'handoff report should include real proof closure work order report');
  511. assert(report.includes(`workOrderCount: ${workOrderCount}`), 'handoff report should include real proof closure work order count');
  512. assert(report.includes('canCloseProofGap: false'), 'handoff report should include real proof closure work order boundary');
  513. if (summary.realProofClosureWorkOrder?.ownerArtifacts?.some(item => item.owner === '商务/投放')) {
  514. assert(report.includes('by-owner/business-media.csv'), 'handoff report should include real proof closure work order business/media csv when present');
  515. }
  516. assert(report.includes('by-owner/tech-ai.md'), 'handoff report should include real proof closure work order tech owner markdown');
  517. assert(report.includes('ownerGroupCount'), 'handoff report should include latest form field owner group count');
  518. assert(report.includes('recheckCommandCount'), 'handoff report should include latest form recheck command count');
  519. assert(report.includes('manual-review-label-guide.md'), 'handoff report should include manual review label guide');
  520. assert(report.includes('plan-execution-status.md'), 'handoff report should include plan execution status');
  521. assert(report.includes('### 长跑前置门禁'), 'handoff report should include latest form long-run readiness section');
  522. assert(report.includes('## 真实验收材料收集总包'), 'handoff report should include real proof intake bundle section');
  523. assert(report.includes('real-proof-intake-bundle.md'), 'handoff report should include real proof intake bundle report');
  524. assert(report.includes('operatorPackEvidenceRowCount: 5'), 'handoff report should include real proof bundle operator evidence row count');
  525. assert(report.includes('operatorPackNextActionCount: 5'), 'handoff report should include real proof bundle operator next action count');
  526. assert(report.includes('## 闀挎湡璁″垝鎵ц鐘舵€侀潰鏉?') || report.includes('## 长期计划执行状态面板'), 'handoff report should include plan execution status section');
  527. assert(report.includes('人工复核标签与归因指南'), 'handoff report should include manual review label guide section');
  528. assert(report.includes('### 商务填表执行视图'), 'handoff report should include business fill guide section');
  529. assert(report.includes('本视图只帮助商务按表补齐真实材料'), 'handoff report should include business fill guide guardrail');
  530. assert(report.includes('### 修复清单入口'), 'handoff report should include latest form repair action section');
  531. assert(report.includes('intake-readiness-repair-actions.csv'), 'handoff report should include data intake repair action csv');
  532. assert(report.includes('video-resource-repair-actions.csv'), 'handoff report should include video resource repair action csv');
  533. assert(report.includes('by-owner/'), 'handoff report should list owner action artifact files');
  534. assert(report.includes('修复附件'), 'handoff report should show owner repair artifact column');
  535. assert(report.includes('## Pipeline 下一步动作'), 'handoff report should include pipeline next actions section');
  536. assert(report.includes('readyForClaim'), 'handoff report should include pipeline readyForClaim status');
  537. assert(report.includes('收集包一键审计 Pipeline'), 'handoff report should include data/video pack pipeline command');
  538. assert(report.includes('真实验收材料收集总包'), 'handoff report should include real proof intake bundle command/section');
  539. assert(report.includes('## 中文 SOP 入口'), 'handoff report should include Chinese SOP section');
  540. assert(report.includes('tihao-experience-optimization-plan.zh-CN.md'), 'handoff report should link Chinese optimization plan document');
  541. assert(report.includes('tihao-experience-optimization-plan.md'), 'handoff report should link main optimization plan document');
  542. assert(report.includes('business-proof-action-order.zh-CN.md'), 'handoff report should link Chinese proof action order document');
  543. assert(report.includes('tihao-handoff-index.md'), 'handoff report should link task handoff index document');
  544. assert(report.includes('不能把 sample、smoke、dry-run'), 'handoff report should include proof boundary');
  545. assert(!/sk-[A-Za-z0-9_-]{10,}/.test(report), 'handoff report should not contain model token patterns');
  546. assert(!/r:[A-Za-z0-9]{20,}/.test(report), 'handoff report should not contain Parse sessionToken patterns');
  547. assert(!/Authorization/i.test(report), 'handoff report should not expose Authorization text');
  548. console.log(JSON.stringify({
  549. ok: true,
  550. outputDir,
  551. readyCapabilities: summary.readyCapabilities.length,
  552. readyNotProven: summary.readyNotProven.length,
  553. externalBlockers: summary.externalBlockers.length
  554. }, null, 2));
  555. }
  556. function assert(condition, message) {
  557. if (!condition) throw new Error(message);
  558. }
  559. main();