optimization-status-audit.js 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const path = require('path');
  4. function main() {
  5. const root = path.resolve(__dirname, '..');
  6. const args = parseArgs(process.argv.slice(2));
  7. const outputDir = path.resolve(args.output || process.env.TIHAO_STATUS_OUTPUT || path.join(root, 'outputs', `optimization-status-${Date.now()}`));
  8. const summary = buildStatusSummary(root);
  9. fs.mkdirSync(outputDir, { recursive: true });
  10. const jsonPath = path.join(outputDir, 'optimization-status-summary.json');
  11. const mdPath = path.join(outputDir, 'optimization-status-report.md');
  12. fs.writeFileSync(jsonPath, JSON.stringify(summary, null, 2), 'utf8');
  13. fs.writeFileSync(mdPath, withBom(renderReport(summary)), 'utf8');
  14. console.log(JSON.stringify({
  15. outputDir,
  16. json: jsonPath,
  17. report: mdPath,
  18. counts: summary.counts,
  19. complete: summary.complete
  20. }, null, 2));
  21. }
  22. function parseArgs(argv) {
  23. const result = {};
  24. for (let index = 0; index < argv.length; index += 1) {
  25. const arg = argv[index];
  26. if (!arg.startsWith('--')) continue;
  27. const key = arg.slice(2).replace(/-([a-z])/g, (_, char) => char.toUpperCase());
  28. const next = argv[index + 1];
  29. if (!next || next.startsWith('--')) {
  30. result[key] = true;
  31. } else {
  32. result[key] = next;
  33. index += 1;
  34. }
  35. }
  36. return result;
  37. }
  38. function buildStatusSummary(root) {
  39. const state = collectState(root);
  40. const items = buildStatusItems(state);
  41. return {
  42. generatedAt: new Date().toISOString(),
  43. root,
  44. counts: countStatuses(items),
  45. complete: items.every(item => item.status === 'passed'),
  46. items
  47. };
  48. }
  49. function collectState(root) {
  50. const packageJson = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
  51. const scripts = packageJson.scripts || {};
  52. const files = fileSet(root, [
  53. 'scripts/live-preflight.js',
  54. 'scripts/long-run-readiness-audit.js',
  55. 'scripts/long-run-readiness-smoke.js',
  56. 'scripts/optimization-pipeline.js',
  57. 'scripts/optimization-pipeline-smoke.js',
  58. 'scripts/optimization-completion-audit.js',
  59. 'scripts/optimization-completion-smoke.js',
  60. 'scripts/generate-optimization-handoff.js',
  61. 'scripts/generate-optimization-handoff-smoke.js',
  62. 'scripts/generate-proof-gap-request.js',
  63. 'scripts/generate-proof-gap-request-smoke.js',
  64. 'scripts/proof-gap-closure-audit.js',
  65. 'scripts/proof-gap-closure-smoke.js',
  66. 'scripts/business-proof-progress.js',
  67. 'scripts/business-proof-progress-smoke.js',
  68. 'scripts/business-proof-next-actions.js',
  69. 'scripts/business-proof-next-actions-smoke.js',
  70. 'scripts/latest-form-index.js',
  71. 'scripts/latest-form-index-smoke.js',
  72. 'scripts/round-deposition-audit.js',
  73. 'scripts/round-deposition-smoke.js',
  74. 'scripts/evidence-index.js',
  75. 'scripts/evidence-index-smoke.js',
  76. 'scripts/acceptance.js',
  77. 'scripts/smoke-package.js',
  78. 'scripts/plan-consistency-smoke.js',
  79. 'scripts/hard-rules-smoke.js',
  80. 'scripts/reference-rules-smoke.js',
  81. 'scripts/platform-sourcing-strategy-smoke.js',
  82. 'scripts/overnight-quality-campaign.js',
  83. 'scripts/overnight-manual-review-smoke.js',
  84. 'scripts/leak-mojibake-audit.js',
  85. 'scripts/video-hit-rate-audit.js',
  86. 'scripts/video-hit-rate-audit-smoke.js',
  87. 'scripts/review-metrics.js',
  88. 'scripts/customer-effect-audit.js',
  89. 'scripts/customer-effect-audit-smoke.js',
  90. 'scripts/create-data-intake-pack.js',
  91. 'scripts/create-data-intake-pack-smoke.js',
  92. 'scripts/intake-readiness-audit.js',
  93. 'scripts/intake-readiness-smoke.js',
  94. 'scripts/create-video-intake-pack.js',
  95. 'scripts/create-video-intake-pack-smoke.js',
  96. 'scripts/video-resource-readiness-audit.js',
  97. 'scripts/video-resource-readiness-smoke.js',
  98. 'scripts/homepage-evidence-readiness-audit.js',
  99. 'scripts/homepage-evidence-readiness-smoke.js',
  100. 'scripts/history-dataset-from-csv.js',
  101. 'scripts/history-dataset-from-csv-smoke.js',
  102. 'scripts/historical-dataset-audit.js',
  103. 'scripts/candidate-pool-smoke.js',
  104. 'scripts/feedback-roundtrip-smoke.js',
  105. 'scripts/feedback-loop-closure-audit.js',
  106. 'scripts/feedback-loop-closure-smoke.js',
  107. 'docs/historical-dataset-guide.md',
  108. 'docs/ai-overnight-optimization-task.md',
  109. 'docs/ai-optimization-job-dedup-software-format.md',
  110. 'docs/overnight-quality-runbook.md',
  111. 'docs/manual-review-handoff.md',
  112. 'mcp/src/features/tihao-sourcing/homepage-evidence-provider.js',
  113. 'mcp/src/features/tihao-sourcing/preference-memory.js',
  114. 'mcp/src/features/tihao-sourcing/report.js'
  115. ]);
  116. const packageText = JSON.stringify(packageJson);
  117. const overnightText = readOptional(path.join(root, 'scripts/overnight-quality-campaign.js'));
  118. const overnightReviewSmokeText = readOptional(path.join(root, 'scripts/overnight-manual-review-smoke.js'));
  119. const reviewMetricsText = readOptional(path.join(root, 'scripts/review-metrics.js'));
  120. const reviewMetricsSmokeText = readOptional(path.join(root, 'scripts/review-metrics-smoke.js'));
  121. const customerEffectAuditText = readOptional(path.join(root, 'scripts/customer-effect-audit.js'));
  122. const customerEffectSmokeText = readOptional(path.join(root, 'scripts/customer-effect-audit-smoke.js'));
  123. const dataIntakePackText = readOptional(path.join(root, 'scripts/create-data-intake-pack.js'));
  124. const dataIntakePackSmokeText = readOptional(path.join(root, 'scripts/create-data-intake-pack-smoke.js'));
  125. const intakeReadinessText = readOptional(path.join(root, 'scripts/intake-readiness-audit.js'));
  126. const intakeReadinessSmokeText = readOptional(path.join(root, 'scripts/intake-readiness-smoke.js'));
  127. const videoIntakePackText = readOptional(path.join(root, 'scripts/create-video-intake-pack.js'));
  128. const videoIntakePackSmokeText = readOptional(path.join(root, 'scripts/create-video-intake-pack-smoke.js'));
  129. const videoResourceReadinessText = readOptional(path.join(root, 'scripts/video-resource-readiness-audit.js'));
  130. const videoResourceReadinessSmokeText = readOptional(path.join(root, 'scripts/video-resource-readiness-smoke.js'));
  131. const historyFromCsvText = readOptional(path.join(root, 'scripts/history-dataset-from-csv.js'));
  132. const historyFromCsvSmokeText = readOptional(path.join(root, 'scripts/history-dataset-from-csv-smoke.js'));
  133. const memoryText = readOptional(path.join(root, 'mcp/src/features/tihao-sourcing/preference-memory.js'));
  134. const reportText = readOptional(path.join(root, 'mcp/src/features/tihao-sourcing/report.js'));
  135. const smokePackageText = readOptional(path.join(root, 'scripts/smoke-package.js'));
  136. const planConsistencyText = readOptional(path.join(root, 'scripts/plan-consistency-smoke.js'));
  137. const candidatePoolSmokeText = readOptional(path.join(root, 'scripts/candidate-pool-smoke.js'));
  138. const hygieneSmokeText = readOptional(path.join(root, 'scripts/leak-mojibake-audit.js'));
  139. const hardRulesSmokeText = readOptional(path.join(root, 'scripts/hard-rules-smoke.js'));
  140. const homepageSmokeText = readOptional(path.join(root, 'scripts/homepage-evidence-provider-smoke.js'));
  141. const homepageReadinessText = readOptional(path.join(root, 'scripts/homepage-evidence-readiness-audit.js'));
  142. const homepageReadinessSmokeText = readOptional(path.join(root, 'scripts/homepage-evidence-readiness-smoke.js'));
  143. const referenceRulesSmokeText = readOptional(path.join(root, 'scripts/reference-rules-smoke.js'));
  144. const platformStrategySmokeText = readOptional(path.join(root, 'scripts/platform-sourcing-strategy-smoke.js'));
  145. const feedbackRoundtripSmokeText = readOptional(path.join(root, 'scripts/feedback-roundtrip-smoke.js'));
  146. const feedbackLoopClosureText = readOptional(path.join(root, 'scripts/feedback-loop-closure-audit.js'));
  147. const feedbackLoopClosureSmokeText = readOptional(path.join(root, 'scripts/feedback-loop-closure-smoke.js'));
  148. const providerContractText = readOptional(path.join(root, 'scripts/provider-contract-audit.js'));
  149. const longRunReadinessText = readOptional(path.join(root, 'scripts/long-run-readiness-audit.js'));
  150. const longRunReadinessSmokeText = readOptional(path.join(root, 'scripts/long-run-readiness-smoke.js'));
  151. const optimizationPipelineText = readOptional(path.join(root, 'scripts/optimization-pipeline.js'));
  152. const optimizationPipelineSmokeText = readOptional(path.join(root, 'scripts/optimization-pipeline-smoke.js'));
  153. const optimizationCompletionText = readOptional(path.join(root, 'scripts/optimization-completion-audit.js'));
  154. const optimizationCompletionSmokeText = readOptional(path.join(root, 'scripts/optimization-completion-smoke.js'));
  155. const handoffText = readOptional(path.join(root, 'scripts/generate-optimization-handoff.js'));
  156. const handoffSmokeText = readOptional(path.join(root, 'scripts/generate-optimization-handoff-smoke.js'));
  157. const proofGapText = readOptional(path.join(root, 'scripts/generate-proof-gap-request.js'));
  158. const proofGapSmokeText = readOptional(path.join(root, 'scripts/generate-proof-gap-request-smoke.js'));
  159. const proofGapClosureText = readOptional(path.join(root, 'scripts/proof-gap-closure-audit.js'));
  160. const proofGapClosureSmokeText = readOptional(path.join(root, 'scripts/proof-gap-closure-smoke.js'));
  161. const businessProofProgressText = readOptional(path.join(root, 'scripts/business-proof-progress.js'));
  162. const businessProofProgressSmokeText = readOptional(path.join(root, 'scripts/business-proof-progress-smoke.js'));
  163. const businessProofNextActionsText = readOptional(path.join(root, 'scripts/business-proof-next-actions.js'));
  164. const businessProofNextActionsSmokeText = readOptional(path.join(root, 'scripts/business-proof-next-actions-smoke.js'));
  165. const latestFormIndexText = readOptional(path.join(root, 'scripts/latest-form-index.js'));
  166. const latestFormIndexSmokeText = readOptional(path.join(root, 'scripts/latest-form-index-smoke.js'));
  167. const roundDepositionText = readOptional(path.join(root, 'scripts/round-deposition-audit.js'));
  168. const roundDepositionSmokeText = readOptional(path.join(root, 'scripts/round-deposition-smoke.js'));
  169. const evidenceIndexText = readOptional(path.join(root, 'scripts/evidence-index.js'));
  170. const evidenceIndexSmokeText = readOptional(path.join(root, 'scripts/evidence-index-smoke.js'));
  171. const videoHitRateAuditText = readOptional(path.join(root, 'scripts/video-hit-rate-audit.js'));
  172. const videoHitRateAuditSmokeText = readOptional(path.join(root, 'scripts/video-hit-rate-audit-smoke.js'));
  173. const historyAuditText = readOptional(path.join(root, 'scripts/historical-dataset-audit.js'));
  174. const historyAuditSmokeText = readOptional(path.join(root, 'scripts/historical-dataset-audit-smoke.js'));
  175. const docsText = [
  176. 'docs/tihao-experience-implementation-log.md',
  177. 'docs/ai-overnight-optimization-task.md',
  178. 'docs/overnight-quality-runbook.md',
  179. 'docs/historical-dataset-guide.md',
  180. 'docs/manual-review-handoff.md',
  181. 'docs/capability-map.md',
  182. 'README.md'
  183. ].map(file => readOptional(path.join(root, file))).join('\n');
  184. return {
  185. root,
  186. scripts,
  187. files,
  188. packageText,
  189. overnightText,
  190. overnightReviewSmokeText,
  191. reviewMetricsText,
  192. reviewMetricsSmokeText,
  193. customerEffectAuditText,
  194. customerEffectSmokeText,
  195. dataIntakePackText,
  196. dataIntakePackSmokeText,
  197. intakeReadinessText,
  198. intakeReadinessSmokeText,
  199. videoIntakePackText,
  200. videoIntakePackSmokeText,
  201. videoResourceReadinessText,
  202. videoResourceReadinessSmokeText,
  203. historyFromCsvText,
  204. historyFromCsvSmokeText,
  205. memoryText,
  206. reportText,
  207. smokePackageText,
  208. planConsistencyText,
  209. candidatePoolSmokeText,
  210. hygieneSmokeText,
  211. hardRulesSmokeText,
  212. homepageSmokeText,
  213. homepageReadinessText,
  214. homepageReadinessSmokeText,
  215. referenceRulesSmokeText,
  216. platformStrategySmokeText,
  217. feedbackRoundtripSmokeText,
  218. feedbackLoopClosureText,
  219. feedbackLoopClosureSmokeText,
  220. providerContractText,
  221. longRunReadinessText,
  222. longRunReadinessSmokeText,
  223. optimizationPipelineText,
  224. optimizationPipelineSmokeText,
  225. optimizationCompletionText,
  226. optimizationCompletionSmokeText,
  227. handoffText,
  228. handoffSmokeText,
  229. proofGapText,
  230. proofGapSmokeText,
  231. proofGapClosureText,
  232. proofGapClosureSmokeText,
  233. businessProofProgressText,
  234. businessProofProgressSmokeText,
  235. businessProofNextActionsText,
  236. businessProofNextActionsSmokeText,
  237. latestFormIndexText,
  238. latestFormIndexSmokeText,
  239. roundDepositionText,
  240. roundDepositionSmokeText,
  241. evidenceIndexText,
  242. evidenceIndexSmokeText,
  243. videoHitRateAuditText,
  244. videoHitRateAuditSmokeText,
  245. historyAuditText,
  246. historyAuditSmokeText,
  247. docsText,
  248. outputs: findOutputEvidence(root)
  249. };
  250. }
  251. function buildStatusItems(state) {
  252. const liveProof = hasLiveProof(state.outputs);
  253. const customerEffectProof = hasCustomerEffectProof(state.outputs);
  254. return [
  255. item({
  256. id: 'release-chain',
  257. requirement: '基础发布链路',
  258. status: hasScripts(state, ['acceptance', 'smoke:package', 'mcp:smoke', 'acceptance:providers:mock']) &&
  259. state.files['scripts/acceptance.js'] &&
  260. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("acceptance:providers:mock") &&
  261. state.smokePackageText.includes('assertFriendlyAuthState') &&
  262. state.smokePackageText.includes('should not expose raw 401/403') &&
  263. state.providerContractText.includes('assertProviderFailurePublicOutputSafe') &&
  264. state.providerContractText.includes('provider failure public output should not expose raw 401/403')
  265. ? 'passed'
  266. : 'missing',
  267. evidence: 'package.json scripts include acceptance/smoke/mcp/provider mock gates; acceptance.js calls provider mock; smoke-package and provider mock cover friendly auth states',
  268. next: '继续用 npm run acceptance 做发布前验收。'
  269. }),
  270. item({
  271. id: 'software-table',
  272. requirement: '软件端表格固定表头、去重和连续排名',
  273. status: state.reportText.includes('SOFTWARE_TABLE_HEADER') &&
  274. state.reportText.includes('dedupeCandidates') &&
  275. state.reportText.includes('getReviewPoolTarget') &&
  276. state.reportText.includes('1.5 倍候选池') &&
  277. state.scripts['candidate-pool:smoke'] &&
  278. state.files['scripts/candidate-pool-smoke.js'] &&
  279. state.candidatePoolSmokeText.includes('duplicate software row key') &&
  280. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("candidate-pool:smoke")
  281. ? 'passed'
  282. : 'missing',
  283. evidence: 'report.js contains fixed software header, candidate dedupe, 1.5x review-pool target, and candidate-pool smoke is in default acceptance',
  284. next: '真实交付前用 review:metrics 和 CSV 抽检验证重复键、排名连续和候选池规模。'
  285. }),
  286. item({
  287. id: 'output-hygiene',
  288. requirement: '输出泄密和中文乱码自动化门禁',
  289. status: state.scripts['hygiene:smoke'] &&
  290. state.files['scripts/leak-mojibake-audit.js'] &&
  291. state.hygieneSmokeText.includes('parse-session-token') &&
  292. state.hygieneSmokeText.includes('common-mojibake') &&
  293. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("hygiene:smoke")
  294. ? 'passed'
  295. : 'missing',
  296. evidence: 'hygiene:smoke scans docs, README, scripts, mcp/src, skills, and fixtures for token-like secrets and common mojibake patterns; default acceptance runs it',
  297. next: '每次发布前继续运行 npm run acceptance,确保输出不含 token 和乱码。'
  298. }),
  299. item({
  300. id: 'plan-consistency',
  301. requirement: '长期计划、AI 任务书和 skill 输出规范口径一致',
  302. status: state.scripts['plan:consistency:smoke'] &&
  303. state.files['scripts/plan-consistency-smoke.js'] &&
  304. state.files['docs/ai-optimization-job-dedup-software-format.md'] &&
  305. state.planConsistencyText.includes('legacy-dedupe-phrase') &&
  306. state.planConsistencyText.includes('softwareTableHeader') &&
  307. state.planConsistencyText.includes('unexpected-task-count') &&
  308. state.planConsistencyText.includes('全局博主唯一') &&
  309. state.planConsistencyText.includes('docs/tihao-experience-optimization-plan.md') &&
  310. state.planConsistencyText.includes('docs/ai-optimization-job-dedup-software-format.md') &&
  311. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("plan:consistency:smoke")
  312. ? 'passed'
  313. : 'missing',
  314. evidence: 'plan:consistency:smoke verifies current plan, software table doc, AI task table, dedup software-format task table, and skill output-format all use global creator dedupe; it also checks fixed 17-column software headers, 9 unique deduped OPT rows, and blocks legacy brief-scoped dedupe phrases',
  315. next: '后续改 SOP 或输出格式时继续让 acceptance 守住计划口径一致性。'
  316. }),
  317. item({
  318. id: 'hard-rules',
  319. requirement: '硬性规则和剔除账号本地验收',
  320. status: state.scripts['hard-rules:smoke'] &&
  321. state.files['scripts/hard-rules-smoke.js'] &&
  322. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("hard-rules:smoke") &&
  323. state.hardRulesSmokeText.includes('male generic account should not be strong recommendation') &&
  324. state.hardRulesSmokeText.includes('low homepage quality creator should not be strong recommendation') &&
  325. state.hardRulesSmokeText.includes('主页质感风险') &&
  326. state.hardRulesSmokeText.includes('excluded competitor should not enter software CSV')
  327. ? 'passed'
  328. : 'missing',
  329. evidence: 'hard-rules smoke verifies hard exclusions, implicit category rules, over-budget downgrade, low homepage quality downgrade, platform filtering, and software CSV exclusion',
  330. next: '继续把新增品类规则加入 hard-rules smoke。'
  331. }),
  332. item({
  333. id: 'homepage-provider-entry',
  334. requirement: '主页最近内容 provider 接入入口',
  335. status: state.files['mcp/src/features/tihao-sourcing/homepage-evidence-provider.js'] &&
  336. state.scripts['homepage:smoke'] &&
  337. state.homepageSmokeText.includes('assertMockHomepageContract') &&
  338. state.homepageSmokeText.includes('recent_posts') &&
  339. state.homepageSmokeText.includes('assertUnauthorizedFallback') &&
  340. state.homepageSmokeText.includes('public output should not expose raw 401/403')
  341. ? 'passed'
  342. : 'missing',
  343. evidence: 'homepage evidence provider module and homepage:smoke cover provider request contract, successful evidence attachment, unauthorized fallback, and public-output safety',
  344. next: '真实 provider 配置后打开 TIHAO_GATE_REQUIRE_HOMEPAGE_PROVIDER。'
  345. }),
  346. item({
  347. id: 'homepage-evidence-readiness',
  348. requirement: '主页近期内容证据就绪审计',
  349. status: state.scripts['homepage:readiness'] &&
  350. state.scripts['homepage:readiness:smoke'] &&
  351. state.files['scripts/homepage-evidence-readiness-audit.js'] &&
  352. state.files['scripts/homepage-evidence-readiness-smoke.js'] &&
  353. state.homepageReadinessText.includes('buildHomepageEvidenceReadiness') &&
  354. state.homepageReadinessText.includes('recentContentWindow') &&
  355. state.homepageReadinessText.includes('directCustomerProof') &&
  356. state.homepageReadinessSmokeText.includes('strict weak evidence should fail readiness gate') &&
  357. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("homepage:readiness:smoke")
  358. ? 'passed'
  359. : 'missing',
  360. evidence: 'homepage readiness audit and smoke verify provider/path evidence, recent 10 posts, cover/title/publish/interactions/risk gates, strict failure, and no customer-proof claim',
  361. next: '真实 provider 或本地主页证据补齐后运行 npm run homepage:readiness -- --strict。'
  362. }),
  363. item({
  364. id: 'feedback-roundtrip',
  365. requirement: '反馈导入后二轮闭环',
  366. status: state.scripts['feedback:roundtrip'] &&
  367. state.scripts['feedback:closure'] &&
  368. state.scripts['feedback:closure:smoke'] &&
  369. state.memoryText.includes('blockedCreators') &&
  370. state.feedbackRoundtripSmokeText.includes('调性不符') &&
  371. state.feedbackRoundtripSmokeText.includes('主页质感不符') &&
  372. state.feedbackRoundtripSmokeText.includes('参考账号不像') &&
  373. state.feedbackRoundtripSmokeText.includes('teamRuleSuggestions') &&
  374. state.feedbackRoundtripSmokeText.includes('should not enter client-ready candidates') &&
  375. state.feedbackLoopClosureText.includes('directCustomerProof') &&
  376. state.feedbackLoopClosureText.includes('leakedCount') &&
  377. state.feedbackLoopClosureSmokeText.includes('leakedCount === 0') &&
  378. state.feedbackLoopClosureSmokeText.includes('directCustomerProof === false')
  379. ? 'passed'
  380. : 'missing',
  381. evidence: 'feedback:roundtrip and feedback:closure cover negative feedback import, blocked creator memory, second-round exclusion, leakedCount audit, proof boundary, blocked style memory, and repeated team-rule suggestions',
  382. next: '导入真实客户反馈 CSV 后复测二轮负样本率。'
  383. }),
  384. item({
  385. id: 'team-rule-suggestions',
  386. requirement: '团队规则升级建议',
  387. status: state.memoryText.includes('teamRuleSuggestions') && state.reportText.includes('反馈记忆与团队规则建议') ? 'passed' : 'missing',
  388. evidence: 'teamRuleSuggestions and report section exist',
  389. next: '团队确认后再把建议固化为正式规则。'
  390. }),
  391. item({
  392. id: 'video-ab-proof',
  393. requirement: '视频 A/B 严格验收',
  394. status: state.scripts['acceptance:video-ab'] &&
  395. state.scripts['acceptance:video-ab:smoke'] &&
  396. state.files['scripts/video-hit-rate-audit.js'] &&
  397. state.files['scripts/video-hit-rate-audit-smoke.js'] &&
  398. state.videoHitRateAuditText.includes('computeVideoAcceptance') &&
  399. state.videoHitRateAuditText.includes('strongNotDegraded') &&
  400. state.videoHitRateAuditText.includes('top10EvidenceImproved') &&
  401. state.videoHitRateAuditSmokeText.includes('provider failure should fail') &&
  402. state.videoHitRateAuditSmokeText.includes('strong count degradation should fail')
  403. ? 'ready_not_proven'
  404. : 'missing',
  405. evidence: 'acceptance:video-ab live script and acceptance:video-ab:smoke cover real video resources, provider ok, evidence cards, top10 non-degradation, and strong-count non-degradation',
  406. next: '需要真实 token/company/provider 运行 npm run acceptance:video-ab。'
  407. }),
  408. item({
  409. id: 'long-run-readiness',
  410. requirement: '真实长跑启动前就绪度审计',
  411. status: state.scripts['longrun:readiness'] &&
  412. state.scripts['longrun:readiness:smoke'] &&
  413. state.files['scripts/long-run-readiness-audit.js'] &&
  414. state.files['scripts/long-run-readiness-smoke.js'] &&
  415. state.longRunReadinessText.includes('readyForLiveAcceptance') &&
  416. state.longRunReadinessText.includes('readyForCustomerEffectProof') &&
  417. state.longRunReadinessText.includes('intake-readiness-overall-ready') &&
  418. state.longRunReadinessText.includes('doubao-seed-2-0-pro') &&
  419. state.longRunReadinessText.includes('video-resource-readiness-present') &&
  420. state.longRunReadinessText.includes('proof-gap-unresolved-zero') &&
  421. state.longRunReadinessSmokeText.includes('customer effect proof should require history audit') &&
  422. state.longRunReadinessSmokeText.includes('failing intake readiness should block full matrix readiness') &&
  423. state.longRunReadinessSmokeText.includes('unresolved proof gaps should block full matrix readiness') &&
  424. state.longRunReadinessSmokeText.includes('video A/B should require video resource readiness summary') &&
  425. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("longrun:readiness:smoke")
  426. ? 'passed'
  427. : 'missing',
  428. evidence: 'longrun:readiness audits live preflight, intake readiness, history audit, video resource readiness, proof-gap status, video A/B readiness, and customer-effect proof prerequisites before starting controlled or full live matrix runs',
  429. next: '真实长跑前把 live-preflight-summary.json、historical-dataset-audit.json、video-resource-readiness-summary.json 和 proof-gap-request-summary.json 传给 npm run longrun:readiness。'
  430. }),
  431. item({
  432. id: 'optimization-pipeline',
  433. requirement: '端到端优化链路编排',
  434. status: state.scripts['optimization:pipeline'] &&
  435. state.scripts['optimization:pipeline:smoke'] &&
  436. state.files['scripts/optimization-pipeline.js'] &&
  437. state.files['scripts/optimization-pipeline-smoke.js'] &&
  438. state.optimizationPipelineText.includes('history:from-csv') &&
  439. state.optimizationPipelineText.includes('customer-effect:audit') &&
  440. state.optimizationPipelineText.includes('evidence:index') &&
  441. state.optimizationPipelineSmokeText.includes('pipeline should run customer-effect-audit') &&
  442. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("optimization:pipeline:smoke")
  443. ? 'passed'
  444. : 'missing',
  445. evidence: 'optimization:pipeline can compose history CSV import, history audit, readiness, review metrics, customer effect audit, and evidence index in one local workflow',
  446. next: '真实数据到位后用 optimization:pipeline 串起审计链路;真实 live 长跑仍需 preflight/readiness 控制。'
  447. }),
  448. item({
  449. id: 'optimization-completion',
  450. requirement: '长期优化完成度审计',
  451. status: state.scripts['optimization:completion'] &&
  452. state.scripts['optimization:completion:smoke'] &&
  453. state.files['scripts/optimization-completion-audit.js'] &&
  454. state.files['scripts/optimization-completion-smoke.js'] &&
  455. state.optimizationCompletionText.includes('readyForClaim') &&
  456. state.optimizationCompletionText.includes('proofGapOpenCount') &&
  457. state.optimizationCompletionText.includes('canClaim') &&
  458. state.optimizationCompletionSmokeText.includes('should not allow tihao-rate claim') &&
  459. state.optimizationCompletionSmokeText.includes('closed fixture should be ready for claim') &&
  460. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("optimization:completion:smoke")
  461. ? 'passed'
  462. : 'missing',
  463. evidence: 'optimization:completion aggregates intake/video/proof-gap/video A/B/live-provider/customer-effect/pipeline/handoff/progress gates and keeps readyForClaim=false until all real proof gates pass',
  464. next: '每轮结束后运行 npm run round:refresh -- --output-root outputs --strict;只需单独判断是否可声明完成时再运行 npm run optimization:completion -- --output outputs\\optimization-completion-latest。'
  465. }),
  466. item({
  467. id: 'optimization-handoff',
  468. requirement: '长期优化交接摘要',
  469. status: state.scripts['handoff:summary'] &&
  470. state.scripts['handoff:summary:smoke'] &&
  471. state.files['scripts/generate-optimization-handoff.js'] &&
  472. state.files['scripts/generate-optimization-handoff-smoke.js'] &&
  473. state.handoffText.includes('buildHandoffSummary') &&
  474. state.handoffText.includes('customer-effect:audit') &&
  475. state.handoffText.includes('不能把 sample、smoke、dry-run') &&
  476. state.handoffSmokeText.includes('handoff must not mark long-term goal complete without real proof') &&
  477. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("handoff:summary:smoke")
  478. ? 'passed'
  479. : 'missing',
  480. evidence: 'handoff:summary combines optimization status, evidence index, data intake pack paths, next commands, and proof guardrails; smoke verifies it stays incomplete without real proof',
  481. next: '常规每轮结束后优先运行 npm run round:refresh -- --output-root outputs --strict;只需单独刷新交接摘要时再运行 npm run handoff:summary。'
  482. }),
  483. item({
  484. id: 'proof-gap-request',
  485. requirement: '真实证明缺口请求包',
  486. status: state.scripts['proof-gap:request'] &&
  487. state.scripts['proof-gap:request:smoke'] &&
  488. state.files['scripts/generate-proof-gap-request.js'] &&
  489. state.files['scripts/generate-proof-gap-request-smoke.js'] &&
  490. state.proofGapText.includes('buildProofGapRequest') &&
  491. state.proofGapText.includes('video-real-candidate') &&
  492. state.proofGapText.includes('historical-dataset') &&
  493. state.proofGapText.includes('manual-review-and-customer-effect') &&
  494. state.proofGapText.includes('不要在 CSV、报告或聊天中写入 sessionToken') &&
  495. state.proofGapSmokeText.includes('should request real candidate video resources') &&
  496. state.evidenceIndexText.includes("type: 'proof-gap-request'") &&
  497. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("proof-gap:request:smoke")
  498. ? 'passed'
  499. : 'missing',
  500. evidence: 'proof-gap:request generates JSON/Markdown/CSV request material for historical data, real video resources, live/provider proof, manual review, customer choices, and manual supplement baseline; smoke verifies boundary and no secret-like values',
  501. next: '每轮结束后运行 npm run proof-gap:request -- --output outputs\\proof-gap-request-latest,交给商务补齐真实证明数据。'
  502. }),
  503. item({
  504. id: 'proof-gap-closure',
  505. requirement: '真实证明缺口关闭审计',
  506. status: state.scripts['proof-gap:closure'] &&
  507. state.scripts['proof-gap:closure:smoke'] &&
  508. state.files['scripts/proof-gap-closure-audit.js'] &&
  509. state.files['scripts/proof-gap-closure-smoke.js'] &&
  510. state.proofGapClosureText.includes('proof-gap-closure-summary.json') &&
  511. state.proofGapClosureText.includes('intake-readiness') &&
  512. state.proofGapClosureText.includes('intakeReady') &&
  513. state.proofGapClosureText.includes('historical-dataset') &&
  514. state.proofGapClosureText.includes('video-ab-live-proof') &&
  515. state.proofGapClosureText.includes('manual-review-and-customer-effect') &&
  516. state.proofGapClosureSmokeText.includes('openCount') &&
  517. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("proof-gap:closure:smoke")
  518. ? 'passed'
  519. : 'missing',
  520. evidence: 'proof-gap:closure audits intake readiness plus whether the real historical dataset, video resource readiness, video A/B live proof, live/provider overnight proof, and customer-effect proof are actually closed; acceptance runs its smoke gate',
  521. next: '真实数据补齐后运行 npm run proof-gap:closure -- --output outputs\\proof-gap-closure-latest,只有 openCount=0 才能进入长期完成判断。'
  522. }),
  523. item({
  524. id: 'round-deposition',
  525. requirement: '每轮沉淀完整性审计',
  526. status: state.scripts['round:deposition'] &&
  527. state.scripts['round:deposition:smoke'] &&
  528. state.files['scripts/round-deposition-audit.js'] &&
  529. state.files['scripts/round-deposition-smoke.js'] &&
  530. state.roundDepositionText.includes('buildRoundDepositionSummary') &&
  531. state.roundDepositionText.includes('implementation-log') &&
  532. state.roundDepositionText.includes('failureCount') &&
  533. state.roundDepositionText.includes('software-table') &&
  534. state.roundDepositionText.includes('video-ab-proof') &&
  535. state.roundDepositionText.includes('customer-effect-proof') &&
  536. state.roundDepositionText.includes('proof-gap-unresolved-recorded') &&
  537. state.roundDepositionText.includes('proofGapUnresolvedCount') &&
  538. state.roundDepositionSmokeText.includes('round-deposition-audit should pass') &&
  539. state.roundDepositionSmokeText.includes('summary should include proof-gap unresolved check') &&
  540. state.evidenceIndexText.includes('classifyRoundDeposition') &&
  541. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("round:deposition:smoke")
  542. ? 'passed'
  543. : 'missing',
  544. evidence: 'round:deposition audits implementation log, status report, evidence index, handoff summary, proof boundaries, live/video/provider indicators, software duplicate/rank status, and review/customer metrics; acceptance runs its smoke gate',
  545. next: '常规每轮结束后优先运行 npm run round:refresh -- --output-root outputs --strict,确保状态、证据台账、交接摘要和沉淀审计按顺序刷新。'
  546. }),
  547. item({
  548. id: 'business-proof-progress',
  549. requirement: '15 步补证进度实时同步',
  550. status: state.scripts['business-proof:progress'] &&
  551. state.scripts['business-proof:progress:smoke'] &&
  552. state.files['scripts/business-proof-progress.js'] &&
  553. state.files['scripts/business-proof-progress-smoke.js'] &&
  554. state.businessProofProgressText.includes('buildBusinessProofProgress') &&
  555. state.businessProofProgressText.includes('business-proof-progress-summary.json') &&
  556. state.businessProofProgressText.includes('blocked_by_external_data') &&
  557. state.businessProofProgressText.includes('不能把 sample、smoke') &&
  558. state.businessProofProgressSmokeText.includes('progress should include 15 steps') &&
  559. state.roundDepositionText.includes('business-proof-progress-summary') &&
  560. state.roundDepositionText.includes('businessProofProgressCounts') &&
  561. state.evidenceIndexText.includes("type: 'business-proof-progress'") &&
  562. state.handoffText.includes('businessProofProgress') &&
  563. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("business-proof:progress:smoke")
  564. ? 'passed'
  565. : 'missing',
  566. evidence: 'business-proof:progress outputs a 15-step pass/pending/fail/blocked_by_external_data progress board; round:deposition requires it, evidence:index classifies it as smoke_or_local, and handoff:summary links it for the next round',
  567. next: '每轮结束后运行 npm run business-proof:progress -- --output outputs\\business-proof-progress-latest;它只同步补证进度,不证明客户效果。'
  568. }),
  569. item({
  570. id: 'business-proof-next-actions',
  571. requirement: '下一步补证行动队列',
  572. status: state.scripts['business-proof:next-actions'] &&
  573. state.scripts['business-proof:next-actions:smoke'] &&
  574. state.files['scripts/business-proof-next-actions.js'] &&
  575. state.files['scripts/business-proof-next-actions-smoke.js'] &&
  576. state.businessProofNextActionsText.includes('buildNextActions') &&
  577. state.businessProofNextActionsText.includes('business-proof-next-actions-summary.json') &&
  578. state.businessProofNextActionsText.includes('ownerGroups') &&
  579. state.businessProofNextActionsText.includes('ownerArtifacts') &&
  580. state.businessProofNextActionsText.includes('writeOwnerArtifacts') &&
  581. state.businessProofNextActionsText.includes('行动队列只用于安排下一步补证') &&
  582. state.businessProofNextActionsSmokeText.includes('history blocker should be first') &&
  583. state.businessProofNextActionsSmokeText.includes('owner groups should include business owner') &&
  584. state.businessProofNextActionsSmokeText.includes('owner artifact files should exist') &&
  585. state.roundDepositionText.includes('business-proof-next-actions-summary') &&
  586. state.handoffText.includes('businessProofNextActions') &&
  587. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("business-proof:next-actions:smoke")
  588. ? 'passed'
  589. : 'missing',
  590. evidence: 'business-proof:next-actions converts progress/proof-gap/intake/video blockers into owner, command, expected artifact, and acceptance rows; handoff links it and round:deposition requires it',
  591. next: '每轮结束后运行 npm run business-proof:next-actions -- --output outputs\\business-proof-next-actions-latest;它只安排补证任务,不证明客户效果。'
  592. }),
  593. item({
  594. id: 'latest-form-index',
  595. requirement: '最新提号表单统一入口',
  596. status: state.scripts['latest-form:index'] &&
  597. state.scripts['latest-form:index:smoke'] &&
  598. state.files['scripts/latest-form-index.js'] &&
  599. state.files['scripts/latest-form-index-smoke.js'] &&
  600. state.latestFormIndexText.includes('buildLatestFormIndex') &&
  601. state.latestFormIndexText.includes('directCustomerProof') &&
  602. state.latestFormIndexText.includes('latest-form-index-summary.json') &&
  603. state.latestFormIndexText.includes('latest-form-index.md') &&
  604. state.latestFormIndexSmokeText.includes('client list must still require review/customer proof') &&
  605. state.roundDepositionText.includes('latest-form-index-summary') &&
  606. state.handoffText.includes('latestFormIndex') &&
  607. state.evidenceIndexText.includes("type: 'latest-form-index'") &&
  608. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("latest-form:index:smoke")
  609. ? 'passed'
  610. : 'missing',
  611. evidence: 'latest-form:index exposes the current proof-gap form, software client list, owner action files, and proof guardrails as one handoff entry; smoke verifies it is not treated as customer-effect proof',
  612. next: '常规每轮结束后优先运行 npm run round:refresh -- --output-root outputs --strict;只需单独定位最新表单时再运行 npm run latest-form:index。'
  613. }),
  614. item({
  615. id: 'evidence-index',
  616. requirement: '长跑产物证据台账',
  617. status: state.scripts['evidence:index'] &&
  618. state.scripts['evidence:index:smoke'] &&
  619. state.files['scripts/evidence-index.js'] &&
  620. state.files['scripts/evidence-index-smoke.js'] &&
  621. state.evidenceIndexText.includes('real_evidence') &&
  622. state.evidenceIndexText.includes('not_business_proof') &&
  623. state.evidenceIndexText.includes('live-preflight-summary.json') &&
  624. state.evidenceIndexText.includes("type: 'live-preflight'") &&
  625. state.evidenceIndexText.includes('video-hit-rate-summary.json') &&
  626. state.evidenceIndexText.includes('customer-effect-summary.json') &&
  627. state.evidenceIndexText.includes("type: 'customer-effect'") &&
  628. state.evidenceIndexText.includes('optimization-pipeline-summary.json') &&
  629. state.evidenceIndexText.includes("type: 'optimization-pipeline'") &&
  630. state.evidenceIndexText.includes('optimization-completion-summary.json') &&
  631. state.evidenceIndexText.includes("type: 'optimization-completion'") &&
  632. state.evidenceIndexText.includes('optimization-handoff-summary.json') &&
  633. state.evidenceIndexText.includes("type: 'optimization-handoff'") &&
  634. state.evidenceIndexText.includes('proof-gap-request-summary.json') &&
  635. state.evidenceIndexText.includes("type: 'proof-gap-request'") &&
  636. state.evidenceIndexText.includes('proof-gap-closure-summary.json') &&
  637. state.evidenceIndexText.includes("type: 'proof-gap-closure'") &&
  638. state.evidenceIndexText.includes('round-deposition-summary.json') &&
  639. state.evidenceIndexText.includes("type: 'round-deposition'") &&
  640. state.evidenceIndexText.includes("type: 'data-intake-pack'") &&
  641. state.evidenceIndexText.includes("type: 'video-intake-pack'") &&
  642. state.evidenceIndexText.includes('intake-readiness-summary.json') &&
  643. state.evidenceIndexText.includes("type: 'intake-readiness'") &&
  644. state.evidenceIndexText.includes('video-resource-readiness-summary.json') &&
  645. state.evidenceIndexText.includes("type: 'video-resource-readiness'") &&
  646. state.evidenceIndexSmokeText.includes('closed proof-gap closure should be real evidence') &&
  647. state.evidenceIndexSmokeText.includes('handoff summary') &&
  648. state.evidenceIndexSmokeText.includes('optimization pipeline orchestration') &&
  649. state.evidenceIndexSmokeText.includes('optimization completion audits') &&
  650. state.evidenceIndexSmokeText.includes('optimization handoff summary') &&
  651. state.evidenceIndexSmokeText.includes('proof gap request summary') &&
  652. state.evidenceIndexSmokeText.includes('proof gap closure summaries') &&
  653. state.evidenceIndexSmokeText.includes('round deposition summary') &&
  654. state.evidenceIndexSmokeText.includes('intake readiness audits') &&
  655. state.evidenceIndexSmokeText.includes('data intake pack readiness') &&
  656. state.evidenceIndexSmokeText.includes('video intake pack readiness') &&
  657. state.evidenceIndexSmokeText.includes('video resource readiness audits') &&
  658. state.evidenceIndexSmokeText.includes('incomplete customer effect') &&
  659. state.evidenceIndexSmokeText.includes('missing preflight') &&
  660. state.evidenceIndexSmokeText.includes('failed live') &&
  661. state.evidenceIndexSmokeText.includes('weak video') &&
  662. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("evidence:index:smoke")
  663. ? 'passed'
  664. : 'missing',
  665. evidence: 'evidence:index scans outputs and classifies overnight, status, readiness, live preflight, review metrics, customer effect, historical dataset, video A/B, proof-gap request, and proof-gap closure artifacts into real_evidence, smoke_or_local, and not_business_proof',
  666. next: '每次长跑或复核后优先运行 npm run round:refresh -- --output-root outputs --strict;只需单独刷新证明等级时再运行 npm run evidence:index。'
  667. }),
  668. item({
  669. id: 'overnight-matrix',
  670. requirement: 'overnight 完整策略矩阵',
  671. status: ['reference-account', 'homepage-evidence', 'video-enhanced', 'result-first-broad'].every(term => state.overnightText.includes(term)) ? 'passed' : 'missing',
  672. evidence: 'overnight script contains baseline/reference/homepage/video/result-first variants',
  673. next: '真实 live 长跑时使用新版 ai-overnight-optimization-task.md。'
  674. }),
  675. item({
  676. id: 'strict-provider-gates',
  677. requirement: '严格 provider 门禁',
  678. status: state.overnightText.includes('TIHAO_GATE_REQUIRE_REFERENCE_PROVIDER') && state.overnightText.includes('TIHAO_GATE_REQUIRE_HOMEPAGE_PROVIDER') ? 'passed' : 'missing',
  679. evidence: 'overnight strict provider gates exist',
  680. next: '真实 provider 联调时打开 strict gates。'
  681. }),
  682. item({
  683. id: 'reference-rules',
  684. requirement: '参考账号冲突和强推荐解释本地验收',
  685. status: state.scripts['reference-rules:smoke'] &&
  686. state.scripts['platform:strategy:smoke'] &&
  687. state.files['scripts/reference-rules-smoke.js'] &&
  688. state.files['scripts/platform-sourcing-strategy-smoke.js'] &&
  689. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("reference-rules:smoke") &&
  690. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("platform:strategy:smoke") &&
  691. state.referenceRulesSmokeText.includes('reference_conflict') &&
  692. state.referenceRulesSmokeText.includes('不能强行合并') &&
  693. state.referenceRulesSmokeText.includes('strong candidate reasons should explain reference/style evidence') &&
  694. state.referenceRulesSmokeText.includes('strong candidates should have reference style or homepage evidence') &&
  695. state.referenceRulesSmokeText.includes('strong reference style hit points should not contain placeholder evidence') &&
  696. state.referenceRulesSmokeText.includes('weak placeholder reference evidence should not be enough for strong recommendation') &&
  697. state.referenceRulesSmokeText.includes('strong candidate reasons should explain homepage evidence') &&
  698. state.platformStrategySmokeText.includes('reference_similarity') &&
  699. state.platformStrategySmokeText.includes('keyword_or_recruitment') &&
  700. state.platformStrategySmokeText.includes('不作为适配证明')
  701. ? 'passed'
  702. : 'missing',
  703. evidence: 'reference-rules and platform strategy smokes verify conflicting references, strong recommendation brief hits, reference/homepage evidence explanation, xiaohongshu reference-similarity path, and douyin keyword/recruitment fallback',
  704. next: '真实参考账号数据接入后继续用 review:metrics 验证参考链路通过率。'
  705. }),
  706. item({
  707. id: 'review-metrics',
  708. requirement: '人工复核/业务指标统计',
  709. status: state.scripts['review:metrics'] &&
  710. state.scripts['review:metrics:smoke'] &&
  711. state.scripts['overnight:review:smoke'] &&
  712. state.files['scripts/overnight-manual-review-smoke.js'] &&
  713. state.overnightText.includes('归因类型') &&
  714. state.overnightText.includes('客户选择') &&
  715. state.overnightReviewSmokeText.includes('manual review sample should include') &&
  716. state.reviewMetricsText.includes('compareReferenceAndKeywordStrategies') &&
  717. state.reviewMetricsText.includes('referencePassRateHigherThanKeyword') &&
  718. state.reviewMetricsSmokeText.includes('reference strategies should outperform keyword baseline') &&
  719. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("overnight:review:smoke")
  720. ? 'passed'
  721. : 'missing',
  722. evidence: 'review metrics command/smoke exist; overnight manual-review sample includes customer decision, attribution, and feedback fields; review metrics compares reference/homepage/video strategies against keyword baseline; default acceptance runs overnight review smoke',
  723. next: '商务标注 CSV 后运行 npm run review:metrics,负样本必须填写归因类型。'
  724. }),
  725. item({
  726. id: 'customer-effect-audit-entry',
  727. requirement: '客户效果证明审计入口',
  728. status: state.scripts['customer-effect:audit'] &&
  729. state.scripts['customer-effect:smoke'] &&
  730. state.files['scripts/customer-effect-audit.js'] &&
  731. state.files['scripts/customer-effect-audit-smoke.js'] &&
  732. state.customerEffectAuditText.includes('manualSupplementReduction50Pass') &&
  733. state.customerEffectAuditText.includes('customerSelectedRate50Pass') &&
  734. state.customerEffectAuditText.includes('referenceCustomerSelectedRate40Pass') &&
  735. state.customerEffectSmokeText.includes('missing current manual supplement count should block overall pass') &&
  736. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("customer-effect:smoke")
  737. ? 'passed'
  738. : 'missing',
  739. evidence: 'customer-effect:audit combines review metrics, historical manual supplement baseline, current manual supplement count, customer selected-rate gates, and a smoke that blocks missing proof',
  740. next: '拿到真实客户选择和人工补号量后运行 customer-effect:audit 生成客户效果证明。'
  741. }),
  742. item({
  743. id: 'history-csv-import',
  744. requirement: '真实历史数据 CSV 导入',
  745. status: state.scripts['history:from-csv'] &&
  746. state.scripts['history:from-csv:smoke'] &&
  747. state.files['scripts/history-dataset-from-csv.js'] &&
  748. state.files['scripts/history-dataset-from-csv-smoke.js'] &&
  749. state.historyFromCsvText.includes('manualFinalList') &&
  750. state.historyFromCsvText.includes('customerFeedback') &&
  751. state.historyFromCsvText.includes('manualSupplementBaseline') &&
  752. state.historyFromCsvSmokeText.includes('converted dataset should be ready for customer effect proof') &&
  753. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("history:from-csv:smoke")
  754. ? 'passed'
  755. : 'missing',
  756. evidence: 'history:from-csv converts business CSV rows into per-brief JSON history records and smoke verifies history:audit readiness',
  757. next: '商务可先按 CSV 模板整理真实历史数据,再运行 history:from-csv 和 history:audit。'
  758. }),
  759. item({
  760. id: 'data-intake-pack',
  761. requirement: '真实数据收集包',
  762. status: state.scripts['data:intake-template'] &&
  763. state.scripts['data:intake-template:smoke'] &&
  764. state.files['scripts/create-data-intake-pack.js'] &&
  765. state.files['scripts/create-data-intake-pack-smoke.js'] &&
  766. state.dataIntakePackText.includes('history-data-template.csv') &&
  767. state.dataIntakePackText.includes('manual-review-template.csv') &&
  768. state.dataIntakePackText.includes('历史人工补号量基线') &&
  769. state.dataIntakePackText.includes('本轮人工补号量') &&
  770. state.dataIntakePackSmokeText.includes('history template header should match history:from-csv fields') &&
  771. state.dataIntakePackSmokeText.includes('review template header should match review/customer-effect fields') &&
  772. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("data:intake-template:smoke")
  773. ? 'passed'
  774. : 'missing',
  775. evidence: 'data:intake-template generates history and manual-review CSV templates plus a README; smoke verifies headers, proof fields, and no secret-like values',
  776. next: '把生成的数据收集包交给商务填写真实历史 Brief、客户选择和本轮人工补号量。'
  777. }),
  778. item({
  779. id: 'intake-readiness',
  780. requirement: '真实材料半成品预审',
  781. status: state.scripts['intake:readiness'] &&
  782. state.scripts['intake:readiness:smoke'] &&
  783. state.files['scripts/intake-readiness-audit.js'] &&
  784. state.files['scripts/intake-readiness-smoke.js'] &&
  785. state.intakeReadinessText.includes('overallReady') &&
  786. state.intakeReadinessText.includes('example.com') &&
  787. state.intakeReadinessText.includes('readyForCustomerEffectAudit') &&
  788. state.intakeReadinessText.includes('真实候选视频') &&
  789. state.intakeReadinessSmokeText.includes('fresh templates should not be treated as real intake data') &&
  790. state.intakeReadinessSmokeText.includes('complete intake fixtures should pass readiness') &&
  791. state.evidenceIndexText.includes("type: 'intake-readiness'") &&
  792. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("intake:readiness:smoke")
  793. ? 'passed'
  794. : 'missing',
  795. evidence: 'intake:readiness audits filled data/video intake packs before formal history/video/review/customer-effect audits; smoke verifies fresh templates fail and complete fixtures pass; evidence:index classifies readiness as startup evidence or not_business_proof',
  796. next: '商务/投放填完模板后先运行 intake:readiness,确认没有示例占位和半成品,再进入 history:audit、video:resource-readiness、review:metrics 和 customer-effect:audit。'
  797. }),
  798. item({
  799. id: 'video-intake-pack',
  800. requirement: '真实视频资源收集包',
  801. status: state.scripts['video:intake-template'] &&
  802. state.scripts['video:intake-template:smoke'] &&
  803. state.files['scripts/create-video-intake-pack.js'] &&
  804. state.files['scripts/create-video-intake-pack-smoke.js'] &&
  805. state.videoIntakePackText.includes('video-resource-template.csv') &&
  806. state.videoIntakePackText.includes('视频链接') &&
  807. state.videoIntakePackText.includes('封面链接') &&
  808. state.videoIntakePackText.includes('字幕或ASR文本') &&
  809. state.videoIntakePackText.includes('帧图链接') &&
  810. state.videoIntakePackText.includes('是否真实资源') &&
  811. state.videoIntakePackText.includes('不得声明视频分析完成') &&
  812. state.videoIntakePackSmokeText.includes('video resource header should match expected fields') &&
  813. state.evidenceIndexText.includes("type: 'video-intake-pack'") &&
  814. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("video:intake-template:smoke")
  815. ? 'passed'
  816. : 'missing',
  817. evidence: 'video:intake-template generates a CSV/README for real reference and candidate video URLs, covers, ASR/subtitles, frame URLs, real-resource flag, and proof boundaries; evidence:index classifies it as smoke_or_local',
  818. next: '交给商务补齐真实视频资源后,再运行 npm run acceptance:video-ab;模板本身不证明视频分析提升命中率。'
  819. }),
  820. item({
  821. id: 'video-resource-readiness',
  822. requirement: '视频 A/B 前置资源就绪审计',
  823. status: state.scripts['video:resource-readiness'] &&
  824. state.scripts['video:resource-readiness:smoke'] &&
  825. state.files['scripts/video-resource-readiness-audit.js'] &&
  826. state.files['scripts/video-resource-readiness-smoke.js'] &&
  827. state.videoResourceReadinessText.includes('readyForVideoAbPreflight') &&
  828. state.videoResourceReadinessText.includes('hasRealReferenceResource') &&
  829. state.videoResourceReadinessText.includes('hasRealCandidateResource') &&
  830. state.videoResourceReadinessText.includes('hasRealVideoUrl') &&
  831. state.videoResourceReadinessText.includes('hasCoverOrAsrOrFrameOrText') &&
  832. state.videoResourceReadinessText.includes('provider ok、证据卡、Top 10 非退化') &&
  833. state.videoResourceReadinessSmokeText.includes('complete video resources should pass preflight readiness') &&
  834. state.videoResourceReadinessSmokeText.includes('missing video resources should fail readiness') &&
  835. state.evidenceIndexText.includes("type: 'video-resource-readiness'") &&
  836. readOptional(path.join(state.root, 'scripts', 'acceptance.js')).includes("video:resource-readiness:smoke")
  837. ? 'passed'
  838. : 'missing',
  839. evidence: 'video:resource-readiness audits filled video-resource CSVs for real reference video, real candidate video, real video URL, cover/ASR/frame/text evidence, no fake-real rows, and no secret-like values; evidence:index classifies it as smoke_or_local when ready',
  840. next: '商务填完 video-resource-template.csv 后先运行 video:resource-readiness;通过后再配置 provider 运行 acceptance:video-ab。'
  841. }),
  842. item({
  843. id: 'historical-dataset',
  844. requirement: '真实历史数据准备验收',
  845. status: state.scripts['history:audit'] &&
  846. state.files['docs/historical-dataset-guide.md'] &&
  847. state.historyAuditText.includes('readyForCustomerEffectProof') &&
  848. state.historyAuditText.includes('allHaveManualSupplementBaseline') &&
  849. state.historyAuditSmokeText.includes('missing manual supplement baseline should block customer effect proof')
  850. ? 'ready_not_proven'
  851. : 'missing',
  852. evidence: 'history:audit, historical dataset guide, customer decision gates, rejection reason gates, and manual supplement baseline gates exist',
  853. next: '先运行 data:intake-template 生成收集包;真实历史 Brief/人工名单/客户选择/人工补号量基线数据填完后通过 history:audit。'
  854. }),
  855. item({
  856. id: 'live-provider-overnight-proof',
  857. requirement: '真实 live/provider 长跑效果',
  858. status: liveProof ? 'passed' : 'blocked_by_external_data',
  859. evidence: liveProof
  860. ? 'outputs include closable live/provider aggregate: liveEnabled=true, overallPass=true, failureCount=0, failedGateCount=0, tokenLeak=false'
  861. : 'no current verified live/provider/customer dataset proof in local outputs',
  862. next: liveProof
  863. ? '该单项只证明真实 live/provider overnight 通过;仍不能替代客户效果、历史数据或视频 A/B proof。'
  864. : '需要有效 sessionToken/company/provider 和真实历史数据运行完整 live 矩阵。'
  865. }),
  866. item({
  867. id: 'customer-effect-proof',
  868. requirement: '客户选中率 30%/40%/50% 和人工补号量减少 50%',
  869. status: customerEffectProof ? 'passed' : 'blocked_by_external_data',
  870. evidence: customerEffectProof ? 'review metrics output includes customer selection evidence' : 'no real customer selection dataset has been audited',
  871. next: '需要客户最终选择字段和人工补号量基线。'
  872. })
  873. ];
  874. }
  875. function item(value) {
  876. return { ...value, title: value.title || value.requirement };
  877. }
  878. function hasScripts(state, names) {
  879. return names.every(name => Boolean(state.scripts[name]));
  880. }
  881. function findOutputEvidence(root) {
  882. const outputs = path.join(root, 'outputs');
  883. if (!fs.existsSync(outputs)) return [];
  884. return fs.readdirSync(outputs, { withFileTypes: true })
  885. .filter(entry => entry.isDirectory())
  886. .map(entry => path.join(outputs, entry.name))
  887. .sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs)
  888. .map(dir => ({
  889. dir,
  890. aggregate: readJsonOptional(path.join(dir, 'aggregate-summary.json')),
  891. review: readJsonOptional(path.join(dir, 'review-metrics-summary.json')),
  892. history: readJsonOptional(path.join(dir, 'historical-dataset-audit.json')),
  893. customerEffect: readJsonOptional(path.join(dir, 'customer-effect-summary.json')) ||
  894. readJsonOptional(path.join(dir, 'customer-effect-audit', 'customer-effect-summary.json'))
  895. }));
  896. }
  897. function hasLiveProof(outputs) {
  898. return outputs.some(item => isClosableLiveAggregate(item.aggregate));
  899. }
  900. function hasCustomerEffectProof(outputs) {
  901. return outputs.some(item => item.customerEffect?.acceptance?.overallPass === true);
  902. }
  903. function isClosableLiveAggregate(aggregate) {
  904. return Boolean(
  905. aggregate?.manifest?.liveEnabled === true &&
  906. aggregate?.acceptance?.overallPass === true &&
  907. Number(aggregate?.failureCount || 0) === 0 &&
  908. Number(aggregate?.acceptance?.failedGateCount ?? aggregate?.failedGateCount ?? 0) === 0 &&
  909. aggregate?.acceptance?.tokenLeak !== true
  910. );
  911. }
  912. function countStatuses(items) {
  913. const result = {};
  914. for (const item of items) result[item.status] = (result[item.status] || 0) + 1;
  915. return result;
  916. }
  917. function renderReport(summary) {
  918. const lines = [
  919. '# 提号优化计划状态审计',
  920. '',
  921. `- 生成时间:${summary.generatedAt}`,
  922. `- 是否全部完成:${summary.complete ? '是' : '否'}`,
  923. '',
  924. '## 状态汇总',
  925. '',
  926. ...Object.entries(summary.counts).map(([key, value]) => `- ${key}: ${value}`),
  927. '',
  928. '## 要求明细',
  929. '',
  930. '| ID | 状态 | 要求 | 当前证据 | 下一步 |',
  931. '| --- | --- | --- | --- | --- |',
  932. ...summary.items.map(item => `| ${escapeCell(item.id)} | ${item.status} | ${escapeCell(item.title || item.requirement)} | ${escapeCell(item.evidence)} | ${escapeCell(item.next)} |`),
  933. '',
  934. '## 说明',
  935. '',
  936. '- `passed` 表示当前代码、脚本或本地验收已覆盖该能力。',
  937. '- `ready_not_proven` 表示验收入口已具备,但需要真实 token/provider/数据运行后才能证明业务效果。',
  938. '- `blocked_by_external_data` 表示当前不能靠代码继续证明,需要真实 live 权限、provider 或客户选择数据。'
  939. ];
  940. return lines.join('\n');
  941. }
  942. function fileSet(root, files) {
  943. return Object.fromEntries(files.map(file => [file, fs.existsSync(path.join(root, file))]));
  944. }
  945. function readOptional(file) {
  946. return fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
  947. }
  948. function readJsonOptional(file) {
  949. if (!fs.existsSync(file)) return null;
  950. try {
  951. return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, ''));
  952. } catch {
  953. return null;
  954. }
  955. }
  956. function escapeCell(value) {
  957. return String(value || '').replace(/\|/g, '/').replace(/\n/g, ' ');
  958. }
  959. function withBom(text) {
  960. return `\uFEFF${text}`;
  961. }
  962. if (require.main === module) {
  963. try {
  964. main();
  965. } catch (error) {
  966. console.error(error && error.stack ? error.stack : String(error));
  967. process.exit(1);
  968. }
  969. }
  970. module.exports = {
  971. buildStatusItems,
  972. buildStatusSummary,
  973. collectState,
  974. findOutputEvidence,
  975. hasLiveProof
  976. };