Quellcode durchsuchen

更新提号技能包

gangvy vor 3 Monaten
Ursprung
Commit
b8cc1b4877
100 geänderte Dateien mit 25904 neuen und 0 gelöschten Zeilen
  1. 5 0
      .gitignore
  2. 8 0
      claude-code-tihao-sourcing/.claude-plugin/plugin.json
  3. 9 0
      claude-code-tihao-sourcing/.gitignore
  4. 126 0
      claude-code-tihao-sourcing/README.md
  5. 42 0
      claude-code-tihao-sourcing/bin/tihao.js
  6. 174 0
      claude-code-tihao-sourcing/docs/acceptance-checklist.md
  7. 36 0
      claude-code-tihao-sourcing/docs/ai-optimization-job-dedup-software-format.md
  8. 33 0
      claude-code-tihao-sourcing/docs/ai-optimization-proof-gap-software-table.md
  9. 64 0
      claude-code-tihao-sourcing/docs/ai-optimization-task-software-table.md
  10. 251 0
      claude-code-tihao-sourcing/docs/ai-overnight-optimization-task.md
  11. 54 0
      claude-code-tihao-sourcing/docs/business-proof-action-order.md
  12. 54 0
      claude-code-tihao-sourcing/docs/business-proof-action-order.zh-CN.md
  13. 30 0
      claude-code-tihao-sourcing/docs/capability-map.md
  14. 172 0
      claude-code-tihao-sourcing/docs/historical-dataset-guide.md
  15. 362 0
      claude-code-tihao-sourcing/docs/install-guide.md
  16. 192 0
      claude-code-tihao-sourcing/docs/live-provider-integration-runbook.md
  17. 130 0
      claude-code-tihao-sourcing/docs/manual-review-handoff.md
  18. 60 0
      claude-code-tihao-sourcing/docs/overnight-quality-latest-evidence.md
  19. 204 0
      claude-code-tihao-sourcing/docs/overnight-quality-runbook.md
  20. 63 0
      claude-code-tihao-sourcing/docs/payment-package-links.md
  21. 145 0
      claude-code-tihao-sourcing/docs/reference-evidence-roadmap.md
  22. 457 0
      claude-code-tihao-sourcing/docs/release-notes.md
  23. 19 0
      claude-code-tihao-sourcing/docs/sk/讯飞env.md
  24. 72 0
      claude-code-tihao-sourcing/docs/skill-database-record.json
  25. 59 0
      claude-code-tihao-sourcing/docs/software-client-table-format.md
  26. 39 0
      claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-business-execution-field-recheck-rows.md
  27. 26 0
      claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-business-fill-guide.md
  28. 40 0
      claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-field-checklist-index.md
  29. 26 0
      claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-handoff-business-fill-guide.md
  30. 37 0
      claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-handoff-zh-sop-entry.md
  31. 44 0
      claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-homepage-repair-index.md
  32. 57 0
      claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-intake-field-checklist-owner-recheck.md
  33. 29 0
      claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-latest-form-business-execution-entry.md
  34. 53 0
      claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-latest-form-recheck-entry.md
  35. 45 0
      claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-readable-sop-entry.md
  36. 43 0
      claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-round-deposition-handoff-sop-guard.md
  37. 38 0
      claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-round-deposition-index-guards.md
  38. 54 0
      claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-round-deposition-latest-form-owner-recheck-guard.md
  39. 40 0
      claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-round-deposition-zh-action-order-default.md
  40. 7972 0
      claude-code-tihao-sourcing/docs/tihao-experience-implementation-log.md
  41. 253 0
      claude-code-tihao-sourcing/docs/tihao-experience-optimization-plan.md
  42. 253 0
      claude-code-tihao-sourcing/docs/tihao-experience-optimization-plan.zh-CN.md
  43. 235 0
      claude-code-tihao-sourcing/docs/tihao-handoff-index.md
  44. 23 0
      claude-code-tihao-sourcing/fixtures/quality-briefs/618-list-seeding.json
  45. 29 0
      claude-code-tihao-sourcing/fixtures/quality-briefs/dha-mom-baby.json
  46. 23 0
      claude-code-tihao-sourcing/fixtures/quality-briefs/healthy-snack.json
  47. 23 0
      claude-code-tihao-sourcing/fixtures/quality-briefs/home-cleaning.json
  48. 23 0
      claude-code-tihao-sourcing/fixtures/quality-briefs/sensitive-skin-repair.json
  49. 7 0
      claude-code-tihao-sourcing/fixtures/sample-briefs/skincare-sample.md
  50. 177 0
      claude-code-tihao-sourcing/install.js
  51. 108 0
      claude-code-tihao-sourcing/mcp/src/core/credentials.js
  52. 183 0
      claude-code-tihao-sourcing/mcp/src/core/files.js
  53. 146 0
      claude-code-tihao-sourcing/mcp/src/core/payment-links.js
  54. 54 0
      claude-code-tihao-sourcing/mcp/src/core/result-envelope.js
  55. 376 0
      claude-code-tihao-sourcing/mcp/src/features/fmode-image-analysis/image-analysis.js
  56. 653 0
      claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/brief-parser.js
  57. 311 0
      claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/experience-rules.js
  58. 233 0
      claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/homepage-evidence-provider.js
  59. 183 0
      claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/homepage-evidence.js
  60. 501 0
      claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/live-provider.js
  61. 536 0
      claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/multimodal-evidence.js
  62. 301 0
      claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/preference-memory.js
  63. 313 0
      claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/ranker.js
  64. 120 0
      claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/reference-baselines.js
  65. 321 0
      claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/reference-enrichment-provider.js
  66. 250 0
      claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/reference-style-fingerprint.js
  67. 673 0
      claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/report.js
  68. 111 0
      claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/sample-data.js
  69. 185 0
      claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/sourcing-workflow.js
  70. 298 0
      claude-code-tihao-sourcing/mcp/src/server.js
  71. 37 0
      claude-code-tihao-sourcing/mcp/src/tools/cli-args.js
  72. 26 0
      claude-code-tihao-sourcing/mcp/src/tools/fmode-image-analysis.js
  73. 26 0
      claude-code-tihao-sourcing/mcp/src/tools/tihao-brief-sourcing-run.js
  74. 24 0
      claude-code-tihao-sourcing/mcp/src/tools/tihao-preference-update.js
  75. 19 0
      claude-code-tihao-sourcing/memory-templates/tihao-sourcing-profile.json
  76. 1161 0
      claude-code-tihao-sourcing/package-lock.json
  77. 162 0
      claude-code-tihao-sourcing/package.json
  78. 139 0
      claude-code-tihao-sourcing/scripts/acceptance.js
  79. 218 0
      claude-code-tihao-sourcing/scripts/brief-fixture-smoke.js
  80. 257 0
      claude-code-tihao-sourcing/scripts/business-execution-index-smoke.js
  81. 543 0
      claude-code-tihao-sourcing/scripts/business-execution-index.js
  82. 132 0
      claude-code-tihao-sourcing/scripts/business-proof-next-actions-missing-proof-smoke.js
  83. 275 0
      claude-code-tihao-sourcing/scripts/business-proof-next-actions-smoke.js
  84. 786 0
      claude-code-tihao-sourcing/scripts/business-proof-next-actions.js
  85. 189 0
      claude-code-tihao-sourcing/scripts/business-proof-progress-smoke.js
  86. 289 0
      claude-code-tihao-sourcing/scripts/business-proof-progress.js
  87. 136 0
      claude-code-tihao-sourcing/scripts/candidate-pool-smoke.js
  88. 54 0
      claude-code-tihao-sourcing/scripts/create-data-intake-pack-smoke.js
  89. 207 0
      claude-code-tihao-sourcing/scripts/create-data-intake-pack.js
  90. 53 0
      claude-code-tihao-sourcing/scripts/create-video-intake-pack-smoke.js
  91. 184 0
      claude-code-tihao-sourcing/scripts/create-video-intake-pack.js
  92. 96 0
      claude-code-tihao-sourcing/scripts/customer-effect-audit-smoke.js
  93. 274 0
      claude-code-tihao-sourcing/scripts/customer-effect-audit.js
  94. 534 0
      claude-code-tihao-sourcing/scripts/evidence-index-smoke.js
  95. 869 0
      claude-code-tihao-sourcing/scripts/evidence-index.js
  96. 86 0
      claude-code-tihao-sourcing/scripts/experience-transcript-index-smoke.js
  97. 392 0
      claude-code-tihao-sourcing/scripts/experience-transcript-index.js
  98. 64 0
      claude-code-tihao-sourcing/scripts/export-proof-gap-software-form-smoke.js
  99. 163 0
      claude-code-tihao-sourcing/scripts/export-proof-gap-software-form.js
  100. 243 0
      claude-code-tihao-sourcing/scripts/export-software-dedup-table.py

+ 5 - 0
.gitignore

@@ -53,3 +53,8 @@ Thumbs.db
 /docs/provider-archives
 /exports
 /uploads
+
+# Local agent / machine-specific (not for VCS)
+.claude/
+.mcp.json
+_relcheck.ps1

+ 8 - 0
claude-code-tihao-sourcing/.claude-plugin/plugin.json

@@ -0,0 +1,8 @@
+{
+  "name": "tihao",
+  "description": "Tihao AI skills for Claude Code: upload/read client briefs, generate business-ready blogger lists, analyze images through Fmode Doubao vision, handle token/recharge states, and keep sourcing preference memory.",
+  "version": "1.0.9",
+  "author": {
+    "name": "nkkj-BrainHack"
+  }
+}

+ 9 - 0
claude-code-tihao-sourcing/.gitignore

@@ -0,0 +1,9 @@
+node_modules/
+outputs/
+.claude/
+.env
+.env.local
+.npmrc
+.npm-cache-acceptance/
+*.tgz
+.tmp-clean/

+ 126 - 0
claude-code-tihao-sourcing/README.md

@@ -0,0 +1,126 @@
+# Tihao SOP
+
+Claude Code 技能包:按提号 SOP 读取客户 Brief,生成商务可复核的博主/达人推荐名单,并提供 Fmode/Doubao 图片识别补充能力。
+
+Published npm package:
+
+```text
+@vocmarket/tihao
+```
+
+Latest version:
+
+```text
+1.0.10
+```
+
+Recommended workspace install:
+
+```powershell
+npx --yes @vocmarket/tihao@latest workspace --smoke
+```
+
+Full installation guide:
+
+```text
+docs/install-guide.md
+```
+
+Installed Claude Code skill name:
+
+```text
+tihao
+fmode-image-analysis
+```
+
+Real-provider integration runbook:
+
+```text
+docs/live-provider-integration-runbook.md
+```
+
+Tihao experience optimization plan:
+
+```text
+docs/tihao-experience-optimization-plan.md
+```
+
+Current task handoff entry:
+
+```text
+docs/tihao-handoff-index.md
+```
+
+## What It Does
+
+- 第一版报告默认走 `live` 高质量首版:`collectionMode=live` + `resultFirstMode=true`,扩大参考感知召回、批量分析候选并按证据重排,直接产出商务可发客户的强名单。
+- 普通“读取 brief 出博主名单/提号/找博主/选号/可直接发客户的名单”都按上面的 live 高质量首版处理;只有用户明确要小规模/省额度时才缩到 `keywordLimit=1`。
+- `live` 模式调用公司 `voc-e-commerce` 代理接口,使用 JustOne-backed 数据;缺 token 时返回友好的开通/充值提示并主动要 sessionToken,而不是退回 sample。
+- `sample` 模式仅在用户明确要求演示、无消耗、不用真实数据或“先跑通 SOP”时使用,无 token 跑通 brief -> 博主名单流程。
+- 输出 Markdown、JSON、CSV 三份结果。
+- 无 token、401、403、额度不足时返回友好的开通/充值提示,不暴露原始错误或 token。
+- 支持偏好记忆,把客户反馈沉淀到下次选号。
+- 支持 Fmode 图片分析:用平台用户 token 调用 `doubao-seed-2-0-pro-260215`,识别截图、产品图、海报、UI 图和图片文字。
+- 支持参考博主链接、TikHub-compatible 基线补证接口和 ffmpeg/ASR/Vision-ready 证据卡接口;真实 provider 通过 `docs/live-provider-integration-runbook.md` 单独验收。
+- 已沉淀商务提号经验优化计划:硬指标、隐性规则、参考账号、主页最近 10/20 篇内容证据、平台相似推荐和客户选中率验收。
+- 支持 overnight 策略矩阵:`baseline-live`、`reference-account`、`homepage-evidence`、`video-enhanced`、`result-first`、`result-first-risk`、`result-first-broad`。
+- 支持人工复核指标统计:商务可用率、负样本率、负样本归因覆盖率、客户选中率、重复键和每个 brief 内排名连续性。
+- 支持真实历史数据集审计:检查真实 Brief、人工最终名单、客户选中/拒绝记录和拒绝原因是否足够做长期优化。
+
+## Local Development
+
+```powershell
+npm install
+npm run sample
+npm run smoke:package
+npm run fmode:image:smoke
+npm run acceptance
+```
+
+Fmode image analysis:
+
+```powershell
+$env:TIHAO_SESSION_TOKEN="<your-platform-token>"
+tihao image --image-path E:\workspace\example\screenshot.png --prompt "识别截图里的文字、主体和风险点"
+```
+
+Workspace install from local source:
+
+```powershell
+node install.js --workspace --smoke
+```
+
+Live small run:
+
+```powershell
+$env:TIHAO_SESSION_TOKEN="<your-session-token>"
+$env:TIHAO_COMPANY="<your-company-object-id>"
+npm run live
+npm run live:preflight
+npm run live:acceptance
+```
+
+Business review metrics:
+
+```powershell
+npm run review:metrics -- --input <已标注CSV> --output <输出目录>
+```
+
+Historical dataset readiness:
+
+```powershell
+npm run history:audit -- --input <history-dataset目录> --output <输出目录> --strict
+```
+
+Optimization plan status:
+
+```powershell
+npm run optimization:status
+```
+
+## MCP Tools
+
+- `fmode_image_analysis`
+- `tihao_brief_sourcing_run`
+- `tihao_token_check`
+- `tihao_preference_update`

+ 42 - 0
claude-code-tihao-sourcing/bin/tihao.js

@@ -0,0 +1,42 @@
+#!/usr/bin/env node
+const path = require('path');
+const { spawnSync } = require('child_process');
+
+const command = process.argv[2] || 'help';
+const root = path.resolve(__dirname, '..');
+
+if (command === 'install' || command === 'workspace') {
+  const args = [path.join(root, 'install.js')];
+  if (command === 'workspace') args.push('--workspace');
+  args.push(...process.argv.slice(3));
+  const result = spawnSync(process.execPath, args, { stdio: 'inherit', cwd: process.cwd() });
+  process.exit(result.status || 0);
+}
+
+if (command === 'sample') {
+  const result = spawnSync(process.execPath, [
+    path.join(root, 'mcp/src/tools/tihao-brief-sourcing-run.js'),
+    '--collection-mode',
+    'sample',
+    ...process.argv.slice(3)
+  ], { stdio: 'inherit', cwd: process.cwd() });
+  process.exit(result.status || 0);
+}
+
+if (command === 'image' || command === 'fmode-image') {
+  const result = spawnSync(process.execPath, [
+    path.join(root, 'mcp/src/tools/fmode-image-analysis.js'),
+    ...process.argv.slice(3)
+  ], { stdio: 'inherit', cwd: process.cwd() });
+  process.exit(result.status || 0);
+}
+
+console.log([
+  'tihao commands:',
+  '  tihao install              Install plugin to user Claude directory',
+  '  tihao workspace --smoke    Install plugin into current workspace and run smoke',
+  '  tihao sample --brief <file> Run sample brief-to-blogger-list workflow',
+  '  tihao image --image-path <file> --prompt <text>  Analyze an image with Fmode Doubao vision',
+  '',
+  'Legacy alias: tihao-sourcing'
+].join('\n'));

+ 174 - 0
claude-code-tihao-sourcing/docs/acceptance-checklist.md

@@ -0,0 +1,174 @@
+# 验收清单
+
+更新日期:2026-06-06
+
+本文档用于确认 `@vocmarket/tihao` 是否达到客户侧商务可直接使用的交付标准:上传或指定 Brief 后,Claude Code 能按提号 SOP 生成可复核、可解释、可沉淀的博主名单。
+
+## 1. 自动化验收
+
+在包根目录执行:
+
+```powershell
+npm install
+npm run acceptance
+```
+
+该命令会检查:
+
+- sample Brief 到博主名单流程能生成 Markdown、JSON、CSV;
+- Brief 解析能提取品牌、目标平台、目标数量、粉丝范围等核心字段;
+- 偏好记忆能写入 memory JSON;
+- live 模式未传 token 时返回 `needs_token`,并保持 `errors=[]`;
+- mock 403 返回 `needs_recharge`,并保持 `errors=[]`;
+- mock 401 返回 `needs_valid_token`,并保持 `errors=[]`;
+- mock 200 live 响应能规范化出至少一个候选博主;
+- 产品经理验收能检查报告章节、参考账号锚点、证据卡、CSV/JSON 证据字段,并阻止 token 或临时定价泄露;
+- MCP 协议 smoke 能连接 server、列出工具、调用 sample/token 工具;
+- `npm pack --dry-run` 只包含预期文件;
+- workspace 安装能写入 `.mcp.json`、`.claude/plugins/tihao` 和 `.claude/skills/tihao`。
+
+## 2. 产品经理专项验收
+
+```powershell
+npm run acceptance:pm
+```
+
+该验收会跑一份 DHA 类型 sample,带参考账号基线和多模态证据卡,并检查:
+
+- Markdown 包含参考锚点、多模态证据卡、商务名单、复核建议、生成文件;
+- JSON 保留 `referenceLinks`、`referenceStyleAnchors`、`evidenceCards`、`referenceSimilarity` 和证据信号;
+- CSV 保留 `referenceSimilarity`、`evidenceSignals`、`evidenceRiskHints`;
+- 报告不泄露 token,也不写入未确认定价。
+
+## 3. Provider 合同验收
+
+本地 mock 合同验收:
+
+```powershell
+npm run acceptance:providers:mock
+```
+
+它用于验证 TikHub 兼容参考账号补证合同、多模态证据卡合同、豆包视频分析 OpenAI 兼容合同。
+
+有真实 provider 权限时再执行:
+
+```powershell
+$env:TIKHUB_BASE_URL="<reference enrichment provider>"
+$env:TIKHUB_TOKEN="<optional tikhub token>"
+$env:TIHAO_EVIDENCE_BASE_URL="<multimodal evidence provider>"
+$env:TIHAO_EVIDENCE_TOKEN="<optional evidence provider token>"
+npm run acceptance:providers
+```
+
+真实 provider gate 通过标准:
+
+- 参考链接能转成至少一个 `referenceBaseline`;
+- 候选博主能获得 `referenceSimilarity`;
+- 证据 provider 返回至少一个 `evidenceCard`;
+- 候选博主能获得 `evidenceSignals`;
+- 输出结果不包含 Authorization header 或环境变量 token;
+- mock 请求包含 `contractVersion=tihao-provider-v1`、参考链接/风格锚点、证据候选、请求能力和可选鉴权;
+- mock 豆包视频分析合同校验模型 `doubao-seed-2-0-pro`、Authorization、视觉能力请求,以及 OpenAI 风格 JSON 证据卡解析。
+
+## 4. 参考视频 A/B 真实验收
+
+该命令会消耗真实额度,不放进默认 `npm run acceptance`:
+
+```powershell
+$env:TIHAO_SESSION_TOKEN="<Parse sessionToken>"
+$env:TIHAO_COMPANY="<Company objectId>"
+$env:VOC_SOCIAL_TOKEN="<Parse sessionToken>"
+$env:VIDEO_ANALYSIS_BASE_URL="https://api.fmode.cn"
+$env:VIDEO_ANALYSIS_MODEL="doubao-seed-2-0-pro"
+$env:VIDEO_ANALYSIS_TOKEN="<runtime model token>"
+npm run acceptance:video-ab
+```
+
+通过标准:
+
+- 同一份 Brief 和同一个参考视频能跑 A/B;
+- A 组只使用 live 提号,不使用 VOC social 视频详情;
+- B 组使用 VOC social 视频详情和豆包分析;
+- B 组能加载真实参考视频资源和证据卡;
+- B 组必须拿到真实视频 URL;
+- B 组必须拿到封面、ASR/字幕、帧图资源中的至少一类;
+- B 组证据卡不能只是 `pending`、`fallback` 或“待补证据”占位;
+- 参考账号占位提示不能当真实参考命中:`需补相似账号证据` 等内容只能进入 `referenceFallbackHitPoints`,`referenceEvidenceConcrete=false` 时不得单独支撑强推荐;
+- B 组证据卡必须包含 text/ASR/visual/frame 中至少一类可解释信号;
+- 参考视频信号可以改善 live 召回关键词,但婚礼、宴会、布景等跑偏事件词不能污染召回;
+- 证据命中点能影响候选博主评分和重排;
+- B 组不得降低强推荐数量、top 10 平均分或参考风格分;
+- B 组输出证据卡效率指标,例如每张证据卡带来的强推荐提升、分数提升;
+- 输出 `video-hit-rate-summary.json` 和中文 `video-hit-rate-report.md`;
+- 默认 live A/B 只分析预排序前 6 个候选,除非显式设置 `TIHAO_AB_EVIDENCE_CREATORS_LIMIT`;
+- 输出文件不能泄露 Parse token、模型 token 或 Authorization。
+
+## 5. 手动 live gate
+
+仅在安全测试账号有足够额度时运行:
+
+```powershell
+$env:TIHAO_SESSION_TOKEN="<Parse sessionToken>"
+$env:TIHAO_COMPANY="<Company objectId>"
+npm run live:acceptance
+```
+
+该 gate 以最小可用规模验证线上 `voc-e-commerce` 代理和扣费链路:
+
+- `keywordLimit=1`;
+- `pagesPerKeyword=1`;
+- 至少产出一个 live 候选;
+- 写出 Markdown、JSON、CSV;
+- 返回结果不包含 token。
+
+## 6. 长跑质量验收
+
+长跑任务参考:
+
+```text
+docs/overnight-quality-runbook.md
+docs/ai-overnight-optimization-task.md
+docs/overnight-quality-latest-evidence.md
+docs/manual-review-handoff.md
+docs/tihao-experience-optimization-plan.md
+```
+
+长跑结果只有在 `manifest.liveEnabled=true` 时才可作为命中率证据。sample 模式只能证明聚合、gate 展示和泄露扫描结构有效,不能证明真实提号质量。
+
+提号经验优化验收重点:
+
+- 硬性量化指标、产品/人群隐性规则、风格调性证据必须分层说明;
+- 有参考账号时,必须判断它是类型锚点、调性锚点、两者都是,还是只作为弱偏好;
+- 强推荐不能只靠平台标签,必须有至少 2 条 Brief 命中点,并在 JSON 中保留参考风格或主页证据命中点;
+- 命中 `封面下沉`、`封面混乱`、`排版混乱` 等主页质感风险时,不得标为强推荐,并需在 `homepageQualityRisks` 和风险提示中保留原因;
+- 软件端交付表必须去重,且排名在每个 brief 内连续;
+- `manual-review-sample.csv` 必须带 `客户选择`、`归因类型`、`反馈原因` 列,方便直接统计客户选中率和负样本归因覆盖率;
+- 长期效果以人工复核通过率、负样本率、客户选中率衡量,不能只看接口 200 或证据卡数量。
+
+## 7. 客户可用标准
+
+客户 demo ready:
+
+- `npm run acceptance` 通过;
+- sample Brief 报告展示目标数量、当前候选数、平台覆盖和缺口;
+- 提供参考链接时,报告能展示参考锚点和证据卡;
+- 聊天输出不展示原始 401/403 或上游错误体;
+- 报告和结构化结果不出现 token;
+- 全新临时目录 workspace 安装成功。
+
+production live ready:
+
+- 客户测试 token 和 company 下,手动 live gate 通过;
+- 线上返回真实候选;
+- 扣费、缓存、报告写出、泄露扫描都通过。
+
+multimodal live ready:
+
+- `npm run acceptance:providers` 能连真实 TikHub/证据 provider;
+- 至少一张真实 ffmpeg/ASR/Vision 证据卡经过人工复核。
+
+reference-video live ready:
+
+- `npm run acceptance:video-ab` 使用客户 Parse sessionToken 和运行时模型 token 通过;
+- summary 能展示真实参考资源;
+- top 10 结果有证据支持的提升。

+ 36 - 0
claude-code-tihao-sourcing/docs/ai-optimization-job-dedup-software-format.md

@@ -0,0 +1,36 @@
+# AI 提号优化任务表(去重后,软件端格式)
+
+本文档把“给 AI 布置的提号 SOP 长期优化任务”压成一张去重表,字段完全沿用软件端博主名单格式,方便软件端、商务看板或任务系统直接展示。这里的“博主名称”对应任务名称,“主页链接”对应任务入口或验收入口。
+
+## 去重原则
+
+- 同一类补证动作只保留一行,例如真实历史 Brief、参考账号、人工名单、客户选择和人工补号基线统一归到“真实历史 Brief 数据集”。
+- 同一类视频动作只保留一行,例如参考视频、候选视频、封面、字幕、帧图和 A/B 验收统一归到“真实视频证据 A/B”。
+- package 验收、泄密扫描、乱码扫描、软件端表 smoke 和状态审计统一归到“运行前健康检查”。
+- 参考账号拆解和主页近期内容证据合并,避免同一条风格/调性要求重复出现。
+- 客户效果、人工复核、负样本归因和二轮记忆保留为不同闭环,因为它们的负责人、输入和验收口径不同。
+
+## 软件端格式任务表
+
+| brief编号 | 策略 | 排名 | 平台 | 博主名称 | 综合分 | brief匹配分 | 参考风格分 | 主页证据分 | 视觉质感分 | 调性一致分 | 证据加分 | 证据风险扣分 | 推荐理由 | 风险提示 | 主页链接 | 人工复核标签 |
+| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
+| OPT-001 | 运行前检查 | 1 | 技能包 | 基础链路健康检查 | 100 | 100 | 0 | 0 | 0 | 0 | 0 | 0 | 让 AI 先确认 package、MCP、mock provider、软件端表、视频 A/B smoke、泄密扫描、乱码扫描和状态审计全部可跑通。 | 只能证明链路可用,不能证明真实命中率;任何 token、sessionToken、Authorization、模型 key 不得写入文件、日志或输出。 | `npm run acceptance`;`npm run acceptance:providers:mock`;`npm run hygiene:smoke` | 必须通过 |
+| OPT-002 | 真实数据准备 | 2 | 商务数据 | 真实历史 Brief 数据集 | 100 | 100 | 80 | 80 | 0 | 80 | 20 | 0 | 准备 5-10 个真实历史 Brief,包含客户原始 Brief、参考账号或参考视频、人工最终名单、客户选中/拒绝记录、拒绝原因和历史人工补号量基线。 | 缺客户选择、拒绝原因或人工补号基线时,只能做流程验证,不能宣称客户效果达标。 | `outputs/data-intake-pack-latest/history-data-template.csv`;`npm run history:audit -- --strict` | 待补真实数据 |
+| OPT-003 | 受控真实召回 | 3 | Live 召回 | 真实接口基线长跑 | 96 | 100 | 40 | 60 | 0 | 50 | 10 | 0 | 用真实运行时 token 和真实 provider 小规模跑基础召回,记录接口可用性、召回数量、候选池规模、失败原因、重复键和排名连续性。 | 接口 200、sample 模式、provider fallback 都不能单独证明提号率提升;失败时先修鉴权、召回、provider 或输出结构。 | `npm run live:preflight -- --strict`;`npm run overnight:quality` | 待真实长跑 |
+| OPT-004 | 参考与主页证据 | 4 | 证据增强 | 参考账号拆解和主页近期内容 | 98 | 92 | 100 | 100 | 90 | 95 | 25 | 0 | 拆解客户参考账号的类型、调性、内容结构、场景和表达方式,并分析候选主页近期内容、封面、标题、互动、发布时间和风险信号。 | 多参考账号风格冲突时不得强行合并;占位证据不得支撑强推荐;明显下沉、低质、竞品密集或调性跑偏账号不得进入强推荐。 | `referenceStyleHitPoints`;`homepageEvidenceHitPoints`;`homepageQualityRisks` | 待真实 provider |
+| OPT-005 | 视频证据增强 | 5 | 视频分析 | 真实视频证据 A/B | 99 | 92 | 100 | 90 | 100 | 100 | 30 | 0 | 用真实参考视频和真实候选视频 URL 调用视频分析,比较无视频证据和有视频证据两组,验证 Top10 质量、参考风格分、证据命中候选和强推荐数是否提升。 | 没有真实参考视频和真实候选视频时只能标记待补证据;不得把视频接口 200 当成命中率提升证明。 | `outputs/video-intake-pack-latest/video-resource-template.csv`;`npm run acceptance:video-ab` | 缺真实候选视频 |
+| OPT-006 | 扩大召回重排 | 6 | 策略矩阵 | 结果优先候选池优化 | 97 | 100 | 90 | 90 | 85 | 90 | 25 | 5 | 在本轮不优先考虑成本的前提下扩大候选池,再按 Brief 匹配、参考风格、主页证据、视频证据、硬规则和风险扣分统一重排。 | 扩大召回不得降低硬性规则门槛;硬规则违规率必须为 0;软件端重复键必须为 0;每个 Brief 排名必须连续。 | `aggregate-summary.json`;`manual-review-sample.csv`;`npm run candidate-pool:smoke` | 待矩阵对比 |
+| OPT-007 | 软件端交付 | 7 | 软件端输出 | 去重博主交付表 | 100 | 100 | 80 | 80 | 80 | 80 | 20 | 0 | 最终博主名单必须使用固定表头和 UTF-8 BOM,按全局博主唯一去重;同一平台加规范化主页链接不得重复,缺链接时同一平台加规范化博主名称不得重复。 | 已剔除账号不得进入交付表;重复键不为 0、排名不连续、中文乱码或表头不一致均视为不通过。 | `docs/software-client-table-format.md`;`npm run software:smoke` | 必须通过 |
+| OPT-008 | 人工复核闭环 | 8 | 商务复核 | 负样本归因和二轮记忆 | 97 | 95 | 90 | 90 | 90 | 95 | 25 | 0 | 商务标注可直接发客户、商务复核、可投但需补证、跑偏、硬性规则违约、调性不符、主页质感不符、参考账号不像;负样本必须填写归因并写入二轮记忆。 | 负样本归因覆盖率不到 100% 时不允许升级规则;同类反馈连续出现 3 次以上才建议升级为团队规则。 | `manual-review-sample.csv`;`npm run review:metrics -- --strict`;`tihao-preference-memory.json` | 待商务标注 |
+| OPT-009 | 客户效果审计 | 9 | 效果审计 | 客户选中率和人工补号减少 | 100 | 100 | 90 | 90 | 80 | 90 | 30 | 0 | 用真实客户反馈证明客户选中率、参考链路客户选中率、历史人工补号基线、本轮人工补号量和人工补号减少率,并沉淀每轮命令、输出目录、provider 状态和失败边界。 | 缺任一客户效果字段时只能标记为 blocked_by_external_data,不得宣布长期优化完成。 | `npm run customer-effect:audit -- --strict`;`npm run evidence:index`;`npm run round:deposition` | 待客户反馈 |
+
+## 软件端验收口径
+
+| 检查项 | 通过标准 | 自动化入口 |
+| --- | --- | --- |
+| 表头固定 | 表头与 `docs/software-client-table-format.md` 完全一致。 | `npm run software:smoke` |
+| 全局去重 | 全局 URL 重复组为 0;缺链接时全局平台加名称重复组为 0;软件端重复键为 0。 | `npm run software:smoke` |
+| 排名连续 | 每个 brief 内排名从 1 开始连续。 | `npm run software:smoke` |
+| 中文编码 | CSV 使用 UTF-8 BOM,Excel 打开中文不乱码;Markdown 文件用 UTF-8 打开不乱码。 | `npm run software:smoke` |
+| 真实证据 | sample/smoke/provider fallback 不算业务证明;模板和接口 200 也不能单独证明命中率提升。 | `npm run evidence:index` |
+| 客户效果 | 客户效果不可宣称,除非真实客户选择、拒绝原因和历史人工补号基线齐全,并通过客户效果审计。 | `npm run customer-effect:audit -- --strict` |

+ 33 - 0
claude-code-tihao-sourcing/docs/ai-optimization-proof-gap-software-table.md

@@ -0,0 +1,33 @@
+# AI 提号优化补证表(去重后,软件端格式)
+
+本文档按软件端博主名单的固定字段输出补证任务表。这里把“博主名称”临时作为任务名称,“主页链接”作为任务入口或验收入口,便于软件端、商务看板或任务系统直接复用同一套表格结构。
+
+## 去重口径
+
+- 同一张历史数据表相关的缺口只保留一行,不再拆成 Brief、人工名单、客户选择、拒绝原因、人工补号量多条重复任务。
+- 同一张视频资源表相关的缺口只保留一行,不再拆成参考视频、候选视频、封面、ASR、帧图多条重复任务。
+- 视频资源准备和视频 A/B 验收分开保留:前者是商务/投放补材料,后者是技术/AI 真实跑通证明。
+- live/provider 长跑和客户效果审计分开保留:前者证明系统链路稳定,后者证明客户选中率和人工补号减少。
+- sample、smoke、接口 200、provider fallback、模板生成都不计入业务效果证明。
+
+## 软件端格式补证表
+
+| brief编号 | 策略 | 排名 | 平台 | 博主名称 | 综合分 | brief匹配分 | 参考风格分 | 主页证据分 | 视觉质感分 | 调性一致分 | 证据加分 | 证据风险扣分 | 推荐理由 | 风险提示 | 主页链接 | 人工复核标签 |
+| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
+| GAP-001 | 真实数据准备 | 1 | 商务数据 | 真实历史 Brief 数据集 | 100 | 100 | 80 | 80 | 0 | 80 | 20 | 0 | 一次性补齐 5-10 个真实历史 Brief、参考账号或参考视频、人工最终名单、客户选中/拒绝记录、拒绝原因和历史人工补号量基线。 | 缺客户选择、拒绝原因或人工补号基线时,只能做流程验证,不能宣称客户效果达标。 | `outputs/data-intake-pack-latest/history-data-template.csv` | 待商务补齐 |
+| GAP-002 | 视频材料准备 | 2 | 商务/投放 | 真实视频资源表 | 98 | 90 | 100 | 80 | 100 | 95 | 25 | 0 | 补至少一个真实参考视频和一个真实候选视频,提供视频 URL,并补封面、ASR、帧图或正文证据之一。 | 只填主页链接、示例 URL 或占位内容时,不能启动真实视频 A/B。 | `outputs/video-intake-pack-latest/video-resource-template.csv` | 待商务/投放补齐 |
+| GAP-003 | 视频分析验证 | 3 | 技术/AI | 真实视频证据 A/B | 99 | 92 | 100 | 90 | 100 | 100 | 30 | 0 | 用真实参考视频和真实候选视频 URL 调用豆包视频分析,对比无视频证据和有视频证据两组,验证 Top10 质量、参考风格分、证据命中候选和强推荐数是否不退化并提升。 | 视频接口 200 不等于提号率提升;必须有 live proofContext、provider ok、真实视频资源和非占位证据卡。 | `npm run acceptance:video-ab` | 待真实 A/B 通过 |
+| GAP-004 | Live 长跑证明 | 4 | 技术/AI | 真实 live/provider 长跑证明 | 96 | 100 | 60 | 80 | 80 | 80 | 20 | 0 | 使用真实 sessionToken、company、VOC social provider、视频分析 provider 和真实历史数据运行策略矩阵,证明 liveEnabled=true、overallPass=true、failureCount=0、failedGateCount=0。 | sample、mock、provider fallback 或单次接口 200 不能作为长跑通过证明。 | `npm run longrun:readiness;npm run overnight:quality 或 npm run optimization:pipeline` | 待真实长跑 |
+| GAP-005 | 客户效果审计 | 5 | 商务数据 | 商务复核和客户效果 | 100 | 100 | 90 | 90 | 80 | 90 | 30 | 0 | 商务标注人工复核标签、客户选择、归因类型、反馈原因和本轮人工补号量,再审计客户选中率、参考链路客户选中率、历史人工补号基线、本轮人工补号量和人工补号减少率。 | 缺任一客户效果字段时只能标记 blocked_by_external_data,不得宣布长期优化完成。 | `outputs/data-intake-pack-latest/manual-review-template.csv` | 待商务标注 |
+
+## 通过标准
+
+| 检查项 | 通过标准 | 自动化入口 |
+| --- | --- | --- |
+| 全局去重 | 上表只保留 5 个唯一补证任务;同一输入表、同一验收动作不重复拆行。 | 人工检查本文档 |
+| 表头固定 | 表头与 `docs/software-client-table-format.md` 完全一致。 | `npm run software:smoke` |
+| 历史数据 | `history:audit --strict` 显示 readyForCustomerEffectProof=true。 | `npm run history:audit -- --strict` |
+| 视频资源 | `video:resource-readiness --strict` 显示 readyForVideoAbPreflight=true 且 failureCount=0。 | `npm run video:resource-readiness -- --strict` |
+| 视频 A/B | 真实 live provider、真实视频 URL、非占位证据卡、Top10 非退化、强推荐不下降全部通过。 | `npm run acceptance:video-ab` |
+| 长跑证明 | liveEnabled=true、overallPass=true、failureCount=0、failedGateCount=0。 | `npm run overnight:quality` |
+| 客户效果 | 客户选中率、参考链路客户选中率和人工补号减少率全部由真实客户反馈证明。 | `npm run customer-effect:audit -- --strict` |

+ 64 - 0
claude-code-tihao-sourcing/docs/ai-optimization-task-software-table.md

@@ -0,0 +1,64 @@
+# AI 提号优化任务表
+
+本文档是给 AI 长跑任务和软件端任务配置直接引用的表格版说明。所有真实 token、sessionToken、模型 key 只能通过运行时环境变量传入,不得写入任务表、日志或输出文件。
+
+## 1. 任务总表
+
+| 阶段 | 输入 | AI 动作 | 输出文件 | 验收标准 | 不通过处理 |
+| --- | --- | --- | --- | --- | --- |
+| 运行前检查 | 本地代码、npm 包、mock provider | 执行 `npm run acceptance`、`npm run acceptance:providers:mock`、`npm run optimization:status` | `outputs/optimization-status-*/optimization-status-report.md` | package、MCP、mock provider、软件端表、视频 A/B smoke 全部通过;泄密和乱码扫描为 0 | 先修自动化失败项,不启动 live 长跑 |
+| 真实数据准备 | 5-10 个历史 Brief、参考账号/视频、人工最终名单、客户选择/拒绝记录、人工补号量基线 | 执行 `npm run history:audit -- --input <目录> --output <目录> --strict` | `historical-dataset-audit-report.md`、`historical-dataset-audit.json` | 每个 Brief 都有人工名单、客户选择或拒绝原因;要证明人工补号减少时必须有历史人工补号基线 | 缺字段时只允许做流程测试,不能宣称客户效果达标 |
+| 受控 live 子集 | 真实 Parse sessionToken、company、VOC social token、视频分析运行时 token | 小规模跑 `baseline-live`、`reference-account`、`homepage-evidence`、`result-first` | `tihao-sourcing-report.md`、`tihao-sourcing-result.json`、`tihao-sourcing-client-list.csv` | 业务接口可用;输出无 token;软件端表重复键为 0;每个 brief 排名连续 | 记录失败原因,先修鉴权、召回、provider 或输出结构 |
+| 完整策略矩阵 | 覆盖多个品类的历史 Brief | 跑 `baseline-live`、`reference-account`、`homepage-evidence`、`video-enhanced`、`result-first`、`result-first-broad` | `aggregate-report.md`、`aggregate-summary.json`、`manual-review-sample.csv` | `failureCount=0`;每个 Brief 至少一个策略通过;每个 Brief 至少产出目标人数 1.5 倍可复核候选 | 每轮只优化一个主要失败原因,重跑同一矩阵对比 |
+| 参考视频 A/B | 同一 Brief、同一参考视频、真实视频 URL、豆包视频分析服务 | 执行 `npm run acceptance:video-ab`,对比无视频证据和有视频证据两组 | `video-hit-rate-report.md`、`video-hit-rate-summary.json` | B 组拿到真实视频 URL;拿到封面、字幕/ASR、帧图至少一类;证据卡不是占位;强推荐数、Top 10 平均分、参考风格分不下降;证据命中候选增加 | 不能说视频分析已提升命中率;按视频资源、证据解析、排序权重分别修 |
+| 人工复核 | 已预置 `客户选择`、`归因类型`、`反馈原因` 列的 `manual-review-sample.csv` | 商务填写人工复核标签;负样本填写归因类型;有客户反馈时填写客户选择 | 标注后的 CSV、`review-metrics-report.md` | 商务可用率 >= 60%;负样本率 <= 10%;负样本归因覆盖率 = 100%;有客户选择时客户选中率 >= 30% | 按归因类型进入下一轮优化,不降低门槛 |
+| 反馈闭环 | 商务负样本反馈、客户偏好、剔除账号 | 导入偏好记忆并重跑第二轮 | `tihao-preference-memory.json`、第二轮软件端 CSV | 被拉黑或跑偏账号不再进入商务名单;调性/主页质感反馈写入偏好;同类反馈 3 次以上才建议团队规则 | 检查记忆写入、剔除规则、排序降权是否生效 |
+| 状态沉淀 | 本轮命令、输出目录、指标和边界 | 更新实施日志并执行 `npm run optimization:status`、`npm run evidence:index` | 实施日志、状态审计报告、证据台账 | `passed`、`ready_not_proven`、`blocked_by_external_data` 状态清楚;未证明项不被标成完成 | 保留边界说明,等待真实 provider 或客户数据 |
+
+## 2. 软件端博主表固定格式
+
+软件端最终接收的博主名单必须使用以下字段,默认文件名为 `tihao-sourcing-client-list.csv`。交付表按“全局博主唯一”去重:同一 `平台 + 规范化主页链接` 不得重复;没有主页链接时,同一 `平台 + 规范化博主名称` 不得重复。同一博主被多个 brief 或多个策略召回时,只保留证据更完整、综合分更高的一条进入交付表。
+
+| brief编号 | 策略 | 排名 | 平台 | 博主名称 | 综合分 | brief匹配分 | 参考风格分 | 主页证据分 | 视觉质感分 | 调性一致分 | 证据加分 | 证据风险扣分 | 推荐理由 | 风险提示 | 主页链接 | 人工复核标签 |
+| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
+| 客户 Brief 编号或品牌名 | baseline-live / reference-account / homepage-evidence / video-enhanced / result-first / result-first-broad | 去重后的连续排名 | 小红书/抖音/B站 | 账号展示名 | 最终排序分 | 硬性条件、人群、预算、关键词匹配分 | 参考账号或参考视频风格相似分 | 主页最近内容证据分 | 封面、画面、排版、场景质感分 | 近期内容调性一致分 | provider 或多模态证据加分 | 竞品、跑偏、内容不符等扣分 | 说明 Brief 命中点、参考风格或主页证据 | 说明报价、档期、竞品、合规或证据缺口 | 博主主页 URL | 商务填写 |
+
+## 3. 人工复核标签表
+
+| 标签 | 判定口径 | 是否计入负样本 | 后续动作 |
+| --- | --- | --- | --- |
+| 可直接发客户 | 硬性条件、调性、主页证据都通过 | 否 | 可进入客户名单 |
+| 商务复核 | 基本可用,但需要确认报价、档期、近期内容或合作状态 | 否 | 补证后决定是否发客户 |
+| 可投但需补证 | 候选有潜力,但缺视频、主页近作或人工确认 | 否 | 优先补证,不直接算失败 |
+| 跑偏 | 内容方向、账号人设或品类明显偏离 Brief | 是 | 写入失败归因并降权或剔除 |
+| 硬性规则违约 | 平台、粉丝、预算、地域、禁投项不符合 | 是 | 进入硬规则拦截 |
+| 调性不符 | 数据可用,但风格不像参考账号或不适合品牌 | 是 | 写入调性偏好或负向风格 |
+| 主页质感不符 | 封面、画面、排版、场景明显低质或下沉 | 是 | 提高主页证据和视觉质感权重 |
+| 参考账号不像 | 参考链路召回但类型/调性相似度不足 | 是 | 调整参考账号拆解和相似度阈值 |
+
+## 4. 去重和候选池验收
+
+| 检查项 | 通过标准 | 自动化入口 |
+| --- | --- | --- |
+| 表头固定 | 表头与 `docs/software-client-table-format.md` 完全一致 | `npm run software:smoke` |
+| 重复键 | 软件端表重复键数量为 0 | `npm run software:smoke`、`npm run candidate-pool:smoke` |
+| 排名连续 | 每个 brief 内排名从 1 开始连续 | `npm run software:smoke`、`npm run candidate-pool:smoke` |
+| 候选池规模 | 每个 Brief 至少输出目标人数 1.5 倍的可复核候选;不足时必须给出补量提示 | `npm run candidate-pool:smoke` |
+| 剔除账号 | 已剔除账号不得进入软件端交付表 | `npm run hard-rules:smoke` |
+| 中文编码 | CSV 使用 UTF-8 BOM,Excel 打开不乱码 | `npm run software:smoke` |
+| 表格版沉淀 | 已去重 CSV 可以转换成同字段 Markdown 表格 | `npm run software:markdown -- --input <CSV> --output <MD>` |
+
+## 5. 对 AI 的执行指令
+
+| 项目 | 指令 |
+| --- | --- |
+| 目标 | 优化 `@vocmarket/tihao` 的提号命中率和商务可用率,让上传 Brief 后能稳定输出商务可复核、可解释、可沉淀的博主名单。 |
+| 优先级 | 本轮优先结果质量,不优先压成本;但不得泄露任何 token、sessionToken、Authorization 或模型 key。 |
+| 策略 | 每个 Brief 跑基础召回、参考账号、主页证据、视频增强、结果优先、扩大召回后重排等策略,并按同一指标比较。 |
+| 指标 | 强推荐数量、Top 10 平均综合分、参考风格分、主页证据覆盖率、商务可用率、负样本率、客户选中率、人工补号量。 |
+| 强推荐证据 | 每个强推荐必须至少有 2 条 Brief 命中点,并至少有 1 条 `referenceStyleHitPoints` 或 `homepageEvidenceHitPoints`。 |
+| 参考占位证据 | `需补相似账号证据`、`待补参考风格证据` 只能写入 `referenceFallbackHitPoints`,不得混入 `referenceStyleHitPoints`;`referenceEvidenceConcrete=false` 时不得单独支撑强推荐。 |
+| 主页质感门槛 | 命中封面下沉、封面混乱、排版混乱、画面模糊、低质等风险时,不得进入强推荐,并保留 `homepageQualityRisks`。 |
+| 归因 | 每个失败样本必须归到需求解析错、隐性规则漏、召回关键词错、主页证据不足、视频证据误判、排序权重错、输出解释错、软件端表重复或排名不连续之一。 |
+| 迭代 | 每轮只优化一个主要失败原因,重跑同一矩阵,记录 baseline 与优化后指标差异。 |
+| 禁止 | 不得把 sample 通过当成真实命中率证明;不得把 provider fallback 当成真实 provider 通过;不得为了通过而降低门槛。 |

+ 251 - 0
claude-code-tihao-sourcing/docs/ai-overnight-optimization-task.md

@@ -0,0 +1,251 @@
+# 给 AI 的提号 SOP 长跑优化任务
+
+## 目标
+
+持续优化 `@vocmarket/tihao`,直到它能稳定把客户 Brief 转成商务可用的博主名单。优先级是结果质量:更高命中率、更强 Brief 相关性、更有效的参考账号/参考视频风格匹配、更低负样本率。
+
+本轮不以成本为第一约束,但任何 token、sessionToken、模型 key、Authorization header 都不能写入代码、文档、报告、日志或输出文件。
+
+## 工作目录
+
+```text
+E:\workspace\tihao-ai\claude-code-tihao-sourcing
+```
+
+## 运行前检查
+
+先跑本地发布验收:
+
+```powershell
+npm run acceptance
+npm run acceptance:providers:mock
+npm run optimization:status
+```
+
+如果要验证真实 live/provider/video,先跑预检:
+
+```powershell
+$env:TIHAO_SESSION_TOKEN="<Parse sessionToken>"
+$env:TIHAO_COMPANY="<Company objectId>"
+$env:VOC_SOCIAL_TOKEN="<Parse sessionToken>"
+$env:VIDEO_ANALYSIS_BASE_URL="https://api.fmode.cn"
+$env:VIDEO_ANALYSIS_MODEL="doubao-seed-2-0-pro"
+$env:VIDEO_ANALYSIS_TOKEN="<runtime model token>"
+npm run live:preflight -- --strict
+```
+
+预检失败时不要启动 live 长跑。
+
+把 live 预检和历史数据审计汇总成可交接的长跑就绪报告:
+
+```powershell
+npm run longrun:readiness -- --mode full-matrix --preflight <live-preflight-summary.json> --history <historical-dataset-audit.json> --output <输出目录> --strict
+```
+
+如果要验证视频 A/B:
+
+```powershell
+npm run longrun:readiness -- --mode video-ab --preflight <live-preflight-summary.json> --strict
+```
+
+如果要验证客户选中率和人工补号减少:
+
+```powershell
+npm run longrun:readiness -- --mode customer-effect --preflight <live-preflight-summary.json> --history <historical-dataset-audit.json> --strict
+```
+
+## 历史数据准备
+
+如要验证客户选中率,先准备 5-10 个真实历史 Brief,包含客户原始 Brief、参考账号/视频、人工最终名单、客户最终选中/拒绝记录和拒绝原因。
+
+验收:
+
+```powershell
+npm run history:audit -- --input <history-dataset目录> --output <输出目录> --strict
+```
+
+只有 `history:audit` 显示可用于长期优化长跑,才可以验证客户选中率 30%/50%。没有真实客户选择字段时,只能验证商务复核通过率。
+
+## 受控 live 子集
+
+正式长跑前先跑小子集。它会消耗真实额度,范围要小。
+
+```powershell
+$env:TIHAO_OVERNIGHT_LIVE="true"
+$env:TIHAO_OVERNIGHT_FIXTURES="dha-mom-baby"
+$env:TIHAO_OVERNIGHT_VARIANTS="baseline-live,reference-account,homepage-evidence,result-first"
+$env:TIHAO_OVERNIGHT_MAX_RUNS="4"
+$env:TIHAO_OVERNIGHT_FAIL_ON_GATES="true"
+npm run overnight:quality
+```
+
+如果要验证真实 reference/homepage provider,打开严格门槛:
+
+```powershell
+$env:TIHAO_GATE_REQUIRE_REFERENCE_PROVIDER="true"
+$env:TIHAO_GATE_REQUIRE_HOMEPAGE_PROVIDER="true"
+```
+
+严格门槛打开后,fallback/sample 失败是正确结果,不要为了通过降低门槛。
+
+## 完整 live 矩阵
+
+受控子集通过后再跑完整矩阵:
+
+```powershell
+$env:TIHAO_OVERNIGHT_FIXTURES="dha-mom-baby,sensitive-skin-repair,healthy-snack,home-cleaning,618-list-seeding"
+$env:TIHAO_OVERNIGHT_VARIANTS="baseline-live,reference-account,homepage-evidence,video-enhanced,result-first,result-first-risk,result-first-broad"
+$env:TIHAO_OVERNIGHT_DELAY_MS="1500"
+$env:TIHAO_OVERNIGHT_RUN_RETRIES="1"
+$env:TIHAO_OVERNIGHT_RESUME="true"
+$env:TIHAO_OVERNIGHT_FAIL_ON_GATES="true"
+npm run overnight:quality
+```
+
+查看:
+
+```text
+outputs/overnight-quality-*/aggregate-summary.json
+outputs/overnight-quality-*/aggregate-report.md
+outputs/overnight-quality-*/manual-review-sample.csv
+```
+
+## 参考视频 A/B
+
+有参考视频时,单独验证视频分析是否真的提升提号结果:
+
+```powershell
+npm run acceptance:video-ab
+```
+
+通过标准:
+
+- B 组必须拿到真实视频 URL。
+- B 组必须拿到封面、ASR/字幕、帧图资源中的至少一类。
+- B 组证据卡不能只是 pending、fallback 或待补证据。
+- B 组证据卡必须包含 text、ASR、visual、frame 中至少一类可解释信号。
+- B 组 Top 10 证据命中候选增加。
+- B 组强推荐数量、Top 10 平均分、参考风格分不得下降。
+
+没有通过这条命令,不要说视频分析已经证明能提升提号率。
+
+## 人工复核和业务指标
+
+人工复核 `manual-review-sample.csv`。长跑表已预置 `客户选择`、`归因类型`、`反馈原因` 三列,商务不用手动加列。
+
+在“人工复核标签”列填写:
+
+- `可直接发客户`
+- `商务复核`
+- `跑偏`
+- `硬性规则违约`
+- `调性不符`
+- `主页质感不符`
+- `参考账号不像`
+- `可投但需补证`
+
+如果拿到客户最终选择,在 `客户选择` 列填写:
+
+- `客户选中`
+- `客户拒绝`
+- `待客户反馈`
+
+所有负样本必须填写 `归因类型`。负样本包括:
+
+- `跑偏`
+- `硬性规则违约`
+- `调性不符`
+- `主页质感不符`
+- `参考账号不像`
+
+归因类型必须落到可优化环节,例如:
+
+- `需求解析错`
+- `隐性规则漏`
+- `召回关键词错`
+- `主页证据不足`
+- `视频证据误判`
+- `排序权重错`
+- `输出解释错`
+- `软件端表重复或排名不连续`
+
+统计业务指标:
+
+```powershell
+npm run review:metrics -- --input <已标注CSV> --output <输出目录> --strict
+```
+
+短期目标:
+
+- 商务可用率 >= 60%。
+- 负样本率 <= 10%。
+- 负样本归因覆盖率 = 100%。
+- 有客户选择字段时,客户选中率 >= 30%。
+
+中长期目标:
+
+- 有参考账号的 Brief,客户选中率 >= 40%。
+- 客户选中率稳定 >= 50%。
+- 同类需求下人工补号量减少 50%。
+
+## 失败后怎么优化
+
+不要降低门槛来凑通过。按失败样本归因:
+
+- 需求解析错。
+- 隐性规则漏。
+- 召回关键词错。
+- 主页证据不足。
+- 视频证据误判。
+- 排序权重错。
+- 输出解释错。
+- 软件端表重复或排名不连续。
+
+每轮只优化一个主要失败原因,重跑同一矩阵并记录提升。
+
+## 必须沉淀
+
+每轮结束后更新:
+
+```text
+docs/tihao-experience-implementation-log.md
+```
+
+记录:
+
+- 运行命令。
+- 输出目录。
+- 是否 live。
+- 是否打开严格 provider 门槛。
+- `failureCount`、`gatePass`、`failedGateCount`。
+- 软件端重复键和排名连续性。
+- provider 状态。
+- 人工复核指标或客户选中率。
+- 明确说明还没有验证的边界。
+
+同时运行:
+
+```powershell
+npm run optimization:status
+npm run evidence:index
+```
+
+如果状态里仍有 `ready_not_proven` 或 `blocked_by_external_data`,不要宣布长期优化目标完成。`evidence:index` 会扫描 `outputs` 下的长跑、状态审计、review metrics、历史数据审计和视频 A/B 产物,区分 `real_evidence`、`smoke_or_local` 和 `not_business_proof`。
+
+## 不允许做的事
+
+- 不要硬编码 Parse token、模型 token、npm token 或 Authorization header。
+- 不要在 `manifest.liveEnabled=false` 时声称已经 live 验证。
+- 不要把 sample 模式通过当成命中率证明。
+- 不要把 fallback/provider 状态存在当成真实 provider 通过。
+- 不要为了通过而降低验收门槛。
+- 没有通过 package acceptance 和目标 live gate 前,不要发布新 npm 版本。
+
+## 交付物
+
+- 已更新的实现文件。
+- 已更新的运行手册和验收文档。
+- 最新 live 输出目录。
+- baseline 与优化策略的指标对比。
+- 人工复核或客户选择后的 `review-metrics` 报告。
+- 明确说明已验证内容,以及仍需真实 provider、真实客户反馈或人工复核的内容。

+ 54 - 0
claude-code-tihao-sourcing/docs/business-proof-action-order.md

@@ -0,0 +1,54 @@
+# 提号长期优化补证操作顺序
+
+本文档用于把 `docs/tihao-experience-optimization-plan.md` 里的长期优化目标,拆成商务、投放、技术和下一轮 AI 可以直接执行的补证顺序。当前结论仍是:本地能力和验收入口已较完整,但客户效果、真实视频提升和真实 live 长跑还不能宣称完成。
+
+## 总原则
+
+- 不要把 sample、smoke、dry-run、模板数据、provider fallback 或接口 200 当成业务证明。
+- 不要在 CSV、Markdown、JSON、日志、截图或聊天里写入 sessionToken、模型 token、Authorization、npm token 或内部鉴权参数。
+- 每一轮先做真实材料预审,再启动长跑;预审不通过时,不允许扩大运行规模。
+- 客户效果只能由 `customer-effect:audit` 证明;缺客户选择、历史人工补号量基线或本轮人工补号量时,只能标记为待补证。
+
+## 操作顺序
+
+以下 15 步必须按顺序推进;前置材料未通过时,不进入后续长跑或效果宣称。
+
+| 顺序 | 负责人 | 目标 | 输入材料 | 执行命令 | 通过标准 | 不通过处理 |
+| --- | --- | --- | --- | --- | --- | --- |
+| 1 | 商务 | 生成统一收集包 | 无 | `npm run data:intake-template -- --output outputs\data-intake-pack-latest` | 生成历史数据模板、商务复核模板和填写说明。 | 先不要手写零散表格,统一用模板收集。 |
+| 2 | 投放/商务 | 生成视频资源收集包 | 无 | `npm run video:intake-template -- --output outputs\video-intake-pack-latest` | 生成参考视频和候选视频资源模板。 | 没有真实视频 URL 时,视频分析只能标记待补证。 |
+| 3 | 商务 | 填真实历史 Brief | 5-10 个真实 Brief、人工名单、客户选择、拒绝原因、历史人工补号量基线 | 无 | 每个 Brief 都有客户原始需求、人工最终名单、客户选中/拒绝、拒绝原因和历史人工补号量基线。 | 缺客户选择或人工补号基线时,只能做流程验证。 |
+| 4 | 商务/投放 | 填真实视频资源 | 参考视频 URL、候选视频 URL、封面、ASR、帧图或正文证据之一 | 无 | 至少一个真实参考视频和一个真实候选视频可用于 A/B。 | 只填主页链接或示例 URL 时,视频 A/B 不启动。 |
+| 5 | 技术/AI | 真实材料预审 | `outputs\data-intake-pack-latest`、`outputs\video-intake-pack-latest` | `npm run intake:readiness -- --data-pack outputs\data-intake-pack-latest --video-pack outputs\video-intake-pack-latest --output outputs\intake-readiness-latest` | `overallReady=true` 且 `failureCount=0`。 | 按报告逐项退回补字段;不能进入 full-matrix 长跑。 |
+| 6 | 技术/AI | 历史数据导入 | 已填历史数据 CSV | `npm run history:from-csv -- --input <历史数据CSV> --output <history-dataset目录>` | 输出历史数据集 JSON。 | 修正 CSV 表头、空值、示例占位和重复 brief。 |
+| 7 | 技术/AI | 历史数据审计 | 历史数据集目录 | `npm run history:audit -- --input <history-dataset目录> --output <历史审计输出目录> --strict` | `readyForCustomerEffectProof=true`。 | 缺字段时回到第 3 步补真实数据。 |
+| 8 | 技术/AI | 视频资源就绪审计 | 已填视频资源 CSV | `npm run video:resource-readiness -- --input <video-resource-template.csv> --output <视频资源审计输出目录> --strict` | `readyForVideoAbPreflight=true` 且 `failureCount=0`。 | 缺 URL、封面、ASR、帧图或正文证据时回到第 4 步。 |
+| 9 | 技术/AI | 长跑前门禁 | intake readiness、proof-gap request、历史审计、视频资源审计 | `npm run longrun:readiness -- --mode full-matrix --intake-readiness outputs\intake-readiness-latest\intake-readiness-summary.json --proof-gap outputs\proof-gap-request-latest\proof-gap-request-summary.json --output outputs\long-run-readiness-latest` | `ready=true` 且 fail 数为 0。 | 不要启动 overnight;先关闭 readiness 失败项。 |
+| 10 | 技术/AI | 运行策略矩阵 | 真实历史数据、有效 provider 配置、真实 token 运行环境 | `npm run optimization:pipeline -- --history-csv <历史数据CSV> --review-csv <已标注CSV> --current-manual-supplement-count <本轮人工补号量> --output <输出目录> --strict` | 每个 Brief 至少一个策略通过,软件端重复键为 0,排名连续。 | 只优化一个主要失败原因后重跑,避免同时改多处无法归因。 |
+| 11 | 技术/AI | 视频 A/B 验收 | 真实 sessionToken/company/provider、真实参考视频和候选视频 | `npm run acceptance:video-ab` | video-enhanced 强推荐数、Top10 综合分、参考风格分不低于 baseline,证据命中候选增加,泄密为 0。 | 不能说视频分析提升命中率;回到视频资源或 provider 配置排查。 |
+| 12 | 商务 | 人工复核标注 | AI 输出的软件端 CSV、商务复核模板 | 无 | 负样本必须有归因类型;客户选择和拒绝原因必须填写。 | 负样本归因不满 100% 时,不能升级团队规则。 |
+| 13 | 技术/AI | 商务复核指标 | 已标注 CSV | `npm run review:metrics -- --input <已标注CSV> --output <复核指标输出目录> --strict` | 负样本率低于 10%,负样本归因覆盖率为 100%,分策略指标齐全。 | 按失败归因选择下一轮唯一优化主题。 |
+| 14 | 技术/AI | 客户效果审计 | 已标注 CSV、历史审计 JSON、本轮人工补号量 | `npm run customer-effect:audit -- --review-csv <已标注CSV> --history-audit <historical-dataset-audit.json> --current-manual-supplement-count <本轮人工补号量> --output <客户效果输出目录> --strict` | 客户选中率、参考链路客户选中率、历史人工补号基线、本轮人工补号量和人工补号减少率全部通过。 | 客户效果不可宣称,继续标记 `blocked_by_external_data`。 |
+| 15 | 技术/AI | 刷新证据和交接 | 本轮全部输出 | 优先运行 `npm run round:refresh -- --output-root outputs --strict`;如需单独排查,再分别运行 `npm run proof-gap:closure -- --output outputs\proof-gap-closure-latest`、`npm run evidence:index -- --output outputs\evidence-index-latest`、`npm run handoff:summary -- --output outputs\optimization-handoff-latest`、`npm run latest-form:index -- --output outputs\latest-form-index-latest --strict`、`npm run round:deposition -- --output outputs\round-deposition-latest --strict` | `round-refresh-summary.json` 10 步全部通过,`latest-form-index` 能指向最新补证表单、候选名单和负责人行动文件,`round:deposition` 通过,且仍未证明边界被明确记录。 | 缺沉淀或任一刷新步骤失败时,不允许口头宣布本轮完成。 |
+
+## 长跑允许启动条件
+
+满足以下条件后,才允许跑一晚上级别的 full-matrix 或 live 长跑:
+
+- `intake-readiness-summary.json` 中 `overallReady=true`。
+- `intake-readiness-summary.json` 中 `failureCount=0`。
+- `history:audit --strict` 通过,并能支撑客户效果证明。
+- `video:resource-readiness --strict` 通过;如果本轮不测视频,必须在报告里明确视频不参与本轮验收。
+- `longrun:readiness` 显示 `ready=true`。
+- 当前输出不含 token、Authorization、sessionToken、npm token 或模型 key。
+
+## 长期完成判定
+
+只有同时满足以下条件,才可以把长期优化目标标记为完成:
+
+- `optimization:status` 中没有 `ready_not_proven` 和 `blocked_by_external_data`。
+- `proof-gap:closure` 中 `openCount=0` 且 `complete=true`。
+- `customer-effect:audit --strict` 通过。
+- 软件端表格表头固定、中文不乱码、重复键为 0、每个 brief 排名连续。
+- 视频提升若被纳入结论,必须有真实视频 A/B 通过证据。
+- 每轮实施日志、证据台账、交接摘要和沉淀审计全部刷新。

+ 54 - 0
claude-code-tihao-sourcing/docs/business-proof-action-order.zh-CN.md

@@ -0,0 +1,54 @@
+# 提号长期优化补证操作顺序(中文执行版)
+
+本文把长期优化目标拆成商务、投放、技术/AI 和下一轮 AI 可以直接执行的补证顺序。当前结论仍是:本地能力和验收入口已经较完整,但客户效果、真实视频提升和真实 live 长跑还不能宣称完成。
+
+## 总原则
+
+- 不要把 sample、smoke、dry-run、模板数据、provider fallback 或接口 200 当成业务证明。
+- 不要在 CSV、Markdown、JSON、日志、截图或聊天里写入 sessionToken、模型 token、Authorization、npm token 或内部鉴权参数。
+- 每一轮先做真实材料预审,再启动长跑;预审不通过时,不允许扩大运行规模。
+- 客户效果只能由 `customer-effect:audit` 证明;缺客户选择、历史人工补号量基线或本轮人工补号量时,只能标记为待补证。
+
+## 15 步补证顺序
+
+| 顺序 | 负责人 | 目标 | 输入材料 | 执行命令 | 通过标准 | 不通过处理 |
+| --- | --- | --- | --- | --- | --- | --- |
+| 1 | 商务 | 生成统一收集包 | 无 | `npm run data:intake-template -- --output outputs\data-intake-pack-latest` | 生成历史数据模板、商务复核模板和填写说明。 | 不手写零散表格,统一用模板收集。 |
+| 2 | 商务/投放 | 生成视频资源收集包 | 无 | `npm run video:intake-template -- --output outputs\video-intake-pack-latest` | 生成参考视频和候选视频资源模板。 | 没有真实视频 URL 时,视频分析只能标记待补证。 |
+| 3 | 商务 | 填真实历史 Brief | 5-10 个真实 Brief、人工名单、客户选择、拒绝原因、历史人工补号量基线 | 无 | 每个 Brief 都有客户原始需求、人工最终名单、客户选中/拒绝、拒绝原因和历史人工补号量基线。 | 缺客户选择或人工补号基线时,只能做流程验证。 |
+| 4 | 商务/投放 | 填真实视频资源 | 参考视频 URL、候选视频 URL、封面、ASR、帧图或正文证据之一 | 无 | 至少一个真实参考视频和一个真实候选视频可用于 A/B。 | 只填主页链接或示例 URL 时,视频 A/B 不启动。 |
+| 5 | 技术/AI | 真实材料预审 | `outputs\data-intake-pack-latest`、`outputs\video-intake-pack-latest` | `npm run intake:readiness -- --data-pack outputs\data-intake-pack-latest --video-pack outputs\video-intake-pack-latest --output outputs\intake-readiness-latest` | `overallReady=true` 且 `failureCount=0`。 | 按报告逐项退回补字段,不能进入 full-matrix 长跑。 |
+| 6 | 技术/AI | 历史数据导入 | 已填历史数据 CSV | `npm run history:from-csv -- --input <历史数据CSV> --output <history-dataset目录>` | 输出历史数据集 JSON。 | 修正 CSV 表头、空值、示例占位和重复 brief。 |
+| 7 | 技术/AI | 历史数据审计 | 历史数据集目录 | `npm run history:audit -- --input <history-dataset目录> --output <历史审计输出目录> --strict` | `readyForCustomerEffectProof=true`。 | 缺字段时回到第 3 步补真实数据。 |
+| 8 | 技术/AI | 视频资源就绪审计 | 已填视频资源 CSV | `npm run video:resource-readiness -- --input <video-resource-template.csv> --output <视频资源审计输出目录> --strict` | `readyForVideoAbPreflight=true` 且 `failureCount=0`。 | 缺 URL、封面、ASR、帧图或正文证据时回到第 4 步。 |
+| 9 | 技术/AI | 长跑前门禁 | intake readiness、proof-gap request、历史审计、视频资源审计 | `npm run longrun:readiness -- --mode full-matrix --intake-readiness outputs\intake-readiness-latest\intake-readiness-summary.json --proof-gap outputs\proof-gap-request-latest\proof-gap-request-summary.json --output outputs\long-run-readiness-latest` | `ready=true` 且 fail 数为 0。 | 不启动 overnight,先关闭 readiness 失败项。 |
+| 10 | 技术/AI | 运行策略矩阵 | 真实历史数据、有效 provider、真实运行环境 | `npm run optimization:pipeline -- --history-csv <历史数据CSV> --review-csv <已标注CSV> --current-manual-supplement-count <本轮人工补号量> --output <输出目录> --strict` | 每个 Brief 至少一个策略通过,软件端重复键为 0,排名连续。 | 每轮只优化一个主要失败原因后重跑,避免无法归因。 |
+| 11 | 技术/AI | 视频 A/B 验收 | 真实 sessionToken/company/provider、真实参考视频和候选视频 | `npm run acceptance:video-ab` | video-enhanced 强推荐数、Top10 综合分、参考风格分不低于 baseline,证据命中候选增加,泄密为 0。 | 不得宣称视频分析提升命中率;回到视频资源或 provider 配置排查。 |
+| 12 | 商务 | 人工复核标注 | AI 输出的软件端 CSV、商务复核模板 | 无 | 负样本必须有归因类型,客户选择和拒绝原因必须填写。 | 负样本归因不满 100% 时,不能升级团队规则。 |
+| 13 | 技术/AI | 商务复核指标 | 已标注 CSV | `npm run review:metrics -- --input <已标注CSV> --output <复核指标输出目录> --strict` | 负样本率低于 10%,负样本归因覆盖率为 100%,分策略指标齐全。 | 按失败归因选择下一轮唯一优化主题。 |
+| 14 | 技术/AI | 客户效果审计 | 已标注 CSV、历史审计 JSON、本轮人工补号量 | `npm run customer-effect:audit -- --review-csv <已标注CSV> --history-audit <historical-dataset-audit.json> --current-manual-supplement-count <本轮人工补号量> --output <客户效果输出目录> --strict` | 客户选中率、参考链路客户选中率、历史人工补号基线、本轮人工补号量和人工补号减少率全部通过。 | 客户效果不可宣称,继续标记 `blocked_by_external_data`。 |
+| 15 | 技术/AI | 刷新证据和交接 | 本轮全部输出 | `npm run round:refresh -- --output-root outputs --strict` | latest-form-index 指向最新补证表单、候选名单和负责人行动文件,optimization-completion 如实记录 `readyForClaim`,round-deposition 通过,并明确记录未证明边界。 | 缺沉淀、`optimization:completion` 未刷新或任一刷新步骤失败时,不允许口头宣布本轮完成。 |
+
+## 长跑启动条件
+
+满足以下条件后,才允许跑一晚上级别的 full-matrix 或 live 长跑:
+
+- `intake-readiness-summary.json` 中 `overallReady=true`。
+- `intake-readiness-summary.json` 中 `failureCount=0`。
+- `history:audit --strict` 通过,并能支撑客户效果证明。
+- `video:resource-readiness --strict` 通过;如果本轮不测视频,必须在报告里明确视频不参与本轮验收。
+- `longrun:readiness` 显示 `ready=true`。
+- 当前输出不含 token、Authorization、sessionToken、npm token 或模型 key。
+
+## 长期完成判定
+
+只有同时满足以下条件,才可以把长期优化目标标记为完成:
+
+- `optimization:status` 中没有 `ready_not_proven` 和 `blocked_by_external_data`。
+- `proof-gap:closure` 中 `openCount=0` 且 `complete=true`。
+- `optimization:completion` 中 `complete=true`、`readyForClaim=true` 且 `proofGapOpenCount=0`。
+- `customer-effect:audit --strict` 通过。
+- 软件端表格表头固定、中文不乱码、重复键为 0、每个 brief 排名连续。
+- 视频提升若被纳入结论,必须有真实视频 A/B 通过证据。
+- 每轮实施日志、证据台账、交接摘要和沉淀审计全部刷新。
+

+ 30 - 0
claude-code-tihao-sourcing/docs/capability-map.md

@@ -0,0 +1,30 @@
+# 能力地图
+
+| 能力 | 工具 | 模式 | 输出 |
+| --- | --- | --- | --- |
+| Brief 解析 | `tihao_brief_sourcing_run` | sample/live | 结构化提号标准 |
+| 参考博主锚点 | `tihao_brief_sourcing_run` | sample/live | `referenceLinks`、`referenceStyleAnchors`、`referenceSignals` |
+| 参考账号基线缓存 | `tihao_brief_sourcing_run` | sample/live | `referenceBaselines`、`referenceSimilarity`、`referenceHitPoints` |
+| 参考账号补证(社媒分析服务) | `tihao_brief_sourcing_run` | sample/live | 社媒分析服务补证结果和友好 warning(未开通/余额不足时给「社媒分析服务充值」链接) |
+| 主页最近内容 provider | `tihao_brief_sourcing_run` | sample/live | `homepageEvidenceStatus`、最近内容匹配、视觉质感、调性一致 |
+| 多模态证据卡 | `tihao_brief_sourcing_run` | sample/live | `evidenceCards`、画面/口播/文本信号、人工复核提示 |
+| 视频/视觉分析(默认即用) | `tihao_brief_sourcing_run` | live | 默认走公司豆包模型(`api.fmode.cn` OpenAI 兼容 chat completions,模型 `doubao-seed-2-0-pro-260215`),复用平台 sessionToken,客户无需自填 baseUrl/Token |
+| 真实 provider 联调 | docs | manual/live | `docs/live-provider-integration-runbook.md` |
+| live 预检 | `npm run live:preflight` | live | sessionToken、company、provider、豆包模型配置状态 |
+| overnight 策略矩阵 | `npm run overnight:quality` | sample/live | baseline/reference/homepage/video/result-first 多策略对比 |
+| provider 严格门禁 | `overnight:quality` env gates | live | reference/homepage provider 必须 `ok` |
+| 博主名单生成 | `tihao_brief_sourcing_run` | sample/live | Markdown、JSON、CSV |
+| 软件端表格验收 | `npm run review:metrics` / `npm run software:smoke` | CSV | 商务可用率、负样本率、负样本归因覆盖率、客户选中率、重复键、每个 brief 内排名连续 |
+| 历史数据集验收 | `npm run history:audit` | JSON dataset | 真实 Brief、人工最终名单、客户选择和拒绝原因覆盖 |
+| token 状态检查 | `tihao_token_check` | live | 友好的 token/充值状态 |
+| 偏好记忆 | `tihao_preference_update` | sample/live | memory JSON |
+| 反馈导入与团队规则建议 | `npm run feedback:import` | CSV | blocked creators、偏好、`teamRuleSuggestions` |
+| 无 token / 需充值体验 | `tihao_brief_sourcing_run` | live | 付费/开通链接,且 `errors=[]` |
+
+## 使用原则
+
+- 用户只需要自然语言描述需求,例如“读取这个 Brief,按提号 SOP 给一版小红书博主名单”。
+- Claude Code 负责选择 sample 或 live、读取 Brief、调用 MCP、沉淀输出文件。
+- token、模型 key、临时定价不得写进文档、报告、日志或 npm 包。
+- 第一轮名单是“初步判断/机会假设/待校准名单”,不是不可修改的最终结论。
+- 没有真实 provider `ok`、真实 live 输出和客户选择字段时,不能宣称真实命中率或客户选中率已达标。

+ 172 - 0
claude-code-tihao-sourcing/docs/historical-dataset-guide.md

@@ -0,0 +1,172 @@
+# 真实历史提号数据集准备指南
+
+本文档用于准备长期优化所需的真实历史样本。只有具备真实 Brief、人工最终名单、客户最终选择和拒绝原因,才能验证客户选中率和人工补号量是否真的改善。
+
+## 目录结构
+
+建议放在一个独立目录,不要把 token 或客户敏感报价写入文件:
+
+```text
+history-dataset/
+  dha-2026-05.json
+  skincare-2026-05.json
+  home-cleaning-2026-05.json
+```
+
+验收命令:
+
+```powershell
+npm run history:audit -- --input <history-dataset目录> --output <输出目录> --strict
+```
+
+默认要求至少 5 个真实 Brief。小范围试跑可以加:
+
+```powershell
+npm run history:audit -- --input <history-dataset目录> --min-briefs 3
+```
+
+## 从 CSV 生成 JSON 数据集
+
+商务可以先用 CSV/Excel 整理历史样本,再转换成 `history:audit` 可读取的 JSON 数据集。
+
+如果需要先生成可直接填写的收集包,运行:
+
+```powershell
+npm run data:intake-template -- --output outputs\data-intake-pack-latest
+```
+
+会生成:
+
+```text
+outputs/data-intake-pack-latest/history-data-template.csv
+outputs/data-intake-pack-latest/manual-review-template.csv
+outputs/data-intake-pack-latest/README.md
+```
+
+其中 `history-data-template.csv` 用于整理历史 Brief,`manual-review-template.csv` 用于整理本轮 AI 名单、商务复核、客户选择和本轮人工补号量。
+
+CSV 表头建议:
+
+```text
+brief编号,项目名称,类目,客户原始Brief,历史人工补号量基线,参考账号或视频,平台,博主名称,主页链接,人工复核标签,客户选择,拒绝原因
+```
+
+一行代表一个历史提报账号;同一个 `brief编号` 的多行会自动合并成一个历史 Brief。
+
+转换命令:
+
+```powershell
+npm run history:from-csv -- --input <历史数据CSV> --output <history-dataset目录>
+npm run history:audit -- --input <history-dataset目录> --output <审计输出目录> --strict
+```
+
+字段说明:
+
+- `brief编号`:必填,同一项目多行保持一致。
+- `客户原始Brief`:必填,写客户原始需求,不要写 token 或敏感报价。
+- `历史人工补号量基线`:如需证明人工补号减少 50%,必须填写。
+- `参考账号或视频`:多个链接可用中文或英文分号分隔。
+- `客户选择`:填写 `客户选中`、`客户拒绝`、`通过`、`未选` 等。
+- `拒绝原因`:客户拒绝或跑偏时必须填写。
+
+## JSON 模板
+
+```json
+{
+  "id": "dha-2026-05",
+  "name": "某客户 DHA 五月提号",
+  "category": "母婴",
+  "briefText": "客户原始 Brief 文本,包含平台、预算、粉丝、人群、参考账号、排除项等。",
+  "manualSupplementBaseline": 6,
+  "referenceLinks": [
+    {
+      "url": "https://example.com/reference-video",
+      "platform": "xiaohongshu",
+      "contentType": "video"
+    }
+  ],
+  "manualFinalList": [
+    {
+      "platform": "小红书",
+      "creatorName": "营养师妈妈",
+      "profileUrl": "https://example.com/creator-a",
+      "manualReviewLabel": "可直接发客户",
+      "customerDecision": "客户选中"
+    },
+    {
+      "platform": "小红书",
+      "creatorName": "泛生活方式号",
+      "profileUrl": "https://example.com/creator-b",
+      "manualReviewLabel": "跑偏",
+      "customerDecision": "客户拒绝",
+      "rejectReason": "客户认为账号人设太泛,不能承接母婴 DHA"
+    }
+  ],
+  "customerFeedback": [
+    {
+      "creatorName": "营养师妈妈",
+      "finalDecision": "客户选中",
+      "reason": "客户认可营养师人设和清单结构"
+    },
+    {
+      "creatorName": "泛生活方式号",
+      "finalDecision": "客户拒绝",
+      "rejectReason": "账号内容方向跑偏"
+    }
+  ]
+}
+```
+
+## 必填要求
+
+- 每个样本必须有 `id` 或 `name`。
+- 每个样本必须有客户原始 `briefText`。
+- 每个样本必须有 `manualFinalList`,即人工最终提报名单。
+- 每个样本必须有客户最终选中/拒绝记录,可以写在 `manualFinalList.customerDecision` 或 `customerFeedback.finalDecision`。
+- 有拒绝或跑偏时,必须提供 `rejectReason`、`feedbackReason` 或 `reason`。
+- 如需证明“人工补号量减少 50%”,必须提供 `manualSupplementBaseline`、`manualSupplementCount` 或 `manualAddedCount` 任一字段,表示该历史 Brief 人工补号量基线。
+- 推荐覆盖至少 3 个类目;完整长期验证建议 5-10 个真实 Brief。
+
+## 验收口径
+
+`history:audit` 输出:
+
+- `historical-dataset-audit.json`
+- `historical-dataset-audit.md`
+
+只有报告里显示“可用于长期优化长跑=是”,才适合拿来验证:
+
+- 客户选中率 30%/50%;
+- 有参考账号 Brief 的通过率;
+- 参考账号链路相对关键词召回是否更好;
+- 每轮反馈是否让下一轮负样本率下降;
+- 人工补号量是否减少。
+
+没有客户最终选择字段时,只能验证商务复核通过率,不能宣称客户选中率达标。
+没有人工补号量基线时,可以做长期优化长跑,但不能宣称人工补号量减少 50% 达标。
+
+## 客户效果证明命令
+
+当 `history:audit` 输出的 `historical-dataset-audit.json` 显示 `readyForCustomerEffectProof=true` 后,再用商务标注后的 CSV 做客户效果审计:
+
+```powershell
+npm run customer-effect:audit -- --review-csv <已标注CSV> --history-audit <historical-dataset-audit.json> --current-manual-supplement-count <本轮人工补号量> --output <输出目录>
+```
+
+输出文件:
+
+```text
+customer-effect-summary.json
+customer-effect-report.md
+```
+
+验收标准:
+
+- 客户选中率 >= 30%。
+- 长期客户选中率 >= 50%。
+- 参考链路客户选中率 >= 40%。
+- 历史人工补号量基线存在。
+- 本轮人工补号量存在。
+- 人工补号量减少率 >= 50%。
+
+如果本轮人工补号量已经写在 CSV 中,可以使用 `本轮人工补号量`、`当前人工补号量`、`人工补号量` 或 `manualSupplementCount` 列,不必再传 `--current-manual-supplement-count`。

+ 362 - 0
claude-code-tihao-sourcing/docs/install-guide.md

@@ -0,0 +1,362 @@
+# Tihao SOP 下载安装指南
+
+适用包名:
+
+```text
+@vocmarket/tihao
+```
+
+当前版本:
+
+```text
+1.0.9
+```
+
+适用对象:
+
+- 客户侧商务、运营或投放同事:在 Claude Code 中上传/指定 Brief,生成可复核的博主名单。
+- 内部交付同事:给客户安装、验收、排查环境问题。
+- 开发/发布同事:确认 npm 包名、安装命令、版本更新与本地验证流程。
+
+> 说明:本指南不包含接口定价。价格与套餐以后单独维护,避免客户侧文档和实际计费口径不一致。
+
+## 1. 安装前准备
+
+客户电脑或演示机需要具备:
+
+- 已安装 Node.js,建议 Node.js 18 或更高版本。
+- 已安装 Claude Code,并能打开目标工作区。
+- 能访问 npm registry。
+- 如需使用真实数据 live 模式,需要已开通的 Tihao / VOC 电商数据中台 token。
+
+检查 Node.js:
+
+```powershell
+node -v
+npm -v
+```
+
+## 2. 推荐安装方式:工作区安装
+
+在需要使用提号 SOP 的项目目录中打开终端,执行:
+
+```powershell
+npx --yes @vocmarket/tihao@latest workspace --smoke
+```
+
+安装成功后,会在当前工作区写入:
+
+```text
+.mcp.json
+.claude/plugins/tihao
+.claude/skills/tihao/SKILL.md
+```
+
+`--smoke` 会自动做一次本地检查,确认:
+
+- npm 包可正常下载;
+- Claude Code 插件文件已复制到工作区;
+- MCP server 入口可识别;
+- 提号技能入口可识别;
+- 不需要真实 token 也能完成基础安装验收。
+
+安装完成后,建议重启 Claude Code 或刷新当前工作区,让新的 skill 和 MCP server 生效。
+
+## 3. 全局安装方式
+
+如果内部交付同事需要在多套客户工作区重复安装,可以先全局安装:
+
+```powershell
+npm install -g @vocmarket/tihao
+```
+
+如果要安装到当前用户的 Claude Code 全局目录,执行:
+
+```powershell
+tihao install --smoke
+```
+
+它会写入:
+
+```text
+%USERPROFILE%\.claude\plugins\tihao
+%USERPROFILE%\.claude\skills\tihao\SKILL.md
+```
+
+如果要使用这个全局 CLI 安装到某个客户项目,进入该工作区后执行:
+
+```powershell
+tihao workspace --smoke
+```
+
+全局安装适合内部交付机;客户侧优先推荐第 2 节的 `npx` 工作区安装方式,因为命令更短,也更容易固定到最新发布版本。
+
+## 4. 在 Claude Code 中使用
+
+安装完成后,在 Claude Code 中用自然语言描述即可。例如:
+
+```text
+读取 E:\workspace\tihao-ai\dha_brief.xlsx,按照提号 SOP 给我一版小红书博主名单。
+```
+
+这类“读取 brief / 出博主名单 / 提号 / 选号 / 商务可用名单”请求默认按 live 小规模检索处理,首轮使用 `keywordLimit=1`、`pagesPerKeyword=1`。如果缺少 token 或额度,技能包会提示补 token、开通或充值,不会静默改成 sample。
+
+或先跑无消耗演示:
+
+```text
+先用 sample 模式跑通这个 brief,输出商务可复核名单。
+```
+
+真实数据小规模验证:
+
+```text
+用 live 小规模检索这个 brief,keywordLimit=1,pagesPerKeyword=1。
+```
+
+常见输入材料:
+
+- `.xlsx` 客户 Brief;
+- `.xls` 表格型 Brief;
+- `.md` 或 `.txt` 文本 Brief;
+- Brief 中的参考博主、参考链接、平台目标、粉丝量要求、内容风格、禁投规则等。
+
+## 5. Token 配置
+
+sample 模式不需要 token。
+
+live 模式需要平台侧可用 token。任选一种方式配置。
+
+当前终端临时配置:
+
+```powershell
+$env:TIHAO_SESSION_TOKEN="<Parse sessionToken>"
+$env:TIHAO_COMPANY="<Company objectId>"
+```
+
+写入当前工作区 `.env.local`:
+
+```text
+TIHAO_SESSION_TOKEN=<Parse sessionToken>
+TIHAO_COMPANY=<Company objectId>
+```
+
+兼容变量名:
+
+```text
+TIHAO_SESSION_TOKEN
+VOC_ECOMMERCE_TOKEN
+VOC_TOKEN
+TIHAO_COMPANY
+VOC_ECOMMERCE_COMPANY
+COMPANY_OBJECT_ID
+```
+
+安全要求:
+
+- 不要把真实 token 发到群里。
+- 不要把 `.env.local` 提交到 git。
+- 不要把 token 写进 Brief、报告或交付文档。
+- 客户侧演示优先用 sample 模式确认流程,再切 live 模式做小规模验证。
+
+## 6. 输出文件
+
+每次运行会输出三类结果:
+
+```text
+tihao-sourcing-report.md
+tihao-sourcing-result.json
+tihao-sourcing-client-list.csv
+```
+
+用途:
+
+- `tihao-sourcing-report.md`:给商务和客户看的可读报告。
+- `tihao-sourcing-result.json`:给后续系统或二次分析使用的结构化结果。
+- `tihao-sourcing-client-list.csv`:给商务筛选、导入表格或继续补充报价信息。
+
+Claude Code 聊天窗口里也会直接返回核心名单摘要,客户不需要只靠文件路径找结果。
+
+## 7. 验收命令
+
+客户侧安装验收:
+
+```powershell
+npx --yes @vocmarket/tihao@latest workspace --smoke
+```
+
+本地源码验收:
+
+```powershell
+cd E:\workspace\tihao-ai\claude-code-tihao-sourcing
+npm run smoke:package
+npm run smoke:briefs
+npm run mcp:smoke
+npm run acceptance
+```
+
+真实数据 live 验收需要有效 token 和额度:
+
+```powershell
+cd E:\workspace\tihao-ai\claude-code-tihao-sourcing
+$env:TIHAO_SESSION_TOKEN="<Parse sessionToken>"
+$env:TIHAO_COMPANY="<Company objectId>"
+npm run live:preflight -- --strict
+npm run live:acceptance
+```
+
+如果没有有效 token,不要把 live 验收写成已通过;只能说明 sample、安装和本地流程已通过。
+
+真实 provider / 视频 / 长跑验收:
+
+```powershell
+npm run acceptance:providers:mock
+npm run acceptance:video-ab
+npm run overnight:quality
+```
+
+`acceptance:video-ab` 和真实 `overnight:quality` 需要运行时提供 VOC social、视频分析 URL、模型和 token。模型固定使用:
+
+```text
+doubao-seed-2-0-pro
+```
+
+商务标注后统计质量指标:
+
+```powershell
+npm run review:metrics -- --input <已标注CSV> --output <输出目录>
+```
+
+准备真实历史样本后验证是否够做长期优化:
+
+```powershell
+npm run history:audit -- --input <history-dataset目录> --output <输出目录> --strict
+```
+
+没有真实客户最终选择字段时,不要宣称客户选中率达到 30%/50%;只能统计商务复核通过率。
+
+## 8. 更新版本
+
+客户侧更新到最新版本:
+
+```powershell
+npx --yes @vocmarket/tihao@latest workspace --smoke
+```
+
+如果本机 npm 缓存导致拿到旧版本,可以换一个临时缓存:
+
+```powershell
+$cache = Join-Path $env:TEMP "npm-cache-tihao"
+npx --yes @vocmarket/tihao@latest workspace --smoke --cache $cache
+```
+
+查看 npm 最新版本:
+
+```powershell
+npm view @vocmarket/tihao version
+```
+
+## 9. 卸载
+
+工作区卸载时,删除当前项目下这些文件或目录:
+
+```text
+.claude/plugins/tihao
+.claude/skills/tihao
+```
+
+然后检查 `.mcp.json`,删除 `tihao` 对应配置。
+如果曾经安装过 1.0.7 或更早版本,也可以删除旧目录 `.claude/plugins/tihao-sourcing`;1.0.8 起安装器会自动清理该 legacy plugin 目录。
+如果曾经安装过 1.0.5 或更早版本,也可以删除旧目录 `.claude/skills/tihao-creator-sourcing`;1.0.6 起安装器会自动清理该 legacy skill 目录。
+
+如果曾经做过全局安装,可以执行:
+
+```powershell
+npm uninstall -g @vocmarket/tihao
+```
+
+## 10. 常见问题
+
+### npx 提示找不到包
+
+确认包名必须是:
+
+```text
+@vocmarket/tihao
+```
+
+不要再使用旧临时包名:
+
+```text
+@gangvy/claude-code-tihao-sourcing
+@gangvy/tihao-sop
+@vocmarket/tihao-sop
+```
+
+旧包名只保留迁移提示,客户安装统一使用 `@vocmarket/tihao`。
+
+### 安装后 Claude Code 没识别技能
+
+处理顺序:
+
+1. 确认安装命令是在目标工作区目录执行的。
+2. 确认 `.claude/skills/tihao/SKILL.md` 存在。
+3. 确认 `.mcp.json` 里存在 `tihao`。
+4. 重启 Claude Code 或重新打开工作区。
+
+### live 提示需要 token
+
+说明当前工作区没有读到可用的 token。按第 5 节重新配置:
+
+```text
+TIHAO_SESSION_TOKEN
+```
+
+或:
+
+```text
+VOC_ECOMMERCE_TOKEN
+VOC_TOKEN
+```
+
+### live 提示需要开通或充值
+
+说明账号或公司维度的 `/apig/voc-e-commerce` 权限、额度或余额不足。
+
+技能包会返回友好的开通/充值提示,不会把原始鉴权报错、上游错误体或 token 暴露给客户。
+
+### 只想演示流程,不想消耗额度
+
+使用 sample 模式:
+
+```text
+先用 sample 模式按这个 brief 跑一版提号名单。
+```
+
+sample 模式适合演示:
+
+- Brief 读取;
+- 提号规则识别;
+- 博主名单结构;
+- 偏好记忆;
+- 报告和 CSV 输出。
+
+它不代表真实平台库存结果,正式交付前仍需用 live 模式小规模复核。
+
+## 11. 内部发布提示
+
+内部发布时使用包内 npm 脚本,不要手写散装命令:
+
+```powershell
+cd E:\workspace\tihao-ai\claude-code-tihao-sourcing
+npm run publish:dry
+npm run publish:npm
+```
+
+发布前必须确认:
+
+- `package.json` 版本号已更新;
+- `npm run acceptance` 已通过;
+- 文档没有真实 token、价格、未确认承诺;
+- npm 登录态或 npm token 有发布 `@vocmarket/tihao` 的权限。
+
+发布到不同包名不需要新 npm token,前提是当前 npm 账号或 token 对目标 scope/package 有发布权限。包名变化由 `package.json` 的 `name` 决定。

+ 192 - 0
claude-code-tihao-sourcing/docs/live-provider-integration-runbook.md

@@ -0,0 +1,192 @@
+# Tihao SOP 真实 Provider 联调 Runbook
+
+本文档用于拿到真实 TikHub、VOC social、视频分析或多模态 evidence provider 权限后的联调验收。它不替代默认发布门禁;默认发布门禁仍然是:
+
+```powershell
+npm run acceptance
+```
+
+真实 provider gate 只有在具备安全测试账号、余额和样本时才运行。
+
+发布红线:不能宣称未验证的 provider 已完成真实接入。没有跑通 `acceptance:providers`,不能说 TikHub 已接通;没有跑通 `acceptance:video-ab`,不能说参考视频分析已经证明能提升提号结果。
+
+## 1. 联调目标
+
+真实联调要证明:
+
+- Brief 里的参考链接可以转成可复核的 `referenceBaselines`;
+- 候选博主可以获得 `referenceSimilarity`、`referenceFitLevel`、`referenceHitPoints`;
+- 视频/图文证据可以写入统一的 `evidenceCards`;
+- 报告、JSON、CSV 不泄露 token、Authorization header、上游原始错误或未确认定价;
+- 视频分析结果确实能影响召回、评分或重排,而不是只出现在报告里做展示。
+
+## 2. 最小输入清单
+
+运行前准备:
+
+- 一份安全测试 Brief,推荐使用 DHA、敏感肌、清洁、零食等已有 fixture;
+- 至少 1 条可访问的参考博主链接,优先选择带视频资源的链接;
+- TikHub 兼容参考补证 provider URL;
+- 多模态 evidence provider URL,或 OpenAI 兼容豆包视频分析 URL;
+- 如 provider 需要鉴权,准备运行时 token;
+- 如要跑 live 候选检索,准备 Tihao `sessionToken` 和 `Company objectId`。
+
+不要把真实 token 写进仓库、Brief、报告或聊天记录。
+
+## 3. 环境变量
+
+PowerShell 示例:
+
+```powershell
+$env:TIKHUB_BASE_URL="<reference-enrichment-provider-url>"
+$env:TIKHUB_TOKEN="<optional-tikhub-token>"
+$env:TIHAO_EVIDENCE_BASE_URL="<multimodal-evidence-provider-url>"
+$env:TIHAO_EVIDENCE_TOKEN="<optional-evidence-provider-token>"
+$env:DOUBAO_VISION_BASE_URL="<user-filled-video-analysis-url>"
+$env:DOUBAO_VISION_TOKEN="<optional-doubao-vision-token>"
+$env:DOUBAO_VISION_MODEL="doubao-seed-2-0-pro"
+```
+
+如果使用 Fmode OpenAI 兼容模型网关:
+
+```powershell
+$env:DOUBAO_VISION_BASE_URL="https://api.fmode.cn"
+$env:DOUBAO_VISION_MODEL="doubao-seed-2-0-pro"
+```
+
+该 base URL 会自动请求 `/v1/chat/completions`。token 只能通过运行时环境变量或工具入参提供。
+
+同时验证 live 候选检索时:
+
+```powershell
+$env:TIHAO_SESSION_TOKEN="<parse-session-token>"
+$env:TIHAO_COMPANY="<company-object-id>"
+```
+
+## 4. 本地合同自测
+
+没有真实 provider 时,先跑 mock 合同测试:
+
+```powershell
+npm run acceptance:providers:mock
+```
+
+通过标准:
+
+- mock reference provider 返回 `referenceBaselines`;
+- mock evidence provider 返回 `evidenceCards`;
+- 候选结果出现 `referenceSimilarity` 和 `evidenceSignals`;
+- mock 豆包请求包含模型 `doubao-seed-2-0-pro`、Authorization、视觉能力和 OpenAI 风格响应解析。
+
+## 5. Live 预检
+
+配置真实 token 或 provider 后,先跑不扣费预检:
+
+```powershell
+npm run live:preflight
+```
+
+需要阻断长跑时使用严格模式:
+
+```powershell
+npm run live:preflight -- --strict
+```
+
+预检会输出 `live-preflight-summary.json`,只记录是否配置齐全,不写入 token 明文。
+
+通过标准:
+
+- `session-token-present=pass`;
+- `parse-user-resolved=pass`;
+- `company-resolved=pass`;
+- `video-model-doubao-seed-2-0-pro=pass`;
+- 如果要验证视频提升,必须同时有视频分析 base URL 和运行时 token;
+- 如果要验证主页最近内容,必须配置真实 homepage provider。
+
+预检通过只说明“可以开始真实测试”,不说明 live 提号或视频分析已经有效。
+
+## 6. 真实 Provider 验收
+
+配置真实 provider 后运行:
+
+```powershell
+npm run acceptance:providers
+```
+
+通过标准:
+
+- `referenceBaselines.length >= 1`;
+- 至少一个候选的 `referenceSimilarity > 0`;
+- `evidenceCards.length >= 1`;
+- 至少一个候选存在 `evidenceSignals`;
+- 结果中不包含 `Authorization: Bearer ...`;
+- 结果中不包含任何环境变量 token 明文;
+- provider 收到的请求体包含 `contractVersion: "tihao-provider-v1"`;
+- reference provider 收到 `referenceLinks`、`referenceStyleAnchors`、`criteria.keywords`;
+- evidence provider 收到 `requestedCapabilities`、`criteria.referenceBaselines`、`creators`。
+
+如果没有配置 provider URL,该命令会安全跳过并输出 skip 提示。skip 不是通过真实联调。
+
+## 7. Live 候选检索验收
+
+只有测试账号有额度时运行:
+
+```powershell
+npm run live:preflight -- --strict
+npm run live:acceptance
+```
+
+通过标准:
+
+- 线上 `voc-e-commerce` 返回 200;
+- 至少返回一个候选博主;
+- Markdown、JSON、CSV 写出成功;
+- APIGAuth 扣费链路正常;
+- 输出不包含 Parse sessionToken 或 Authorization。
+
+## 8. 参考视频 A/B 验收
+
+用于证明视频分析对提号命中率有帮助:
+
+```powershell
+$env:TIHAO_SESSION_TOKEN="<Parse sessionToken>"
+$env:TIHAO_COMPANY="<Company objectId>"
+$env:VOC_SOCIAL_TOKEN="<Parse sessionToken>"
+$env:VIDEO_ANALYSIS_BASE_URL="https://api.fmode.cn"
+$env:VIDEO_ANALYSIS_MODEL="doubao-seed-2-0-pro"
+$env:VIDEO_ANALYSIS_TOKEN="<runtime model token>"
+npm run acceptance:video-ab
+```
+
+核心判断:
+
+- A 组只跑 live 提号;
+- B 组在同一 Brief 上增加 VOC social 视频详情和豆包分析;
+- B 组能拿到真实视频 URL、封面、标题、作者、字幕或其他资源;
+- B 组必须拿到视频 URL;
+- B 组必须拿到封面、ASR/字幕、帧图资源中的至少一类;
+- B 组能产出证据卡;
+- B 组证据卡不能只是 `pending`、`fallback` 或“待补证据”占位;
+- B 组证据卡必须包含 text/ASR/visual/frame 中至少一类可解释信号;
+- B 组 top 10 中证据命中候选增加;
+- B 组强推荐数量、平均参考风格分、平均总分不得下降;
+- 输出不泄露 token。
+
+## 9. 失败处理
+
+- 网络抖动:允许带重试重新跑,不要把一次 TLS 或上游短暂失败当成质量失败。
+- 参考链接无法解析:保留原参考链接并写 warning,不能中断整个提号流程。
+- 视频资源缺失:证据卡必须标注“待补口播证据”或“待补帧图证据”,不能假装已经分析过帧图/ASR。
+- 召回跑偏:禁止把婚礼、宴会、布景、仪式等与 Brief 无关的场景词扩展成 live 召回关键词。
+
+## 10. 交付记录
+
+真实联调完成后,需要沉淀:
+
+- `live-preflight-summary.json`;
+- 本次运行目录;
+- `aggregate-summary.json` 或对应 summary;
+- 是否 `liveEnabled=true`;
+- 候选数量、强推荐数量、证据卡数量;
+- 是否有 token leak scan 命中;
+- 需要人工复核的问题。

+ 130 - 0
claude-code-tihao-sourcing/docs/manual-review-handoff.md

@@ -0,0 +1,130 @@
+# 人工复核交接说明
+
+这份文档用于 live / overnight / sample 提号结果生成后,交给商务或运营复核候选博主。所有 CSV 均按 Excel 直接打开做了 UTF-8 BOM 处理。
+
+## 复核表位置
+
+常见输出位置:
+
+```text
+outputs/<本次运行目录>/manual-review-sample.csv
+outputs/<本次运行目录>/tihao-sourcing-client-list.csv
+outputs/software-client-latest/software-client-list.final.csv
+```
+
+如果还没有本轮复核表,或需要先给商务发一套历史数据/复核数据收集模板,可以运行:
+
+```powershell
+npm run data:intake-template -- --output outputs\data-intake-pack-latest
+```
+
+生成的 `manual-review-template.csv` 已包含 `客户选择`、`归因类型`、`反馈原因` 和 `本轮人工补号量` 字段,可直接用于后续 `review:metrics` 和 `customer-effect:audit`。
+
+## 复核标签
+
+请在 CSV 的 `人工复核标签` 列填写下列任一标签:
+
+| 标签 | 适用场景 | 是否必须填写归因 |
+| --- | --- | --- |
+| 可直接发客户 | 符合 Brief、调性、主页证据和风险边界,可进入客户预览名单 | 否 |
+| 商务复核 | 大体匹配,但报价、档期、竞品合作、近期内容或平台风险仍需确认 | 否 |
+| 跑偏 | 人群、品类、平台、预算、场景或合作方向明显偏离 Brief | 是 |
+| 硬性规则违约 | 命中必须剔除项,例如平台、粉丝、预算、地域、类目或客户明确排除项不符 | 是 |
+| 调性不符 | 硬指标可用,但内容表达、视觉质感、语气、人设或参考账号风格明显不一致 | 是 |
+| 主页质感不符 | 最近内容质量、封面、互动、更新频率、商业化痕迹或风险信号不足以支持推荐 | 是 |
+| 参考账号不像 | 客户给了参考账号/视频,但候选人与参考的人群、内容结构或风格相似度不足 | 是 |
+| 待客户反馈 | 已发客户或准备发客户,但客户尚未给最终结果 | 否 |
+| 客户选中 | 客户最终确认选择该候选人,可用于客户效果审计 | 否 |
+| 客户拒绝 | 客户最终拒绝该候选人,必须补拒绝原因或反馈原因 | 是 |
+
+负样本包括:`跑偏`、`硬性规则违约`、`调性不符`、`主页质感不符`、`参考账号不像`、`客户拒绝`。负样本必须填写 `归因类型` 和 `反馈原因`,否则严格验收不通过。
+
+## 归因类型
+
+| 归因类型 | 说明 |
+| --- | --- |
+| 需求解析错 | Brief 里的品类、人群、预算、平台、地域或硬性限制被解析错 |
+| 隐性规则漏 | 客户偏好、历史排除项、风格禁区或团队经验规则没有进入筛选 |
+| 召回关键词错 | 关键词召回方向太宽、太窄,或平台搜索词不贴合真实场景 |
+| 主页证据不足 | 最近内容、封面、标题、互动、发布时间或风险证据不足 |
+| 视频证据误判 | 参考视频或候选视频的风格、场景、口播、画面质感、ASR 被误判 |
+| 排序权重错 | 候选已召回,但弱相关、低质感或高风险账号排得过高 |
+| 输出解释错 | 推荐理由、风险提示或证据卡表达不清,导致商务难以判断 |
+| 软件端表重复或排名不连续 | 重复键异常、排名断档、字段不稳定或软件端表格问题 |
+
+## 复核顺序
+
+1. 先看 `result-first-broad`,这是完整矩阵里表现最好的发布策略。
+2. 再看 `result-first`,这是消耗更低的发布备选策略。
+3. `video-enhanced` 和 `result-first-risk` 只作诊断样本,用来观察视频证据和风险提示是否合理。
+4. 多个策略重复出现的博主,发客户前必须合并去重,并保持同一 Brief 下排名连续。
+
+## 复核重点
+
+- 主页链接是否有效,账号是否仍然是同一个博主。
+- 最近 10 篇内容是否真的匹配 Brief 品类、人群和内容调性。
+- 博主是否能承接客户要的风格,而不是只命中了一个关键词。
+- 报价、粉丝数、主页信息需要商务二次确认。
+- 涉及功效、母婴、食品、护肤等敏感表达时,必须人工确认合规风险。
+- 有参考账号或参考视频时,要看“像不像参考账号/视频”,不要只看平台标签。
+
+## 质量指标统计
+
+人工标注完成后运行:
+
+```powershell
+npm run review:metrics -- --input <已标注CSV> --output <输出目录> --strict
+```
+
+会生成:
+
+```text
+review-metrics-summary.json
+review-metrics-report.md
+```
+
+核心指标:
+
+- 商务可用率 = `可直接发客户 + 商务复核` / 已标注总数,短期目标 >= 60%。
+- 负样本率 = 负样本 / 已标注总数,目标 <= 10%。
+- 负样本归因覆盖率 = 已填写归因类型的负样本 / 负样本总数,目标 100%。
+- 客户选中率 = `客户选中` / 已标注总数,短期目标 >= 30%;`客户选择` 未标注时不得宣称达到客户选中率。
+- 参考链路客户选中率 = 有参考账号/参考视频链路的 `客户选中` / 这些策略已标注总数,中期目标 >= 40%。
+- 参考链路通过率对照必须高于 `baseline-live / brief-only / keyword-only`,否则不能宣称参考链路优于纯关键词召回。
+- 软件端重复键必须为 0。
+- 每个 Brief 排名必须连续。
+
+## 客户效果证明
+
+如果已经拿到真实历史数据集审计结果,并且本轮有人工补号量,可以继续运行:
+
+```powershell
+npm run customer-effect:audit -- --review-csv <已标注CSV> --history-audit <historical-dataset-audit.json> --current-manual-supplement-count <本轮人工补号量> --output <输出目录> --strict
+```
+
+客户效果证明的硬性口径:
+
+- 客户选中率达到 30% 以上,才可证明短期客户效果。
+- 长期客户选中率达到 50% 以上,才可证明长期目标。
+- 有参考账号/参考视频的 Brief,参考链路客户选中率达到 40% 以上。
+- 历史数据集必须通过 `readyForCustomerEffectProof`。
+- 必须同时有历史人工补号量基线和本轮人工补号量,才可证明“人工补号量减少 50%”。
+
+缺少任一真实证据时,报告会标记为 `待补数据` 或 `未通过`,不能宣称客户效果已经达标。
+
+## 当前自动化证据边界
+
+目前自动化可以证明:
+
+- SOP、表单、复核标签、归因字段、去重、连续排名、软件端导出、安装和发布前验收链路可运行。
+- sample/smoke/provider mock 可以验证流程完整性。
+- `manual-review-label-guide` 已沉淀 10 个复核标签和 8 个归因类型。
+
+目前仍不能单独证明:
+
+- 命中率已经提升。
+- 客户选中率已经达标。
+- 视频分析一定提升提号效果。
+- 人工补号量已经减少。
+
+这些必须等真实客户复核 CSV、真实历史 Brief 数据集、真实视频资源和 live provider 审计补齐后,再进入效果验收。

+ 60 - 0
claude-code-tihao-sourcing/docs/overnight-quality-latest-evidence.md

@@ -0,0 +1,60 @@
+# 最新 live 质量验证证据
+
+更新日期:2026-06-06
+
+这份文档记录提号 SOP 优化后的最新 live 验证结果。文档中不包含 token 或 Authorization header。
+
+## 最新完整矩阵通过记录
+
+```text
+outputs/overnight-quality-1780682025651
+```
+
+运行范围:
+
+- 模式:live
+- fixture 数:5
+- 策略:`baseline-live,video-enhanced,result-first,result-first-risk,result-first-broad`
+- run 数:25
+- 程序失败数:0
+- 发布门禁:通过
+- 发布策略覆盖:通过
+- 发布门禁失败数:0
+- 诊断 warning:2
+- token 泄漏扫描:0
+
+矩阵中 `video-enhanced` 和 `result-first-risk` 是诊断策略,用于观察视频证据与风险提示,不作为发布阻断项。发布候选策略是 `result-first` 和 `result-first-broad`。
+
+## 最佳发布策略
+
+`result-first-broad` 在 5 个 fixture 中都是最佳发布策略。
+
+| Brief | baseline强推数 | 最佳强推数 | 最佳策略 | 最佳召回数 | 证据覆盖 | 平均分 | 负样本风险率 |
+| --- | ---: | ---: | --- | ---: | ---: | ---: | ---: |
+| 618-list-seeding | 6 | 11 | result-first-broad | 150 | 100% | 78.4 | 0% |
+| dha-mom-baby | 0 | 4 | result-first-broad | 103 | 100% | 79.5 | 0% |
+| healthy-snack | 1 | 6 | result-first-broad | 318 | 100% | 78.5 | 0% |
+| home-cleaning | 4 | 8 | result-first-broad | 160 | 100% | 79.2 | 0% |
+| sensitive-skin-repair | 3 | 10 | result-first-broad | 160 | 100% | 83.8 | 0% |
+
+## 诊断 warning
+
+| Brief | 策略 | warning | 当前值 | 目标 |
+| --- | --- | --- | ---: | --- |
+| 618-list-seeding | video-enhanced | 强推数低于 baseline | 5 | >= baseline 6 |
+| healthy-snack | result-first-risk | 证据覆盖不足 | 50% | >= 80% |
+
+这些 warning 用于后续继续调优,但不影响当前发布策略,因为对应 brief 的发布策略已经通过。
+
+## 这轮验证证明了什么
+
+- 完整 5 x 5 live 矩阵已经用真实 provider 跑完。
+- 每个 brief 都至少有一个通过门禁的发布策略。
+- 最佳发布策略在 5 个 fixture 上都超过 baseline 强推数。
+- 最佳发布策略 top-10 证据覆盖均达到 100%。
+- 自动化负样本风险率在 5 个 fixture 上都是 0%(基于 fixture 的 offTopicTerms 规则,不等同于真实人工负样本率)。
+- 输出文件没有写入 token。
+
+## 仍需人工复核
+
+这是自动化 live 验证证据。正式给客户前,商务仍需要在 `manual-review-sample.csv` 中标注账号是否可直接发客户、是否需要报价/主页/合规复核,以及是否存在自动门禁没识别出的细微跑偏。

+ 204 - 0
claude-code-tihao-sourcing/docs/overnight-quality-runbook.md

@@ -0,0 +1,204 @@
+# Overnight 质量验证运行手册
+
+本文档用于长时间运行提号质量矩阵,验证“上传 Brief 后生成商务可用博主名单”的稳定性、去重质量、主页证据覆盖和策略效果。
+
+## 快速 Smoke
+
+不需要 live token,只验证 fixture、聚合报告、软件端表格、人工复核抽样、门禁和泄密扫描。
+
+```powershell
+$env:TIHAO_OVERNIGHT_LIVE="false"
+npm run overnight:quality
+```
+
+限制运行次数:
+
+```powershell
+$env:TIHAO_OVERNIGHT_LIVE="false"
+$env:TIHAO_OVERNIGHT_MAX_RUNS="5"
+npm run overnight:quality
+```
+
+## 受控 Live 子集
+
+正式长跑前先跑一个小子集。会消耗真实额度,范围要小。
+
+启动前先跑长跑就绪度审计,确认 live 预检和历史数据审计都满足当前目标:
+
+```powershell
+npm run longrun:readiness -- --mode live-subset --preflight <live-preflight-summary.json> --strict
+```
+
+```powershell
+$env:TIHAO_SESSION_TOKEN="<Parse sessionToken>"
+$env:TIHAO_COMPANY="<Company objectId>"
+$env:VOC_SOCIAL_TOKEN="<Parse sessionToken>"
+$env:VIDEO_ANALYSIS_BASE_URL="https://api.fmode.cn"
+$env:VIDEO_ANALYSIS_MODEL="doubao-seed-2-0-pro"
+$env:VIDEO_ANALYSIS_TOKEN="<runtime model token>"
+$env:TIHAO_OVERNIGHT_LIVE="true"
+$env:TIHAO_OVERNIGHT_FIXTURES="dha-mom-baby"
+$env:TIHAO_OVERNIGHT_VARIANTS="baseline-live,result-first"
+$env:TIHAO_OVERNIGHT_MAX_RUNS="2"
+$env:TIHAO_OVERNIGHT_FAIL_ON_GATES="true"
+npm run overnight:quality
+```
+
+## 完整 Live 矩阵
+
+受控子集通过后再跑完整矩阵。
+
+```powershell
+$env:TIHAO_OVERNIGHT_FIXTURES="dha-mom-baby,sensitive-skin-repair,healthy-snack,home-cleaning,618-list-seeding"
+$env:TIHAO_OVERNIGHT_VARIANTS="baseline-live,reference-account,homepage-evidence,video-enhanced,result-first,result-first-risk,result-first-broad"
+$env:TIHAO_OVERNIGHT_DELAY_MS="1500"
+$env:TIHAO_OVERNIGHT_RUN_RETRIES="1"
+$env:TIHAO_OVERNIGHT_RESUME="true"
+$env:TIHAO_OVERNIGHT_FAIL_ON_GATES="true"
+npm run overnight:quality
+```
+
+## 常用参数
+
+- `TIHAO_OVERNIGHT_FIXTURES`:指定要跑的 fixture id,用逗号分隔。
+- `TIHAO_OVERNIGHT_VARIANTS`:指定策略变体,用逗号分隔。
+- `TIHAO_OVERNIGHT_MAX_RUNS`:最多新增执行多少个 run。
+- `TIHAO_OVERNIGHT_DELAY_MS`:live run 之间的等待时间。
+- `TIHAO_OVERNIGHT_RUN_RETRIES`:live 网络错误时整轮重试次数。
+- `TIHAO_OVERNIGHT_RESUME=true`:复用同一输出目录下已有 `quality-summary.json`。
+- `TIHAO_OVERNIGHT_FAIL_ON_GATES=true`:发布门禁失败时返回非 0 exit code。
+- `TIHAO_GATE_MIN_EVIDENCE_COVERAGE`:有视频证据时的最低证据覆盖率。
+- `TIHAO_GATE_MAX_NEGATIVE_RISK_RATE`:最高负样本风险率,默认 10%。兼容旧环境变量 `TIHAO_GATE_MAX_OFF_TOPIC_RATE`。
+- `TIHAO_GATE_MAX_SCORE_DROP`:live 模式 top-10 均分相对 baseline 允许下降的最大分数。
+- `TIHAO_GATE_REQUIRE_REFERENCE_PROVIDER=true`:`reference-account` 策略必须拿到真实参考补证 provider `ok`,否则门禁失败。
+- `TIHAO_GATE_REQUIRE_HOMEPAGE_PROVIDER=true`:`homepage-evidence` 策略必须拿到真实主页最近内容 provider `ok`,否则门禁失败。
+
+严格 provider 门禁只在真实 provider 联调或 live 长跑验收时打开。sample/fallback 环境打开后失败是正常结果,表示不能把 fallback 证据当成真实 provider 证据。
+
+## 输出目录
+
+```text
+outputs/overnight-quality-<timestamp>/
+  manifest.json
+  runs/<brief-id>/<variant>/
+  aggregate-summary.json
+  aggregate-report.md
+  failures.json
+  manual-review-sample.csv
+```
+
+`manual-review-sample.csv` 已加 UTF-8 BOM,Windows Excel 直接打开应显示中文。
+
+## 证据台账
+
+长跑或复核结束后运行:
+
+```powershell
+npm run evidence:index
+```
+
+它会扫描 `outputs`,生成 `evidence-index-summary.json` 和 `evidence-index-report.md`,用于区分:
+
+- `real_evidence`:包含 live、客户选择、历史数据或视频 A/B 的真实证明入口;
+- `smoke_or_local`:只证明结构、门禁或启动前置条件,例如 `live-preflight` 已就绪;
+- `not_business_proof`:明确显示仍缺真实数据或状态未完成。
+
+`live-preflight-summary.json` 会作为 `live-preflight` 类型进入证据台账。它只能证明 sessionToken、company、provider 和视频模型等启动前置条件,不等同于 live 长跑结果或客户效果证明。
+
+## 自动化门禁
+
+必须满足:
+
+- `acceptance.overallPass=true`
+- `acceptance.releaseCoveragePass=true`
+- `failureCount=0`
+- `failedGateCount=0`
+- 软件端表重复键为 0。
+- 软件端表排名从 1 开始连续。
+- 每个 run 都输出主页证据状态。
+- 开启严格 provider 门禁时,`reference-account` 的参考补证状态必须为 `ok`。
+- 开启严格 provider 门禁时,`homepage-evidence` 的主页证据状态必须为 `ok`。
+- live 发布策略有真实召回。
+- 发布策略强推荐数不低于 baseline。
+- 有视频证据时 top-10 证据覆盖达到门槛。
+- 负样本风险率不高于 10%。
+- live 模式 top-10 均分相对 baseline 下降不超过门槛。
+- token 泄密扫描为 0。
+
+## 策略角色
+
+- `baseline-live`:基线策略,只用于对比。
+- `reference-account`:发布候选策略,用于验证参考账号/参考链接补证和相似度链路。
+- `homepage-evidence`:发布候选策略,用于验证主页最近内容、封面质感和调性一致性链路。
+- `result-first`:发布候选策略,参与发布门禁。
+- `result-first-broad`:发布候选策略,参与发布门禁。
+- `video-enhanced`:诊断策略,用于观察视频证据影响。
+- `result-first-risk`:诊断策略,用于观察风险提示和证据批处理。
+
+诊断策略 warning 需要复盘,但只要每个 Brief 都有通过的发布策略,就不阻断发布验收。
+
+## 人工复核
+
+先打开 `aggregate-report.md` 看整体结果,再打开 `manual-review-sample.csv` 标注“人工复核标签”。夜跑表已预置 `客户选择`、`归因类型`、`反馈原因` 三列,商务不用手动加列。
+
+- 可直接发客户
+- 商务复核
+- 跑偏
+- 硬性规则违规
+- 调性不符
+- 主页质感不符
+- 参考账号不像
+- 可投但需补证
+
+如果是负样本,必须填写 `归因类型`:
+
+- 需求解析错
+- 隐性规则漏
+- 召回关键词错
+- 主页证据不足
+- 视频证据误判
+- 排序权重错
+- 输出解释错
+- 软件端表重复或排名不连续
+
+如果已经拿到客户最终反馈,在 `客户选择` 列填写:
+
+- 客户选中
+- 客户拒绝
+- 待客户反馈
+
+人工复核重点:
+
+- top 博主是否真的能给客户解释清楚推荐理由。
+- 视频/参考证据是否真的提升了风格调性判断。
+- 是否有活动词、明星词、无关品类词污染召回。
+- 是否有重复、低质量账号、报价或合规风险。
+
+自动化门禁只证明结构和方向,不能替代最终合规、报价、主页有效性和客户选中率验证。
+
+## 客户效果收口
+
+拿到客户最终反馈和本轮人工补号量后,先跑复核指标,再跑客户效果证明:
+
+```powershell
+npm run review:metrics -- --input <已标注CSV> --output <输出目录> --strict
+npm run customer-effect:audit -- --review-csv <已标注CSV> --history-audit <historical-dataset-audit.json> --current-manual-supplement-count <本轮人工补号量> --output <输出目录> --strict
+npm run evidence:index -- --output outputs\evidence-index-latest
+```
+
+如果历史数据是 CSV,可以使用 pipeline 串起历史导入、历史审计、复核指标、客户效果审计和证据台账:
+
+```powershell
+npm run optimization:pipeline -- --history-csv <历史数据CSV> --review-csv <已标注CSV> --current-manual-supplement-count <本轮人工补号量> --output <输出目录> --strict
+```
+
+客户效果证明必须同时满足:
+
+- 客户选中率 >= 30%。
+- 有参考账号/视频时,参考链路客户选中率 >= 40%。
+- 长期客户选中率目标 >= 50%。
+- 历史人工补号基线存在。
+- 本轮人工补号量存在。
+- 人工补号量减少率 >= 50%。
+
+缺少任一项时,`customer-effect-summary.json` 会被证据台账标记为 `not_business_proof`,不能宣称客户效果已经达标。

+ 63 - 0
claude-code-tihao-sourcing/docs/payment-package-links.md

@@ -0,0 +1,63 @@
+# 付费链接与 Token 流程
+
+## 当前开通链接
+
+技能包在需要开通或充值时,会生成面向用户的链接:
+
+```text
+https://app.fmode.cn/dev/apig-pay/?apigid=G0vmsUI44d&fun_id=voc-e-commerce
+```
+
+如果运行时能通过 sessionToken 解析到 Parse 用户,链接会追加 `user=<objectId>`,方便用户直接进入对应账号的开通/充值页面。
+
+## APIG 包信息
+
+```text
+APIG objectId: G0vmsUI44d
+APIG path: /apig/voc-e-commerce
+Title: 国内电商数据中台
+Service: Fmode
+```
+
+本文档不写具体定价。价格和套餐后续单独维护,避免客户侧文档与实际计费口径不一致。
+
+## 社媒分析服务开通链接
+
+参考账号补证(作者主页、近作、互动中位数)和视频参考详情补证,统一走「社媒分析服务」。需要开通或充值时,会生成面向用户的链接:
+
+```text
+https://app.fmode.cn/dev/apig-pay/?apigid=Vo3ROWEvDy&fun_id=HOkkX72PMF
+```
+
+同样地,若运行时能通过 sessionToken 解析到 Parse 用户,链接会追加 `user=<objectId>`。
+
+```text
+APIG objectId: Vo3ROWEvDy
+fun_id: HOkkX72PMF
+Title: 社媒分析服务
+Service: Fmode
+```
+
+对外只暴露「社媒分析服务」这一名称,不暴露任何底层供应商名(如 TikHub 等)。社媒分析服务未开通或余额不足时,返回 `needs_recharge` 并展示上面的「社媒分析服务充值」链接,保持 `errors=[]`。
+
+## 友好状态
+
+| 场景 | 状态 | 用户侧处理 |
+| --- | --- | --- |
+| 未提供 token | `needs_token` | 展示开通/充值链接和配置下一步 |
+| 401 | `needs_valid_token` | 提示用户提供有效 sessionToken |
+| 403 | `needs_recharge` | 展示开通/充值链接 |
+| 正常 | `ok` | 返回报告正文和文件 |
+
+这些友好状态下保持 `errors=[]`,避免 Claude Code 把原始错误块展示给客户。
+
+## 脱敏规则
+
+任何报告、日志、CSV、JSON、Markdown 和聊天输出都不能展示:
+
+- `Authorization`
+- `Bearer ...`
+- sessionToken 明文
+- `sk-...` 模型 token 明文
+- 原始 401/403 响应体
+- 上游 stack trace

+ 145 - 0
claude-code-tihao-sourcing/docs/reference-evidence-roadmap.md

@@ -0,0 +1,145 @@
+# 参考博主证据链优化路线图
+
+本文档用于 Tihao SOP 技能包后续优化和验收。目标是把 Brief 里的参考博主链接,升级成可解释、可复核、能影响排序的证据链,而不是只把链接贴进报告。
+
+真实 provider 联调请按 `docs/live-provider-integration-runbook.md` 执行。没有真实 gate 证据前,只能称为合同验证、mock 验证或待联调能力。
+
+## 1. 当前已落地能力
+
+### Brief 解析
+
+已支持从 Brief 中解析:
+
+```json
+{
+  "referenceLinks": [],
+  "referenceAccounts": [],
+  "referenceStyleAnchors": [],
+  "referenceSignals": []
+}
+```
+
+字段含义:
+
+- `referenceLinks`:Brief 里的参考链接,例如小红书、抖音、B 站链接;
+- `referenceAccounts`:用户直接给出的参考账号名、redId、userId 或账号描述;
+- `referenceStyleAnchors`:把参考链接归类为干货科普、618 合集、图文、视频等上下文;
+- `referenceSignals`:从参考区提取风格词,例如育婴师、营养师、真实测评、清单推荐、成分党。
+
+### 排序与报告
+
+已接入:
+
+- `referenceSignals` 参与候选内容和风格匹配;
+- 候选结果带 `referenceMatchedSignals`;
+- 传入 `referenceBaselines` 或 `referenceBaselinePath` 后,候选带 `referenceSimilarity`、`referenceFitLevel`、`referenceHitPoints`;
+- Markdown 报告包含“参考视频风格指纹”“多模态证据卡”“商务可用名单”“剔除/降级原因”等章节;
+- CSV/JSON 保留证据字段,方便人工复核。
+
+### 偏好记忆
+
+已支持:
+
+- `preferredReferenceAccounts`;
+- `blockedCreators`;
+- 客户反馈中的“像某某账号”“不要某类账号”可以沉淀为下一轮提号偏好。
+
+## 2. TikHub / VOC social 参考账号补证
+
+目标:把 Brief 中的参考链接升级为参考账号基线,而不是只看链接文本。
+
+建议处理链路:
+
+1. 解析短链,得到最终笔记或视频 URL;
+2. 提取 noteId / awemeId / userId 等平台标识;
+3. 查询笔记或视频详情,得到作者、标题、内容类型和视频资源;
+4. 查询作者主页,得到粉丝、简介、地区、认证等信息;
+5. 查询作者近作,得到标题、描述、互动、发布时间;
+6. 生成 `referenceBaselines`;
+7. 将基线写入排序、报告和复核 CSV。
+
+Provider 输入建议:
+
+```json
+{
+  "referenceEnrichmentBaseUrl": "https://<provider>",
+  "referenceEnrichmentPath": "reference-baselines",
+  "tikhubToken": "<runtime optional token>",
+  "referencePostsLimit": 20
+}
+```
+
+约定:
+
+- provider 接收 `referenceLinks`、`referenceAccounts`、`referenceStyleAnchors`;
+- provider 返回 `referenceBaselines` 数组;
+- provider 失败、401、403、网络错误或空结果都不能中断提号,只能写 warning,并保留原参考链接。
+
+## 3. 视频证据链
+
+当参考账号或候选账号的关键样本是视频时,不能只看标题和简介,需要补充画面、口播、字幕和产品露出证据。
+
+推荐链路:
+
+```text
+视频/笔记详情
+-> 读取视频 URL、封面、字幕或可下载资源
+-> 抽帧
+-> ASR 转写
+-> 豆包视觉/多模态分析
+-> evidenceCards
+-> 排序、重排、报告复核
+```
+
+证据卡至少包含:
+
+- 命中的 Brief 需求;
+- 命中的参考风格;
+- 画面或口播依据;
+- 风险提示;
+- 对候选排序的影响;
+- 缺失证据时的人工复核提示。
+
+真实性保护:
+
+- 模型返回的风格结论不能冒充 ASR 或帧图证据;
+- 没有 transcript 时标注“待补口播证据”;
+- 没有 frame/resource 时标注“待补帧图证据”;
+- 证据卡不得写入 token 或 Authorization。
+
+## 4. 对命中率的提升路径
+
+优先优化顺序:
+
+1. Brief 关键词召回:从品类、人设、场景、内容形态、节点词扩展,但过滤跑偏事件词。
+2. 参考风格召回:用参考视频指纹补充“DHA 专业科普”“清单推荐”“真实测评”等与 Brief 绑定的关键词。
+3. 预排序:先用数据质量、Brief 匹配、预算、风险筛出高潜候选。
+4. 证据分析:优先分析预排序前列候选,结果优先服务重排。
+5. 重排与降级:证据命中可升级;混类、竞品、预算、合规风险必须降级。
+6. 人工复核:把不确定项写进 CSV,让商务能标注“命中/跑偏/待复核/不可投”。
+
+## 5. 验收标准
+
+技术验收:
+
+- `npm run acceptance` 通过;
+- `npm run acceptance:providers:mock` 通过;
+- 有真实权限时 `npm run acceptance:providers` 通过;
+- 有真实 token 和模型 token 时 `npm run acceptance:video-ab` 通过;
+- 输出文件 token leak scan 为 0。
+
+质量验收:
+
+- 每个 Brief 至少有一个发布策略通过;
+- 强推荐数量不低于基线;
+- top 10 平均分不低于基线;
+- 证据覆盖率达到目标;
+- 负样本风险率为 0 或可解释并降级;
+- top 博主能用 Brief 匹配和参考风格证据解释给客户。
+
+业务验收:
+
+- 商务打开 Markdown 能直接看到推荐名单、推荐原因、风险和复核建议;
+- Excel 直接打开 CSV 不乱码;
+- 人工复核 CSV 有中文列名和“人工复核标签”列;
+- 首轮输出明确是“初步判断/机会假设/待校准名单”。

+ 457 - 0
claude-code-tihao-sourcing/docs/release-notes.md

@@ -0,0 +1,457 @@
+# 发布记录
+
+本文档记录 `@vocmarket/tihao` 的重要变更、验证情况和仍需注意的边界。命令名、字段名、包名保留英文,因为它们是实际运行契约。
+
+## 1.0.10
+
+Status: published to npm. This release makes the first report a high-quality live result-first list by default, instead of opening with a sample demo.
+
+Positioning:
+- The `tihao` SKILL now defines a "First Report Standard": normal brief-to-list requests default to `collectionMode=live` with `resultFirstMode=true`, and enable reference enrichment when reference links/accounts exist, so the first list is business-ready 高质量首版 rather than a sample demo.
+- `sample` mode is now demo-only. The skill must never run sample just to "show the flow" or because a token is missing.
+- When the live Parse sessionToken is missing or invalid, the skill returns the friendly `needs_token` / `needs_valid_token` state and asks the user for a valid sessionToken instead of silently falling back to sample.
+- `README.md`, `skill-package-manifest.json` recommended prompts, and `docs/skill-database-record.json` were updated to stop steering the first run toward sample.
+
+Notes:
+- No runtime code default changed: `resolveCollectionMode` already defaulted to `live`, and `resultFirstMode` stays an explicit flag. This release changes the agent-facing rules/docs so the first round is driven to live + result-first.
+
+## 1.0.9
+
+Status: published to npm. `@vocmarket/tihao@1.0.9` is `latest`, with registry verification and fresh `@latest` workspace install smoke passed. This release fixes the real live first-round Tihao output so a user can drop in a Brief and get a business-reviewable blogger table before later calibration.
+
+Positioning:
+- The real live CSV now exports a 44-column business table aligned with the reference workbook fields, including target city, budget rule, matched price type/value, account ID, fans, image/video/min price, quote status, location, content/persona tags, Pugongying judgment, CPM/CPE, commercial order metrics, growth, reply rate, low-activity flag, data source, and manual review fields.
+- Live provider normalization now extracts pricing, CPM/CPE, commercial metrics, growth, reply-rate, low-activity, and Douyin profile URLs from multiple real provider aliases instead of leaving key cells as "not fetched".
+- Brief parsing now handles real inputs such as `粉丝要求:1k-10w`, `单个预算:2000内`, and `主页互动数据200+`.
+- Ranking now penalizes missing quote data, prevents no-price rows from becoming strong recommendations, and downgrades over-budget rows for review.
+
+Validation:
+- Real live small run passed at `E:\test-tihao\outputs\tihao-real-fixed-1781542350326`: 15 rows, 13 rows with prices, 15 rows with profile URLs, and no strong recommendation without price.
+- `npm run acceptance` passed on `@vocmarket/tihao@1.0.9`, including package contents smoke with 185 files and package size about 644.2 kB.
+- `npm publish --dry-run --access public --registry https://registry.npmjs.org/` passed.
+- `npm publish --access public --registry https://registry.npmjs.org/` succeeded and returned `+ @vocmarket/tihao@1.0.9`.
+- `npm view @vocmarket/tihao@1.0.9 version --registry https://registry.npmjs.org/` returned `1.0.9`.
+- `npm view @vocmarket/tihao dist-tags.latest version --registry https://registry.npmjs.org/` returned `latest=1.0.9`.
+- Fresh `npx --yes @vocmarket/tihao@latest workspace --smoke` passed.
+
+## 1.0.8
+
+Status: published to npm. `@vocmarket/tihao@1.0.8` is `latest`, with registry verification and fresh `@latest` workspace/user install smoke passed. This release renames the public skill package, primary CLI command, plugin install directory, and MCP server name to `tihao`.
+
+Positioning:
+- New public npm package: `@vocmarket/tihao`.
+- Primary install command: `npx --yes @vocmarket/tihao@latest workspace --smoke`.
+- Primary CLI command: `tihao`; legacy alias `tihao-sourcing` is still shipped for transition.
+- Workspace plugin path and MCP server name are now `.claude/plugins/tihao` and `tihao`.
+- The installer removes legacy `.claude/plugins/tihao-sourcing` and `.claude/skills/tihao-creator-sourcing`, and removes the legacy `tihao-sourcing` entry from workspace `.mcp.json`.
+- Database `skills` table source record now uses `npmPackage=@vocmarket/tihao`, `pluginName=tihao`, and `mcpServerName=tihao`.
+- Legacy package `@vocmarket/tihao-sop` remains historical; customers should install `@vocmarket/tihao` going forward.
+
+Validation:
+- Syntax passed: `node --check install.js`, `node --check bin/tihao.js`, `node --check mcp/src/server.js`, `node --check scripts/acceptance.js`, and `node --check scripts/package-contents-smoke.js`.
+- `npm run package:contents:smoke`, `npm run smoke:package`, `npm run mcp:smoke`, `npm run skill:refs:smoke`, and `npm run acceptance` passed.
+- `npm publish --dry-run --access public --registry https://registry.npmjs.org/` passed.
+- `npm publish --access public --registry https://registry.npmjs.org/` succeeded and returned `+ @vocmarket/tihao@1.0.8`.
+- `npm view @vocmarket/tihao@1.0.8 version --registry https://registry.npmjs.org/` returned `1.0.8`.
+- `npm view @vocmarket/tihao dist-tags.latest version --registry https://registry.npmjs.org/` returned `latest=1.0.8`.
+- Fresh `npx --yes @vocmarket/tihao@latest workspace --smoke` passed, wrote `.claude/plugins/tihao` and `.claude/skills/tihao/SKILL.md`, and removed legacy `.claude/plugins/tihao-sourcing` plus `.claude/skills/tihao-creator-sourcing`.
+- Fresh `npm exec --yes --package @vocmarket/tihao@latest -- tihao install --smoke` passed for user-level install.
+
+## 1.0.7
+
+Status: published to npm. `@vocmarket/tihao-sop@1.0.7` is `latest`, with registry verification and fresh `@latest` workspace install smoke passed.
+
+Positioning:
+- Aligns the database `skills` table source record and npm management docs with the two real install targets:
+  - project/workspace skill install: `npx --yes @vocmarket/tihao-sop@latest workspace --smoke`
+  - user/global Claude Code skill install: `npm install -g @vocmarket/tihao-sop && tihao-sourcing install --smoke`
+- Keeps the installed Claude Code skill name as `tihao`.
+- Keeps npm package name, CLI command, plugin name, and MCP server name compatible: `@vocmarket/tihao-sop`, `tihao-sourcing`, and `tihao-sourcing`.
+- Keeps the legacy `.claude/skills/tihao-creator-sourcing` cleanup behavior from 1.0.6.
+
+Validation:
+- `node --check install.js` and `node --check mcp/src/server.js` passed.
+- `npm run smoke:package`, `npm run skill:refs:smoke`, `npm run package:contents:smoke`, and `npm run acceptance` passed.
+- `npm publish --dry-run --access public --registry https://registry.npmjs.org/` passed.
+- `npm publish --access public --registry https://registry.npmjs.org/` succeeded and returned `+ @vocmarket/tihao-sop@1.0.7`.
+- `npm view @vocmarket/tihao-sop@1.0.7 version --registry https://registry.npmjs.org/` returned `1.0.7`.
+- `npm view @vocmarket/tihao-sop dist-tags.latest version --registry https://registry.npmjs.org/` returned `latest=1.0.7`.
+- Fresh `npm exec --yes --package @vocmarket/tihao-sop@latest -- tihao-sourcing workspace --smoke` passed, wrote `.claude/skills/tihao/SKILL.md`, and removed legacy `.claude/skills/tihao-creator-sourcing`.
+
+## 1.0.6
+
+Status: published to npm. `@vocmarket/tihao-sop@1.0.6` published the installed Claude Code skill rename from `tihao-creator-sourcing` to `tihao`.
+
+Positioning:
+- Workspace and global installs now write `.claude/skills/tihao/SKILL.md`.
+- The installer removes the legacy `.claude/skills/tihao-creator-sourcing` directory under the same target `.claude/skills` root to avoid duplicate skill entries.
+- npm package name, CLI command, plugin name, and MCP server name stay compatible: `@vocmarket/tihao-sop`, `tihao-sourcing`, and `tihao-sourcing`.
+- Added `docs/skill-database-record.json` as the database `skills` table source record for `skillName=tihao`.
+
+Validation:
+- `node --check install.js` passed.
+- `npm run smoke:package`, `npm run skill:refs:smoke`, `npm run package:contents:smoke`, and `npm run acceptance` passed.
+- `npm publish --dry-run --access public --registry https://registry.npmjs.org/` passed.
+- `npm publish --access public --registry https://registry.npmjs.org/` succeeded and returned `+ @vocmarket/tihao-sop@1.0.6`.
+- `npm view @vocmarket/tihao-sop@1.0.6 version --registry https://registry.npmjs.org/` returned `1.0.6`.
+- `npm view @vocmarket/tihao-sop dist-tags.latest version --registry https://registry.npmjs.org/` returned `latest=1.0.6`.
+- Fresh `npm exec --yes --package @vocmarket/tihao-sop@latest -- tihao-sourcing workspace --smoke` passed, wrote `.claude/skills/tihao/SKILL.md`, and removed legacy `.claude/skills/tihao-creator-sourcing`.
+
+## 1.0.5
+
+Status: published to npm. `@vocmarket/tihao-sop@1.0.5` is `latest`, with registry verification and fresh `npx @latest workspace --smoke` passed. This release absorbs the two new 提号经验 transcripts and extends the live brief-to-list workflow without changing the small-live default.
+
+Positioning:
+- 1.0.5 adds multi-input Brief parsing for product introductions, product context, customer chat records, and conversation snippets.
+- Broad briefs such as 精致生活/时尚穿搭 now refine into executable subtypes from product/chat/reference context, including 港风、成熟风、通勤穿搭、女装/男装; 美妆护肤 is split into 护肤、妆教、测评、产品合集、日常妆容.
+- Homepage evidence now tracks visual positives/risks plus 近30天无更新、百赞以下、重复评论/数据真实性 risks; these affect strong recommendation eligibility, risk notes, manual review fields, and CSV review labels.
+- The experience transcript index now covers 13 rules across 4 transcripts while preserving the `not_business_proof` boundary.
+
+Validation:
+- Syntax passed: `node --check` for brief parser, experience rules, homepage evidence, ranker, MCP server, and experience transcript index.
+- `npm run smoke:briefs`, `npm run hard-rules:smoke`, `npm run experience-transcript:index:smoke`, `npm run smoke:package`, `npm run mcp:smoke`, `npm run package:contents:smoke`, and `npm run acceptance` passed.
+- Local real transcript index passed with `transcriptCount=4`, `coveredRuleCount=13`, `canCloseProofGap=false`.
+- `npm publish --access public --registry https://registry.npmjs.org/`, `npm view @vocmarket/tihao-sop@1.0.5 version`, `npm view @vocmarket/tihao-sop dist-tags.latest version`, and fresh `npx --yes @vocmarket/tihao-sop@latest workspace --smoke` passed.
+
+## 1.0.4
+
+Status: published to npm. `@vocmarket/tihao-sop@1.0.4` is `latest`, with registry verification and fresh `npx @latest workspace --smoke` passed.
+
+Positioning:
+- 1.0.4 fixes natural-language Brief generalization beyond the original fixture-heavy skincare/mom-baby cases.
+- The parser now handles healthy snack, home cleaning, B2B SaaS, and local food/catering briefs with better category, keyword, platform target, fan range, budget, city, style, and exclusion extraction.
+- Fan range parsing no longer reads same-line budget ranges as fan ranges, e.g. `粉丝 5000-20万,预算 800-5000`.
+- Live recall keywords are cleaned so platform words like `小红书/抖音` and dirty fragments like `个健身` do not become the first search keyword.
+
+Validation:
+- `node --check mcp/src/features/tihao-sourcing/brief-parser.js` passed.
+- `npm run smoke:briefs`, `npm run smoke:package`, `npm run mcp:smoke`, `npm run package:contents:smoke`, and `npm run acceptance` passed.
+- `npm publish --dry-run --access public --registry https://registry.npmjs.org/` passed.
+- `npm publish --access public --registry https://registry.npmjs.org/`, registry verification, and fresh `npx --yes @vocmarket/tihao-sop@latest workspace --smoke` passed.
+
+## 1.0.3
+
+Status: published to npm. `@vocmarket/tihao-sop@1.0.3` is `latest`, with registry verification and fresh `npx @latest workspace --smoke` passed.
+
+Positioning:
+- 1.0.3 fixes the brief-to-blogger-list default mode: normal user briefs and requests like "用 live 小规模检索出博主名单", "真实跑", "真实数据", "调用接口", or "小规模检索" resolve to live mode.
+- First live runs stay intentionally small when limits are omitted: `keywordLimit=1` and `pagesPerKeyword=1`.
+- Sample mode is now weaker by design and should be used only when the user explicitly asks for `sample`, demo/no-cost mode, no real data, or "先跑通 SOP".
+- Missing live credentials, token, balance, or recharge state must return a friendly token/open/recharge message instead of silently falling back to sample data.
+
+Validation:
+- Syntax passed: `node --check mcp/src/features/tihao-sourcing/sourcing-workflow.js`, `node --check mcp/src/server.js`, `node --check scripts/smoke-package.js`.
+- Business smoke passed: `npm run smoke:package`, `npm run mcp:smoke`, `npm run package:contents:smoke`, `npm run acceptance`.
+- Release smoke passed: `npm publish --dry-run --access public --registry https://registry.npmjs.org/`, `npm publish --access public --registry https://registry.npmjs.org/`, `npm view @vocmarket/tihao-sop@1.0.3 version`, `npm view @vocmarket/tihao-sop dist-tags.latest version`, and fresh `npx --yes @vocmarket/tihao-sop@latest workspace --smoke`.
+
+## 1.0.2
+
+状态:已发布到 npm;`@vocmarket/tihao-sop@1.0.2` 已成为 `latest`,并通过 registry 查询和 fresh `npx` 工作区安装 smoke。
+
+版本定位:
+
+- 1.0.2 是 1.0.1 的收尾加固版:把 `real-gap-material-audit` 从独立审计推进到 `round:refresh` 主链路,并同步写入 latest form、handoff、evidence index 和 round deposition 守卫。
+- 该版本证明“三个真实缺口在项目里都有材料且已接入审计链路”,不证明业务效果已经达标;仍不得宣称提号率提升、客户效果达标或人工补号量下降。
+- 当前真实业务证明仍以审计输出为准:`gapCount=3`、`materialFoundCount=3`、`openGapCount=3`、`canCloseProofGap=false`。
+
+主要变化:
+
+- `npm run round:refresh -- --output-root outputs --strict` 现在包含 29 个步骤,并在 `local-seed-to-intake-worklist` 后执行 `real-gap-material-audit`。
+- `latest-form:index` 和 `handoff:summary` 已传播 `realGapMaterialAudit`,接手方不用再单独猜三缺口材料审计产物位置。
+- `evidence:index` 将 `real-gap-material-audit` 固定归类为 `not_business_proof`,防止把材料存在误升格为业务证明。
+- `round-deposition` 新增三缺口材料审计守卫,校验 standalone、latest-form 和 handoff 三处都保留 `canCloseProofGap=false` 边界。
+
+验证情况:
+
+- `npm run real-gap:materials:smoke` 通过。
+- `npm run evidence:index:smoke` 通过,当前 fixture `total=44`、`not_business_proof=19`、`real_evidence=6`。
+- `npm run task:handoff-index:smoke` 通过。
+- `npm run round:refresh:smoke` 通过,29 步全部 `pass`,`round-deposition.passCount=142`、`failCount=0`。
+- `npm run round:refresh -- --output-root outputs --strict` 通过,真实输出已刷新。
+- `npm run package:contents:smoke`、`npm run acceptance` 和 `npm publish --dry-run --access public --registry https://registry.npmjs.org/` 通过。
+- `npm publish --access public --registry https://registry.npmjs.org/` 成功,npm 返回 `+ @vocmarket/tihao-sop@1.0.2`。
+- `npm view @vocmarket/tihao-sop@1.0.2 version --registry https://registry.npmjs.org/` 返回 `1.0.2`。
+- `npm view @vocmarket/tihao-sop dist-tags.latest version --registry https://registry.npmjs.org/` 返回 `latest=1.0.2`。
+- `npx --yes @vocmarket/tihao-sop@latest workspace --smoke` 在 fresh workspace 通过。
+
+## 1.0.1
+
+状态:已发布到 npm;`@vocmarket/tihao-sop@1.0.1` 已成为 `latest`,并通过 registry 查询和 fresh `npx` 工作区安装 smoke。
+
+版本定位:
+
+- 1.0.1 不是改业务结论,而是把“项目里确实有资料”做成可复跑审计:`real-gap:materials` 会同时读取 DHA PoC 原始资料、候选视频资源、提号经验逐字稿、`proof-gap:closure` 和 video A/B preflight。
+- 该版本用于关闭“没找到资料/没接资料”的误会;不关闭业务证明缺口,不宣称提号率提升、客户效果达标或人工补号量下降。
+- 当前真实业务证明仍以审计输出为准:`real-gap-material-audit.openGapCount=3`、`materialFoundCount=3`、`canCloseProofGap=false`。
+
+主要变化:
+
+- 新增 `npm run real-gap:materials`,输出 `outputs/real-gap-material-audit-latest/real-gap-material-audit-summary.json`、Markdown 和 CSV。
+- 新增 `real-gap:materials:smoke` 并纳入 `npm run acceptance`,确保三个缺口都能找到项目材料,但 `canCloseNow=false` 不被误改。
+- 审计明确三个缺口的当前证据:
+  - `historical-dataset`:DHA Brief/PoC/TikHub 工作簿已找到,`historyDraftRows=12`,逐字稿记录了 DHA 已提报但客户未反馈/未选新号;仍缺至少 5 个真实历史 Brief、客户决策、拒绝原因和历史人工补号量。
+  - `video-ab-live-proof`:`videoResourceReady=true`、`realCandidateRows=1`、`realReferenceRows=1`、候选视频 note 线索 15 条;仍缺 `acceptance:video-ab` 的 live `proofContext` 和 runtime/company/provider。
+  - `manual-review-and-customer-effect`:`manualReviewDraftRows=12`,逐字稿覆盖客户选中率目标和人工复核逻辑;仍缺真实人工复核标签、客户选中/拒绝、反馈原因、本轮人工补号量,并且必须绑定通过的 history audit。
+- 审计报告把候选名单、逐字稿经验、视频资源 readiness 和旧 failure artifacts 的边界写清楚:它们是补证入口和失败证据,不是业务效果证明。
+
+验证情况:
+
+- `npm run real-gap:materials:smoke` 通过。
+- `npm run real-gap:materials` 通过,当前输出 `gapCount=3`、`materialFoundCount=3`、`openGapCount=3`、`canCloseProofGap=false`。
+- `npm run acceptance` 通过,包内容 smoke 显示 `version=1.0.1`、`entryCount=178`、`package size=607.4 kB`。
+- `npm publish --dry-run --access public --registry https://registry.npmjs.org/` 通过,确认 tarball 为 `@vocmarket/tihao-sop@1.0.1`。
+- `npm publish --access public --registry https://registry.npmjs.org/` 成功,npm 返回 `+ @vocmarket/tihao-sop@1.0.1`。
+- `npm view @vocmarket/tihao-sop@1.0.1 version --registry https://registry.npmjs.org/` 返回 `1.0.1`。
+- `npm view @vocmarket/tihao-sop dist-tags.latest version --registry https://registry.npmjs.org/` 返回 `latest=1.0.1`、`version=1.0.1`。
+- `npx --yes @vocmarket/tihao-sop@latest workspace --smoke` 在 fresh temp workspace 通过,安装 `.claude/plugins/tihao-sourcing` 和 `.claude/skills/tihao-creator-sourcing` 后输出 `tihao smoke ok`。
+
+## 1.0.0
+
+状态:已发布到 npm;`@vocmarket/tihao-sop@1.0.0` 已成为 `latest`,并通过 registry 查询和 `npx` 工作区安装 smoke。
+
+版本定位:
+
+- `@vocmarket/tihao-sop` 的客户可安装 1.0 技能包版本,面向 Claude Code 工作区安装、Brief 到商务可复核名单、sample/live 小规模运行、偏好记忆、真实 provider runbook 和补证闭环交接。
+- 1.0 可以声明“技能包交付链路可安装、可验收、可继续补真实材料”,不能声明“提号率已提升、客户效果已达标、人工补号量已下降”。
+- 当前真实业务证明仍以 `proof-gap:closure` 和 `optimization:completion` 为准:`businessOpenCount/openCount=3`、`closedCount=2`、`readyForClaim=false`。
+
+主要变化:
+
+- 为每个参考链接生成结构化 `referenceStyleFingerprints`,覆盖视频/图文/账号类型、人设、内容结构、语气、视觉元素、卖点承接、商业强度、合规风险和证据可用性。
+- 增加真实性保护:模型返回的风格结论不能冒充 ASR 或帧图证据;缺少视频证据时明确标注“待补口播证据”或“待补帧图证据”。
+- live 召回关键词从 Brief 和参考风格信号中扩展,覆盖品类、人设、场景、内容形态和节点词;同时记录平台、关键词、页码、API 路径、HTTP 状态、返回数量和规范化数量。
+- 重构排序字段,输出 `briefFitScore`、`referenceStyleFitScore`、`dataQualityScore`、`budgetFitScore`、`riskPenalty`、命中条件、缺失条件和人工复核字段。
+- 强推荐必须同时通过 Brief 和参考风格门槛;超预算、硬排除或高风险账号会被降级或剔除。
+- Markdown 和 CSV 输出面向商务验收增强,包含 `Brief 解析摘要`、`参考视频风格指纹`、`候选召回记录`、`多模态证据卡`、`商务可用名单`、`剔除/降级原因`、`下一轮校准问题`。
+- PM 验收和 smoke 测试升级,强制检查高标准字段、真实性 gate 和 token 泄露扫描。
+- 增加真实 VOC social 参考视频补证指引:使用 `https://server.fmode.cn/api/voc-social` 获取小红书视频详情,再把真实视频、封面、字幕或帧图资源传给豆包;不能拿 Parse sessionToken 直接调用公开 TikHub。
+- 增加 `npm run acceptance:video-ab`,用于同一 Brief 和参考视频的 live A/B 验收。
+- 证据卡现在会影响候选参考风格命中点、分数和重排,不再只是报告里的被动展示。
+- 增加防跑偏的参考感知召回:允许生成 `DHA 专业科普` 这类和 Brief 绑定的召回词;禁止婚礼、宴会、布景、满月宴、仪式感等跑偏场景词污染召回。
+- JSON、Markdown、CSV 增加 `evidenceScoreBoost`,让商务能看到验证过的视频/证据分析对候选的贡献。
+- 增加成本可控的证据分析目标选择:先预排序,再默认分析前 6 个候选。
+- 增加 A/B 效率指标:证据卡数量、每张证据卡带来的强推荐提升、总分提升、参考风格分提升。
+- 增加 `resultFirstMode`,用于本轮不优先考虑成本、只优先追求结果质量的场景。
+- 增加 `evidenceRiskPenalty`,让混类或合规风险账号即使命中基础母婴信号也能被压低。
+- CSV 写入增加 UTF-8 BOM,方便 Excel 直接打开中文不乱码。
+- 增加软件端固定表头、跨策略去重和连续排名验收。
+- 增加 `live:preflight`,在真实 live/video 长跑前检查 sessionToken、company、provider 和豆包模型配置。
+- 增加 overnight 策略矩阵:`reference-account`、`homepage-evidence`、`video-enhanced`、`result-first`、`result-first-risk`、`result-first-broad`。
+- 增加严格 provider 门禁:`TIHAO_GATE_REQUIRE_REFERENCE_PROVIDER` 和 `TIHAO_GATE_REQUIRE_HOMEPAGE_PROVIDER`。
+- 增加 `review:metrics`,统计商务可用率、负样本率、负样本归因覆盖率、客户选中率、重复键和每个 brief 内排名连续性。
+- 增加 `history:audit`,验收真实历史 Brief、人工最终名单、客户选中/拒绝记录和拒绝原因是否足够做长期优化。
+- 增加反馈导入二轮闭环和 `teamRuleSuggestions`,同类反馈累计 3 次后建议升级为团队规则。
+- 打通本地资料到正式补证入口的桥梁:`local-seed-to-intake-worklist` 现在生成 `history-intake-draft.csv`、`manual-review-draft.csv`、`video-resource-worklist.csv` 和 `candidate-seed-worklist.csv`,并在 `proofGapMaterialBridge` 中标记三个仍 open 缺口的资料来源和不能关闭原因。
+- `proof-gap:closure` 现在读取最新 `local-seed-to-intake-worklist-summary.json`,把 `historical-dataset`、`video-ab-live-proof`、`manual-review-and-customer-effect` 的 `materialBridge` 写入 open 行,避免继续误判为“项目没有资料”。
+- 三个真实缺口已从“找不到资料”推进为“资料已接入但还不能构成业务证明”:历史草稿 12 行仍缺 5 个以上真实历史 Brief 与客户决策;视频资源线索仍缺 live `acceptance:video-ab` proofContext;人工复核草稿 12 行仍缺真实标注、客户选择、归因和本轮人工补号量。
+
+验证情况:
+
+- `npm run acceptance` 通过。
+- `npm publish --dry-run --access public` 通过,确认 tarball 为 `@vocmarket/tihao-sop@1.0.0`,共 176 个文件,package size 约 598.5 kB。
+- `npm publish --access public --registry https://registry.npmjs.org/` 成功,npm 返回 `+ @vocmarket/tihao-sop@1.0.0`。
+- `npm view @vocmarket/tihao-sop@1.0.0 version --registry https://registry.npmjs.org/` 返回 `1.0.0`。
+- `npm view @vocmarket/tihao-sop dist-tags.latest version --registry https://registry.npmjs.org/` 返回 `latest=1.0.0`、`version=1.0.0`。
+- `npx --yes @vocmarket/tihao-sop@latest workspace --smoke` 通过,fresh workspace 安装写入 `.claude/plugins/tihao-sourcing`、`.claude/skills/tihao-creator-sourcing` 并跑通 `tihao smoke ok`。
+- `npm run acceptance:providers:mock` 通过。
+- `npm run review:metrics:smoke` 通过。
+- `npm run history:audit:smoke` 通过。
+- 本地 fresh tarball 安装和 `npx tihao-sourcing workspace --smoke` 通过。
+- 真实 `voc-e-commerce` live 验收使用有效运行时 sessionToken/company,返回 20 个候选和 10 条客户可用名单。
+- 真实 live + 豆包组合测试使用模型 `doubao-seed-2-0-pro`,返回 97 个 live 候选、10 条客户可用名单、31 个强推荐、2 个参考指纹、1 个视频指纹、5 条召回记录,并生成 Markdown/JSON/CSV。
+- 真实 VOC social 视频详情测试返回视频 URL、封面、作者/标题、字幕资源和可用参考指纹;泄露扫描通过。
+- 手动 live A/B 在证据重排后通过:B 组加载 1 个真实参考资源和 10 张豆包证据卡;top 10 证据命中候选增加 6 个,平均参考风格分增加 1.0,平均总分增加 0.2。
+- 加入防跑偏召回和证据加分后,手动 live A/B 提升更明显:B 组使用 `DHA 专业科普` 替代基线 `母婴`,加载 1 个真实参考资源和 10 张豆包证据卡,强推荐增加 3 个,top 10 证据命中候选增加 7 个,平均参考风格分增加 8.2,平均总分增加 7.4。
+- 成本可控的 top 6 证据分析仍通过:只使用 6 张豆包证据卡,强推荐增加 4 个,top 10 证据命中候选增加 6 个,平均总分增加 3.1。
+- result-first live A/B 通过:增强召回跑 8 次 live 请求,关键词包含 `DHA 专业科普`、`DHA 节点合集`、`DHA 清单推荐`、`DHA 理性科普`;豆包返回 20 张证据卡;强推荐从 5 个提升到 10 个;top 10 证据命中候选从 0 个提升到 10 个;平均参考风格分增加 2.1,平均总分增加 5.5。
+- 输出泄露扫描未发现运行时 sessionToken、模型 token 或 `Authorization` 字符串。
+
+已知边界:
+
+- 首次组合 live 运行出现过一次短暂 `live_network_error`,重试后通过。后续 runbook 保留 retry/backoff,并建议对单关键词失败做软失败处理。
+- 未经过真实 provider gate 的能力只能称为合同验证或 mock 验证,不能宣称真实 TikHub、ffmpeg、讯飞 ASR 或豆包视觉已完成上线。
+- 没有真实历史客户选择数据前,不能宣称客户选中率达到 30%/50%,只能使用 `review:metrics` 统计已标注样本。
+- npm 发布状态必须以 `npm publish` 成功和 registry 验证为准;仅 `npm pack` 或 dry-run 通过时只能称为本地包已重建。
+
+## 0.1.16
+
+状态:本地待发布版本;包含最新交接文档、递归证明产物发现说明和 proof-gap 失败条件沉淀。发布前必须通过 `npm run acceptance`、`npm run package:contents:smoke` 和 npm publish 权限校验。
+
+主要变化:
+
+- `docs/tihao-handoff-index.md` 和 `docs/tihao-experience-implementation-log.md` 已同步最新 proof-gap 边界:`round-deposition.passCount=137`、`optimization-completion.missingProofRequirementCount=113`、`proof-gap-closure.businessOpenCount/openCount=3`。
+- 交接文档新增 closure/completion 递归证明产物发现记录:旧 `optimization-pipeline-latest` 内嵌的 `history-audit/historical-dataset-audit.json` 与 `customer-effect-audit/customer-effect-summary.json` 会被读取为失败证据,不能被误判为“文件缺失”或“可完成证明”。
+- 明确三个真实缺口仍未关闭:真实历史 Brief 数据集、真实视频 A/B live proof、商务复核和客户效果证明。
+- 明确当前没有业务效果宣称资格:`optimization-completion.readyForClaim=false` 时,不得宣称提号率提升、客户效果达标或人工补号量下降。
+
+为什么三个真实缺口还没有完成:
+
+- 历史 Brief 缺口不是代码没跑,而是当前真实材料只有 `briefCount=1`,缺 5 条以上真实 Brief、人工最终名单、客户选中/拒绝、拒绝原因和历史人工补号量基线。用 seed、示例、模板或本地 PoC 候选补数会造成错误业务宣称。
+- 视频 A/B 缺口不是视频资源表完全缺失,真实候选视频资源已单项 closed;当前卡在 live proof:缺真实运行凭证/company/VOC social provider/video analysis provider 上下文,本地只会生成 `video-hit-rate-preflight-summary.json`,不能生成 `proofContext.generatedBy=acceptance:video-ab` 的 live 证明。
+- 客户效果缺口必须由 `customer-effect:audit` 证明,当前缺客户最终选择、参考链路客户选择、本轮人工补号量,并且绑定的历史审计仍未通过客户效果证明门槛。
+
+验证:
+
+- `npm.cmd run task:handoff-index:smoke`
+- `npm.cmd run hygiene:smoke`
+- `npm.cmd run round:refresh:smoke`
+- `npm.cmd run package:contents:smoke`
+- `npm.cmd run install:smoke`
+- `npm.cmd run smoke:package`
+- `npm.cmd run acceptance`
+- `npm.cmd pack --dry-run --json`
+
+## 0.1.15
+
+状态:VOCMarket npm scope 迁移版本。
+
+主要变化:
+
+- 客户侧 npm 包名从 `@gangvy/tihao-sop` 改为 `@vocmarket/tihao-sop`。
+- CLI 命令仍为 `tihao-sourcing`,Claude Code plugin/skill 标识保持不变。
+- 安装文档和包 manifest 使用 `npx --yes @vocmarket/tihao-sop@latest workspace --smoke`。
+- 保留 `0.1.13` 的真实豆包 OpenAI 兼容服务适配。
+
+验证:
+
+- `npm run acceptance`
+- `npm run acceptance:providers:mock`
+- 发布后 fresh npm 安装 smoke:`npx --yes @vocmarket/tihao-sop@latest workspace --smoke`
+
+备注:
+
+- `@vocmarket/tihao-sop@0.1.14` registry `PUT 200` 且 dist-tag 显示 latest,但 public `npm view`/`npx` 返回 404,因此未作为客户可安装版本。
+
+## 0.1.13
+
+状态:真实豆包 OpenAI 兼容服务适配版本。
+
+主要变化:
+
+- 增加豆包/视频分析 provider 的 OpenAI 兼容 chat completions 请求模式。
+- `doubaoVisionBaseUrl: "https://api.fmode.cn"` 默认请求 `/v1/chat/completions`,默认模型为 `doubao-seed-2-0-pro`。
+- 非 chat provider URL 继续沿用内部 evidence-card provider 合同。
+- 增加 OpenAI 兼容请求体和 `choices[0].message.content` 证据卡解析的 mock 覆盖。
+
+验证:
+
+- 真实请求 `https://api.fmode.cn/v1/chat/completions` 返回 HTTP 200,模型为 `doubao-seed-2-0-pro-260215`。
+- 真实 Tihao sample run 使用豆包服务返回 3 张 `doubao_vision_verified` 证据卡,并写出 Markdown/JSON/CSV。
+- 输出扫描未发现 API token 或 Authorization header 泄露。
+- `npm run acceptance:providers:mock`
+- `npm run mcp:smoke`
+
+备注:
+
+- 该版本验证了豆包模型能基于候选/Brief 信号生成证据卡。若未提供真实帧图或 ASR,证据卡会正确标注缺少视频帧/口播证据。
+
+## 0.1.12
+
+状态:已发布,豆包 provider 验证加固版本。
+
+主要变化:
+
+- 加强豆包 provider 合同验收:当返回 OpenAI 风格 evidenceCards 时,报告必须包含 `## 多模态证据卡` 和 `doubao_vision_verified`。
+- 产品验收扫描忽略本地 `.npm-cache-*`,避免 npm registry cache 被误判为源码泄露。
+
+验证:
+
+- 本地 HTTP mock 调用豆包 provider,覆盖模型、Authorization 和 evidenceCards 解析。
+- `npm run acceptance:providers:mock`
+- `npm run acceptance`
+
+备注:
+
+- 该版本是合同/mock 验证,不等于真实外部豆包服务已跑通。
+
+## 0.1.11
+
+状态:已发布,豆包视频分析 provider 合同版本。
+
+主要变化:
+
+- 增加可选豆包/视频分析输入:`doubaoVisionBaseUrl`、`doubaoVisionPath`、`doubaoVisionModel`、`doubaoVisionToken`、`videoAnalysisBaseUrl`、`videoAnalysisPath`、`videoAnalysisModel`、`videoAnalysisToken`。
+- 默认模型为 `doubao-seed-2-0-pro`。
+- 请求复用 evidence-card 合同,并包含 `providerType`、`model`、`requestedCapabilities`、Brief 标准和候选博主。
+- provider 响应解析支持标准 `evidenceCards` 和 OpenAI 风格 `choices[0].message.content` JSON。
+- 安全扫描阻止 `sk-...` 形式 token 写入包文件。
+- skill 文档和 runbook 明确 URL/token 只能由用户运行时填写,不能硬编码。
+
+验证:
+
+- `npm run acceptance:providers:mock`
+- `npm run acceptance:pm`
+- `npm run acceptance`
+
+## 0.1.10
+
+状态:已发布,provider 失败状态加固版本。
+
+主要变化:
+
+- `referenceEvidenceStatus` 增加结构化 provider 状态字段:`providerRequested`、`providerStatus`、`providerHttpStatus`、`providerWarning`。
+- `evidenceStatus` 增加同样的状态字段。
+- reference provider 区分 `not_requested`、`missing_base_url`、`network_error`、`unauthorized_or_quota`、`http_error`、`empty_result`、`parse_error`、`ok`。
+- evidence provider 区分 `not_requested`、`network_error`、`unauthorized_or_quota`、`http_error`、`empty_result`、`parse_error`、`ok`。
+- mock 验收加入 reference 403 和 empty evidence response,确保提号流程不中断且 warning 可审计。
+
+验证:
+
+- `npm run acceptance:providers:mock`
+- `npm run acceptance:pm`
+- `npm run acceptance`
+
+## 0.1.9
+
+状态:已发布,provider 合同加固版本。
+
+主要变化:
+
+- TikHub 兼容参考补证和多模态 evidence provider 请求增加 `contractVersion: "tihao-provider-v1"`。
+- reference provider 请求带上品牌、品类、平台、关键词和参考信号。
+- 多模态 evidence provider 请求带上文本、ASR、视觉、帧图证据和风险复核能力。
+- 支持通过运行时 token 为 evidence provider 鉴权。
+- `acceptance:providers:mock` 会验证入站请求体、候选身份、参考锚点和可选 Authorization。
+
+验证:
+
+- `npm run acceptance:providers:mock`
+- `npm run acceptance:pm`
+- `npm run acceptance`
+
+## 0.1.8 及更早版本摘要
+
+已发布版本主要完成:
+
+- 校准闭环报告:把下一轮问题、复核建议和结构化 next actions 固化在客户可用输出中。
+- CLI 反馈和剔除项报告:支持保存商务反馈,报告中说明剔除/降级原因。
+- 提号规则与记忆循环加固:更明确地区分首轮假设、硬排除规则和偏好记忆。
+- 文档和 PM gate 加固:把真实 provider runbook、roadmap、README 链接和“未跑真实 gate 不得宣称 live 完成”的措辞纳入验收。
+- 参考证据路线:增加 `referenceEnrichmentBaseUrl` / `tikhubToken` provider 接口、`evidenceCards` / `evidenceCardsPath`、`multimodalEvidenceBaseUrl` 和对应验收。
+- workspace 安装:支持将 Claude Code plugin、skill 和 `.mcp.json` 写入客户工作区。
+- 首个 npm 发布版本曾使用 `@gangvy/tihao-sop`,后续迁移到 `@vocmarket/tihao-sop`。
+
+长期边界:
+
+- 没有真实 provider gate 前,只能称为接口合同或 mock 验证。
+- 没有客户有效 token/company 前,不能宣称客户账号 live 提号已通过。
+- 没有真实帧图/ASR/视频资源前,不能把模型风格总结写成已验证的视频证据。
+
+## 常用验证命令
+
+```powershell
+npm run acceptance
+npm run acceptance:providers:mock
+npm run acceptance:pm
+npm run live:acceptance
+npm run acceptance:video-ab
+```
+
+`live:acceptance` 需要运行时提供:
+
+```powershell
+$env:TIHAO_SESSION_TOKEN="<Parse sessionToken>"
+$env:TIHAO_COMPANY="<Company objectId>"
+```
+
+`acceptance:video-ab` 还需要运行时提供视频分析 URL、模型和 token。所有 token 都不能写入文件。

+ 19 - 0
claude-code-tihao-sourcing/docs/sk/讯飞env.md

@@ -0,0 +1,19 @@
+# 讯飞 / 豆包环境变量模板
+
+真实凭证不能保存到仓库、npm 包、报告、CSV、JSON、日志或聊天记录中。
+
+如需做真实 provider 测试,请使用本地 `.env.local` 或系统环境变量。示例只写占位说明,不写任何真实值:
+
+```text
+XFYUN_APPID=<填入运行时讯飞 app id>
+XFYUN_SECRET_KEY=<填入运行时讯飞 secret>
+DOUBAO_API_KEY=<填入运行时豆包 key>
+```
+
+运行 provider 验证:
+
+```powershell
+npm run acceptance:providers
+```
+
+如果使用豆包 OpenAI 兼容视频分析网关,请在运行时传入 URL、模型和 token,不要写进文件。

+ 72 - 0
claude-code-tihao-sourcing/docs/skill-database-record.json

@@ -0,0 +1,72 @@
+{
+  "table": "skills",
+  "updatedAt": "2026-06-17",
+  "records": [
+    {
+      "skillName": "tihao",
+      "displayName": "提号",
+      "status": "published",
+      "npmPackage": "@vocmarket/tihao",
+      "npmVersion": "1.0.10",
+      "npmUrl": "https://www.npmjs.com/package/@vocmarket/tihao",
+      "tarballUrl": "https://registry.npmjs.org/@vocmarket/tihao/-/tihao-1.0.10.tgz",
+      "installCommand": "npx --yes @vocmarket/tihao@latest workspace --smoke",
+      "globalInstallCommand": "npm install -g @vocmarket/tihao && tihao install --smoke",
+      "workspaceSkillPath": ".claude/skills/tihao/SKILL.md",
+      "globalSkillPath": "%USERPROFILE%/.claude/skills/tihao/SKILL.md",
+      "pluginName": "tihao",
+      "workspacePluginPath": ".claude/plugins/tihao",
+      "mcpServerName": "tihao",
+      "entrySkill": "tihao",
+      "legacyNpmPackages": [
+        "@vocmarket/tihao-sop"
+      ],
+      "legacyCliNames": [
+        "tihao-sourcing"
+      ],
+      "legacyPluginNames": [
+        "tihao-sourcing"
+      ],
+      "legacySkillNames": [
+        "tihao-creator-sourcing"
+      ],
+      "description": "Claude Code skill for Tihao brief-to-creator sourcing: parse client briefs, default to a live result-first high-quality first report, rank candidates, and export business-ready blogger lists; sample mode is demo-only.",
+      "recommendedUserPrompts": [
+        "读取这个 brief,给我一版可直接发客户的高质量博主名单(默认 live + resultFirstMode)。",
+        "用 live 小规模检索出博主名单。",
+        "用 Fmode 帮我识别这张截图,提取文字、主体和风险点。",
+        "客户说不要硬广、偏素人感,记到下次。"
+      ],
+      "mcpTools": [
+        "fmode_image_analysis",
+        "tihao_brief_sourcing_run",
+        "tihao_token_check",
+        "tihao_preference_update"
+      ]
+    },
+    {
+      "skillName": "fmode-image-analysis",
+      "displayName": "Fmode 图片分析",
+      "status": "local",
+      "npmPackage": "@vocmarket/tihao",
+      "npmVersion": "1.0.10",
+      "installCommand": "npx --yes @vocmarket/tihao@latest workspace --smoke",
+      "globalInstallCommand": "npm install -g @vocmarket/tihao && tihao install --smoke",
+      "workspaceSkillPath": ".claude/skills/fmode-image-analysis/SKILL.md",
+      "globalSkillPath": "%USERPROFILE%/.claude/skills/fmode-image-analysis/SKILL.md",
+      "pluginName": "tihao",
+      "workspacePluginPath": ".claude/plugins/tihao",
+      "mcpServerName": "tihao",
+      "entrySkill": "fmode-image-analysis",
+      "description": "Claude Code skill for image, screenshot, product photo, UI capture, chart, poster, and OCR-like image analysis through Fmode Doubao vision using the platform user token.",
+      "recommendedUserPrompts": [
+        "用 Fmode 分析这张截图,提取文字、主体和风险点。",
+        "DeepSeek 看不了图,帮我用 doubao-seed-2-0-pro-260215 识别这张图片。",
+        "把这张产品图里的卖点和合规风险整理出来。"
+      ],
+      "mcpTools": [
+        "fmode_image_analysis"
+      ]
+    }
+  ]
+}

+ 59 - 0
claude-code-tihao-sourcing/docs/software-client-table-format.md

@@ -0,0 +1,59 @@
+# 软件端交付表格格式
+
+本文档用于约束 `@vocmarket/tihao` 提号技能包导出的商务交付表。导出的 `software-client-list.final.csv` 可以直接给商务复核,也可以接入软件端展示。
+
+## 固定表头
+
+| 字段 | 说明 | 示例 |
+| --- | --- | --- |
+| brief编号 | 当前客户 Brief 或项目编号;没有编号时用品牌/项目名兜底。 | 敏感肌修护 |
+| 策略 | 产出该候选的提号策略。 | video-enhanced |
+| 排名 | 去重后的连续排名,每个 brief 从 1 开始。 | 1 |
+| 平台 | 中文平台名。 | 小红书 |
+| 博主名称 | 账号展示名称。 | 敏感肌成分研究所 |
+| 综合分 | 最终排序分。 | 86 |
+| brief匹配分 | 品类、关键词、人群、场景和硬性要求匹配分。 | 82 |
+| 参考风格分 | 与参考账号或参考视频风格的相似度得分。 | 76 |
+| 主页证据分 | 主页近期内容与 Brief 的匹配得分。 | 80 |
+| 视觉质感分 | 封面、画面、排版、场景质感得分。 | 78 |
+| 调性一致分 | 近期内容调性与 Brief/参考账号的一致性得分。 | 81 |
+| 证据加分 | 多模态或真实 provider 证据带来的加分。 | 4 |
+| 证据风险扣分 | 证据里发现的竞品、调性跑偏、内容不符等扣分。 | 0 |
+| 推荐理由 | 可直接给商务解释的推荐原因。 | Brief 命中敏感肌和屏障修护,主页近作集中在成分测评。 |
+| 风险提示 | 需要商务或人工复核的具体事项。 | 需确认报价有效期和近 30 天竞品合作。 |
+| 主页链接 | 博主主页链接;缺失时必须在风险提示里说明。 | https://www.xiaohongshu.com/user/profile/... |
+| 人工复核标签 | 由商务复核后填写。 | 可直接发客户 |
+
+## 去重规则
+
+- 软件端交付表按“全局博主唯一”去重,同一 `平台 + 规范化主页链接` 不得重复。
+- 没有主页链接时,同一 `平台 + 规范化博主名称` 不得重复。
+- 同一博主如果被多个 brief 或多个策略召回,只保留证据更完整、综合分更高的一条进入交付表,其他来源只进入摘要的重复样例。
+- URL 规范化会统一 http/https、小红书移动端/桌面端域名、去掉 query、fragment 和尾斜杠。
+- 博主名称规范化会去掉空格、常见括号补充信息和符号,用于识别“账号A”和“账号A(备孕中)”这类展示重复。
+- 如果重复账号同时出现多条,保留证据更完整、综合分更高的一条。
+- 已剔除账号不得进入软件端交付表,只能在报告里的“剔除/降级原因”展示。
+- 去重和剔除后,每个 brief 内排名必须从 1 开始连续编号。
+
+## 人工复核标签建议
+
+| 标签 | 使用场景 |
+| --- | --- |
+| 可直接发客户 | 硬性条件、调性、主页证据都通过。 |
+| 商务复核 | 基本可用,但需要确认报价、档期、主页近作或合作状态。 |
+| 跑偏 | 内容方向、人设或调性明显偏离 Brief。 |
+| 硬性规则违约 | 平台、粉丝、预算、地域、禁投项等明确不符合。 |
+| 调性不符 | 数据可用,但风格不像参考账号或不适合品牌。 |
+| 主页质感不符 | 封面、画面、排版、场景明显低质或下沉。 |
+| 参考账号不像 | 参考账号链路下召回,但类型或调性相似度不足。 |
+| 可投但需补证 | 候选有潜力,但需要补视频、主页近期内容或人工确认。 |
+
+## 验收标准
+
+- CSV 使用 UTF-8 BOM,Excel 打开中文不乱码。
+- 表头与本文档完全一致。
+- 软件端表内重复键数量为 0。
+- 每个 brief 内排名连续。
+- 推荐理由能解释 Brief 匹配、参考风格或主页证据。
+- 风险提示能说明具体待复核事项,不写空泛提醒。
+- 摘要文件必须输出重复审计结果:同 brief URL、同 brief 名称、全局 URL、全局平台+名称四类重复组,其中全局重复组必须为 0。

+ 39 - 0
claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-business-execution-field-recheck-rows.md

@@ -0,0 +1,39 @@
+# 2026-06-08 商务执行总表字段负责人和复验命令沉淀
+
+## 本轮目标
+
+把最新提号表单里的字段级补齐负责人和补齐后复验命令同步展开到商务补证执行总表,避免商务或下一轮 AI 只看到文件入口,却看不到谁补、补什么、补完后跑什么命令。
+
+## 已完成
+
+- `scripts/business-execution-index.js` 已把 `intake-field-checklist-summary.json` 中的 `ownerGroups` 展开为 `字段补齐负责人` 行。
+- `scripts/business-execution-index.js` 已把 `recheckCommands` 展开为 `补齐后复验命令` 行。
+- 执行总表每一行都会统一补齐证明边界;如果上游只写“不得宣称效果”,生成侧会补上“不证明客户效果”。
+- `scripts/round-deposition-audit.js` 已加入守卫:商务执行总表必须包含 `字段补齐负责人` 和 `补齐后复验命令` 两类行。
+- `scripts/round-deposition-smoke.js` 已加入对应 fixture 和断言,避免后续改动把这两类行删掉。
+
+## 已验证命令
+
+```powershell
+node --check scripts\business-execution-index.js
+node --check scripts\business-execution-index-smoke.js
+npm.cmd run business-execution:index:smoke
+node --check scripts\round-deposition-audit.js
+node --check scripts\round-deposition-smoke.js
+npm.cmd run round:deposition:smoke
+npm.cmd run round:refresh -- --output-root outputs --strict
+```
+
+## 当前真实状态
+
+- `outputs/business-execution-index-latest/business-execution-index.md` 已包含 `字段补齐负责人` 3 行。
+- `outputs/business-execution-index-latest/business-execution-index.md` 已包含 `补齐后复验命令` 5 行。
+- `outputs/business-execution-index-latest/business-execution-index-summary.json` 当前 `rowCount=22`、`fieldOwnerRowCount=3`、`recheckCommandRowCount=5`。
+- `outputs/round-deposition-latest/round-deposition-summary.json` 当前 `passCount=74`、`failCount=0`。
+
+## 仍未完成的业务证明
+
+- `proof-gap-closure.openCount=5`,真实证明缺口仍未关闭。
+- `optimization-status.complete=false`,不能宣称长期优化目标完成。
+- 仍缺真实历史 Brief、真实视频资源、真实视频 A/B 证明、live/provider 长跑证明和客户效果审计。
+- 本轮只提升商务执行分派和复验入口,不证明命中率或客户选中率提升。

+ 26 - 0
claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-business-fill-guide.md

@@ -0,0 +1,26 @@
+# 2026-06-08 最新提号表单索引加入商务填表执行视图
+
+## 本轮修正
+
+- `latest-form:index` 新增 `businessFillGuide`,把字段补齐负责人分组转换为商务可直接使用的填表执行视图。
+- 最新表单索引 Markdown 新增“商务填表执行视图”,按 history、video、review 展示填表文件、优先 FIELD、问题数、填表动作、补齐后复验命令、验收标准和证明边界。
+- `latest-form-index:smoke` 新增对 `businessFillGuide` 的结构、复验命令和“不证明客户效果”边界校验。
+- `round-deposition` 新增检查项 `latest-form-index-business-fill-guide`,防止最新提号表单索引退化为只给文件路径、不告诉商务如何填和如何复验。
+
+## 已验证
+
+- `node --check scripts\latest-form-index.js` 通过。
+- `node --check scripts\latest-form-index-smoke.js` 通过。
+- `node --check scripts\round-deposition-audit.js` 通过。
+- `node --check scripts\round-deposition-smoke.js` 通过。
+- `npm.cmd run latest-form:index:smoke` 通过。
+- `npm.cmd run round:deposition:smoke` 通过,`passCount=76`,`failCount=0`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,真实 outputs 的 `round-deposition.passCount=76`,`failCount=0`。
+- `npm.cmd run hygiene:smoke` 通过,`hitCount=0`。
+- `npm.cmd run acceptance` 通过。
+
+## 当前边界
+
+- 本轮提升的是商务填表和复验的可执行性,不证明客户命中率、客户选中率或视频分析提升提号率。
+- 当前 `proof-gap-closure.openCount=5`,`optimization-status.complete=false`。
+- 最新入口仍是 `outputs/latest-form-index-latest/latest-form-index.md`;其中“商务填表执行视图”只能用于补齐真实材料和安排复验。

+ 40 - 0
claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-field-checklist-index.md

@@ -0,0 +1,40 @@
+# 2026-06-08 字段级补齐核对表纳入最新表单索引
+
+## 问题
+
+- `intake-field-checklist-latest/intake-field-checklist.md` 已经能按行号和字段列出真实材料缺口。
+- 但商务最常打开的是 `latest-form-index-latest/latest-form-index.md`,之前该入口只展示补证表单、候选名单、负责人行动文件和修复清单,字段级核对表不在“当前可用文件”里。
+- 这会让商务知道“要补历史数据/视频资源”,但还要额外找目录才能知道具体补哪一行、哪一列。
+
+## 本轮修正
+
+- 更新 `scripts/latest-form-index.js`:
+  - 新增 `intakeFieldChecklist` 摘要字段。
+  - 读取 `outputs/intake-field-checklist-latest/intake-field-checklist-summary.json`。
+  - 在最新表单索引的“当前可用文件”中新增“真实材料字段级补齐核对表”。
+  - 展示当前 `itemCount`、`missingRequiredCount` 和 `placeholderCount`,并明确它不等同于客户效果证明。
+- 更新 `scripts/latest-form-index-smoke.js`:
+  - 增加 summary 和 Markdown 报告断言,确保 `intake-field-checklist.md` 与 `intake-field-checklist.csv` 不会从最新入口中丢失。
+
+## 已验证
+
+- `node --check scripts\latest-form-index.js` 通过。
+- `node --check scripts\latest-form-index-smoke.js` 通过。
+- `npm.cmd run latest-form:index:smoke` 通过。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过:
+  - `latest-form-index-latest/latest-form-index.md` 已展示 `outputs/intake-field-checklist-latest/intake-field-checklist.md`。
+  - 字段级核对表当前显示 `itemCount=25`、`missingRequiredCount=8`、`placeholderCount=17`。
+  - `round-deposition.passCount=69`。
+  - `round-deposition.failCount=0`。
+  - `optimization-status.complete=false`。
+  - `proof-gap-closure.openCount=5`。
+- `npm.cmd run acceptance` 通过。
+- `npm.cmd run hygiene:smoke` 通过:`scannedFiles=133`,`hitCount=0`。
+
+## 当前边界
+
+- 本轮提升的是商务补料入口的精确度,不证明真实客户命中率、客户选中率或人工补号减少。
+- 当前字段级核对表仍显示缺必填和占位内容,说明真实材料仍未补齐。
+- 当前仍缺真实历史 Brief、真实候选视频资源、真实视频 A/B live proof、真实 live/provider 长跑证明和客户效果审计材料。
+- 当前 `optimization-status.complete=false`,`proof-gap-closure.openCount=5`,长期优化目标仍不能标记完成。
+

+ 26 - 0
claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-handoff-business-fill-guide.md

@@ -0,0 +1,26 @@
+# 2026-06-08 交接摘要承接商务填表执行视图
+
+## 本轮修正
+
+- `handoff:summary` 已从最新提号表单索引读取 `businessFillGuide`,并写入 `optimization-handoff-summary.json` 的 `latestFormIndex.businessFillGuide`。
+- `optimization-handoff-report.md` 的“最新提号表单索引”段落新增“商务填表执行视图”,展示 history、video、review 三类填表任务的文件、优先 FIELD、复验命令、验收标准和证明边界。
+- `handoff:summary:smoke` 新增对 `businessFillGuide` 的结构、真实模板文件、视频资源复验命令和证明边界断言。
+- `round-deposition` 新增检查项 `handoff-latest-form-business-fill-guide`,确保下一轮 AI 从交接摘要即可看到商务填表顺序,不需要只靠人工打开 latest-form-index。
+
+## 已验证
+
+- `node --check scripts\generate-optimization-handoff.js` 通过。
+- `node --check scripts\generate-optimization-handoff-smoke.js` 通过。
+- `node --check scripts\round-deposition-audit.js` 通过。
+- `node --check scripts\round-deposition-smoke.js` 通过。
+- `npm.cmd run handoff:summary:smoke` 通过。
+- `npm.cmd run round:deposition:smoke` 通过,`passCount=77`,`failCount=0`。
+- `npm.cmd run hygiene:smoke` 通过,`hitCount=0`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,真实 outputs 的 `round-deposition.passCount=77`,`failCount=0`。
+- `npm.cmd run acceptance` 通过。
+
+## 当前边界
+
+- 本轮提升的是交接摘要对商务填表流程的承接能力,不证明客户命中率、客户选中率或视频分析提升提号率。
+- 当前 `proof-gap-closure.openCount=5`,`optimization-status.complete=false`。
+- 最新交接入口:`outputs/optimization-handoff-latest/optimization-handoff-report.md`。

+ 37 - 0
claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-handoff-zh-sop-entry.md

@@ -0,0 +1,37 @@
+# 提号体验优化实现日志:交接摘要中文 SOP 入口
+
+- 日期:2026-06-08
+- 范围:长期优化交接摘要、中文 SOP 可读入口、smoke 验收
+- 结论:本轮让下一轮 AI 和商务从交接摘要中优先看到中文 SOP 入口,但不证明客户效果完成。
+
+## 本轮完成
+
+1. `optimization-handoff` 摘要新增 `supportDocs`。
+   - 暴露 `docs/tihao-experience-optimization-plan.zh-CN.md`。
+   - 暴露 `docs/business-proof-action-order.zh-CN.md`。
+
+2. `optimization-handoff-report.md` 新增 `中文 SOP 入口` 小节。
+   - 用表格列出文档、路径和用途。
+   - 补证操作顺序优先指向 `business-proof-action-order.zh-CN.md`。
+
+3. `generate-optimization-handoff-smoke.js` 增加中文入口断言。
+   - 防止后续交接摘要退回只指向旧文档。
+   - 继续检查报告不包含 sessionToken、鉴权头、模型 token 或 npm token。
+
+## 已验证命令
+
+```powershell
+node --check scripts\generate-optimization-handoff.js
+node --check scripts\generate-optimization-handoff-smoke.js
+npm.cmd run handoff:summary:smoke
+npm.cmd run round:refresh -- --output-root outputs --strict
+npm.cmd run acceptance
+npm.cmd run hygiene:smoke
+```
+
+## 当前边界
+
+- `optimization-status.complete=false`。
+- `proof-gap-closure.openCount=5`。
+- 本轮只提升交接可读性和执行入口稳定性,不证明视频分析提号率提升,也不证明客户效果完成。
+

+ 44 - 0
claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-homepage-repair-index.md

@@ -0,0 +1,44 @@
+# 2026-06-08 主页近期内容证据修复清单纳入商务统一入口
+
+## 问题
+
+- 上一轮已经生成了 `homepage-evidence-readiness-latest/homepage-evidence-repair-actions.csv`,可以指出主页近期内容证据缺口。
+- 但商务实际会优先打开 `latest-form-index-latest/latest-form-index.md` 和负责人行动文件;如果主页修复清单没有进入这些统一入口,商务/投放和技术/AI 仍可能只看到 intake/video 修复项,漏掉主页近 10/20 条内容证据、封面、标题正文、发布时间、互动字段和风险复核。
+- 这会影响下一轮 AI 和商务按同一张表补证,但不能把该可见性优化理解为客户命中率已经提升。
+
+## 本轮修正
+
+- 更新 `scripts/latest-form-index.js`:`repairActions` 新增 `homepageEvidence`,最新提号表单索引展示 `homepage-evidence-repair-actions.csv`,负责人行动文件保留主页修复附件。
+- 更新 `scripts/business-proof-next-actions.js`:读取 `homepage-evidence-readiness-summary.json`,`ownerRepairArtifacts` 新增主页近期内容证据修复清单,`sourceState.homepageFailureCount` 纳入下一步行动摘要。
+- 更新 `scripts/generate-optimization-handoff.js`:handoff 的 latest form index 区块展示主页修复清单,fallback repair summary 也覆盖 homepage readiness。
+- 更新 `scripts/round-deposition-audit.js` 和 `scripts/round-deposition-smoke.js`:沉淀审计要求 latest form index、owner artifacts、handoff 都能看到主页修复 CSV。
+
+## 已验证
+
+- `node --check scripts\latest-form-index.js` 通过。
+- `node --check scripts\business-proof-next-actions.js` 通过。
+- `node --check scripts\generate-optimization-handoff.js` 通过。
+- `node --check scripts\round-deposition-audit.js` 通过。
+- `node --check scripts\round-deposition-smoke.js` 通过。
+- `npm.cmd run latest-form:index:smoke` 通过。
+- `npm.cmd run business-proof:next-actions:smoke` 通过。
+- `npm.cmd run handoff:summary:smoke` 通过。
+- `npm.cmd run round:deposition:smoke` 通过:`passCount=69`,`failCount=0`。
+- `npm.cmd run round:refresh:smoke` 通过:`roundPassCount=69`,`failCount=0`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过:
+  - `latest-form-index-latest/latest-form-index.md` 已展示 `homepage-evidence-repair-actions.csv`。
+  - `business-proof-next-actions-latest` 里 owner repair artifacts 已包含主页修复附件。
+  - `round-deposition.passCount=69`。
+  - `round-deposition.failCount=0`。
+  - `optimization-status.complete=false`。
+  - `proof-gap-closure.openCount=5`。
+- `npm.cmd run acceptance` 通过,包含 homepage readiness smoke、latest form index smoke、round refresh smoke、round deposition smoke、MCP、workspace install、npm pack 和 provider mock。
+- `npm.cmd run hygiene:smoke` 通过:`scannedFiles=129`,`hitCount=0`。
+
+## 当前边界
+
+- 本轮提升的是商务统一入口、负责人派工附件和下一轮 AI 交接的完整性,不证明真实客户命中率、客户选中率或人工补号减少。
+- 当前真实 outputs 里主页近期内容证据仍不足,`homepage-evidence-readiness-latest.ready=false`,`proofLevel=not_business_proof`,`failureCount=9`,`repairActionCount=9` 是正确状态。
+- 当前仍缺真实历史 Brief、真实视频资源、真实视频 A/B live proof、真实 live/provider 长跑证明和客户效果审计材料。
+- 当前 `optimization-status.complete=false`,`proof-gap-closure.openCount=5`,长期优化目标仍不能标记完成。
+

+ 57 - 0
claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-intake-field-checklist-owner-recheck.md

@@ -0,0 +1,57 @@
+# 提号体验优化实现日志:字段核对表负责人补齐和复验命令
+
+- 日期:2026-06-08
+- 范围:真实材料字段级补齐核对表、每轮沉淀审计、商务补证执行
+- 结论:本轮把字段级核对表从“只列缺字段”增强为“按负责人补齐 + 补齐后复验命令”的执行入口。
+
+## 本轮完成
+
+1. `intake-field-checklist` summary 新增 `ownerGroups`。
+   - 按负责人和区域聚合问题数、缺必填数、占位数和文件入口。
+   - 给出每组优先处理项、修复动作和验收标准。
+
+2. `intake-field-checklist` summary 新增 `recheckCommands`。
+   - 真实材料预审:`npm run intake:readiness`
+   - 视频资源就绪审计:`npm run video:resource-readiness`
+   - 刷新本轮交接:`npm run round:refresh`
+   - 客户效果审计:`npm run customer-effect:audit`
+   - 视频 A/B 验收:`npm run acceptance:video-ab`
+
+3. `intake-field-checklist.md` 新增两个中文区块。
+   - `按负责人补齐顺序`
+   - `补齐后复验命令`
+
+4. `round-deposition` 增加守卫。
+   - 字段核对表必须包含负责人分组。
+   - 字段核对表必须包含复验命令。
+   - 报告观测新增 `intakeFieldChecklist.ownerGroupCount` 和 `intakeFieldChecklist.recheckCommandCount`。
+
+## 已验证命令
+
+```powershell
+node --check scripts\intake-field-checklist.js
+node --check scripts\intake-field-checklist-smoke.js
+node --check scripts\round-deposition-audit.js
+node --check scripts\round-deposition-smoke.js
+npm.cmd run intake:field-checklist:smoke
+npm.cmd run round:deposition:smoke
+npm.cmd run round:refresh -- --output-root outputs --strict
+npm.cmd run acceptance
+npm.cmd run hygiene:smoke
+```
+
+## 当前结果
+
+- `intake-field-checklist.ownerGroups=3`
+- `intake-field-checklist.recheckCommands=5`
+- `round-deposition.passCount=72`
+- `round-deposition.failCount=0`
+- `proof-gap-closure.openCount=5`
+- `optimization-status.complete=false`
+
+## 边界
+
+- 本轮只提升商务补证执行效率和复验顺序清晰度。
+- 字段核对表、复验命令和 round refresh 都不证明客户效果。
+- 未关闭真实历史 Brief、真实视频资源、真实视频 A/B、live/provider 长跑和客户效果证明缺口前,不得宣称长期优化完成。
+

+ 29 - 0
claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-latest-form-business-execution-entry.md

@@ -0,0 +1,29 @@
+# 2026-06-08 最新提号表单索引加入商务执行总表入口
+
+## 本轮目标
+
+让商务打开最新提号表单索引时,不只看到补证表单、候选名单和字段核对表,也能在第一屏直接进入商务补证执行总表。
+
+## 已完成
+
+- `scripts/latest-form-index.js` 新增 `businessExecutionIndex` 摘要对象。
+- `outputs/latest-form-index-latest/latest-form-index.md` 的当前可用文件表新增 `商务补证执行总表` 入口。
+- `scripts/latest-form-index-smoke.js` 新增断言,要求 summary/report 都包含 `business-execution-index.md`、`business-execution-index.csv`、字段负责人行数和复验命令行数。
+- `scripts/round-deposition-audit.js` 新增 `latest-form-index-business-execution-index` 检查项。
+- `scripts/round-deposition-smoke.js` 新增对应 fixture、观测值和报告断言。
+
+## 已验证命令
+
+```powershell
+node --check scripts\latest-form-index.js
+node --check scripts\latest-form-index-smoke.js
+node --check scripts\round-deposition-audit.js
+node --check scripts\round-deposition-smoke.js
+npm.cmd run latest-form:index:smoke
+npm.cmd run round:deposition:smoke
+```
+
+## 当前边界
+
+- 本轮提升的是最新表单索引的可执行入口,不证明客户效果或命中率提升。
+- 商务执行总表仍是补证分派和复验工具;只有真实历史 Brief、真实视频、live/provider 长跑和客户效果审计全部通过后,才能关闭证明缺口。

+ 53 - 0
claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-latest-form-recheck-entry.md

@@ -0,0 +1,53 @@
+# 提号体验优化实现日志:最新表单索引补齐负责人和复验入口
+
+- 日期:2026-06-08
+- 范围:最新提号表单索引、优化交接摘要、商务补证执行入口
+- 结论:本轮把已经生成的字段级负责人分组和补齐后复验命令同步到最新表单索引与交接摘要,让商务和下一轮 AI 打开最新入口时能直接看到“谁补什么、补完跑什么”。
+
+## 本轮完成
+
+1. `latest-form-index` summary 新增字段核对表执行信息。
+   - `intakeFieldChecklist.ownerGroups`
+   - `intakeFieldChecklist.recheckCommands`
+   - 文件路径统一尽量输出为项目相对路径,便于软件端和文档引用。
+
+2. `latest-form-index.md` 新增两个中文区块。
+   - `字段补齐负责人顺序`
+   - `补齐后复验命令`
+
+3. `optimization-handoff` summary 和报告同步字段核对表执行信息。
+   - 交接摘要中的 `真实材料字段级补齐核对表` 区块展示负责人分组和复验命令。
+   - `最新提号表单索引` 区块展示 `ownerGroupCount` 和 `recheckCommandCount`。
+   - 当 latest-form-index 尚未刷新时,交接摘要会从 intake-field-checklist summary 回填关键数组,避免交接信息缺失。
+
+4. Smoke 增加守卫。
+   - `latest-form:index:smoke` 检查负责人分组、历史数据模板、`intake:readiness` 和 `acceptance:video-ab` 复验命令。
+   - `handoff:summary:smoke` 检查交接摘要同步暴露负责人分组、复验命令和最新索引计数。
+
+## 已验证命令
+
+```powershell
+node --check scripts\latest-form-index.js
+node --check scripts\latest-form-index-smoke.js
+node --check scripts\generate-optimization-handoff.js
+node --check scripts\generate-optimization-handoff-smoke.js
+npm.cmd run latest-form:index:smoke
+npm.cmd run handoff:summary:smoke
+npm.cmd run round:refresh -- --output-root outputs --strict
+```
+
+## 当前结果
+
+- `latest-form-index.passed=true`
+- `intakeFieldChecklist.ownerGroups=3`
+- `intakeFieldChecklist.recheckCommands=5`
+- `round-deposition.passCount=72`
+- `round-deposition.failCount=0`
+- `proof-gap-closure.openCount=5`
+- `optimization-status.complete=false`
+
+## 边界
+
+- 本轮只提升最新入口的执行清晰度和交接可读性。
+- 最新表单索引、字段核对表、复验命令和 round refresh 都不证明客户效果完成。
+- 未关闭真实历史 Brief、真实视频资源、真实视频 A/B、live/provider 长跑和客户效果证明缺口前,不得宣称长期优化完成。

+ 45 - 0
claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-readable-sop-entry.md

@@ -0,0 +1,45 @@
+# 2026-06-08 中文 SOP 执行入口修复
+
+## 问题
+
+- `docs/tihao-experience-optimization-plan.md` 和 `docs/business-proof-action-order.md` 在普通 Windows 读取下仍有历史编码乱码。
+- 虽然最新提号表单索引、商务执行总表和补证 CSV 已经可用,但长期优化计划和 15 步补证顺序如果不可读,会影响商务、投放和下一轮 AI 准确理解验收边界。
+- 该问题影响的是执行可读性和交接效率,不代表真实客户效果或命中率证明缺口已经关闭。
+
+## 本轮修正
+
+- 新增 `docs/tihao-experience-optimization-plan.zh-CN.md`:
+  - 用中文重写长期优化计划、核心判断、关键差距、策略矩阵、每轮输出、软件端表格验收、质量验收标准、业务效果标准和每轮沉淀要求。
+  - 明确 `customer-effect:audit` 是客户效果证明的唯一审计入口。
+  - 明确仍有 `ready_not_proven` 或 `blocked_by_external_data` 时,不得宣布长期优化完成。
+- 新增 `docs/business-proof-action-order.zh-CN.md`:
+  - 用中文表格重写 15 步补证顺序。
+  - 明确商务、投放、技术/AI 的输入材料、执行命令、通过标准和不通过处理。
+  - 明确长跑启动条件和长期完成判定。
+- 更新 `scripts/latest-form-index.js`:
+  - `summary.supportDocs` 新增两份中文 SOP 文档。
+  - `latest-form-index.md` 新增“中文 SOP 入口”区块,商务打开最新索引即可看到两份中文执行文档。
+- 更新 `scripts/latest-form-index-smoke.js`:
+  - 增加断言,确保中文 SOP 文档路径出现在 summary 和 Markdown 报告中。
+
+## 已验证
+
+- `node --check scripts\latest-form-index.js` 通过。
+- `node --check scripts\latest-form-index-smoke.js` 通过。
+- `npm.cmd run latest-form:index:smoke` 通过。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过:
+  - `latest-form-index-latest/latest-form-index.md` 已包含“中文 SOP 入口”。
+  - `supportDocs` 已指向 `docs/tihao-experience-optimization-plan.zh-CN.md` 和 `docs/business-proof-action-order.zh-CN.md`。
+  - `round-deposition.passCount=69`。
+  - `round-deposition.failCount=0`。
+  - `optimization-status.complete=false`。
+  - `proof-gap-closure.openCount=5`。
+- `npm.cmd run acceptance` 通过,npm pack 已包含两份 `.zh-CN.md` 文档。
+- `npm.cmd run hygiene:smoke` 通过:`scannedFiles=132`,`hitCount=0`。
+
+## 当前边界
+
+- 本轮修复的是中文 SOP 可读性和入口化,不证明真实客户命中率、客户选中率或人工补号减少。
+- 当前仍缺真实历史 Brief、真实候选视频资源、真实视频 A/B live proof、真实 live/provider 长跑证明和客户效果审计材料。
+- 当前 `optimization-status.complete=false`,`proof-gap-closure.openCount=5`,长期优化目标仍不能标记完成。
+

+ 43 - 0
claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-round-deposition-handoff-sop-guard.md

@@ -0,0 +1,43 @@
+# 提号体验优化实现日志:交接摘要中文 SOP 守卫
+
+- 日期:2026-06-08
+- 范围:每轮沉淀审计、交接摘要、中文 SOP 入口
+- 结论:本轮把 `optimization-handoff` 的中文 SOP 入口纳入 round-deposition 审计,防止交接摘要回退到旧入口。
+
+## 本轮完成
+
+1. `round-deposition-audit` 新增 `handoff-support-docs` 检查。
+   - 要求交接摘要包含 `docs/tihao-experience-optimization-plan.zh-CN.md`。
+   - 要求交接摘要包含 `docs/business-proof-action-order.zh-CN.md`。
+
+2. `round-deposition-report.md` 新增观测字段。
+   - `handoff.chinesePlanDoc`
+   - `handoff.chineseActionOrderDoc`
+
+3. `round-deposition-smoke` 增加 fixture 和断言。
+   - 确认 summary 中包含 `handoff-support-docs`。
+   - 确认报告中展示 handoff 中文 SOP 文档路径。
+
+## 已验证命令
+
+```powershell
+node --check scripts\round-deposition-audit.js
+node --check scripts\round-deposition-smoke.js
+npm.cmd run round:deposition:smoke
+npm.cmd run round:refresh -- --output-root outputs --strict
+npm.cmd run acceptance
+npm.cmd run hygiene:smoke
+```
+
+## 当前结果
+
+- `round-deposition.passCount=72`
+- `round-deposition.failCount=0`
+- `proof-gap-closure.openCount=5`
+- `optimization-status.complete=false`
+
+## 边界
+
+- 本轮只增强交接摘要中文 SOP 入口的审计守卫。
+- 未关闭真实历史 Brief、真实视频资源、真实视频 A/B、live/provider 长跑和客户效果证明缺口前,不得宣称长期优化完成。
+

+ 38 - 0
claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-round-deposition-index-guards.md

@@ -0,0 +1,38 @@
+# 提号体验优化实现日志:表单索引交接守卫
+
+- 日期:2026-06-08
+- 范围:最新提号表单索引、轮次沉淀审计、交接可用性
+- 结论:本轮增强了最新表单入口的交接守卫,但不证明客户效果完成。
+
+## 本轮完成
+
+1. 在轮次沉淀审计中新增 `latest-form-index-intake-field-checklist` 检查。
+   - 确认 `outputs/latest-form-index-latest/latest-form-index.md` 已明确暴露真实材料字段级补齐核对表。
+   - 确认商务、投放可以从最新表单入口继续定位缺必填字段、占位字段和对应补齐动作。
+
+2. 在轮次沉淀审计中新增 `latest-form-index-support-docs` 检查。
+   - 确认最新表单入口已暴露中文长期优化计划。
+   - 确认最新表单入口已暴露中文补证操作顺序。
+
+3. 更新 smoke fixture 和断言。
+   - `round-deposition.passCount` 从 69 增加到 71。
+   - `round-deposition.failCount` 保持为 0。
+
+## 已验证命令
+
+```powershell
+node --check scripts\round-deposition-audit.js
+node --check scripts\round-deposition-smoke.js
+npm.cmd run round:deposition:smoke
+npm.cmd run round:refresh -- --output-root outputs --strict
+npm.cmd run acceptance
+npm.cmd run hygiene:smoke
+```
+
+## 验收边界
+
+- 这次增强证明“最新表单入口可被交接和审计”,不证明提号命中率提升。
+- `optimization-status.complete` 仍应保持为 `false`。
+- 仍需真实历史 Brief、真实视频资源、真实视频 A/B、live/provider 长跑证明、商务复核和客户效果数据后,才能进入客户效果完成判断。
+- 不能把 sample、smoke、模板、provider fallback 或接口 200 当成业务证明。
+

+ 54 - 0
claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-round-deposition-latest-form-owner-recheck-guard.md

@@ -0,0 +1,54 @@
+# 提号体验优化实现日志:最新表单索引负责人和复验命令沉淀守卫
+
+- 日期:2026-06-08
+- 范围:每轮沉淀审计、最新提号表单索引、优化交接摘要
+- 结论:本轮把“最新表单索引必须带字段补齐负责人分组和补齐后复验命令”纳入 round-deposition 守卫,避免软件端入口后续退化成只展示文件路径。
+
+## 本轮完成
+
+1. `round-deposition-audit` 新增最新表单索引执行入口守卫。
+   - `latest-form-index-intake-owner-recheck`
+   - 检查 `latestFormIndex.intakeFieldChecklist.ownerGroups`
+   - 检查 `latestFormIndex.intakeFieldChecklist.recheckCommands`
+   - 要求包含 `intake:readiness`、`round:refresh`、`customer-effect:audit` 和 `acceptance:video-ab` 等复验链路。
+
+2. `round-deposition-audit` 新增交接摘要同步守卫。
+   - `handoff-latest-form-index-intake-owner-recheck`
+   - 检查 `handoff.latestFormIndex.intakeFieldChecklist` 中的负责人分组数和复验命令数。
+   - 防止下一轮 AI 只看交接摘要时漏掉软件端补齐顺序。
+
+3. `round-deposition-report` 新增观测字段。
+   - `latestFormIndex.intakeFieldChecklistOwnerGroupCount`
+   - `latestFormIndex.intakeFieldChecklistRecheckCommandCount`
+   - `handoff.latestFormIndex.ownerGroupCount`
+   - `handoff.latestFormIndex.recheckCommandCount`
+
+4. `round-deposition-smoke` 更新样例和断言。
+   - 字段核对表、最新表单索引和交接摘要都覆盖负责人分组与复验命令。
+   - passCount 从 72 增加到 74。
+
+## 已验证命令
+
+```powershell
+node --check scripts\round-deposition-audit.js
+node --check scripts\round-deposition-smoke.js
+npm.cmd run round:deposition:smoke
+npm.cmd run round:refresh -- --output-root outputs --strict
+```
+
+## 当前结果
+
+- `round-deposition.passCount=74`
+- `round-deposition.failCount=0`
+- `latestFormIndex.intakeFieldChecklistOwnerGroupCount=3`
+- `latestFormIndex.intakeFieldChecklistRecheckCommandCount=5`
+- `handoff.latestFormIndex.ownerGroupCount=3`
+- `handoff.latestFormIndex.recheckCommandCount=5`
+- `proof-gap-closure.openCount=5`
+- `optimization-status.complete=false`
+
+## 边界
+
+- 本轮只增强每轮沉淀审计和入口防回退能力。
+- 最新表单索引、交接摘要和 round-deposition 通过不证明客户效果完成。
+- 未补齐真实历史 Brief、真实候选视频、真实视频 A/B live proof、live/provider 长跑和客户效果审计前,不得宣称长期优化完成。

+ 40 - 0
claude-code-tihao-sourcing/docs/tihao-experience-implementation-log-20260608-round-deposition-zh-action-order-default.md

@@ -0,0 +1,40 @@
+# 提号体验优化实现日志:轮次沉淀默认中文补证顺序
+
+- 日期:2026-06-08
+- 范围:每轮沉淀审计、补证操作顺序文档入口、中文交接可用性
+- 结论:本轮把 `round-deposition` 的默认补证操作顺序文档切换为中文执行版,旧文档仅作为 fallback。
+
+## 本轮完成
+
+1. `round-deposition-audit` 默认优先读取 `docs/business-proof-action-order.zh-CN.md`。
+   - 未找到中文执行版时,才回退到 `docs/business-proof-action-order.md`。
+   - `round-deposition-report.md` 的来源和检查项现在显示中文执行版路径。
+
+2. `round-deposition-smoke` 新增默认路径断言。
+   - 不传 `--proof-action-order` 时,也必须优先使用中文执行版。
+   - 显式传入测试文档的原有 smoke 仍保留,确保外部测试场景不受影响。
+
+## 已验证命令
+
+```powershell
+node --check scripts\round-deposition-audit.js
+node --check scripts\round-deposition-smoke.js
+npm.cmd run round:deposition:smoke
+npm.cmd run round:refresh -- --output-root outputs --strict
+npm.cmd run acceptance
+npm.cmd run hygiene:smoke
+```
+
+## 当前结果
+
+- `round-deposition.passCount=72`
+- `round-deposition.failCount=0`
+- `round-deposition.sources.proofActionOrder=docs/business-proof-action-order.zh-CN.md`
+- `proof-gap-closure.openCount=5`
+- `optimization-status.complete=false`
+
+## 边界
+
+- 本轮只修正默认文档入口,提升商务和下一轮 AI 的中文可读性。
+- 真实历史 Brief、真实视频资源、真实视频 A/B、live/provider 长跑和客户效果证明仍未关闭前,不得宣称长期优化完成。
+

+ 7972 - 0
claude-code-tihao-sourcing/docs/tihao-experience-implementation-log.md

@@ -0,0 +1,7972 @@
+# 提号经验优化执行进度
+
+## 2026-06-13 发布完成:1.0.2 主链路材料审计加固与 npm 验证同步
+
+### 本轮推进
+
+- 包版本同步到 `@vocmarket/tihao-sop@1.0.2`:`package.json`、`package-lock.json`、`skill-package-manifest.json`、`.claude-plugin/plugin.json` 和 MCP server version 已对齐。
+- `real-gap-material-audit` 已进入 `round:refresh` 主链路,当前刷新链路为 29 步,并在 `local-seed-to-intake-worklist` 后执行三缺口材料审计。
+- `latest-form:index`、`handoff:summary`、`evidence:index` 和 `round-deposition` 已同步接入 `realGapMaterialAudit`,接手方可以从 latest form、handoff 和沉淀审计中看到同一份材料审计边界。
+- 当前三缺口材料审计结果为 `gapCount=3`、`materialFoundCount=3`、`openGapCount=3`、`canCloseProofGap=false`;这说明项目材料已连接,不说明业务证明已完成。
+- `@vocmarket/tihao-sop@1.0.2` 已正式发布到 npm,并验证 `latest` 可通过 `npx` 在 fresh workspace 安装使用。
+
+### 已验证
+
+- `node --check scripts\refresh-round-artifacts.js`
+- `node --check scripts\evidence-index.js`
+- `node --check scripts\latest-form-index.js`
+- `node --check scripts\generate-optimization-handoff.js`
+- `node --check scripts\round-deposition-audit.js`
+- `node --check scripts\real-gap-material-audit.js`
+- `npm.cmd run real-gap:materials:smoke`
+- `npm.cmd run evidence:index:smoke`,当前 fixture `total=44`、`not_business_proof=19`、`real_evidence=6`
+- `npm.cmd run task:handoff-index:smoke`
+- `npm.cmd run round:refresh:smoke`,29 步全部 `pass`,`round-deposition.passCount=142`、`failCount=0`
+- `npm.cmd run round:refresh -- --output-root outputs --strict`,29 步全部 `pass`,真实 outputs 已刷新
+- `npm.cmd run package:contents:smoke`
+- `npm.cmd run acceptance`
+- `npm.cmd publish --dry-run --access public --registry https://registry.npmjs.org/`
+- `npm.cmd publish --access public --registry https://registry.npmjs.org/` 成功,npm 返回 `+ @vocmarket/tihao-sop@1.0.2`
+- `npm.cmd view @vocmarket/tihao-sop@1.0.2 version --registry https://registry.npmjs.org/` 返回 `1.0.2`
+- `npm.cmd view @vocmarket/tihao-sop dist-tags.latest version --registry https://registry.npmjs.org/` 返回 `latest=1.0.2`
+- `npx.cmd --yes @vocmarket/tihao-sop@latest workspace --smoke` 通过,fresh workspace 安装写入 `.claude/plugins/tihao-sourcing` 和 `.claude/skills/tihao-creator-sourcing`,并输出 `tihao smoke ok`。
+
+### 当前边界
+
+- 1.0.2 发布是技能包审计链路和 npm 交付状态收尾,不是长期业务优化目标完成状态。
+- 当前真实输出仍是 `proof-gap-closure.complete=false`、`businessOpenCount/openCount=3`、`closedCount=2`、`precheckOpenCount=1`。
+- 当前 `optimization-completion.complete=false`、`readyForClaim=false`、`proofGapOpenCount=3`;仍不能宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-13 发布完成:1.0 版本元数据、资料桥接与 npm 验证同步
+
+### 本轮推进
+
+- 包版本同步到 `@vocmarket/tihao-sop@1.0.0`:`package.json`、`package-lock.json`、`skill-package-manifest.json`、`.claude-plugin/plugin.json` 和 MCP server version 已对齐。
+- `docs/release-notes.md` 从“未发布版本”改为 `1.0.0` 正式版发布收尾记录,明确技能包 1.0 可以作为可安装、可验收、可继续补真实材料的交付版本推进,但不能替代业务效果证明。
+- `docs/tihao-handoff-index.md` 已补入 2026-06-13 当前结论:项目资料已经被接入 `proofGapMaterialBridge`,但三个真实业务证明缺口仍 open。
+- 最新资料桥接状态:`historical-dataset` 连接 `history-intake-draft.csv` 12 行;`video-ab-live-proof` 连接 `video-resource-worklist.csv`、4 条参考种子和 3 条候选视频种子;`manual-review-and-customer-effect` 连接 `manual-review-draft.csv` 12 行。
+- `@vocmarket/tihao-sop@1.0.0` 已正式发布到 npm,并验证 `latest` 可通过 `npx` 在 fresh workspace 安装使用。
+
+### 已验证
+
+- `npm.cmd run acceptance` 通过。
+- `npm.cmd publish --dry-run --access public` 通过,确认 tarball 为 `@vocmarket/tihao-sop@1.0.0`,共 176 个文件,package size 约 598.5 kB。
+- `npm.cmd publish --access public --registry https://registry.npmjs.org/` 成功,npm 返回 `+ @vocmarket/tihao-sop@1.0.0`。
+- `npm.cmd view @vocmarket/tihao-sop@1.0.0 version --registry https://registry.npmjs.org/` 返回 `1.0.0`。
+- `npm.cmd view @vocmarket/tihao-sop dist-tags.latest version --registry https://registry.npmjs.org/` 返回 `dist-tags.latest = '1.0.0'`、`version = '1.0.0'`。
+- `npx.cmd --yes @vocmarket/tihao-sop@latest workspace --smoke` 通过,fresh workspace 安装写入 `.claude/plugins/tihao-sourcing` 和 `.claude/skills/tihao-creator-sourcing`,并输出 `tihao smoke ok`。
+
+### 当前边界
+
+- 1.0 发布是技能包交付状态,不是长期业务优化目标完成状态。
+- 当前真实输出仍是 `proof-gap-closure.complete=false`、`businessOpenCount/openCount=3`、`closedCount=2`、`precheckOpenCount=1`。
+- 当前 `optimization-completion.complete=false`、`readyForClaim=false`、`proofGapOpenCount=3`;仍不能宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-12 推进优化:递归证明产物发现与具体失败条件沉淀
+
+### 本轮推进
+
+- `proof-gap:closure` 和 `optimization:completion` 已递归发现旧 `optimization-pipeline-latest` 内嵌的历史审计与客户效果审计产物,并在 summary 顶层 `sourceFiles.history`、`sourceFiles.customerEffect` 中记录来源。
+- `round-deposition` 新增 `proof-gap-closure-recursive-proof-artifact-discovery` 和 `optimization-completion-recursive-proof-artifact-discovery` 守卫,沉淀 closure/completion 两侧的 nested history/customer-effect 来源字段。
+- 旧 pipeline 产物现在会被读取为“找到但失败”的具体证据:历史 Brief 仍缺 `briefCount>=5`、`items.length>=5`、客户决策、拒绝原因和人工补号基线;customer-effect 仍缺 `overallPass=true`、客户选中率实测、参考链路和人工补号量证明。
+- 下游 smoke fixture 已从“缺文件”改为“有文件但不达标”,`real-proof-intake-bundle:smoke` 也改为校验具体证据/证明要求,避免后续又退回到粗粒度 `missing historical-dataset-audit.json` 或 `missing customer-effect-summary.json`。
+
+### 已验证
+
+- `node --check scripts\round-deposition-audit.js`
+- `node --check scripts\round-deposition-smoke.js`
+- `npm.cmd run round:deposition:smoke`,`passCount=137`、`failCount=0`
+- `npm.cmd run round:refresh -- --output-root outputs --strict`,28 步全部 `pass`,`round-deposition.passCount=137`、`failCount=0`
+- `npm.cmd run task:handoff-index:smoke`
+- `npm.cmd run hygiene:smoke`
+- `npm.cmd run proof-gap:intake-bundle:smoke`
+- `npm.cmd run round:refresh:smoke`
+- `npm.cmd run acceptance`
+
+### 当前边界
+
+- 当前真实输出仍是 `proof-gap-closure.complete=false`、`businessOpenCount/openCount=3`、`closedCount=2`、`precheckOpenCount=1`。
+- 当前 `optimization-completion.complete=false`、`readyForClaim=false`、`proofGapOpenCount=3`、`blockingReasonCount=8`、`missingProofRequirementCount=113`、`videoAbPreflightReadyForVideoAb=false`、`videoAbPreflightFailureCount=4`。
+- 本轮只是把“真实失败产物已找到且失败条件具体化”纳入审计和交接,不补真实历史 Brief、不生成真实视频 A/B live proof、不关闭客户效果证明;仍不能宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-12 推进优化:round-deposition 锁定派工链路 video A/B preflight 上下文
+### 本轮推进
+
+- `round-deposition` 新增并通过 3 个派工链路守卫:`proof-gap-operator-pack-video-ab-preflight-context`、`real-proof-intake-bundle-video-ab-preflight-context`、`real-proof-closure-work-order-video-ab-preflight-context`。
+- 这些守卫要求 `video-ab-live-proof` 在操作包、真实材料总包、真实证明闭环工单里同时保留 preflight 失败上下文:`preflight.readyForVideoAb=false`、`preflight.failureCount=4`、`preflight.generatedBy=acceptance:video-ab-preflight`、`runtimeCredentialPresent=false`、`companyResolved=false`、`vocSocialProviderTokenPresent=false`、`videoAnalysisTokenPresent=false`。
+- 同一批守卫还要求 live proof 缺失条件不能被 preflight 覆盖:`proofContext.mode=live`、`proofContext.collectionMode=live`、`proofContext.generatedBy=acceptance:video-ab`、runtime/VOC/video analysis provider requirements、`videoAbPreflight.readyForVideoAb=true` 与 `videoAbPreflight.company resolved for live run` 必须继续出现在下游派工材料里。
+- `round-deposition-smoke` 的 fixture 已补齐 operator pack、real proof intake bundle、real proof closure work order 三处 video A/B preflight 上下文,并新增 summary/report 断言,避免只靠单脚本 smoke 而每轮沉淀审计漏守。
+
+### 已验证
+
+- `node --check scripts\round-deposition-audit.js`
+- `node --check scripts\round-deposition-smoke.js`
+- `npm.cmd run round:deposition:smoke`,`passCount=137`、`failCount=0`
+- `npm.cmd run round:refresh -- --output-root outputs --strict`,28 步全部 `pass`,`round-deposition.passCount=137`、`failCount=0`
+- `npm.cmd run hygiene:smoke`
+- `npm.cmd run acceptance`
+
+### 当前边界
+
+- 当前真实输出仍是 `proof-gap-closure.complete=false`、`businessOpenCount/openCount=3`、`closedCount=2`、`precheckOpenCount=1`。
+- 当前 `optimization-completion.complete=false`、`readyForClaim=false`、`proofGapOpenCount=3`、`missingProofRequirementCount=113`、`videoAbPreflightReadyForVideoAb=false`、`videoAbPreflightFailureCount=4`。
+- 本轮只把下游派工材料纳入每轮沉淀守卫;preflight 仍是非业务证明上下文,不能关闭 `video-ab-live-proof`,也不能宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-12 推进优化:派工包锁定 video A/B preflight 上下文
+
+### 本轮推进
+
+- `proof-gap-operator-pack:smoke` 已新增 `video-ab-live-proof` 行级断言:当前 evidence 必须保留 `preflight.readyForVideoAb=false`、`preflight.failureCount=4`、`preflight.generatedBy=acceptance:video-ab-preflight`、`runtimeCredentialPresent=false`、`companyResolved=false`、`vocSocialProviderTokenPresent=false`、`videoAnalysisTokenPresent=false` 等非证明上下文。
+- 同一 smoke 也校验 `missingProofRequirements` 必须继续列出 live proof 要求:`proofContext.mode=live`、`proofContext.collectionMode=live`、`proofContext.generatedBy=acceptance:video-ab`、runtime/provider requirements 以及 `videoAbPreflight.readyForVideoAb=true`。preflight 仍不能关闭 `video-ab-live-proof`。
+- `proof-gap-operator-pack:smoke` 不再硬编码 3 个负责人附件;当前 `video-real-candidate` 已 closed,因此按 open owner 动态校验 `ownerArtifactCount=2`,同时继续在总表、CSV 和 `by-owner/tech-ai.md/csv` 中锁住 video A/B preflight 上下文。
+- `real-proof-intake-bundle:smoke` 已校验总包的 proof gap 行、CSV 导出行、Markdown/CSV 都保留 video A/B preflight 失败上下文和 live proof 缺失要求。
+- `real-proof-closure-work-order` 的总报告和负责人附件新增“缺失证明要求”列;`real-proof:work-order:smoke` 已校验 summary、CSV、总 Markdown 和 `by-owner/tech-ai.md` 都能看到 video A/B preflight 失败原因及 live proof 要求。
+
+### 已验证
+
+- `node --check scripts\proof-gap-operator-pack-smoke.js`
+- `node --check scripts\real-proof-intake-bundle-smoke.js`
+- `node --check scripts\real-proof-closure-work-order.js`
+- `node --check scripts\real-proof-closure-work-order-smoke.js`
+- `npm.cmd run proof-gap:operator-pack:smoke`
+- `npm.cmd run proof-gap:intake-bundle:smoke`
+- `npm.cmd run real-proof:work-order:smoke`
+- `npm.cmd run round:refresh -- --output-root outputs --strict`,28 步全部 `pass`,最新 `round-deposition.counts.pass=137`、`counts.fail=0`。
+- `npm.cmd run handoff:summary:smoke`
+- `npm.cmd run task:handoff-index:smoke`
+- `npm.cmd run hygiene:smoke`
+- `npm.cmd run acceptance`
+
+### 当前边界
+
+- 当前真实输出仍是 `proof-gap-closure.complete=false`、`businessOpenCount/openCount=3`、`closedCount=2`、`precheckOpenCount=1`。
+- 当前 `optimization-completion.complete=false`、`readyForClaim=false`、`proofGapOpenCount=3`、`missingProofRequirementCount=113`、`videoAbPreflightReadyForVideoAb=false`、`videoAbPreflightFailureCount=4`。
+- 本轮只增强派工包、真实材料总包和闭环工单的可执行性与防误报 smoke 覆盖;仍不能宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-12 推进优化:handoff.latestFormIndex 补齐 optimization completion 上下文
+
+### 本轮推进
+
+- `handoff:summary` 的 `latestFormIndex` 嵌套区块现在会透传 `optimizationCompletion`,不再只在 handoff 顶层和 businessExecutionIndex 子区块展示完成度审计。
+- `optimization-handoff-report.md` 的“最新提号表单索引”段新增“优化完成度审计”小节,显示 `readyForClaim=false`、`proofGapOpenCount=3`、`videoAbPreflightReadyForVideoAb=false`、`videoAbPreflightFailureCount=4` 等边界。
+- `task:handoff-index:smoke` 已新增防回退断言:校验当前 latest outputs、临时生成 latest-form、临时生成 handoff、business-execution 行都能看到 video A/B preflight 上下文,且仍保持 `directCustomerProof=false` 和禁止宣称边界。
+- `generate-optimization-handoff:smoke` 已同步断言 `summary.latestFormIndex.optimizationCompletion` 和报告小节,避免只修 task smoke 而漏掉 handoff 自身回归。
+
+### 已验证
+
+- `node --check scripts\generate-optimization-handoff.js`
+- `node --check scripts\generate-optimization-handoff-smoke.js`
+- `node --check scripts\task-handoff-index-smoke.js`
+- `npm.cmd run handoff:summary:smoke`
+- `npm.cmd run task:handoff-index:smoke`
+- `npm.cmd run round:refresh -- --output-root outputs --strict`,28 步全部 `pass`,`round-deposition.counts.pass=137`、`counts.fail=0`。
+
+### 当前边界
+
+- 当前真实输出仍是 `proof-gap-closure.complete=false`、`businessOpenCount/openCount=3`、`optimization-completion.readyForClaim=false`。
+- 本轮只让 handoff 的嵌套 latest-form 索引不丢 completion/preflight 上下文,不补真实历史 Brief、客户效果证明或真实视频 A/B live proof;仍不能宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-12 推进优化:video A/B preflight 失败上下文贯通 closure/completion
+
+### 本轮推进
+
+- `proof-gap:closure` 和 `optimization:completion` 已读取最新 `video-hit-rate-preflight-summary.json`,在真实 live `video-hit-rate-summary.json` 缺失或不可关闭时,把 runtime preflight 的失败状态写进 `video-ab-live-proof` 的 evidence 与 missing requirements。
+- preflight 上下文保持 `not_business_proof`:只记录运行条件布尔状态和失败数量,不记录 token 明文,不关闭 `video-ab-live-proof`,也不替代 `proofContext.mode=live`、`collectionMode=live`、`generatedBy=acceptance:video-ab` 的真实验收产物。
+- `latest-form:index`、`business-execution:index` 和 `handoff:summary` 已展示 `videoAbPreflightReadyForVideoAb`、`videoAbPreflightFailureCount`;当前真实输出为 `false/4`。
+- `round-deposition` 已新增 `proof-gap-closure-video-ab-preflight-context` 与 `optimization-completion-video-ab-preflight-context` 守卫,沉淀 standalone closure/completion 两边的 preflight 上下文。
+- 文档已同步:`docs/tihao-handoff-index.md` 记录该上下文只是 live proof 缺口解释,不是业务证明;当前 `optimization-completion.missingProofRequirementCount=113`。
+
+### 已验证
+
+- `node --check scripts\proof-gap-closure-audit.js`
+- `node --check scripts\optimization-completion-audit.js`
+- `node --check scripts\round-deposition-audit.js`
+- `node --check scripts\latest-form-index.js`
+- `node --check scripts\business-execution-index.js`
+- `node --check scripts\generate-optimization-handoff.js`
+- `npm.cmd run proof-gap:closure:smoke`
+- `npm.cmd run optimization:completion:smoke`
+- `npm.cmd run business-execution:index:smoke`
+- `npm.cmd run latest-form:index:smoke`
+- `npm.cmd run handoff:summary:smoke`
+- `npm.cmd run round:deposition:smoke`
+- `npm.cmd run task:handoff-index:smoke`
+- `npm.cmd run round:refresh -- --output-root outputs --strict`,28 步全部 `pass`,`round-deposition.counts.pass=137`、`counts.fail=0`。
+- `npm.cmd run hygiene:smoke`
+- `npm.cmd run acceptance`
+
+### 当前边界
+
+- 最新真实输出仍是 `proof-gap-closure.complete=false`、`businessOpenCount/openCount=3`、`closedCount=2`、`precheckOpenCount=1`。
+- 最新 `optimization-completion.complete=false`、`readyForClaim=false`、`proofGapOpenCount=3`、`blockingReasonCount=8`、`missingProofRequirementCount=113`、`videoAbPreflightReadyForVideoAb=false`、`videoAbPreflightFailureCount=4`。
+- 本轮只增强真实视频 A/B live proof 缺口的可解释性、交接可读性和防误闭合能力;不能宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-12 推进优化:history/proof-gap/pipeline 薄摘要防误闭合
+
+### 本轮推进
+
+- `proof-gap:closure` 和 `optimization:completion` 现在会二次校验 `historical-dataset-audit.json` 的细分门槛,不再只看 `acceptance.readyForCustomerEffectProof=true` 和 `briefCount>=5`。
+- 历史审计必须暴露可审计明细和覆盖字段:`parseOk`、`minBriefsMet`、`allHaveBriefText`、`allHaveManualFinalList`、`allHaveCustomerDecision`、`allHaveFeedbackReason`、`allHaveManualSupplementBaseline`、`categoryCoverageMet`、`items.length>=5`、`withCustomerDecision>=5`、`withRejectionReason>=1`、`withManualSupplementBaseline>=5`。
+- `optimization:completion` 不再接受只有 `complete=true` 的薄 `proof-gap-closure-summary.json`;必须同时满足 `closedCount>=5`、`businessGapCount>=5`,并带至少 5 条业务 proof-gap 行。
+- `optimization:completion` 不再接受只有总数的薄 `business-proof-progress-summary.json`;必须满足 `counts.pass>=15`,并带至少 15 条步骤行。
+- `optimization:pipeline` 现在只有在 proof-gap closure 可声明时才会输出 `readyForClaim=true`:`complete=true`、`openCount=0`、`closedCount>=5`、`businessGapCount>=5`。
+
+### 已验证
+
+- `node --check scripts\proof-gap-closure-audit.js`
+- `node --check scripts\optimization-completion-audit.js`
+- `node --check scripts\optimization-pipeline.js`
+- `node --check scripts\proof-gap-closure-smoke.js`
+- `node --check scripts\optimization-completion-smoke.js`
+- `node --check scripts\optimization-pipeline-smoke.js`
+- `npm.cmd run proof-gap:closure:smoke`
+- `npm.cmd run optimization:completion:smoke`
+- `npm.cmd run optimization:pipeline:smoke`
+- `npm.cmd run round:refresh -- --output-root outputs --strict`
+- `npm.cmd run acceptance`
+
+### 当前边界
+
+- 最新真实输出仍是 `proof-gap-closure.complete=false`、`proof-gap-closure.openCount=3`、`proof-gap-closure.closedCount=2`。
+- 最新 `optimization-completion` 仍是 `complete=false`、`readyForClaim=false`、`proofGapOpenCount=3`,且 `canClaim.*=false`。
+- 本轮只增强证明链和防误判能力,不证明提号率已提升、客户效果已达标或人工补号量已下降。
+
+## 2026-06-12 推进优化:customer-effect closure/completion 防薄摘要与旧 history 误闭合
+
+### 本轮推进
+
+- 继续按提号真实证明链推进:当前仍不能用 `dha_brief.xlsx`、PoC 候选名单、旧 pipeline、worklist 或 smoke 结果替代真实历史 Brief、真实视频 A/B live proof、商务复核和客户效果证明。
+- 收紧 `proof-gap:closure` 的 `manual-review-and-customer-effect` 门槛:不再只看 `customerEffect.acceptance.overallPass=true`,还要求 customer-effect 摘要包含 `reviewPath`、`historyAuditPath`、客户选中率、参考链路客户选中率、历史人工补号基线、本轮人工补号量、人工补号减少率,以及完整 acceptance 子门槛。
+- 收紧 `optimization:completion` 的 `customer-effect-proof` 门槛:完成度审计同样要求 customer-effect 绑定当前 `historical-dataset-audit.json`;如果 customer-effect 摘要绑定旧 history audit,或只写了薄的 `overallPass=true`,即使其他 gate 看似通过,也不能进入 `readyForClaim=true`。
+- `sourceFiles` 现在记录 `history` 和 `customerEffect` 产物路径,用于审计 customer-effect 是否绑定当前历史审计产物,防止旧 history/customer-effect 组合绕过当前 proof-gap closure。
+
+### 已验证
+
+- `node --check scripts\proof-gap-closure-audit.js` 通过。
+- `node --check scripts\optimization-completion-audit.js` 通过。
+- `node --check scripts\proof-gap-closure-smoke.js` 通过。
+- `node --check scripts\optimization-completion-smoke.js` 通过。
+- `npm.cmd run proof-gap:closure:smoke` 通过,新增薄 customer-effect 摘要和旧 history 绑定两个防回归用例。
+- `npm.cmd run optimization:completion:smoke` 通过,新增薄 customer-effect 摘要和旧 history 绑定两个防回归用例。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,28 步全为 `pass`。
+- `npm.cmd run acceptance` 通过。
+
+### 当前边界
+
+- 最新真实输出仍是 `proof-gap-closure.complete=false`、`proof-gap-closure.openCount=3`、`proof-gap-closure.closedCount=2`。
+- 最新 `optimization-completion.complete=false`、`optimization-completion.readyForClaim=false`、`proofGapOpenCount=3`,`canClaim.tihaoRateImproved=false`、`canClaim.customerEffectAchieved=false`、`canClaim.manualSupplementReduced=false`。
+- 可以说:证明链更严格,薄 customer-effect 摘要和旧 history 绑定不能误闭合客户效果证明。
+- 不能说:提号率已经提升、客户效果已经达标、人工补号量已经下降。
+
+## 2026-06-12 推进优化:evidence-index pipeline customer-effect 防误判
+
+### 本轮推进
+
+- 继续按提号需求主线推进:真实历史 Brief、真实视频 A/B live proof、商务复核和客户效果证明仍是完成条件;本轮没有把 `dha_brief.xlsx`、DHA PoC 推荐表、TikHub 参考分析版、旧 pipeline 或本地 worklist 升格为业务证明。
+- 复查本地可用材料后确认:`dha_brief.xlsx` 只有 1 个 DHA Brief、参考账号链接和通用规则;缺至少 5 个真实历史 Brief、客户选中/拒绝、拒绝原因、历史人工补号基线和本轮人工补号量,因此仍只能作为 seed,不能关闭 `historical-dataset` 或 `manual-review-and-customer-effect`。
+- 新增 `evidence:index` 防误判:当 `customer-effect-summary.json` 位于同一个输出目录的 `optimization-pipeline-summary.json` 下时,只有 pipeline 自身 `readyForClaim=true` 且 `proofGapClosure.complete=true/openCount=0`,该 customer-effect 才允许进入 `real_evidence`;否则即使 customer-effect 字段看似通过,也保持 `proofLevel=not_business_proof`,并在 summary 里写出 `pipelineBlocked` 原因。
+- `scripts/evidence-index-smoke.js` 新增回归用例:构造 `pipeline-claim-with-open-closure/customer-effect-audit/customer-effect-summary.json`,其中 customer-effect 自身门槛全为 true,但 pipeline `proofGapClosure.openCount=1`,断言该条仍为 `not_business_proof`,同时 standalone passing customer-effect 仍保持 `real_evidence`。
+
+### 已验证
+
+- `node --check scripts\evidence-index.js` 通过。
+- `node --check scripts\evidence-index-smoke.js` 通过。
+- `npm.cmd run evidence:index:smoke` 通过,新增 fixture 后 summary 为 `total=40`、`real_evidence=5`、`not_business_proof=16`、`smoke_or_local=19`。
+
+### 当前边界
+
+- 可以说:证据台账现在能区分 standalone customer-effect 和未完成 pipeline 内嵌 customer-effect,旧 pipeline/open proof-gap 不能绕过 completion 审计。
+- 不能说:提号率已经提升、客户效果已经达标、人工补号量已经下降。
+- 下一步仍必须补真实历史 Brief、客户选中/拒绝、拒绝原因、历史与本轮人工补号量,并跑真实视频 A/B live `proofContext`。
+
+## 2026-06-12 方向审视与 optimization-status live/provider 口径对齐
+
+### 本轮审视
+
+- 当前优化工作仍符合提号需求:核心仍是用真实历史 Brief、真实视频 A/B、商务复核和客户效果审计来证明提号效果,而不是用 smoke、模板、候选名单、worklist 或旧 A/B 产物替代业务证明。
+- 当前工程链路保持健康:`round:refresh --strict` 通过,28 步全部 `pass`;`acceptance` 通过;`proof-gap-closure.complete=false`、`openCount=3`、`closedCount=2`;`optimization-completion.readyForClaim=false`、`proofGapOpenCount=3`。
+- 当前真实业务证明仍未完成:`historical-dataset`、`video-ab-live-proof`、`manual-review-and-customer-effect` 仍 open;本地直接运行 `npm.cmd run acceptance:video-ab` 仍因缺 `TIHAO_SESSION_TOKEN` 等运行凭证停在前置检查,不能产出真实 live proof。
+- 本轮发现一个交接口径瑕疵:`proof-gap-closure` 已正确把 `live-provider-overnight-proof` 判为 closed,但 `optimization-status` 只扫描 `outputs` 目录末尾 80 个子目录,较旧但可关闭的 `outputs/overnight-quality-1780682025651/aggregate-summary.json` 可能被窗口挤出,导致 `optimization-handoff.externalBlockers` 仍误列 `live-provider-overnight-proof`。
+
+### 本轮修正
+
+- `scripts/optimization-status-audit.js` 不再对输出目录做 `slice(-80)` 窗口截断,改为按目录 mtime 排序后扫描全部可见输出目录。
+- `optimization-status` 的 live/provider 判断改为复用可关闭条件:`liveEnabled=true`、`overallPass=true`、`failureCount=0`、`failedGateCount=0`、`tokenLeak=false`,与 `proof-gap:closure` 的 closed 口径保持一致。
+- `live-provider-overnight-proof` 在 `optimization-status` 中现在为 `passed`,但说明边界改为“只证明真实 live/provider overnight 通过;不能替代客户效果、历史数据或视频 A/B proof”。
+- `scripts/optimization-status-smoke.js` 新增回归用例:构造超过 80 个输出目录,再放入一个较旧但可关闭的 live aggregate,断言 `findOutputEvidence + hasLiveProof` 仍能识别,防止该误判回归。
+
+### 已验证
+
+- `node --check scripts\optimization-status-audit.js` 通过。
+- `node --check scripts\optimization-status-smoke.js` 通过。
+- `npm.cmd run optimization:status:smoke` 通过。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,最新 `optimization-status.counts.passed=31`、`ready_not_proven=2`、`blocked_by_external_data=1`,`optimization-handoff.readyCapabilities=31`、`externalBlockers=1`。
+- `npm.cmd run acceptance` 通过。
+
+### 当前边界
+
+- 可以说:`live-provider-overnight-proof` 不再被交接摘要误列为外部阻塞;当前唯一 `optimization-handoff.externalBlockers` 是 `customer-effect-proof`,`readyNotProven` 是 `video-ab-proof` 和 `historical-dataset`。
+- 不能说:提号率已经提升、客户效果已经达标、人工补号量已经下降。
+- 下一步仍必须补真实历史 Brief、客户选择/拒绝记录、历史与本轮人工补号量,并配置真实视频 A/B 运行凭证产出 live `proofContext`。
+
+## 2026-06-11 证明链口径复核与防误判修正
+
+### 本轮审视
+
+- 当前优化没有偏离提号需求:优化重心仍是把真实历史 Brief、真实视频 A/B、商务复核和客户效果证明放进可审计闭环,而不是把候选名单、worklist、操作包、smoke 或旧 pipeline 结果包装成业务效果。
+- 当前到位的是证明链和交接链路的准确性:`proof-gap-closure.openCount=3`、`closedCount=2`,`video-real-candidate` 与 `live-provider-overnight-proof` 已关闭,`video-resource-readiness.readyForVideoAbPreflight=true`、`realCandidateRows=1`。
+- 当前未到位的是业务效果证明本身:`optimization-completion.complete=false`、`readyForClaim=false`、`proofGapOpenCount=3`、`missingProofRequirementCount=61`,仍不能宣称提号率提升、客户效果达标或人工补号量下降。
+- 本轮追加搜证确认:`dha_brief.xlsx`、DHA PoC 推荐表和 TikHub 参考分析版仍只能作为种子材料;当前统一 intake 模板仍有示例/占位,历史数据不足 5 个真实 Brief,也缺客户选择/拒绝记录、拒绝原因、历史人工补号基线和本轮人工补号量。
+- 本轮追加验证确认:旧 video A/B 产物虽出现 `acceptance.passed=true`,但 `proofContext.mode`、`proofContext.collectionMode` 和 `proofContext.generatedBy` 仍为 `unknown`;本地环境只发现 `ANTHROPIC_AUTH_TOKEN`,直接运行 `npm.cmd run acceptance:video-ab` 因缺 `TIHAO_SESSION_TOKEN` 等运行凭证失败,不能关闭真实视频 A/B live proof。
+
+### 本轮修正
+
+- `docs/tihao-handoff-index.md` 已同步最新事实:`proof-gap-request.unresolvedCount=3`,closed 缺口在请求包中保留为 `ready` 证据链,不再回到待补队列。
+- 交接入口明确 `longrun:readiness` 当前以 `proof-gap-closure.openCount=3` 为准,避免旧 request 数量或旧产物造成“已关闭缺口又被当作 open”的误判。
+- 交接入口明确旧 `optimization-pipeline-latest` 已被 `optimization-completion` 标记为 `stale=true`,必须在真实材料补齐后重跑;旧 pipeline 不能作为当前完成证据。
+- 交接入口新增旧 video A/B 边界:必须重跑 `npm run acceptance:video-ab`,并生成 `proofContext.generatedBy=acceptance:video-ab`、`proofContext.mode=live`、`proofContext.collectionMode=live` 的当前 live 产物,旧 A/B 结果不能替代。
+- `longrun:readiness` 现在默认读取 `outputs/live-preflight-latest/live-preflight-summary.json`,不再在 preflight 已存在时笼统报“缺 preflight summary”;当前最新 preflight 显示 `readyForLiveAcceptance=false`、`readyForVideoAb=false`、`failures=2`、`warnings=3`,真实缺口是缺 `sessionToken`、`company` 和视频/证据 provider。
+- `longrun:readiness` 已兼容当前 `video-resource-readiness-summary.json` 的顶层 `readyForVideoAbPreflight=true` 字段,避免真实视频资源已 ready 时被旧 summary 结构误判。
+
+### 已验证
+
+- `npm.cmd run task:handoff-index:smoke` 通过,交接入口仍对齐当前 3 个 open 缺口、2 个 closed 缺口和 2 份负责人附件。
+- `npm.cmd run plan:consistency:smoke` 通过,静态 5 类缺口全集与当前 3 条 open action 队列保持一致。
+- `npm.cmd run longrun:readiness:smoke` 通过;真实 `longrun:readiness` 刷新后为 `ready=false`、`failCount=9`,阻塞原因比之前更具体。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,28 步全部 `pass`,但这只代表工程产物刷新成功,不代表业务证明完成。
+- `npm.cmd run acceptance` 通过,包级 smoke、计划一致性、每轮刷新 smoke、安全审计、包内容门禁和 workspace install smoke 均保持通过;即使通过,长期优化目标仍必须等 `proof-gap-closure.openCount=0` 且 `optimization-completion.readyForClaim=true`。
+
+### 当前边界
+
+- 可以说:当前优化工作方向对准了提号需求,证明链更准确,旧产物更不容易被误当作当前业务证据。
+- 不能说:提号率已经提升、客户效果已经达标、人工补号量已经下降。
+- 下一步仍必须补真实历史 Brief、客户选择/拒绝记录、历史与本轮人工补号量,并跑真实视频 A/B live `proofContext`。
+
+## 2026-06-11 方向复核与真实视频资源证明闭合
+
+### 本轮审视
+
+- 当前优化方向符合提号需求:这轮不是继续堆协调文档,而是把真实候选视频资源从 open 缺口推进到可审计闭合,并同步清理行动队列、操作包、闭环工单、交接入口和 smoke 里的旧口径。
+- 当前已真实关闭 2 个证明缺口:`video-real-candidate` 和 `live-provider-overnight-proof`。其中视频资源审计为 `readyForVideoAbPreflight=true`、`failureCount=0`、`realCandidateRows=1`;proof-gap closure 为 `closedCount=2`、`openCount=3`。
+- 当前长期优化目标仍未完成:`optimization-completion.readyForClaim=false`、`proofGapOpenCount=3`、`longrun:readiness.ready=false`、`intake-readiness.failureCount=6`。不能宣称提号率提升、客户效果达标或人工补号量下降。
+- 剩余未关闭缺口是:真实历史 Brief 数据集、真实视频 A/B live proof、商务复核和客户效果证明。下一步应继续补真实历史数据、客户选择/拒绝、人工补号量和 live 视频 A/B proofContext,不能再把补证入口本身当成果。
+
+### 本轮修正
+
+- `business-proof:next-actions` 跳过已闭合的视频资源补证动作,当前 `businessProofNextActions.actionCount=3`,不再把“补真实候选视频资源”拉回待办。
+- `proof-gap:operator-pack`、`real-proof:intake-bundle` 和 `real-proof:work-order` 对 closed 行改为保留证据链;当前 `ownerArtifactCount=2`、`workOrderCount=13`、`proofGapOpenCount=3`、`realCandidateRows=1`。
+- `round:deposition`、`task:handoff-index:smoke`、`plan:execution-status:smoke`、`real-proof:work-order:smoke` 和 `round:refresh:smoke` 从固定 3/4/5 个旧缺口断言改为读取当前 proof-gap、operator-pack、video readiness 和 work-order 产物,防止已关闭缺口被误判回待办。
+- `docs/tihao-handoff-index.md` 已重写为当前单一事实源:明确 `proof-gap-closure.openCount=3`、`closedCount=2`、视频资源已 ready、剩余 3 个业务证明缺口和禁用宣称边界。
+
+### 已验证
+
+- `npm.cmd run task:handoff-index:smoke` 通过,交接入口已对齐 3 个 open 缺口、2 个 closed 缺口。
+- `npm.cmd run plan:consistency:smoke` 通过,静态 5 类缺口全集与当前 3 条 open action 队列不冲突。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,27 步全部 `pass`,刷新后仍为 `proof-gap-closure.openCount=3`、`closedCount=2`、`video-resource-readiness.readyForVideoAbPreflight=true`。
+- `npm.cmd run acceptance` 通过,包级 smoke、计划一致性、每轮刷新 smoke、安全审计、包内容门禁和 workspace install smoke 均通过。
+
+### 当前边界
+
+- 可以说:真实视频资源证明已经闭合,当前可以进入真实视频 A/B 前置。
+- 可以说:整体优化方向没有偏离提号目标,工程交接链路已经围绕真实证明缺口收敛。
+- 不能说:提号率已经提升、客户效果已经达标、人工补号量已经下降。
+
+## 2026-06-11 方向复核与 live/provider overnight 证明缺口闭合
+
+### 本轮审视
+
+- 当前优化方向总体符合提号需求:补证入口、操作包、商务执行总表、完成度审计和每轮沉淀都在服务真实 Brief、真实视频资源、真实视频 A/B、客户复核和客户效果证明,没有把候选名单、worklist、闭环工单或本地种子材料误升格为业务效果证明。
+- 本轮发现并修正一个状态口径问题:`proof-gap:closure` 之前只取最新 `aggregate-summary.json`,会被更新但不可关闭的 smoke/坏 aggregate 覆盖;实际已有可关闭的真实 live/provider overnight aggregate:`outputs/overnight-quality-1780682025651/aggregate-summary.json`。
+- 修正后只关闭 `live-provider-overnight-proof` 这一项,长期优化目标仍未完成:`proof-gap-closure.openCount=4`、`optimization-completion.readyForClaim=false`、`longrun:readiness.ready=false`、`intake-readiness.failureCount=9`、`video-resource-readiness.realCandidateRows=0`。
+
+### 本轮修正
+
+- `proof-gap:closure` 和 `optimization:completion` 均改为优先选择可关闭的 overnight aggregate,并把选中的 source 写入 summary/report;较新的坏 aggregate 不再覆盖较旧但真实可关闭的 aggregate。
+- `live-provider-overnight-proof` 的关闭条件统一为 `liveEnabled=true`、`overallPass=true`、`failureCount=0`、`failedGateCount=0`、`tokenLeak=false`;token leak 会作为缺失证明要求阻塞关闭。
+- `round-deposition` 的守卫从固定“5 个 open 业务缺口”改为跟随当前未关闭缺口,避免已关闭的 live/provider 行仍被要求带缺失证明或生成下一步行动。
+- `plan:consistency:smoke` 的当前行动队列检查改为读取 `proof-gap-closure-summary.json`:静态软件补证表仍保留 5 类业务缺口全集,但下一步行动队列只要求覆盖当前 4 个 open 业务缺口,并禁止已 closed 的 `live-provider-overnight-proof` 回到待办。
+- 最新真实输出已同步为:`proof-gap-closure.closedCount=1`、`proof-gap-closure.openCount=4`、`business-proof-next-actions.actionCount=4`、`real-proof-closure-work-order.workOrderCount=18`、`proofGapOpenCount=4`、`optimization-completion.blockingReasonCount=9`、`missingProofRequirementCount=61`。
+
+### 已验证
+
+- `node --check scripts\proof-gap-closure-audit.js`、`node --check scripts\optimization-completion-audit.js`、`node --check scripts\proof-gap-closure-smoke.js`、`node --check scripts\optimization-completion-smoke.js`、`node --check scripts\round-deposition-audit.js` 通过。
+- `npm.cmd run proof-gap:closure:smoke`、`npm.cmd run optimization:completion:smoke`、`npm.cmd run round:deposition` 通过。
+- `npm.cmd run plan:consistency:smoke` 通过,确认静态 5 类缺口表与当前 4 条 open action 队列不再互相误判。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,27 步全部 `pass`,`round-deposition.passCount=127`、`failCount=0`。
+- `npm.cmd run acceptance` 通过,包级 smoke、计划一致性、每轮刷新 smoke、安全审计、包内容门禁和 workspace install smoke 均保持通过。
+
+### 当前边界
+
+- 可以说“真实 live/provider overnight 证明这一项已由 `outputs/overnight-quality-1780682025651/aggregate-summary.json` 关闭,当前剩余 4 个业务证明缺口”;不能说提号率已提升、客户效果已达标或人工补号量已下降。
+- 剩余未关闭缺口是:真实历史 Brief 数据集、真实候选视频资源、真实视频 A/B live proof、商务复核和客户效果证明。
+
+## 2026-06-11 优化完成度审计缺失证明计数下沉到 summary
+
+### 本轮审视
+
+- 当前长期目标仍不能标完成,原因没有变化:`proof-gap-closure.openCount=5`、`optimization-completion.readyForClaim=false`、`longrun:readiness.ready=false`、`intake-readiness.failureCount=9`、`video-resource-readiness.realCandidateRows=0`。
+- 这轮不继续新增补证入口,而是补强完成度审计本身的机器可读性:之前 `round-deposition` 和报告能看到 `blockingReasonsWithMissingProofRequirements=10`、`missingProofRequirementCount=65`,但 `optimization-completion-summary.json` 顶层没有直接写这两个计数,下一轮容易从报告或沉淀审计反推。
+
+### 本轮修正
+
+- `optimization:completion` 顶层新增 `materialType=optimization_completion_audit`、`directCustomerProof=false`、`proofLevel=not_business_proof`、`blockingReasonCount=10`、`blockingReasonsWithMissingProofRequirements=10`、`missingProofRequirementCount=65`。
+- `latest-form:index`、`handoff:summary` 和 `business-execution:index` 改为优先读取完成度审计 summary 顶层计数,并在报告/执行总表中展示 `missingProofRequirementCount`。
+- `optimization-completion-smoke`、`latest-form-index-smoke`、`generate-optimization-handoff-smoke`、`business-execution-index-smoke` 新增防回退断言,要求完成度审计缺失证明计数可见且保持非业务证明边界。
+
+### 已验证
+
+- `node --check scripts\optimization-completion-audit.js`、`node --check scripts\optimization-completion-smoke.js`、`node --check scripts\latest-form-index.js`、`node --check scripts\latest-form-index-smoke.js`、`node --check scripts\generate-optimization-handoff.js`、`node --check scripts\generate-optimization-handoff-smoke.js`、`node --check scripts\business-execution-index.js`、`node --check scripts\business-execution-index-smoke.js` 通过。
+- `npm.cmd run optimization:completion:smoke`、`npm.cmd run latest-form:index:smoke`、`npm.cmd run handoff:summary:smoke`、`npm.cmd run business-execution:index:smoke` 通过。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,真实 outputs 27 步全部 `pass`;`outputs/optimization-completion-latest/optimization-completion-summary.json`、`latest-form-index-summary.json`、`optimization-handoff-summary.json` 均已透传 `blockingReasonsWithMissingProofRequirements=10`、`missingProofRequirementCount=65`、`directCustomerProof=false`、`proofLevel=not_business_proof`。
+
+### 当前边界
+
+- 本轮只是让完成度审计自身更可机器复验,不补真实客户材料、不关闭 proof-gap。
+- 可以说“完成度审计 summary 顶层已记录 10 个阻塞项和 65 条唯一缺失证明要求,并已进入最新索引和交接摘要”;不能说提号率已提升、客户效果已达标或人工补号量已下降。
+
+## 2026-06-11 真实证明闭环工单纳入第 27 步刷新与交接守卫
+
+### 本轮审视
+
+- 优化方向仍符合提号需求:新增的 `real-proof-closure-work-order` 不是继续堆说明文档,而是把 `intake-readiness`、`video-resource-readiness`、`proof-gap-closure`、`optimization-completion` 中仍未关闭的阻塞,拆成负责人、目标文件、目标字段、行范围、种子线索和复验命令,能直接服务商务/投放补真实材料。
+- 本轮到位的是“真实材料补证派工可执行、可索引、可交接、可防回退”:最新表单索引、交接摘要、证据台账、round refresh 和 round deposition 都能看到闭环工单,并且都保留 `not_business_proof`、`directCustomerProof=false`、`canCloseProofGap=false` 边界。
+- 本轮仍未到位的是业务证明本身:`proof-gap-closure.openCount=5`、`optimization-completion.readyForClaim=false`、`longrun:readiness.ready=false`、`intake-readiness.failureCount=9`、`video-resource-readiness.realCandidateRows=0` 仍然成立;不能宣称提号率提升、客户效果达标或人工补号量下降。
+
+### 本轮修正
+
+- `round:refresh` 固定为 27 步,在 `local-seed-to-intake-worklist` 之后、`latest-form-index` 之前刷新 `real-proof-closure-work-order`,确保最新表单索引和交接摘要读到最新工单。
+- `evidence:index` 已把 `real-proof-closure-work-order` 归类为 `not_business_proof`,当前工单 `workOrderCount=19`、`ownerArtifactCount=3`、`proofGapOpenCount=5`、`canCloseProofGap=false`。
+- `latest-form:index` 和 `handoff:summary` 已透传闭环工单的 Markdown、CSV、summary、3 份 by-owner 附件、19 条工单和非证明边界。
+- `round-deposition` 新增 7 个守卫,覆盖工单 summary 存在、非证明边界、字段级可执行、latest-form 可见、handoff 顶层可见、handoff.latestFormIndex 可见、evidence 台账可见;当前 `round-deposition.passCount=127`。
+- `docs/tihao-handoff-index.md` 已同步第 27 步刷新、闭环工单阅读入口、最近复验结果、角色分工、可说/不可说和复验命令。
+
+### 已验证
+
+- `node --check scripts\refresh-round-artifacts-smoke.js`、`node --check scripts\evidence-index-smoke.js`、`node --check scripts\latest-form-index-smoke.js`、`node --check scripts\generate-optimization-handoff-smoke.js`、`node --check scripts\task-handoff-index-smoke.js`、`node --check scripts\round-deposition-audit.js`、`node --check scripts\round-deposition-smoke.js` 通过。
+- `npm.cmd run real-proof:work-order:smoke` 通过,`workOrderCount=19`、`ownerArtifactCount=3`、`proofGapOpenCount=5`。
+- `npm.cmd run evidence:index:smoke` 通过,临时台账 `total=39`、`not_business_proof=15`、`real-proof-closure-work-order=1`。
+- `npm.cmd run latest-form:index:smoke`、`npm.cmd run handoff:summary:smoke`、`npm.cmd run round:deposition:smoke`、`npm.cmd run round:refresh:smoke` 通过。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,真实 outputs 27 步全部 `pass`,`round-deposition.passCount=127`,`evidenceIndex.byType["real-proof-closure-work-order"]=1`。
+
+### 当前边界
+
+- 本轮只把未关闭证明缺口拆成可执行工单并纳入主交接链路;没有补真实客户材料,也没有关闭 proof-gap。
+- 可以说“真实证明闭环工单已进入最新表单索引、交接摘要、证据台账和每轮沉淀审计,且保持 `not_business_proof` 边界”;不能说提号率已提升、客户效果已达标或人工补号量已下降。
+
+## 2026-06-11 本地种子转 intake 补表 worklist 纳入主交接与防回退审计
+
+### 本轮审视
+
+- 当前优化方向更贴近提号需求:上一轮已经让本地 DHA Brief、PoC 推荐表和参考账号解析可发现,本轮把这些种子进一步转成商务/投放可转写的 `history-intake-draft.csv`、`video-resource-worklist.csv` 和 `candidate-seed-worklist.csv`,减少“只新增索引、不推动补真实表”的漂移。
+- 当前“到位”的是从种子材料到正式 intake 的桥梁、主交接可见性和防回退审计:`latest-form-index`、`optimization-handoff`、`evidence-index`、`round:refresh` 和 `round-deposition` 都能看见该 worklist。
+- 当前“未到位”的仍是真实业务证明:`proof-gap-closure.openCount=5`、`optimization-completion.readyForClaim=false`、`longrun:readiness.ready=false`、`intake-readiness.failureCount=9`、`video-resource-readiness.realCandidateRows=0`,不能宣称提号率提升、客户效果达标或人工补号量下降。
+
+### 本轮修正
+
+- `latest-form:index` 现在展示“本地种子转 intake 补表 worklist”,并透传 Markdown、CSV、summary、历史草稿、视频资源 worklist、候选复核 worklist、行数和 `canCloseProofGap=false` 边界。
+- `handoff:summary` 现在在顶层和 `latestFormIndex.localSeedToIntakeWorklist` 同步该 worklist,并把运行命令加入下一步命令区。
+- `round:refresh` 顺序固定为 26 步,`local-seed-material-index` 和 `local-seed-to-intake-worklist` 都在 `latest-form-index` 与 `optimization-handoff` 之前刷新。
+- `round-deposition` 新增 5 个守卫:worklist summary 存在、worklist 非证明边界、latest-form-index 可见、handoff 顶层可见、handoff.latestFormIndex 可见;当前真实 `round-deposition.passCount=120`。
+- `docs/tihao-handoff-index.md` 已同步阅读顺序、最近复验结果、角色分工、可说/不可说和补齐材料后的复验命令,明确 worklist 只能作为补正式 intake 的草稿,不写入正式模板,也不关闭 proof-gap。
+
+### 已验证
+
+- `node --check scripts\round-deposition-audit.js`、`node --check scripts\round-deposition-smoke.js`、`node --check scripts\latest-form-index.js`、`node --check scripts\generate-optimization-handoff.js` 通过。
+- `npm.cmd run local-seed-to-intake:worklist:smoke`、`npm.cmd run latest-form:index:smoke`、`npm.cmd run handoff:summary:smoke`、`npm.cmd run round:refresh:smoke` 通过。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,真实 outputs 中 26 步全部 `pass`,`round-deposition.passCount=120`,`evidenceIndex.byType["local-seed-to-intake-worklist"]=1`。
+- `npm.cmd run evidence:index:smoke`、`npm.cmd run task:handoff-index:smoke`、`npm.cmd run hygiene:smoke` 通过;安全扫描 `scannedFiles=173`、`hitCount=0`。
+- `npm.cmd run acceptance` 通过,包内容门禁、任务交接入口 smoke、安全审计和 workspace install smoke 均保持通过。
+
+### 当前边界
+
+- 本轮只把本地种子材料转成可执行补表草稿,并让主交接/每轮沉淀防止该入口丢失;没有补真实客户材料,也没有关闭 proof-gap。
+- 当前 worklist 计数为 `historyDraftRows=1`、`referenceSeedRows=4`、`candidateSeedRows=12`、`videoWorklistRows=5`、`videoCandidateNeedsResourceRows=3`、`canCloseProofGap=false`。
+- 可以说“本地种子转 intake 补表 worklist 已进入最新表单索引、交接摘要、证据台账和每轮沉淀审计”;不能说提号率已提升、客户效果已达标或人工补号量已下降。
+
+## 2026-06-11 经验逐字稿索引纳入每轮刷新与方向复核
+
+### 本轮审视
+
+- 当前优化方向仍符合提号需求:新增的经验逐字稿索引把黄圆、邓发两份提号经验逐字稿变成可追踪、可审计的 `experience_seed`,能辅助校准参考账号拆解、相似链路、主页近作、视觉调性、品类隐性人群、视频深看和客户效果边界。
+- 当前“到位”的仍是工程与交接层:经验来源可发现、规则覆盖可复验、证据台账可分类、每轮刷新可防回退;“未到位”的仍是真实业务证明本身。
+- 当前主要偏离风险没有消失:如果下一步继续新增索引/审计入口,而不补真实历史 Brief、真实候选视频、客户选择/拒绝、人工补号量和 live/provider 长跑材料,就会偏离提号优化的最终目标。
+
+### 本轮修正
+
+- 新增 `experience-transcript:index`,默认读取 `../docs/提号经验` 下的 `黄圆-AI提号经验逐字稿.md` 和 `邓发-AI提号经验逐字稿.md`,输出 summary、Markdown 和 CSV。
+- 索引覆盖 8 条经验规则:参考账号类型/调性、小红书/蒲公英相似链路、抖音/星图兜底、主页近 10/20 篇、视觉质感、产品隐性人群、视频深看、客户效果目标。
+- `evidence:index` 新增 `experience-transcript-index` 分类,固定为 `not_business_proof`;`round:refresh` 新增 `experience-transcript-index` 步骤,位于 `local-seed-material-index` 和 `evidence-index` 之间,因此每轮证据台账会读到最新经验逐字稿索引。
+- `acceptance` 纳入 `experience-transcript:index:smoke`,防止后续把逐字稿索引误升级为业务证明或泄露原文中的 secret-like 内容。
+- `task-handoff-index:smoke` 同步为 25 步刷新口径,并新增逐字稿索引 `not_business_proof`、`transcriptCount=2`、`coveredRuleCount=8` 的防回退断言。
+- `experience-transcript-index-smoke` 的 secret-like 测试夹具改为运行时拼接,保留“不输出敏感文本”的测试意图,同时避免源码触发安全审计。
+
+### 已验证
+
+- `node --check scripts\experience-transcript-index.js` 通过。
+- `node --check scripts\experience-transcript-index-smoke.js` 通过。
+- `node --check scripts\task-handoff-index-smoke.js` 通过。
+- `node --check scripts\evidence-index.js` 通过。
+- `node --check scripts\refresh-round-artifacts.js` 通过。
+- `node --check scripts\refresh-round-artifacts-smoke.js` 通过。
+- `npm.cmd run experience-transcript:index -- --output outputs\experience-transcript-index-latest` 通过,`transcriptCount=2`、`coveredRuleCount=8`、`canCloseProofGap=false`。
+- `npm.cmd run experience-transcript:index:smoke` 通过。
+- `npm.cmd run evidence:index:smoke` 通过,临时台账中 `experience-transcript-index` 归类为 `not_business_proof`。
+- `npm.cmd run round:refresh:smoke` 通过,临时刷新链路已稳定为 25 步。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,真实 outputs 中 25 步全部 `pass`,`round-deposition.passCount=115`,`evidence-index.byType["experience-transcript-index"]=1`。
+- `npm.cmd run task:handoff-index:smoke` 通过,交接入口已守住 25 步、逐字稿索引和 `readyForClaim=false` 边界。
+- `npm.cmd run hygiene:smoke` 通过,`scannedFiles=171`、`hitCount=0`。
+- `npm.cmd run acceptance` 通过。
+
+### 当前边界
+
+- 本轮只让提号经验来源可追踪、可复验、可纳入每轮刷新;不补真实客户材料,也不关闭 proof-gap。
+- 当前仍为 `proof-gap-closure.openCount=5`、`optimization-completion.readyForClaim=false`、`longrun:readiness.ready=false`、`intake-readiness.failureCount=9`、`video-resource-readiness.realCandidateRows=0`。
+- 可以说“经验逐字稿索引已纳入每轮刷新和证据台账,并保持 `not_business_proof` 边界”;不能说提号率已提升、客户效果已达标或人工补号量已下降。
+
+## 2026-06-10 本地种子材料索引 URL 展示清洗与方向审视
+
+### 本轮审视
+
+- 当前优化方向仍符合提号需求:工程侧已把补证入口、负责人派工、证据分级、完成度审计、每轮刷新和禁止误宣称门禁串起来;这些都服务于真实 Brief、真实候选视频、live/provider 长跑和客户效果证明。
+- 当前“到位”的是可发现、可派工、可复验、可防回退;“未到位”的仍是真实业务证明本身。
+- 当前主要偏离风险仍是继续增加协调/审计产物,而不是补真实历史 Brief、真实候选视频、客户选择/拒绝、人工补号量和 live/provider 长跑材料。
+
+### 本轮修正
+
+- `local-seed-material:index` 的工作簿表头、样例行和 `signals.sampleUrls` 现在会清洗展示 URL,去掉 query/hash 等平台临时分享参数,避免把 `xsec_token`、`access_token`、`Authorization` 等敏感或噪声参数带入索引产物。
+- `local-seed-material-index-smoke` 新增 URL 清洗和 secret-like redaction 断言,防止后续把分享 token 参数写回本地种子材料索引。
+- 真实输出已重新刷新,`outputs/local-seed-material-index-latest/local-seed-material-index-summary.json` 中样例 URL 不再包含 query/hash;`proofLevel=not_business_proof`、`directCustomerProof=false`、`canCloseProofGap=false` 保持不变。
+
+### 已验证
+
+- `node --check scripts\local-seed-material-index.js` 通过。
+- `node --check scripts\local-seed-material-index-smoke.js` 通过。
+- `npm.cmd run local-seed-material:index:smoke` 通过。
+- `npm.cmd run local-seed-material:index -- --outputs outputs --output outputs\local-seed-material-index-latest` 通过,`existingMaterialCount=3`、`canCloseProofGap=false`。
+- `Select-String` 检查 `outputs/local-seed-material-index-latest` 的 summary/Markdown/CSV 未发现 `xsec_token`、`access_token`、`refresh_token`、`sessionToken` 或 `Authorization`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,24 步全部 `pass`,`round-deposition.passCount=115`。
+- `npm.cmd run evidence:index:smoke`、`npm.cmd run round:refresh:smoke`、`npm.cmd run round:deposition:smoke`、`npm.cmd run task:handoff-index:smoke`、`npm.cmd run hygiene:smoke` 和 `npm.cmd run acceptance` 均通过。
+
+### 当前边界
+
+- 本轮只收紧本地种子材料索引的展示卫生和交接可用性,不补真实客户材料,也不关闭 proof-gap。
+- 当前仍为 `proof-gap-closure.openCount=5`、`optimization-completion.readyForClaim=false`、`longrun:readiness.ready=false`、`intake-readiness.failureCount=9`、`video-resource-readiness.realCandidateRows=0`。
+- 可以说“本地种子材料索引已更干净地纳入每轮刷新和证据台账”;不能说提号率已提升、客户效果已达标或人工补号量已下降。
+
+## 2026-06-10 本地种子材料索引纳入每轮刷新
+
+### 本轮修正
+
+- 新增 `local-seed-material:index`,把 `../dha_brief.xlsx`、`../output/dha-tihao-poc/AI提号-DHA小红书推荐名单-PoC.xlsx` 和 `../output/dha-tihao-poc/AI提号-DHA小红书推荐名单-TikHub参考分析版.xlsx` 做成本地种子材料索引。
+- 索引输出 `outputs/local-seed-material-index-latest/local-seed-material-index-summary.json`、Markdown 和 CSV,当前识别 `existingMaterialCount=3`、`workbookCount=3`、`promisingLiveAggregateCount=1`,并强制保留 `proofLevel=not_business_proof`、`directCustomerProof=false`、`canCloseProofGap=false`。
+- XLSX 解析兼容带命名空间前缀和绝对 workbook relationship 的工作簿,当前能读出 DHA Brief 的 `brief/通用提号规则/参考媒体信息`,以及 PoC 表的 `结论/主推账号/备选复核/参考账号解析/Brief与方法` 等 sheet、行数和表头。
+- `evidence:index` 新增 `local-seed-material-index` 分类,固定为 `not_business_proof`;`round:refresh` 新增 `local-seed-material-index` 步骤,位于 `optimization-handoff` 和 `evidence-index` 之间,因此每轮证据台账会读到最新种子材料索引。
+
+### 已验证
+
+- `node --check scripts\local-seed-material-index.js` 通过。
+- `node --check scripts\local-seed-material-index-smoke.js` 通过。
+- `npm.cmd run local-seed-material:index:smoke` 通过。
+- `npm.cmd run local-seed-material:index -- --outputs outputs --output outputs\local-seed-material-index-latest` 通过,`existingMaterialCount=3`、`canCloseProofGap=false`。
+- `npm.cmd run evidence:index:smoke` 通过,临时台账中 `local-seed-material-index` 归类为 `not_business_proof`。
+
+### 当前边界
+
+- 本轮只让本地 DHA Brief、PoC 推荐表、TikHub 参考分析版和旧 pipeline/live 自动化候选可发现,方便商务/投放转写真实模板和补真实候选视频;不补真实客户反馈,也不关闭 proof-gap。
+- 当前仍为 `proof-gap-closure.openCount=5`、`optimization-completion.readyForClaim=false`、`longrun:readiness.ready=false`。
+- 可以说“本地种子材料已纳入可复验索引并进入证据台账”;不能说提号率已提升、客户效果已达标或人工补号量已下降。
+
+## 2026-06-10 下一步行动队列继承逐缺口缺失证明要求
+
+### 本轮审视
+
+- 当前优化工作符合提号需求里的工程承接方向:补证入口、责任分工、最新索引、完成度审计和防误宣称门禁都在围绕真实 Brief、真实候选视频、live proof 和客户效果证明推进。
+- 当前“到位”的是可补证、可派工、可复验、可防回退;“未到位”的仍是真实业务证明本身。
+- 主要偏离风险仍是继续增加协调/审计产物,却没有补真实历史 Brief、真实候选视频、live/provider 长跑和客户反馈材料。
+
+### 本轮修正
+
+- `business-proof:next-actions` 现在会把 `proof-gap-closure` 中被主行动吸收的缺口行合并回主行动,避免 `step-03`、`step-04`、`step-12` 有下一步文字但没有结构化关闭条件。
+- 当前 5 个业务行动均已带 `missingProofRequirements`:真实历史 Brief、真实视频资源、人工复核标注、真实视频 A/B 验收、真实 live/provider 长跑证明。
+- `round-deposition` 新增 `business-proof-next-actions-missing-proof-requirements` 防回退守卫,并沉淀 `businessProofNextActions.withMissingProofRequirements=5`。
+- 最新真实输出中 `round-deposition.passCount=115`,`round:refresh --strict` 仍为 23 步全通过。
+
+### 已验证
+
+- `node --check scripts\business-proof-next-actions.js` 通过。
+- `node --check scripts\business-proof-next-actions-smoke.js` 通过。
+- `npm.cmd run business-proof:next-actions:smoke` 通过。
+- `npm.cmd run business-proof:next-actions:missing-proof:smoke` 通过。
+- `node --check scripts\round-deposition-audit.js` 通过。
+- `node --check scripts\round-deposition-smoke.js` 通过。
+- `npm.cmd run round:deposition:smoke` 通过,`passCount=115`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,真实 outputs 中 23 步全部 `pass`,`round-deposition.passCount=115`。
+- `npm.cmd run round:refresh:smoke` 通过,`roundPassCount=115`。
+
+### 当前边界
+
+- 本轮只让下一步行动队列的关闭条件机器可读,并纳入每轮沉淀守卫;没有补真实业务材料,也没有关闭真实证明缺口。
+- 当前仍为 `proof-gap-closure.openCount=5`、`optimization-completion.readyForClaim=false`、`longrun:readiness.ready=false`。
+- 可以说“5 个补证动作已携带结构化缺失证明要求并进入防回退审计”;不能说提号率已提升、客户效果已达标或人工补号量已下降。
+
+## 2026-06-10 每轮沉淀审计纳入候选名单交付质量守卫
+
+### 本轮修正
+
+- `round-deposition` 新增 `latest-form-index-client-list-delivery-quality` 防回退守卫,要求最新表单索引里的软件候选名单同时满足:`directCustomerProof=false`、`rowCount>0`、`sourceDuplicateRemovedCount>=0`、`finalDuplicateGroupCount=0`、`duplicateCountMeaning=source_duplicate_removed_count`、`rankContinuous=true`。
+- `round-deposition` 的 observed/report 新增候选名单交付质量字段:`latestFormIndex.clientListRowCount`、`latestFormIndex.clientListSourceDuplicateRemovedCount`、`latestFormIndex.clientListFinalDuplicateGroupCount`、`latestFormIndex.clientListDuplicateCountMeaning`、`latestFormIndex.clientListRankContinuous`、`latestFormIndex.clientListDirectCustomerProof`。
+- `round-deposition:smoke` 已加入 fixture 和断言,防止后续只保留候选名单路径,却丢失“最终重复组”和“源表去重丢弃”的业务口径。
+
+### 已验证
+
+- `node --check scripts\round-deposition-audit.js` 通过。
+- `node --check scripts\round-deposition-smoke.js` 通过。
+- `npm.cmd run round:deposition:smoke` 通过,`passCount=114`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,真实 outputs 中 23 步全部 `pass`,已包含 `software-client-list` 且位于 `latest-form-index` 之前,`round-deposition.passCount=114`。
+- `npm.cmd run task:handoff-index:smoke`、`npm.cmd run hygiene:smoke`、`npm.cmd run round:refresh:smoke` 和 `npm.cmd run acceptance` 均通过。
+
+### 当前边界
+
+- 本轮只把软件候选名单交付质量纳入每轮沉淀审计,不补真实业务材料,也不关闭真实证明缺口。
+- 当前仍为 `proof-gap-closure.openCount=5`、`optimization-completion.readyForClaim=false`、`longrun:readiness.ready=false`。
+- 可以说“候选名单最终重复组和源表去重口径已进入防回退审计”;不能说提号率已提升、客户效果已达标或人工补号量已下降。
+
+## 2026-06-10 审视结论与软件候选名单去重口径澄清
+
+### 本轮审视
+
+- 当前优化方向整体符合提号需求里的基础设施部分:补证 SOP、最新表单索引、负责人派工、完成度审计和防误宣称门禁,都在围绕“真实 Brief、真实候选视频、live proof、客户效果证明”推进。
+- 当前仍未到位的是业务效果证明本身:5 个真实证明缺口仍打开,`optimization-completion.readyForClaim=false`,`longrun:readiness.ready=false`。
+- 发现的偏离风险是近期工作偏重审计、索引和交接产物;下一步若要继续产生实质业务进展,必须补真实 Brief、真实视频、live/provider 长跑和客户反馈,而不是继续堆协调入口。
+- 发现一个交付口径风险:`software-client-list.final.summary.json` 里的兼容字段 `duplicateCount=32` 表示源表去重丢弃行数,不表示最终软件端名单还有 32 个重复项,容易误导商务或下一轮 AI。
+
+### 本轮修正
+
+- `software:export` summary 新增 `sourceDuplicateRemovedCount`、`finalDuplicateGroupCount`、`duplicateCountMeaning=source_duplicate_removed_count`,保留 legacy `duplicateCount` 但明确其含义。
+- `software:markdown` 将顶部摘要改为“源表去重丢弃行数”和“最终名单重复组数”,避免把源表去重丢弃误读成最终名单重复。
+- `latest-form:index` 已在候选名单入口展示 `最终重复组`、`排名连续` 和 `源表去重丢弃`,并继续标记 `directCustomerProof=false`。
+- 已刷新真实软件端候选名单:`sourceRows=60`、`outputRows=28`、`sourceDuplicateRemovedCount=32`、`finalDuplicateGroupCount=0`、`rankContinuous=true`。
+
+### 已验证
+
+- `node --check scripts\export-software-table.js` 通过。
+- `node --check scripts\export-software-markdown.js` 通过。
+- `node --check scripts\software-table-smoke.js` 通过。
+- `node --check scripts\latest-form-index.js` 通过。
+- `node --check scripts\latest-form-index-smoke.js` 通过。
+- `npm.cmd run software:smoke` 通过。
+- `npm.cmd run latest-form:index:smoke` 通过。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,真实 outputs 中 22 步全部 `pass`,`round-deposition.passCount=114`。
+- `npm.cmd run acceptance` 通过。
+
+### 当前边界
+
+- 本轮只修正审视结论和候选名单交付口径,不补真实业务材料,也不关闭真实证明缺口。
+- 当前仍为 `proof-gap-closure.openCount=5`、`optimization-completion.readyForClaim=false`、`longrun:readiness.ready=false`。
+- 可以说“最终候选名单当前无重复组、排名连续”;不能说提号率已提升、客户效果已达标或人工补号量已下降。
+
+## 2026-06-10 完成度审计逐门槛缺失证明要求补齐
+
+### 本轮修正
+
+- `optimization:completion` 现在为 10 个完成门槛中的每个 blocked gate 输出结构化 `missingProofRequirements`,覆盖 intake readiness、真实候选视频、proof-gap closure、视频 A/B live proof、live/provider 长跑、客户效果、pipeline、handoff、15 步补证进度和 longrun readiness。
+- 已通过 gate 会保持空 `missingProofRequirements`,避免在 closed/ready 场景残留旧缺失项。
+- `round-deposition` 新增 `optimization-completion-missing-proof-requirements` 防回退守卫,并沉淀 `optimizationCompletion.blockingReasonsWithMissingProofRequirements=10`、`optimizationCompletion.missingProofRequirementCount=65`。
+- `optimization-completion-report.md` 的完成门槛表现在直接展示每个 blocked gate 的“缺失证明”,下一轮不需要只凭 `readyForClaim=false` 反推关闭条件。
+
+### 已验证
+
+- `node --check scripts\optimization-completion-audit.js` 通过。
+- `node --check scripts\optimization-completion-smoke.js` 通过。
+- `node --check scripts\round-deposition-audit.js` 通过。
+- `node --check scripts\round-deposition-smoke.js` 通过。
+- `npm.cmd run optimization:completion:smoke` 通过。
+- `npm.cmd run round:deposition:smoke` 通过,`passCount=113`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,真实 outputs 中 22 步全部 `pass`,`round-deposition.passCount=113`。
+
+### 当前边界
+
+- 本轮只把“长期完成度审计的每个 blocked gate 到底缺哪条证明”结构化并进入每轮沉淀,不补真实业务材料,也不关闭真实证明缺口。
+- 当前仍为 `proof-gap-closure.openCount=5`、`optimization-completion.readyForClaim=false`、`longrun:readiness.ready=false`。
+- 不得宣称提号率提升、客户效果达标、人工补号量下降;`optimization-completion` 和 `round-deposition` 仍只是完成度审计与防回退入口。
+
+## 2026-06-10 逐缺口缺失证明要求补齐
+
+### 本轮修正
+
+- `proof-gap:closure` 现在为每个未关闭行输出结构化 `missingProofRequirements`:intake readiness、历史数据集、真实候选视频、视频 A/B live proof、live/provider 长跑、商务复核和客户效果都会列出缺哪条证明。
+- 已关闭行会清空缺失证明要求,避免旧缺失项残留在 closed 证据里。
+- `proof-gap-operator-pack` 和 `real-proof-intake-bundle` 自动透传每个业务 gap 的缺失证明要求;当前真实输出 `operatorPackMissingProofRequirementCount=38`,5 个 gap row 中 3 个 open row 均有缺失证明要求,closed 行只保留证据链。
+- `round-deposition` 新增 `proof-gap-closure-missing-proof-requirements` 和 `proof-gap-operator-pack-missing-proof-by-gap` 两个防回退守卫,并沉淀 `proofGapClosure.openRowsWithMissingProofRequirements=5`、`proofGapOperatorPack.openRowsWithMissingProofRequirements=5`。
+- `docs/tihao-handoff-index.md` 已同步当前阅读入口和最近复验结果,当前 `round-deposition.passCount=112`。
+
+### 已验证
+
+- `node --check scripts\proof-gap-closure-audit.js` 通过。
+- `node --check scripts\proof-gap-closure-smoke.js` 通过。
+- `node --check scripts\round-deposition-audit.js` 通过。
+- `node --check scripts\round-deposition-smoke.js` 通过。
+- `npm.cmd run proof-gap:closure:smoke` 通过。
+- `npm.cmd run round:deposition:smoke` 通过,`passCount=112`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,真实 outputs 中 22 步全部 `pass`,`round-deposition.passCount=112`。
+
+### 当前边界
+
+- 本轮只把“每个未关闭缺口到底缺哪条证明”结构化并透传,不补真实业务材料,也不关闭真实证明缺口。
+- 当前仍为 `proof-gap-closure.openCount=5`、`optimization-completion.readyForClaim=false`、`longrun:readiness.ready=false`。
+- 不得宣称提号率提升、客户效果达标、人工补号量下降;`proof-gap-closure`、`proof-gap-operator-pack`、`real-proof-intake-bundle` 和 `round-deposition` 都只是补证协调与防回退审计入口。
+
+## 2026-06-10 最新表单索引透传下一步行动队列摘要
+
+### 本轮修正
+
+- `latest-form:index` 的 `nextActions` 现在同步输出 `complete`、`actionCount`、`proofOpenCount`、`ownerGroupCount`、`ownerArtifactCount`、`sourceState.proofGapOpenCount` 和前 5 个高优先级 `topActions`。
+- `handoff:summary` 的 `latestFormIndex` 现在透传最新表单索引中的下一步行动队列摘要,下一轮 AI 只读交接摘要也能看到 `step-03`、`gap-video-ab-live-proof` 等首批补证动作。
+- `round-deposition` 新增 `latest-form-index-next-actions-top-actions` 和 `handoff-latest-form-index-next-actions-top-actions` 两个防回退守卫,并在报告中沉淀 `latestFormIndex.nextActionsTopCount`、`handoff.latestFormIndex.nextActionsTopCount` 和对应 `proofGapOpenCount`。
+- `task:handoff-index:smoke` 已补充当前产物和临时重生成产物的双层校验,确保 `business-proof-next-actions-summary.json`、`latest-form-index-summary.json` 和 `optimization-handoff-summary.json` 的行动队列摘要链路不断。
+- `docs/tihao-handoff-index.md` 已同步阅读顺序和最近复验结果,当前 `round-deposition.passCount=110`、`latestFormIndex.nextActionsTopCount=5`、`handoff.latestFormIndex.nextActionsTopCount=5`。
+
+### 已验证
+
+- `node --check scripts\latest-form-index.js` 通过。
+- `node --check scripts\latest-form-index-smoke.js` 通过。
+- `node --check scripts\generate-optimization-handoff.js` 通过。
+- `node --check scripts\generate-optimization-handoff-smoke.js` 通过。
+- `node --check scripts\round-deposition-audit.js` 通过。
+- `node --check scripts\round-deposition-smoke.js` 通过。
+- `node --check scripts\task-handoff-index-smoke.js` 通过。
+- `npm.cmd run latest-form:index:smoke` 通过,`topActionCount=5`。
+- `npm.cmd run handoff:summary:smoke` 通过。
+- `npm.cmd run round:deposition:smoke` 通过,`passCount=110`、`latestFormIndexNextActionsTopCount=5`、`handoffLatestFormIndexNextActionsTopCount=5`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,真实 outputs 中 22 步全部 `pass`,`round-deposition.passCount=110`。
+- `npm.cmd run task:handoff-index:smoke` 通过,当前 `latestFormIndexNextActionTopCount=5`、`handoffLatestFormIndexNextActionTopCount=5`。
+- `npm.cmd run round:refresh:smoke` 通过,临时输出 `roundPassCount=110`。
+- `npm.cmd run hygiene:smoke` 通过,`scannedFiles=166`、`hitCount=0`。
+- `npm.cmd run acceptance` 通过,包内容门禁 `entryCount=167`,workspace install smoke 通过。
+
+### 当前边界
+
+- 本轮只补齐最新表单索引和交接摘要里的补证行动队列可见性,不补真实业务材料,也不关闭真实证明缺口。
+- 当前仍为 `proof-gap-closure.openCount=5`、`optimization-completion.readyForClaim=false`、`longrun:readiness.ready=false`。
+- 不得宣称提号率提升、客户效果达标、人工补号量下降;`latest-form-index`、`optimization-handoff` 和 `round-deposition` 都只是补证协调与防回退审计入口。
+
+## 2026-06-10 长跑前置门禁与优化完成度审计入口补强
+
+### 本轮修正
+
+- `latest-form:index` 将 `outputs/long-run-readiness-latest/long-run-readiness-report.md` 和 `long-run-readiness-summary.json` 纳入一等入口,暴露 `ready=false`、`failCount=6`、`proofLevel=not_business_proof` 和 `directCustomerProof=false`。
+- `handoff:summary` 在顶层、`latestFormIndex.longRunReadiness` 和 `businessExecutionIndex.longRunReadiness` 同步长跑前置门禁,并把 `longrun:readiness` 纳入下一轮复验命令。
+- `handoff:summary` 同步新增顶层 `optimizationCompletion` 和 `businessExecutionIndex.optimizationCompletion`,当前 `readyForClaim=false`、`proofGapOpenCount=5`、`blockingReasonCount=10`,继续禁止提号率提升、客户效果达标和人工补号量下降声明。
+- `real-proof-intake-bundle` 在打开顺序、`sourceState` 和补齐后复验命令中新增长跑前置门禁入口,当前 `longRunReadinessReady=false`、`longRunReadinessFailCount=6`、`exportRowCount=34`。
+- `business-execution-index` 将长跑前置门禁和优化完成度审计纳入关键文件行,当前 `rowCount=24`、`fileRowCount=6`;`长跑前置门禁报告` 行为 `status=blocked`、`actionCount=6`,`优化完成度审计报告` 行为 `status=blocked`、`actionCount=10`。
+- `round-deposition` 新增 7 个长跑入口防回退守卫:`latest-form-index-longrun-readiness`、`handoff-longrun-readiness`、`handoff-latest-form-longrun-readiness`、`business-execution-index-longrun-readiness`、`handoff-business-execution-index-longrun-readiness`、`real-proof-intake-bundle-summary`、`real-proof-intake-bundle-longrun-readiness`。
+- `round-deposition` 新增 5 个优化完成度审计防回退守卫:`optimization-completion-summary`、`optimization-completion-boundary`、`business-execution-index-optimization-completion`、`handoff-optimization-completion`、`handoff-business-execution-index-optimization-completion`。
+- `round:refresh` 调整执行顺序,先刷新 `optimization-completion`,再生成 `business-execution-index` 和 `handoff:summary`,确保执行总表与交接摘要都能透传完成度审计行。
+- `docs/tihao-handoff-index.md` 已同步当前结论、阅读顺序、最近复验结果、完成判定矩阵和复验命令,明确 `longrun:readiness.ready=false` 和 `optimization-completion.readyForClaim=false` 不能被 22 步 refresh 通过或 smoke 通过替代。
+- `round:refresh --strict` 已刷新真实 outputs,当前 22 步全部 `pass`;`round-deposition` 当前 `passCount=97`、`failCount=0`,且观测到 `businessExecutionIndexOptimizationCompletionReadyForClaim=false`、`handoffBusinessExecutionIndexOptimizationCompletionStatus=blocked`、`optimizationCompletionBlockingReasonCount=10`。
+
+### 已验证
+
+- `node --check scripts\latest-form-index.js` 通过。
+- `node --check scripts\generate-optimization-handoff.js` 通过。
+- `node --check scripts\latest-form-index-smoke.js` 通过。
+- `node --check scripts\generate-optimization-handoff-smoke.js` 通过。
+- `node --check scripts\business-execution-index.js` 通过。
+- `node --check scripts\business-execution-index-smoke.js` 通过。
+- `node --check scripts\refresh-round-artifacts.js` 通过。
+- `node --check scripts\refresh-round-artifacts-smoke.js` 通过。
+- `node --check scripts\round-deposition-audit.js` 通过。
+- `node --check scripts\round-deposition-smoke.js` 通过。
+- `node --check scripts\task-handoff-index-smoke.js` 通过。
+- `npm.cmd run latest-form:index:smoke` 通过。
+- `npm.cmd run handoff:summary:smoke` 通过。
+- `npm.cmd run business-execution:index:smoke` 通过,商务执行总表已包含 `long-run-readiness-summary.json`、`optimization-completion-summary.json` 来源和对应关键文件行。
+- `npm.cmd run proof-gap:intake-bundle:smoke` 通过,`exportRowCount=34`,总包已包含 `long-run-readiness-report.md` 和 `longrun:readiness` 复验命令。
+- `npm.cmd run round:deposition:smoke` 通过,`passCount=97`、`failCount=0`。
+- `npm.cmd run task:handoff-index:smoke` 通过。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,`round-refresh-summary.json` 显示 22 步全部 `pass`。
+- `npm.cmd run round:refresh:smoke` 通过,临时输出中 22 步顺序稳定且 `roundPassCount=97`。
+- `npm.cmd run hygiene:smoke` 通过,`scannedFiles=166`、`hitCount=0`。
+- `npm.cmd run acceptance` 通过;包内容门禁 `entryCount=167`,workspace install smoke 通过。
+
+### 当前边界
+
+- 本轮补强的是长跑前置门禁的可发现性和防回退审计,不补真实业务材料,也不关闭真实证明缺口。
+- 当前 `proof-gap-closure.openCount=5`,`optimization-completion.readyForClaim=false`、`blockingReasonCount=10`,`longrun:readiness.ready=false`。
+- 不得宣称提号率提升、客户效果达标、人工补号量下降,也不得把 `real-proof-intake-bundle`、`proof-gap-request`、`proof-gap-operator-pack`、最新索引、22 步 refresh 或 `long-run-readiness` 报告当作业务证明。
+
+## 2026-06-08 补充:最新表单索引负责人和复验命令沉淀守卫
+
+已更新:
+
+- `scripts/round-deposition-audit.js`
+- `scripts/round-deposition-smoke.js`
+
+处理结果:
+
+- `round-deposition` 新增 `latest-form-index-intake-owner-recheck` 守卫,要求最新提号表单索引必须保留字段补齐负责人分组和补齐后复验命令。
+- `round-deposition` 新增 `handoff-latest-form-index-intake-owner-recheck` 守卫,要求交接摘要同步保留最新表单索引中的负责人分组数和复验命令数。
+- round deposition 当前通过项从 72 增加到 74,`failCount=0`。
+- 当前观测为 `latestFormIndex.intakeFieldChecklistOwnerGroupCount=3`、`latestFormIndex.intakeFieldChecklistRecheckCommandCount=5`、`handoff.latestFormIndex.ownerGroupCount=3`、`handoff.latestFormIndex.recheckCommandCount=5`。
+
+边界:
+
+- 本轮只增强最新表单入口和交接摘要的防回退审计,不证明客户效果完成。
+- 当前 `proof-gap-closure.openCount=5`,`optimization-status.complete=false`。
+
+更新时间:2026-06-06
+
+执行目标:落实 `docs/tihao-experience-optimization-plan.md`,把商务提号经验转成技能包可执行能力、可验收输出和可持续反馈闭环。
+
+## 2026-06-07 补充:真实补证操作顺序纳入每轮沉淀
+
+已新增:
+
+- `docs/business-proof-action-order.md`
+
+已更新:
+
+- `scripts/round-deposition-audit.js`
+- `scripts/round-deposition-smoke.js`
+- `scripts/generate-optimization-handoff.js`
+- `scripts/generate-optimization-handoff-smoke.js`
+
+处理结果:
+
+- 把真实历史 Brief、真实视频资源、intake readiness、history audit、video readiness、longrun readiness、视频 A/B、商务复核、客户效果审计和每轮沉淀整理成 15 步中文操作顺序。
+- 明确每一步负责人、输入材料、执行命令、通过标准和不通过处理,便于商务、投放、技术和下一轮 AI 接力。
+- `round:deposition` 现在会检查补证操作顺序文档存在,并确认文档包含 `intake:readiness`、`longrun:readiness`、`customer-effect:audit`、sample/smoke 边界和“客户效果不可宣称”口径。
+- `handoff:summary` 现在会在交接摘要中直接列出 `docs/business-proof-action-order.md`,下一轮 AI 或商务可以先按这份 15 步清单补齐真实证据。
+
+原因:
+
+- 当前 `optimization:status` 仍为 `complete=false`,真实材料预审和长跑就绪度未通过。
+- 需要把“缺什么、谁来补、补完跑什么命令、什么算通过”固定成可复用交付物,避免只沉淀状态报告但下一轮无法执行。
+
+## 2026-06-07 补充:15 步补证进度表
+
+已新增:
+
+- `scripts/business-proof-progress.js`
+- `scripts/business-proof-progress-smoke.js`
+- npm script:`business-proof:progress`
+- npm script:`business-proof:progress:smoke`
+
+处理结果:
+
+- 基于最新 intake readiness、video resource readiness、longrun readiness、proof-gap closure、handoff、evidence 和 round deposition,自动输出 15 步补证进度。
+- 每一步标记为 `pass`、`pending`、`fail` 或 `blocked_by_external_data`,避免只同步“总体未完成”而看不出下一步卡在哪里。
+- 当前进度表会明确显示:真实历史 Brief、真实候选视频、商务复核、客户效果审计仍受真实外部数据阻塞;视频 A/B 已有关闭证据但不能替代客户效果证明。
+- `round:deposition` 现在会检查 `business-proof-progress-summary.json` 是否存在、是否包含 15 步、是否记录 `pass/pending/fail/blocked_by_external_data` 统计。
+- `optimization:status` 现在新增 `business-proof-progress` 稳定状态项,用于标记 15 步进度同步能力已就绪;该项不代表客户效果证明完成。
+
+原因:
+
+- 长期优化计划要求实时同步进度;只给状态审计和交接摘要还不够直观。
+- 15 步进度表用于过程同步,不把 sample、smoke、模板、provider fallback 或接口 200 计为客户效果证明。
+
+## 2026-06-07 补充:下一步行动队列
+
+已新增:
+
+- `scripts/business-proof-next-actions.js`
+- `scripts/business-proof-next-actions-smoke.js`
+- npm script:`business-proof:next-actions`
+- npm script:`business-proof:next-actions:smoke`
+
+处理结果:
+
+- 基于 `business-proof-progress-summary.json`、`proof-gap-closure-summary.json`、`intake-readiness-summary.json` 和 `video-resource-readiness-summary.json` 生成下一步行动队列。
+- 队列输出 JSON、Markdown 和 CSV,字段包括优先级、负责人、动作、当前证据、执行命令、预期产物和通过标准。
+- 当前会把“填真实历史 Brief”“填真实视频资源”“真实材料预审”排在最前,避免只看到多个 blocked 项但无法决定先补什么。
+- 行动队列现在增加“按负责人汇总”,方便商务、投放/商务、技术/AI 分别领取自己的补证动作。
+- 行动队列现在额外生成 `by-owner/*.md` 和 `by-owner/*.csv`,每个负责人可直接打开自己的补证任务文件。
+- `evidence:index` 现在会把行动队列识别为 `business-proof-next-actions`,证明等级为 `smoke_or_local`。
+- `round:deposition` 现在会检查行动队列是否存在且至少有一条可分派动作。
+- `optimization:status` 现在新增 `business-proof-next-actions` 稳定状态项。
+- 默认 `acceptance` 现在会运行 `business-proof:progress:smoke` 和 `business-proof:next-actions:smoke`,发布/总验收时会守住进度同步与行动队列链路。
+
+原因:
+
+- 长期优化计划要求持续执行和实时同步;行动队列用于把状态同步转成可分派任务。
+- 行动队列只安排补证,不证明客户效果完成。
+
+## 2026-06-06 补充:软件端去重表与负样本归因门禁
+
+已新增:
+
+- `scripts/export-software-table.js`
+- `scripts/software-table-smoke.js`
+- npm script:`software:export`
+- npm script:`software:smoke`
+
+处理结果:
+
+- 扫描历史 `manual-review-sample.csv`,确认旧 overnight 汇总存在跨策略重复。例如 `outputs/overnight-quality-1780675711800/manual-review-sample.csv` 原始 60 行,按 `brief编号 + 平台 + 主页链接/博主名称` 去重后 15 行,重复 45 条。
+- 已输出软件端格式去重表:`outputs/overnight-quality-1780675711800/software-client-list.dedup.csv`。
+- 输出表固定中文表头、UTF-8 BOM、重复键为 0、排名从 1 连续。
+- `software:smoke` 已纳入 `npm run acceptance`,发布前会自动校验表头、去重和连续排名。
+
+已更新:
+
+- `scripts/review-metrics.js`
+- `scripts/review-metrics-smoke.js`
+- `docs/manual-review-handoff.md`
+- `docs/ai-overnight-optimization-task.md`
+
+负样本口径调整:
+
+- 负样本现在包括:`跑偏`、`硬性规则违规`、`调性不符`、`主页质感不符`、`参考账号不像`。
+- 负样本必须填写 `归因类型`、`失败归因` 或 `归因` 任一字段。
+- `review:metrics -- --strict` 会在负样本缺归因时失败,避免只有人工标签、没有可反哺原因。
+
+本轮验证:
+
+- `node --check scripts/export-software-table.js`
+- `node --check scripts/software-table-smoke.js`
+- `npm.cmd run software:smoke`
+- `node --check scripts/review-metrics.js`
+- `node --check scripts/review-metrics-smoke.js`
+- `npm.cmd run review:metrics:smoke`
+- `npm.cmd run acceptance`
+
+## 2026-06-06 补充:provider mock 合约纳入发布验收
+
+已更新:
+
+- `scripts/acceptance.js`
+- `scripts/optimization-status-audit.js`
+
+处理结果:
+
+- `npm run acceptance` 现在会执行 `npm run acceptance:providers:mock`。
+- `optimization:status` 的“基础发布链路”现在要求:
+  - `acceptance`
+  - `smoke:package`
+  - `mcp:smoke`
+  - `acceptance:providers:mock`
+  - `acceptance.js` 实际调用 provider mock 门禁。
+
+原因:
+
+- 长期优化计划的基础链路要求明确包含 `npm run acceptance:providers:mock`。
+- 之前该命令单独存在且可通过,但没有被发布总验收强制执行,容易出现“文档要求”和“发布前检查”不一致。
+
+## 2026-06-06 补充:跨 Brief 软件表排名口径修正
+
+已更新:
+
+- `scripts/export-software-table.js`
+- `scripts/overnight-quality-campaign.js`
+- `scripts/software-table-smoke.js`
+- `docs/tihao-experience-optimization-plan.md`
+- `docs/capability-map.md`
+- `README.md`
+- `docs/release-notes.md`
+
+处理结果:
+
+- 软件端汇总表不再使用全局排名。
+- 同一 `brief编号` 内的候选从 1 开始连续排名;多个 brief 同表输出时,每个 brief 独立连续。
+- `software:smoke` 已增加多 brief 样例,防止后续回退为全局排名。
+- 长期优化计划中的业务指标口径从“跑偏 + 硬性规则违规率”统一为“负样本率 + 负样本归因覆盖率”。
+
+## 2026-06-06 补充:no-token 友好状态自动验收
+
+已更新:
+
+- `scripts/smoke-package.js`
+- `scripts/optimization-status-audit.js`
+
+处理结果:
+
+- live 模式未配置 token 时,`smoke:package` 不只检查 `needs_token`,还会断言:
+  - `errors=[]`
+  - 不暴露原始 `401/403`
+  - 不暴露 `Authorization` header
+  - 不暴露 bearer token、sessionToken 或模型 token
+- `smoke-package` 的软件端 CSV 排名断言同步为“每个 brief 内连续”。
+- `optimization:status` 的基础发布链路现在会检查 `smoke-package` 是否包含友好鉴权状态断言。
+
+原因:
+
+- 长期优化计划要求 live/no-token/403/401 返回友好状态,不暴露原始错误和密钥。
+- 该要求属于本地可自动化验证项,不应只停留在文档说明。
+
+## 2026-06-06 补充:provider 失败路径公开输出安全验收
+
+已更新:
+
+- `scripts/provider-contract-audit.js`
+- `scripts/optimization-status-audit.js`
+
+处理结果:
+
+- provider mock 的失败路径现在会复现:
+  - 参考账号补证 provider 返回 403;
+  - 多模态 evidence provider 返回空结果。
+- 验收保留 JSON 里的结构化 provider 状态,供工程诊断。
+- 商务可见输出(assistantMessage、Markdown 报告、CSV)必须不出现:
+  - 原始 `401/403`
+  - 原始上游错误文本
+  - `Authorization` header
+  - bearer token
+  - 模型 token
+- `optimization:status` 的基础发布链路会检查 provider mock 是否包含公开输出安全断言。
+
+原因:
+
+- 长期优化计划要求 401/403/额度不足返回友好状态,不暴露原始错误和密钥。
+- provider 失败属于常见线上场景,必须在 mock 合约验收里固定下来。
+
+## 2026-06-07 补充:硬性规则和剔除账号本地验收
+
+已更新:
+
+- `scripts/hard-rules-smoke.js`
+- `package.json`
+- `scripts/acceptance.js`
+- `scripts/optimization-status-audit.js`
+
+处理结果:
+
+- 新增 `npm run hard-rules:smoke`。
+- `npm run acceptance` 现在会强制执行 hard-rules smoke。
+- hard-rules smoke 覆盖:
+  - 美容仪/脱毛仪场景下,男性泛账号不得标为强推荐;
+  - 明确竞品排除账号必须标为 `已剔除`;
+  - 已剔除账号不得进入软件端 CSV;
+  - 超预算账号不得标为强推荐,并必须带报价风险提示;
+  - 错平台账号在排序前过滤。
+- `optimization:status` 增加“硬性规则和剔除账号本地验收”项,要求 hard-rules smoke 存在并纳入 acceptance。
+
+原因:
+
+- 长期优化计划要求硬性约束违规率为 0,明确排除账号不得进入软件端交付表。
+- 这部分不依赖真实 provider,可用本地构造样例持续验收。
+
+## 2026-06-07 补充:参考账号冲突和强推荐解释验收
+
+已更新:
+
+- `scripts/reference-rules-smoke.js`
+- `package.json`
+- `scripts/acceptance.js`
+- `scripts/optimization-status-audit.js`
+
+处理结果:
+
+- 新增 `npm run reference-rules:smoke`。
+- `npm run acceptance` 现在会强制执行 reference-rules smoke。
+- reference-rules smoke 覆盖:
+  - 多个参考账号/参考视频风格冲突时,`referenceUsability.status` 必须为 `reference_conflict`;
+  - 报告必须明确提示“不能强行合并为单一调性标准”;
+  - 强推荐账号必须至少有 2 条 Brief 命中点;
+  - 强推荐账号必须至少有 1 条参考/风格证据;
+  - 强推荐推荐理由必须解释参考/风格命中依据。
+- `optimization:status` 增加“参考账号冲突和强推荐解释本地验收”项。
+
+原因:
+
+- 长期优化计划要求多参考账号风格冲突时不得强行合并。
+- 有参考账号时,强推荐必须说明“类型相似”或“调性相似”的依据。
+- 这些能力可以用构造指纹在 sample 模式下稳定验收。
+
+## 本轮已完成
+
+### 1. 需求三分层与隐性规则
+
+已新增:
+
+- `mcp/src/features/tihao-sourcing/experience-rules.js`
+- `criteria.requirementLayers`
+- `criteria.categoryRules`
+- `criteria.referenceUsability`
+- `criteria.sourcingStrategy`
+
+当前支持:
+
+- 硬性量化指标:平台、粉丝、预算、目标数量、地域、排除项;
+- 产品/人群隐性规则:美容仪/脱毛仪、母婴 DHA、敏感肌护肤、食品饮料、家清家居;
+- 参考账号可参考性:类型锚点、调性锚点、风格冲突、弱偏好;
+- 分平台找号策略:小红书/蒲公英参考相似链路,抖音星图标签不作为充分证明。
+
+### 2. 主页最近内容证据化
+
+已新增:
+
+- `mcp/src/features/tihao-sourcing/homepage-evidence.js`
+- `candidate.homepageEvidence`
+- `candidate.recentContentFitScore`
+- `candidate.visualQualityScore`
+- `candidate.toneConsistencyScore`
+
+当前逻辑:
+
+- 优先使用候选已有 `recentEvidence`、内容标签、人设标签;
+- 计算最近内容匹配、视觉质感、调性一致性;
+- 低置信度或命中风险时给出深挖建议;
+- 强推荐资格新增主页证据分和隐性规则约束。
+
+### 3. 排序与强推荐规则升级
+
+已更新:
+
+- `mcp/src/features/tihao-sourcing/ranker.js`
+
+变化:
+
+- 排序分加入主页最近内容证据和隐性规则分;
+- 硬排除和隐性规则违规会增加风险扣分;
+- 强推荐必须满足:
+  - 无硬性排除;
+  - 无关键隐性规则违规;
+  - 至少 2 条 Brief 命中;
+  - 至少 1 条参考/风格命中;
+  - 最近内容证据分达到最低门槛。
+
+### 4. 报告与 CSV 输出升级
+
+已更新:
+
+- `mcp/src/features/tihao-sourcing/report.js`
+
+新增报告章节:
+
+- `需求三分层与隐性规则`
+- `参考账号可参考性判断`
+- `主页最近内容证据`
+
+CSV 新增字段:
+
+- `主页证据分`
+- `视觉质感分`
+- `调性一致分`
+- `隐性规则命中`
+- `隐性规则违规`
+- `homepageEvidenceNotes`
+
+修复:
+
+- `剔除/降级原因` 不再把普通备选误写成“不进入商务名单”;已剔除才不能进商务可用名单,需复核必须补证后再判断。
+
+### 5. 商务体验反馈回灌
+
+已新增:
+
+- `scripts/import-feedback-memory.js`
+- `scripts/feedback-import-smoke.js`
+- npm script:`feedback:import`
+- npm script:`feedback:smoke`
+
+已更新:
+
+- `mcp/src/features/tihao-sourcing/preference-memory.js`
+
+当前支持从 CSV 导入:
+
+- `brief编号`
+- `平台`
+- `博主名称`
+- `主页链接`
+- `人工复核标签`
+- `反馈标签`
+- `反馈原因`
+- `归因类型`
+- `影响范围`
+- `是否升级为团队规则`
+- `下一轮动作`
+
+沉淀到 memory:
+
+- `feedbackRecords`
+- `feedbackSummary`
+- `personalPreferences`
+- `clientBrandPreferences`
+- `teamRuleCandidates`
+- `blockedCreators`
+- `blockedStyles`
+
+## 验收证据
+
+已通过:
+
+```powershell
+npm run feedback:smoke
+npm run acceptance:pm
+npm run smoke:briefs
+npm run acceptance:providers:mock
+npm run mcp:smoke
+npm run smoke:package
+npm run acceptance
+```
+
+小规模质量矩阵:
+
+```powershell
+$env:TIHAO_OVERNIGHT_FIXTURES="dha-mom-baby,sensitive-skin-repair,healthy-snack"
+$env:TIHAO_OVERNIGHT_VARIANTS="baseline-live,result-first,result-first-broad"
+$env:TIHAO_OVERNIGHT_MAX_RUNS="9"
+$env:TIHAO_OVERNIGHT_FAIL_ON_GATES="false"
+npm run overnight:quality
+```
+
+结果:
+
+- 输出目录:`outputs/overnight-quality-1780730115072`
+- `liveEnabled=false`
+- fixtures:3
+- variants:3
+- runs:9
+- failures:0
+- gatePass:true
+- leak scan:0
+
+说明:这次矩阵是 sample/结构性验证,不证明真实 live 命中率。真实结果提升仍需用客户 token 和 live provider 跑矩阵验证。
+
+## 下一批优先任务
+
+### P0:真实最近内容 provider
+
+当前主页证据主要来自候选已有 `recentEvidence` 和标签。下一步需要接入真实主页最近 10/20 篇内容 provider:
+
+- 标题;
+- 封面;
+- 内容类型;
+- 互动;
+- 发布时间;
+- 视频/图文资源;
+- 竞品或风险信号。
+
+验收:
+
+- `homepageEvidence.source=provider`;
+- top 10 最近内容证据覆盖率 >= 80%;
+- 低置信度账号能自动进入补证/需复核。
+
+### P0:反馈 CSV 回灌进入二轮提号
+
+当前已能导入 memory。下一步需要做端到端二轮验证:
+
+1. 第一轮导出软件端表;
+2. 人工填写反馈标签和原因;
+3. `npm run feedback:import -- --feedback <csv> --memory <json>`;
+4. 第二轮 `tihao_brief_sourcing_run` 使用同一 memory;
+5. 验证被拉黑/硬性违规账号不再进入商务可用名单;
+6. 报告说明本轮应用了哪些反馈。
+
+### P1:团队规则候选晋升机制
+
+当前已沉淀 `teamRuleCandidates`,还需要:
+
+- 同类反馈连续出现 3 次以上再建议升级;
+- 每条团队规则可追溯来源 brief、账号、反馈人、原因;
+- 区分个人偏好、客户偏好和团队规则。
+
+### P1:真实 live 矩阵
+
+需要在有安全 token/额度时跑:
+
+- 至少 3 个 fixture;
+- `baseline-live,result-first,result-first-broad`;
+- 有参考账号时加入 `reference-account/homepage-evidence` 策略;
+- 输出人工复核通过率、负样本率、客户选中率指标。
+
+## 当前边界
+
+- 本轮没有接入新的真实主页最近内容 provider;
+- 本轮没有跑真实 live 质量矩阵;
+- 反馈导入当前支持 CSV,XLSX 需要后续加入稳定解析方案或要求软件端导出 CSV;
+- 客户选中率需要真实客户最终选择数据,当前只能用人工复核标签做近似指标。
+
+## 2026-06-06 追加优化记录
+
+### 已完成
+
+- 软件端交付表改为固定表头,字段见 `docs/software-client-table-format.md`。
+- 报告和 CSV 输出加入去重:同一 `brief编号 + 平台 + 主页链接` 不重复;无主页链接时按 `brief编号 + 平台 + 博主名称` 去重。
+- 已剔除账号不进入软件端交付表;去重和剔除后排名重新从 1 连续编号。
+- 修复报告、排序器、主页证据、偏好记忆和 sample 数据里的乱码输出。
+- 修复“低硬广”被误判为“硬广”排除的问题,避免把低商业痕迹账号误杀。
+- 接入主页最近内容 provider 工作流:
+  - 支持 `homepageEvidenceBaseUrl` / `homepageProviderBaseUrl`。
+  - 支持 `homepageEvidenceFile` / `homepageEvidenceJsonPath` 本地证据。
+  - 支持 `homepageEvidenceToken` / `homepageProviderToken`。
+  - 输出 `criteria.homepageEvidenceStatus`。
+- 新增 `homepage:smoke`,用本地 mock provider 真实跑通主页证据接入。
+- 新增 `feedback:roundtrip`,验证反馈 CSV 导入 memory 后,第二轮自动剔除被反馈拉黑/跑偏账号。
+- 重写 `smoke:package` 为中文和软件端表格新契约。
+- 新增 ASCII 文件名 sample brief:`fixtures/sample-briefs/skincare-sample.md`,避免 npm 脚本中文路径在 Windows 下被命令行编码污染。
+
+### 验收结果
+
+已通过:
+
+```powershell
+npm run smoke:package
+npm run smoke:briefs
+npm run feedback:smoke
+npm run feedback:roundtrip
+npm run homepage:smoke
+npm run mcp:smoke
+npm run acceptance:pm
+npm run acceptance:providers:mock
+npm run acceptance
+```
+
+软件端表格抽检:
+
+- `duplicateKeys=0`
+- 排名连续
+- CSV 使用 UTF-8 BOM,Excel 友好
+- 表头与 `docs/software-client-table-format.md` 一致
+
+泄密/乱码扫描:
+
+- 范围:`docs`、`skills`、`mcp/src`、`scripts`、`fixtures`
+- 结果:未命中 token、Authorization、常见乱码标记
+
+### 当前边界更新
+
+- 主页最近内容 provider 已完成 mock 合同验收,但还没有用真实线上 provider 做质量矩阵。
+- 反馈二轮闭环已完成 sample 验收;真实客户选中率仍需要历史/线上反馈数据验证。
+- 本轮证明的是结构、去重、闭环和 mock provider 可用,不等同于真实 live 命中率已经达到 30%/50%。
+
+## 2026-06-06 夜跑矩阵增强记录
+
+### 已完成
+
+- `overnight:quality` 增加软件端交付表验收:
+  - 读取每个 run 的 `tihao-sourcing-client-list.csv`。
+  - 校验固定软件端表头。
+  - 统计重复键数量。
+  - 校验去重/剔除后排名是否从 1 连续。
+- `overnight:quality` 增加主页证据状态验收:
+  - 每个 run 必须输出 `criteria.homepageEvidenceStatus`。
+  - aggregate summary 中保留 `homepageProviderStatus`。
+- aggregate summary 新增字段:
+  - `softwareDuplicateKeyCount`
+  - `softwareRankContinuous`
+  - `homepageProviderStatus`
+- aggregate report 改为中文新版,并给 `.md` 输出加 UTF-8 BOM,避免 Windows 打开乱码。
+- `manual-review-sample.csv` 表头改为中文复核字段。
+- `docs/overnight-quality-runbook.md` 已重写为中文手册,包含 sample、受控 live、完整 live、门禁和人工复核说明。
+
+### 验收结果
+
+小矩阵验证:
+
+```powershell
+$env:TIHAO_OVERNIGHT_LIVE="false"
+$env:TIHAO_OVERNIGHT_FIXTURES="sensitive-skin-repair"
+$env:TIHAO_OVERNIGHT_VARIANTS="baseline-live,result-first"
+$env:TIHAO_OVERNIGHT_MAX_RUNS="2"
+$env:TIHAO_OVERNIGHT_FAIL_ON_GATES="true"
+npm run overnight:quality
+```
+
+结果:
+
+- `runs=2`
+- `failures=0`
+- `gatePass=true`
+- `softwareDuplicateKeyCount=0`
+- `softwareRankContinuous=true`
+- `homepageProviderStatus=not_requested`
+
+完整验收:
+
+```powershell
+npm run acceptance
+```
+
+结果:通过。
+
+泄密/乱码扫描:
+
+- 范围:`docs`、`skills`、`mcp/src`、`scripts`、`fixtures`
+- 结果:`Count=0`
+
+### 当前边界更新
+
+- 夜跑矩阵现在能验证软件端结构和主页证据状态,但本轮仍是 sample 小矩阵。
+- 真实 live 命中率、客户选中率、视频证据提升幅度仍需要用真实 token、真实 Brief 和客户反馈数据长跑验证。
+
+## 2026-06-06 Live 长跑前置预检记录
+
+### 已完成
+
+- 新增 `scripts/live-preflight.js`。
+- 新增 npm script:`npm run live:preflight`。
+- `docs/live-provider-integration-runbook.md` 已加入预检、严格模式、live acceptance、video A/B 的执行顺序。
+
+预检能力:
+
+- 检查是否存在 Parse sessionToken。
+- 尝试解析 Parse 用户身份,不把 token 写入输出。
+- 检查 company 是否可从环境变量或用户信息解析。
+- 检查 reference provider、homepage provider、evidence provider、豆包视频分析 provider 是否配置。
+- 检查视频模型是否为 `doubao-seed-2-0-pro`。
+- 输出 `live-preflight-summary.json`,只记录布尔状态、provider 配置状态和下一步动作,不记录密钥明文。
+
+### 验收结果
+
+本地无真实环境变量时运行:
+
+```powershell
+npm run live:preflight
+```
+
+结果:
+
+- 输出目录:`outputs/live-preflight-1780733665849`
+- `readyForLiveAcceptance=false`
+- `readyForVideoAb=false`
+- `failures=2`
+- `warnings=3`
+- 失败项:缺少 sessionToken、缺少 company。
+- 警告项:未配置 reference/homepage/evidence 或 video provider。
+
+完整验收:
+
+```powershell
+npm run acceptance
+```
+
+结果:通过。
+
+### 当前边界更新
+
+- `live:preflight` 不扣费,也不证明 live 提号命中率。
+- 预检通过只代表可以开始真实 live 或 video A/B 测试。
+- 当前环境没有运行真实 live 矩阵,也没有运行真实视频 A/B;这些仍需要运行时 token、company、真实 provider URL 和测试 Brief。
+
+## 2026-06-06 参考视频 A/B 验收增强记录
+
+### 已完成
+
+- 更新 `scripts/video-hit-rate-audit.js`,把参考视频 A/B 从“资源数量检查”升级为门禁化验收。
+- 更新 `docs/acceptance-checklist.md` 和 `docs/live-provider-integration-runbook.md`,统一视频验收口径。
+
+新增门禁:
+
+- 视频模型必须是 `doubao-seed-2-0-pro`。
+- B 组必须拿到真实参考资源。
+- B 组必须拿到视频 URL。
+- B 组必须拿到封面、ASR/字幕、帧图资源中的至少一类。
+- 视频分析 provider 状态必须为 `ok`。
+- B 组必须生成证据卡。
+- 证据卡不能只是 `pending`、`fallback` 或“待补证据”占位。
+- 证据卡必须包含 text/ASR/visual/frame 中至少一类可解释信号。
+- B 组 top 10 证据命中候选必须增加,且 top 10 平均分、参考风格分不得下降。
+- B 组强推荐数量不得下降。
+
+新增输出:
+
+- `video-hit-rate-summary.json`
+- `video-hit-rate-report.md`
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check scripts/video-hit-rate-audit.js
+npm run acceptance
+npm run acceptance:providers:mock
+```
+
+精确泄密/乱码扫描:
+
+- 范围:`docs`、`scripts`、`mcp/src`、`skills`、`fixtures`
+- 结果:`Count=0`
+
+### 当前边界更新
+
+- 本轮增强了真实视频 A/B 的验收标准,但当前环境没有真实 token/company/provider,所以没有运行 `npm run acceptance:video-ab`。
+- 只有 `acceptance:video-ab` 在真实环境通过,才可以说“本次样本里视频分析确实提升了提号结果”。
+
+## 2026-06-06 团队规则升级机制记录
+
+### 已完成
+
+- 更新 `mcp/src/features/tihao-sourcing/preference-memory.js`。
+- 更新 `scripts/import-feedback-memory.js`。
+- 更新 `scripts/feedback-import-smoke.js`。
+- 更新 `mcp/src/features/tihao-sourcing/report.js`。
+- 更新 `skills/tihao-creator-sourcing/references/output-format.md`。
+
+新增能力:
+
+- memory 新增 `teamRuleSuggestions`。
+- 同类反馈累计达到 3 次后,自动生成“建议升级为团队规则”。
+- 每条建议保留:
+  - 反馈次数;
+  - 影响范围;
+  - 反馈标签;
+  - 归因类型;
+  - 建议规则;
+  - 来源 brief、账号、平台、主页链接、反馈原因和时间。
+- `feedbackSummary` 新增 `teamRuleSuggestionCount`。
+- 导入反馈后的命令摘要会显示团队规则升级建议数量。
+- 生成提号报告时,如果 memory 中有团队规则建议,会新增 `反馈记忆与团队规则建议` 章节。
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check mcp/src/features/tihao-sourcing/preference-memory.js
+node --check scripts/import-feedback-memory.js
+node --check scripts/feedback-import-smoke.js
+npm run feedback:smoke
+npm run feedback:roundtrip
+npm run acceptance
+```
+
+额外端到端检查:
+
+- 构造 3 条同类“脱毛仪不应推男性泛账号”反馈;
+- 写入 memory;
+- sample 模式生成提号报告;
+- 报告中出现 `反馈记忆与团队规则建议`;
+- `criteria.memory.teamRuleSuggestions.length=1`。
+
+精确泄密/乱码扫描:
+
+- 范围:`docs`、`scripts`、`mcp/src`、`skills`、`fixtures`
+- 结果:`Count=0`
+
+### 当前边界更新
+
+- `teamRuleSuggestions` 是建议升级,不会静默替代人工决策。
+- 团队确认后,仍需要后续把建议转成正式品类规则或客户规则。
+
+## 2026-06-06 人工复核质量指标入口记录
+
+### 已完成
+
+- 新增 `scripts/review-metrics.js`。
+- 新增 `scripts/review-metrics-smoke.js`。
+- 新增 npm script:
+  - `npm run review:metrics`
+  - `npm run review:metrics:smoke`
+- `npm run acceptance` 已纳入 `review:metrics:smoke`。
+- 更新 `docs/manual-review-handoff.md`,补充人工复核标签、客户选择字段和质量指标统计方法。
+
+新增能力:
+
+- 读取商务标注后的软件端 CSV。
+- 校验软件端固定表头前缀。
+- 统计重复键数量。
+- 校验每个 brief 排名连续。
+- 统计商务可用率:`可直接发客户 + 商务复核` / 已标注总数。
+- 统计负样本率:`跑偏 + 硬性规则违规 + 调性不符 + 主页质感不符 + 参考账号不像` / 已标注总数。
+- 统计负样本归因覆盖率:已填写 `归因类型/失败归因/归因` 的负样本 / 负样本总数。
+- 统计客户选中率:`客户选中` / 已标注总数。
+- 输出:
+  - `review-metrics-summary.json`
+  - `review-metrics-report.md`
+
+门禁:
+
+- 商务可用率 >= 60%。
+- 负样本率 <= 10%。
+- 负样本归因覆盖率 = 100%。
+- 重复键 = 0。
+- 排名连续。
+- 客户选中率如果有客户选择字段,则短期目标 >= 30%;没有客户选择字段时只标记为待补,不能宣称达标。
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check scripts/review-metrics.js
+node --check scripts/review-metrics-smoke.js
+npm run review:metrics:smoke
+npm run acceptance
+```
+
+精确泄密/乱码扫描:
+
+- 范围:`docs`、`scripts`、`mcp/src`、`skills`、`fixtures`
+- 结果:`Count=0`
+
+### 当前边界更新
+
+- 这轮补齐了人工标注后的业务效果计算入口。
+- 当前仍没有真实客户最终选择数据;客户选中率 30%/50% 目标必须等真实 `客户选择` 字段回填后才能验证。
+
+## 2026-06-06 Overnight 策略矩阵补齐记录
+
+### 已完成
+
+- 更新 `scripts/overnight-quality-campaign.js`。
+- 更新 `docs/overnight-quality-runbook.md`。
+
+新增 overnight 策略:
+
+- `reference-account`:发布候选策略,用于验证参考账号/参考链接补证和相似度链路。
+- `homepage-evidence`:发布候选策略,用于验证主页最近内容、封面质感和调性一致性链路。
+
+aggregate 报告新增:
+
+- `参考补证状态`
+- `主页证据状态`
+
+完整 live 矩阵现已包含:
+
+```text
+baseline-live,reference-account,homepage-evidence,video-enhanced,result-first,result-first-risk,result-first-broad
+```
+
+### 验收结果
+
+新策略 sample 子集:
+
+```powershell
+$env:TIHAO_OVERNIGHT_LIVE="false"
+$env:TIHAO_OVERNIGHT_FIXTURES="sensitive-skin-repair"
+$env:TIHAO_OVERNIGHT_VARIANTS="baseline-live,reference-account,homepage-evidence"
+$env:TIHAO_OVERNIGHT_MAX_RUNS="3"
+$env:TIHAO_OVERNIGHT_FAIL_ON_GATES="true"
+npm run overnight:quality
+```
+
+结果:
+
+- 输出目录:`outputs/overnight-quality-1780734584209`
+- `runs=3`
+- `failures=0`
+- `gatePass=true`
+- 软件端重复键为 0。
+- 排名连续。
+
+带参考链接 sample 子集:
+
+```powershell
+$env:TIHAO_OVERNIGHT_LIVE="false"
+$env:TIHAO_OVERNIGHT_FIXTURES="dha-mom-baby"
+$env:TIHAO_OVERNIGHT_VARIANTS="baseline-live,reference-account"
+$env:TIHAO_OVERNIGHT_MAX_RUNS="2"
+$env:TIHAO_OVERNIGHT_FAIL_ON_GATES="true"
+npm run overnight:quality
+```
+
+结果:
+
+- 输出目录:`outputs/overnight-quality-1780734606550`
+- `runs=2`
+- `failures=0`
+- `gatePass=true`
+- 参考补证状态显示为 `needs_tikhub_enrichment`,不会误报真实补证完成。
+
+完整验收:
+
+```powershell
+npm run acceptance
+```
+
+结果:通过。
+
+精确泄密/乱码扫描:
+
+- 范围:`docs`、`scripts`、`mcp/src`、`skills`、`fixtures`
+- 结果:`Count=0`
+
+### 当前边界更新
+
+- 这轮补齐的是策略矩阵结构和状态展示。
+- 当前 sample 环境没有真实参考补证 provider 或主页 provider,因此不能宣称 `reference-account`、`homepage-evidence` 已在真实 provider 下提升命中率。
+
+## 2026-06-06 Provider 严格门禁记录
+
+### 已完成
+
+- 更新 `scripts/overnight-quality-campaign.js`。
+- 更新 `docs/overnight-quality-runbook.md`。
+
+新增环境门禁:
+
+- `TIHAO_GATE_REQUIRE_REFERENCE_PROVIDER=true`
+  - `reference-account` 策略的参考补证状态必须为 `ok`。
+  - 没有真实参考补证 provider、VOC social 视频详情或 TikHub 补证时会失败。
+- `TIHAO_GATE_REQUIRE_HOMEPAGE_PROVIDER=true`
+  - `homepage-evidence` 策略的主页证据状态必须为 `ok`。
+  - 只使用 fallback/标签轻量判断时会失败。
+
+这两个门禁默认关闭,不影响 sample smoke 和普通发布验收。真实 provider 联调或 live 长跑验收时应打开。
+
+### 验收结果
+
+默认门禁小矩阵:
+
+```powershell
+$env:TIHAO_OVERNIGHT_LIVE="false"
+$env:TIHAO_OVERNIGHT_FIXTURES="dha-mom-baby"
+$env:TIHAO_OVERNIGHT_VARIANTS="baseline-live,reference-account"
+$env:TIHAO_OVERNIGHT_MAX_RUNS="2"
+$env:TIHAO_OVERNIGHT_FAIL_ON_GATES="true"
+npm run overnight:quality
+```
+
+结果:
+
+- 输出目录:`outputs/overnight-quality-1780734793179`
+- `runs=2`
+- `failures=0`
+- `gatePass=true`
+
+严格参考 provider 门禁:
+
+```powershell
+$env:TIHAO_GATE_REQUIRE_REFERENCE_PROVIDER="true"
+npm run overnight:quality
+```
+
+结果:
+
+- 输出目录:`outputs/overnight-quality-1780734793371`
+- `gatePass=false`
+- 失败门禁:`reference-provider-ok`
+- 当前值:`needs_tikhub_enrichment`
+- 目标:`ok`
+
+严格主页 provider 门禁:
+
+```powershell
+$env:TIHAO_GATE_REQUIRE_HOMEPAGE_PROVIDER="true"
+npm run overnight:quality
+```
+
+结果:
+
+- 输出目录:`outputs/overnight-quality-1780734804090`
+- `gatePass=false`
+- 当前 sample/fallback 环境被正确阻断。
+
+完整验收:
+
+```powershell
+npm run acceptance
+```
+
+结果:通过。
+
+精确泄密/乱码扫描:
+
+- 范围:`docs`、`scripts`、`mcp/src`、`skills`、`fixtures`
+- 结果:`Count=0`
+
+### 当前边界更新
+
+- 严格门禁证明系统不会把 fallback 证据误当作真实 provider 验收。
+- 当前仍未在真实 provider 下跑出 `reference-provider-ok=pass` 或 `homepage-provider-ok=pass`。
+
+## 2026-06-06 历史数据集准备验收入口记录
+
+### 已完成
+
+- 新增 `scripts/historical-dataset-audit.js`。
+- 新增 `scripts/historical-dataset-audit-smoke.js`。
+- 新增 `docs/historical-dataset-guide.md`。
+- 新增 npm script:
+  - `npm run history:audit`
+  - `npm run history:audit:smoke`
+- `npm run acceptance` 已纳入 `history:audit:smoke`。
+
+新增能力:
+
+- 校验真实历史样本 JSON 是否可解析。
+- 校验是否达到最少 Brief 数,默认 5 个。
+- 校验每个样本是否包含客户原始 Brief。
+- 校验每个样本是否包含人工最终提报名单。
+- 校验每个样本是否包含客户最终选中/拒绝记录。
+- 校验拒绝样本是否包含拒绝原因或商务复核原因。
+- 校验类目覆盖是否足够。
+- 输出:
+  - `historical-dataset-audit.json`
+  - `historical-dataset-audit.md`
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check scripts/historical-dataset-audit.js
+node --check scripts/historical-dataset-audit-smoke.js
+npm run history:audit:smoke
+npm run acceptance
+```
+
+精确泄密/乱码扫描:
+
+- 范围:`docs`、`scripts`、`mcp/src`、`skills`、`fixtures`
+- 结果:`Count=0`
+
+### 当前边界更新
+
+- 这轮补齐的是历史数据到位前的验收入口和模板。
+- 当前仓库仍只有 sample/fixture 数据,没有真实历史客户最终选择数据。
+- 客户选中率 30%/50%、人工补号量减少 50% 仍必须等真实历史数据通过 `history:audit` 后再验证。
+
+## 2026-06-06 AI 夜跑任务书同步记录
+
+### 已完成
+
+- 重写 `docs/ai-overnight-optimization-task.md`,同步到当前完整 SOP。
+
+新版任务书已包含:
+
+- `npm run acceptance`
+- `npm run acceptance:providers:mock`
+- `npm run live:preflight -- --strict`
+- `npm run history:audit`
+- 受控 live 子集;
+- 完整 live 矩阵;
+- `reference-account` 和 `homepage-evidence` 策略;
+- 严格 provider 门禁:
+  - `TIHAO_GATE_REQUIRE_REFERENCE_PROVIDER=true`
+  - `TIHAO_GATE_REQUIRE_HOMEPAGE_PROVIDER=true`
+- `npm run acceptance:video-ab`
+- 人工复核标签;
+- `npm run review:metrics`
+- 每轮必须更新实施日志;
+- 禁止把 sample/fallback 当作真实命中率证明。
+
+### 验收结果
+
+已通过:
+
+```powershell
+npm run acceptance
+```
+
+精确泄密/乱码扫描:
+
+- 范围:`docs`、`scripts`、`mcp/src`、`skills`、`fixtures`
+- 结果:`Count=0`
+
+### 当前边界更新
+
+- 这轮是任务书同步,不新增真实 live 证据。
+- 后续 AI 夜跑应按新版任务书执行,不能再使用旧策略矩阵或旧复核标签。
+
+## 2026-06-06 对外安装与能力文档同步记录
+
+### 已完成
+
+- 更新 `README.md`。
+- 更新 `docs/install-guide.md`。
+- 更新 `docs/capability-map.md`。
+- 更新 `docs/release-notes.md`。
+
+同步内容:
+
+- `npm run live:preflight`
+- `npm run review:metrics`
+- `npm run history:audit`
+- overnight 完整策略矩阵;
+- 严格 provider 门禁;
+- 软件端表格验收;
+- 人工复核/客户选中率统计;
+- 历史数据集准备验收;
+- 反馈二轮闭环和 `teamRuleSuggestions`;
+- 没有真实 provider/live/客户选择字段时不能宣称命中率达标。
+
+### 验收结果
+
+已通过:
+
+```powershell
+npm run acceptance
+```
+
+精确泄密/乱码扫描:
+
+- 范围:`docs`、`README.md`、`scripts`、`mcp/src`、`skills`、`fixtures`
+- 排除:`node_modules`、`.claude` 本地安装副本
+- 结果:`Count=0`
+
+### 当前边界更新
+
+- 这轮只同步用户侧文档和发布记录,不新增真实 live/provider 证据。
+- README、安装指南和能力地图已能看到最新验收入口。
+
+## 2026-06-06 优化计划状态审计入口记录
+
+### 已完成
+
+- 新增 `scripts/optimization-status-audit.js`。
+- 新增 npm script:`npm run optimization:status`。
+- 更新 `README.md`。
+- 更新 `docs/ai-overnight-optimization-task.md`。
+
+新增能力:
+
+- 按优化计划要求输出状态矩阵。
+- 区分:
+  - `passed`:当前代码、脚本或本地验收已覆盖;
+  - `ready_not_proven`:入口已具备,但需要真实 token/provider/数据证明;
+  - `blocked_by_external_data`:需要真实 live 权限、provider 或客户选择数据;
+  - `missing`:实现缺失。
+- 输出:
+  - `optimization-status-summary.json`
+  - `optimization-status-report.md`
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check scripts/optimization-status-audit.js
+npm run optimization:status
+```
+
+状态审计结果:
+
+- `passed=8`
+- `ready_not_proven=2`
+- `blocked_by_external_data=2`
+- `complete=false`
+
+未完成项:
+
+- 视频 A/B 严格验收需要真实 token/company/provider 运行。
+- 真实历史数据准备验收需要真实历史 Brief、人工名单和客户选择数据。
+- 真实 live/provider 长跑效果仍缺真实 live/provider 证据。
+- 客户选中率 30%/40%/50% 和人工补号量减少 50% 仍缺真实客户选择和人工补号量基线。
+
+精确泄密/乱码扫描:
+
+- 范围:`docs`、`README.md`、`scripts`、`mcp/src`、`skills`、`fixtures`
+- 排除:`node_modules`、`.claude` 本地安装副本
+- 结果:`Count=0`
+
+### 当前边界更新
+
+- 这个审计入口帮助追踪长期目标状态,不替代真实 live/provider/客户反馈验证。
+- 当前优化目标仍不能标记完成。
+
+## 2026-06-06 优化状态审计 Smoke 记录
+
+### 已完成
+
+- 更新 `scripts/optimization-status-audit.js`,导出 `buildStatusSummary`。
+- 新增 `scripts/optimization-status-smoke.js`。
+- 新增 npm script:`npm run optimization:status:smoke`。
+- `npm run acceptance` 已纳入 `optimization:status:smoke`。
+
+Smoke 校验:
+
+- 状态审计覆盖优化计划要求数量足够。
+- 至少存在 `passed` 项。
+- 至少存在 `ready_not_proven` 项。
+- 至少存在 `blocked_by_external_data` 项。
+- 在缺少真实 live/provider/客户选择数据时,不得把长期目标标为 complete。
+- 必须跟踪视频 A/B 证明项。
+- 必须跟踪客户选中率和人工补号量证明项。
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check scripts/optimization-status-audit.js
+node --check scripts/optimization-status-smoke.js
+npm run optimization:status:smoke
+npm run acceptance
+```
+
+精确泄密/乱码扫描:
+
+- 范围:`docs`、`README.md`、`scripts`、`mcp/src`、`skills`、`fixtures`
+- 排除:`node_modules`、`.claude` 本地安装副本
+- 结果:`Count=0`
+
+### 当前边界更新
+
+- 状态审计入口已被默认验收保护。
+- 目标仍因真实 live/provider/客户选择数据缺口不能标记完成。
+
+## 2026-06-07 夜跑负样本风险率口径统一
+
+### 已完成
+
+- 更新 `scripts/overnight-quality-campaign.js`:
+  - 夜跑门禁名从 `off-topic-rate` 改为 `negative-risk-rate`。
+  - 门禁阈值优先使用 `TIHAO_GATE_MAX_NEGATIVE_RISK_RATE`,并兼容旧变量 `TIHAO_GATE_MAX_OFF_TOPIC_RATE`。
+  - 报表用户可见列名统一为“负样本风险率”。
+  - 保留 `offTopicRate` 输出字段作为历史兼容字段,同时新增/优先使用 `negativeRiskRate`。
+- 更新夜跑 runbook、最新证据、长期优化计划、AI 夜跑任务、验收清单、历史数据集指南和参考证据路线图的指标表述。
+
+### 当前边界
+
+- 本次只统一自动化夜跑和文档口径,不新增真实 live/provider/客户选择证据。
+- `negativeRiskRate` 当前仍来自 fixture 的 offTopicTerms 自动规则,只能作为夜跑风险门禁;真实业务负样本率仍以人工复核表为准。
+
+## 2026-06-07 状态审计结构化进度同步
+
+### 已完成
+
+- 更新 `scripts/optimization-status-audit.js`:
+  - 每个状态项新增稳定 `id`。
+  - 每个状态项新增 `title`,默认等于中文 `requirement`。
+  - 状态报告明细表新增 `ID` 列,方便软件端或自动化任务读取。
+  - “反馈导入后二轮闭环”的下一步口径改为复测“负样本率”。
+- 更新 `scripts/optimization-status-smoke.js`:
+  - 验证 `video-ab-proof`、`customer-effect-proof` 等关键项稳定 id。
+  - 验证每个状态项都有 `id/title`。
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check scripts\optimization-status-audit.js
+node --check scripts\optimization-status-smoke.js
+npm run optimization:status:smoke
+npm run optimization:status
+npm run acceptance
+```
+
+状态审计结果:
+
+- `passed=10`
+- `ready_not_proven=2`
+- `blocked_by_external_data=2`
+- `complete=false`
+
+精确泄密/乱码扫描:
+
+- 范围:`docs`、`README.md`、`scripts`、`mcp/src`、`skills`、`fixtures`
+- 结果:`Count=0`
+
+### 当前边界
+
+- 状态同步入口现在可读、可机器解析,但长期目标仍需要真实 video A/B、真实历史 Brief、真实 live/provider 和客户选择数据证明。
+
+## 2026-06-07 历史数据客户效果证明门禁
+
+### 已完成
+
+- 更新 `scripts/historical-dataset-audit.js`:
+  - 新增 `manualSupplementBaseline`、`manualSupplementCount`、`manualAddedCount` 和 `metrics.manualSupplementCount` 识别。
+  - 新增 `withManualSupplementBaseline` 统计。
+  - 新增 `acceptance.allHaveManualSupplementBaseline`。
+  - 新增 `acceptance.readyForCustomerEffectProof`,用于区分“可长期长跑”和“可证明客户效果/人工补号减少”。
+  - 报告新增“是否可用于客户效果证明”和“人工补号基线”列。
+- 更新 `scripts/historical-dataset-audit-smoke.js`:
+  - 完整样本必须通过 `readyForCustomerEffectProof`。
+  - 缺少人工补号量基线的样本可以通过基础长跑准备,但不能通过客户效果证明。
+- 更新 `scripts/optimization-status-audit.js`:
+  - `historical-dataset` 状态项现在检查客户选择、拒绝原因和人工补号量基线门禁是否存在。
+- 更新 `docs/historical-dataset-guide.md`:
+  - JSON 模板新增 `manualSupplementBaseline`。
+  - 明确没有人工补号量基线时,不能宣称“人工补号量减少 50%”达标。
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check scripts\historical-dataset-audit.js
+node --check scripts\historical-dataset-audit-smoke.js
+npm run history:audit:smoke
+npm run optimization:status:smoke
+npm run optimization:status
+npm run acceptance
+```
+
+状态审计结果:
+
+- `passed=10`
+- `ready_not_proven=2`
+- `blocked_by_external_data=2`
+- `complete=false`
+
+精确泄密/乱码扫描:
+
+- 范围:`docs`、`README.md`、`scripts`、`mcp/src`、`skills`、`fixtures`
+- 结果:`Count=0`
+
+### 当前边界
+
+- 本轮增强了真实历史数据的验收入口,但仍没有真实历史 Brief、人工名单、客户选择和人工补号量基线数据,因此不能把长期目标标记为完成。
+
+## 2026-06-07 视频 A/B 验收逻辑 Smoke
+
+### 已完成
+
+- 更新 `scripts/video-hit-rate-audit.js`:
+  - 抽出 `computeVideoAcceptance`,让视频 A/B 验收逻辑可以被本地 smoke 复用。
+  - 导出 `buildAcceptanceGates`、`computeVideoAcceptance`、`summarizeResourceEvidence`、`summarizeEvidenceQuality`、`summarizeRun`。
+  - `require()` 该脚本时不再触发真实 live 请求。
+- 新增 `scripts/video-hit-rate-audit-smoke.js`:
+  - 覆盖完整视频 A/B 证据通过路径。
+  - 覆盖缺视频 URL、provider 非 ok、证据卡待补、top10 分数下降、强推荐数量下降等失败路径。
+- 更新 `package.json`:
+  - 新增 `npm run acceptance:video-ab:smoke`。
+- 更新 `scripts/acceptance.js`:
+  - 默认 acceptance 纳入 `acceptance:video-ab:smoke`。
+- 更新 `scripts/optimization-status-audit.js`:
+  - `video-ab-proof` 状态项现在检查 live 脚本和 smoke 是否覆盖关键 A/B 门禁。
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check scripts\video-hit-rate-audit.js
+node --check scripts\video-hit-rate-audit-smoke.js
+npm run acceptance:video-ab:smoke
+npm run optimization:status:smoke
+npm run optimization:status
+npm run acceptance
+```
+
+状态审计结果:
+
+- `passed=10`
+- `ready_not_proven=2`
+- `blocked_by_external_data=2`
+- `complete=false`
+
+精确泄密/乱码扫描:
+
+- 范围:`docs`、`README.md`、`scripts`、`mcp/src`、`skills`、`fixtures`
+- 结果:`Count=0`
+
+### 当前边界
+
+- 本轮只保护视频 A/B 的本地判定逻辑,没有新增真实 video/provider/token 运行证据。
+- `acceptance:video-ab` 仍需要真实 token/company/provider 才能证明视频分析对真实提号命中率有提升。
+
+## 2026-06-07 反馈二轮闭环多标签验收
+
+### 已完成
+
+- 更新 `scripts/feedback-roundtrip-smoke.js`:
+  - 从单条“跑偏”反馈扩展为多条商务负样本反馈。
+  - 覆盖 `跑偏`、`调性不符`、`主页质感不符`、`参考账号不像`。
+  - 验证反馈导入后 `blockedCreators` 写入记忆。
+  - 验证第二轮 sample 提号结果中,已阻断账号不会进入 client-ready candidates。
+  - 验证调性/主页质感类反馈会写入 `blockedStyles`。
+  - 验证 3 条同类团队范围反馈会生成 `teamRuleSuggestions`。
+- 更新 `scripts/optimization-status-audit.js`:
+  - `feedback-roundtrip` 状态项现在检查多标签负样本、二轮剔除、blocked style 和团队规则建议覆盖。
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check scripts\feedback-roundtrip-smoke.js
+npm run feedback:roundtrip
+npm run optimization:status:smoke
+npm run optimization:status
+npm run acceptance
+```
+
+状态审计结果:
+
+- `passed=10`
+- `ready_not_proven=2`
+- `blocked_by_external_data=2`
+- `complete=false`
+
+精确泄密/乱码扫描:
+
+- 范围:`docs`、`README.md`、`scripts`、`mcp/src`、`skills`、`fixtures`
+- 结果:`Count=0`
+
+### 当前边界
+
+- 本轮验证的是 sample 二轮闭环和规则沉淀能力;真实客户反馈 CSV 导入后,还需要重新跑 `review:metrics` 和 live 矩阵,才能证明真实负样本率下降。
+
+## 2026-06-07 主页最近内容 Provider 契约与降级验收
+
+### 已完成
+
+- 更新 `scripts/homepage-evidence-provider-smoke.js`:
+  - 成功路径继续验证 provider 证据能进入候选主页证据。
+  - 新增 provider 请求契约断言:
+    - `POST /homepage-evidence`
+    - `contractVersion=tihao-homepage-evidence-v1`
+    - `requestedCapabilities` 包含 `recent_posts`、`cover_quality`、`category_consistency`、`tone_consistency`、`risk_review`
+    - 请求包含候选账号、`displayName`、`profileUrl`,并遵守 creators limit
+    - token 存在时只发给 provider,不写入公开输出
+  - 新增 401/额度不可用失败路径:
+    - 主流程仍返回 `ok`
+    - 结构化状态保留 `unauthorized_or_quota`
+    - 候选回退到本地轻量主页证据
+    - Markdown、CSV、assistantMessage 不暴露原始 401/403、上游错误文本、Authorization 或 bearer token
+- 更新 `mcp/src/features/tihao-sourcing/report.js`:
+  - 主页 provider 状态在公开报告中改为中文友好展示。
+  - 结构化 JSON 仍保留机器可读状态。
+- 更新 `scripts/optimization-status-audit.js`:
+  - `homepage-provider-entry` 状态项现在检查请求契约、成功补证、未授权降级和公开输出安全。
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check mcp\src\features\tihao-sourcing\report.js
+node --check scripts\homepage-evidence-provider-smoke.js
+npm run homepage:smoke
+npm run optimization:status:smoke
+npm run optimization:status
+npm run acceptance
+```
+
+状态审计结果:
+
+- `passed=10`
+- `ready_not_proven=2`
+- `blocked_by_external_data=2`
+- `complete=false`
+
+精确泄密/乱码扫描:
+
+- 范围:`docs`、`README.md`、`scripts`、`mcp/src`、`skills`、`fixtures`
+- 结果:`Count=0`
+
+### 当前边界
+
+- 本轮固定的是 provider 契约和失败降级体验;真实主页最近 10/20 篇内容质量仍需要真实 provider 数据证明。
+
+## 2026-06-07 分平台参考链路策略验收
+
+### 已完成
+
+- 新增 `scripts/platform-sourcing-strategy-smoke.js`:
+  - 使用 sample Brief 同时覆盖小红书和抖音。
+  - 验证小红书策略为 `reference_similarity`。
+  - 验证小红书报告说明“蒲公英/参考账号相似推荐 + 类型/调性二次筛选”。
+  - 验证抖音策略为 `keyword_or_recruitment`。
+  - 验证抖音报告说明“星图标签只作为候选入口,不作为适配证明;召回弱时用 APP 关键词搜索或招募补量”。
+- 更新 `package.json`:
+  - 新增 `npm run platform:strategy:smoke`。
+- 更新 `scripts/acceptance.js`:
+  - 默认 acceptance 纳入 `platform:strategy:smoke`。
+- 更新 `scripts/optimization-status-audit.js`:
+  - `reference-rules` 状态项现在同时检查参考冲突 smoke 和分平台策略 smoke。
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check scripts\platform-sourcing-strategy-smoke.js
+npm run platform:strategy:smoke
+npm run optimization:status:smoke
+npm run optimization:status
+npm run acceptance
+```
+
+状态审计结果:
+
+- `passed=10`
+- `ready_not_proven=2`
+- `blocked_by_external_data=2`
+- `complete=false`
+
+精确泄密/乱码扫描:
+
+- 范围:`docs`、`README.md`、`scripts`、`mcp/src`、`skills`、`fixtures`
+- 结果:`Count=0`
+
+### 当前边界
+
+- 本轮锁住的是分平台找号策略和报告解释;真实小红书/蒲公英相似账号 provider、抖音星图/招募补量效果仍需要 live 数据证明。
+
+## 2026-06-07 软件端表去重与 1.5 倍候选池验收
+
+### 已完成
+
+- 更新 `mcp/src/features/tihao-sourcing/report.js`:
+  - 软件端 `tihao-sourcing-client-list.csv` 继续按 `brief编号 + 平台 + 主页链接/博主名称` 去重。
+  - 有 `targetCount` 时,软件端可复核候选池改为 `Math.ceil(targetCount * 1.5)`。
+  - 没有目标人数时,仍默认输出 10 位可复核候选。
+  - 候选池不足时,报告 warning、校准问题和 nextActions 都明确提示“1.5 倍候选池”缺口。
+  - summary 新增 `reviewPoolTarget`,方便软件端或自动化任务读取。
+- 新增 `scripts/candidate-pool-smoke.js`:
+  - 验证重复主页链接只保留高分账号。
+  - 验证软件端表重复键为 0。
+  - 验证软件端表排名连续。
+  - 验证目标 4 人时输出 6 位可复核候选。
+  - 验证目标 10 人但候选不足时提示 15 位候选池缺口。
+- 更新 `package.json`:
+  - 新增 `npm run candidate-pool:smoke`。
+- 更新 `scripts/acceptance.js`:
+  - 默认 `npm run acceptance` 纳入 `candidate-pool:smoke`。
+- 更新 `scripts/optimization-status-audit.js`:
+  - `software-table` 状态项现在检查 1.5 倍候选池逻辑和 smoke 是否接入默认验收。
+- 新增 `docs/ai-optimization-task-software-table.md`:
+  - 把给 AI 的夜跑优化任务、软件端博主表格式、人工复核标签、去重和候选池验收整理成中文表格版。
+  - 去掉重复标签清单,改用一张人工复核标签表统一说明。
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check mcp\src\features\tihao-sourcing\report.js
+node --check scripts\candidate-pool-smoke.js
+node --check scripts\optimization-status-audit.js
+npm run software:smoke
+npm run optimization:status:smoke
+npm run candidate-pool:smoke
+npm run optimization:status
+npm run acceptance
+```
+
+状态审计结果:
+
+- `passed=10`
+- `ready_not_proven=2`
+- `blocked_by_external_data=2`
+- `complete=false`
+
+精确泄密/乱码扫描:
+
+- 范围:`docs`、`README.md`、`scripts`、`mcp/src`、`skills`、`fixtures`
+- 结果:`Count=0`
+
+### 当前边界
+
+- 本轮修的是软件端表格输出稳定性、重复处理和候选池规模,不新增真实 live/provider/客户选择证据。
+- 长期目标仍需要真实视频 A/B、真实历史 Brief、真实 live/provider 和客户选择数据证明。
+
+## 2026-06-07 夜跑人工复核工作表归因字段预置
+
+### 已完成
+
+- 更新 `scripts/overnight-quality-campaign.js`:
+  - `manual-review-sample.csv` 在软件端 17 列后新增 `客户选择`、`归因类型`、`反馈原因` 三列。
+  - 聚合报告的人工复核说明改为完整标签口径:`可直接发客户`、`商务复核`、`可投但需补证`、`跑偏`、`硬性规则违规`、`调性不符`、`主页质感不符`、`参考账号不像`。
+  - 报告明确负样本必须填写 `归因类型`,并给出可优化归因:需求解析错、隐性规则漏、召回关键词错、主页证据不足、视频证据误判、排序权重错、输出解释错、软件端表重复或排名不连续。
+  - 报告明确有客户反馈时填写 `客户选择`:客户选中、客户拒绝、待客户反馈。
+- 新增 `scripts/overnight-manual-review-smoke.js`:
+  - 跑最小 sample 夜跑。
+  - 检查 `manual-review-sample.csv` 已写出。
+  - 检查表头包含 `人工复核标签`、`客户选择`、`归因类型`、`反馈原因`。
+  - 检查人工复核表能被 `review:metrics` 读取。
+  - 检查重复键为 0、每个 brief 排名连续。
+  - 检查 `aggregate-report.md` 明确说明客户选择、归因类型和参考账号不像标签。
+- 更新 `package.json`:
+  - 新增 `npm run overnight:review:smoke`。
+- 更新 `scripts/acceptance.js`:
+  - 默认 `npm run acceptance` 纳入 `overnight:review:smoke`。
+- 更新 `scripts/optimization-status-audit.js`:
+  - `review-metrics` 状态项现在检查夜跑人工复核表是否预置客户选择、归因和反馈字段。
+- 更新文档:
+  - `docs/manual-review-handoff.md`
+  - `docs/overnight-quality-runbook.md`
+  - `docs/ai-overnight-optimization-task.md`
+  - `docs/ai-optimization-task-software-table.md`
+  - `docs/acceptance-checklist.md`
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check scripts\overnight-quality-campaign.js
+node --check scripts\overnight-manual-review-smoke.js
+node --check scripts\optimization-status-audit.js
+npm run overnight:review:smoke
+npm run review:metrics:smoke
+npm run optimization:status:smoke
+npm run optimization:status
+npm run acceptance
+```
+
+状态审计结果:
+
+- `passed=10`
+- `ready_not_proven=2`
+- `blocked_by_external_data=2`
+- `complete=false`
+
+精确泄密/乱码扫描:
+
+- 范围:`docs`、`README.md`、`scripts`、`mcp/src`、`skills`、`fixtures`
+- 结果:`Count=0`
+
+### 当前边界
+
+- 本轮提升的是人工复核工作表的可标注性和归因闭环,不新增真实客户选择数据。
+- 客户选中率、人工补号量减少和真实负样本率下降仍必须等商务/客户回填后用 `review:metrics` 验证。
+
+## 2026-06-07 强推荐参考/主页证据命中点结构化
+
+### 已完成
+
+- 更新 `mcp/src/features/tihao-sourcing/ranker.js`:
+  - 候选账号新增 `homepageEvidenceHitPoints`。
+  - `homepageEvidenceHitPoints` 从主页 matched terms、有效 review notes 和真实 provider 状态中提取。
+  - 推荐理由新增“主页证据命中”,让商务可以直接看到强推荐除 Brief 命中外的主页证据依据。
+- 更新 `scripts/reference-rules-smoke.js`:
+  - 强推荐仍必须至少有 2 条 `briefHitConditions`。
+  - 强推荐必须至少有 1 条 `referenceStyleHitPoints` 或 `homepageEvidenceHitPoints`。
+  - 强推荐推荐理由必须同时解释参考/风格证据和主页证据。
+- 更新 `scripts/product-acceptance-audit.js`:
+  - 产品验收检查候选 JSON 必须包含 `homepageEvidenceHitPoints`。
+  - 强推荐验收改为 Brief 命中 + 参考风格或主页证据命中。
+- 更新 `scripts/overnight-quality-campaign.js`:
+  - 夜跑 top10 摘要增加 `homepageEvidenceHitPoints` 和 `referenceStyleHitPoints`,方便后续人工抽检和策略对比。
+- 更新 `scripts/optimization-status-audit.js`:
+  - `reference-rules` 状态项现在检查参考风格或主页证据的结构化命中门禁。
+- 更新文档:
+  - `docs/tihao-experience-optimization-plan.md`
+  - `docs/acceptance-checklist.md`
+  - `docs/ai-optimization-task-software-table.md`
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check mcp\src\features\tihao-sourcing\ranker.js
+node --check scripts\reference-rules-smoke.js
+node --check scripts\product-acceptance-audit.js
+node --check scripts\overnight-quality-campaign.js
+node --check scripts\optimization-status-audit.js
+npm run reference-rules:smoke
+npm run acceptance:pm
+npm run optimization:status:smoke
+npm run acceptance
+npm run optimization:status
+```
+
+状态审计结果:
+
+- `passed=10`
+- `ready_not_proven=2`
+- `blocked_by_external_data=2`
+- `complete=false`
+
+精确泄密/乱码扫描:
+
+- 范围:`docs`、`README.md`、`scripts`、`mcp/src`、`skills`、`fixtures`
+- 结果:`Count=0`
+
+### 当前边界
+
+- 本轮提升的是强推荐解释和结构化质检能力,不新增真实 provider 或客户选择证据。
+- 真实“强推荐更准”仍需要 live 矩阵和商务/客户标注后用 `review:metrics` 验证。
+
+## 2026-06-07 主页质感风险强推荐硬门槛
+
+### 已完成
+
+- 更新 `mcp/src/features/tihao-sourcing/homepage-evidence.js`:
+  - 新增 `visualRiskSignals` 识别,覆盖 `封面下沉`、`封面混乱`、`画面模糊`、`排版混乱`、`低质`、`低质量`、`搬运`、`粗糙`、`杂乱`、`硬广感`。
+  - 视觉质感分会因 `visualRiskSignals` 扣分。
+  - `reviewNotes` 会输出 `主页质感风险:...`。
+- 更新 `mcp/src/features/tihao-sourcing/ranker.js`:
+  - 新增 `homepageQualityRisks`。
+  - 强推荐资格要求不存在 `homepageQualityRisks`,且 `visualQualityScore >= 55`。
+  - 风险提示会说明 `主页质感风险` 或 `视觉质感分低于强推荐门槛`。
+- 更新 `mcp/src/features/tihao-sourcing/homepage-evidence-provider.js`:
+  - 真实 provider 或本地文件返回的 `visualRiskSignals` 会被透传并参与强推荐降级。
+- 更新 `scripts/hard-rules-smoke.js`:
+  - 新增“低质封面护理号”样本,即使命中女性、护肤、精致、真人出镜和预算,也不能标为强推荐。
+  - 检查 `homepageQualityRisks` 包含 `封面下沉`。
+  - 检查风险提示包含 `主页质感风险`。
+- 更新 `scripts/homepage-evidence-provider-smoke.js`:
+  - 新增 provider/file 证据路径下的低质主页风险样本。
+  - 检查 provider 返回 `visualRiskSignals` 后候选不能强推荐。
+- 更新 `scripts/optimization-status-audit.js`:
+  - `hard-rules` 状态项现在检查低质主页风险降级。
+- 更新文档:
+  - `docs/tihao-experience-optimization-plan.md`
+  - `docs/acceptance-checklist.md`
+  - `docs/ai-optimization-task-software-table.md`
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check mcp\src\features\tihao-sourcing\homepage-evidence.js
+node --check mcp\src\features\tihao-sourcing\ranker.js
+node --check mcp\src\features\tihao-sourcing\homepage-evidence-provider.js
+node --check scripts\hard-rules-smoke.js
+node --check scripts\homepage-evidence-provider-smoke.js
+npm run hard-rules:smoke
+npm run homepage:smoke
+npm run optimization:status:smoke
+npm run acceptance
+npm run optimization:status
+```
+
+状态审计结果:
+
+- `passed=10`
+- `ready_not_proven=2`
+- `blocked_by_external_data=2`
+- `complete=false`
+
+精确泄密/乱码扫描:
+
+- 范围:`docs`、`README.md`、`scripts`、`mcp/src`、`skills`、`fixtures`
+- 结果:`Count=0`
+
+### 当前边界
+
+- 本轮把主页质感风险纳入强推荐硬门槛,不新增真实客户选中率或真实 provider 长跑效果证明。
+- 低质主页降级是否改善商务可用率,仍需在真实 live 矩阵和商务标注后用 `review:metrics` 验证。
+
+## 2026-06-07 参考占位证据拆分与软件端去重表格版
+
+### 已完成
+
+- 复核 `mcp/src/features/tihao-sourcing/ranker.js`:
+  - `referenceStyleHitPoints` 只保留真实匹配到的参考信号。
+  - `需补相似账号证据`、`待补参考风格证据` 等弱提示进入 `referenceFallbackHitPoints`。
+  - 候选 JSON 保留 `referenceEvidenceSource` 和 `referenceEvidenceConcrete`。
+  - 强推荐要求至少有真实参考信号或主页证据,不能只靠 fallback reference hint。
+  - 推荐理由里 fallback 提示使用 `参考待补`,与真实 `参考/风格命中` 区分。
+- 复核并强化 `scripts/reference-rules-smoke.js`:
+  - 强推荐的 `referenceStyleHitPoints` 不得包含 `待补`、`需补` 等占位证据。
+  - 只有占位参考提示的候选必须标记 `referenceEvidenceConcrete=false`,且不能进入强推荐。
+- 更新文档:
+  - `docs/tihao-experience-optimization-plan.md`
+  - `docs/acceptance-checklist.md`
+  - `docs/ai-optimization-task-software-table.md`
+- 新增 `scripts/export-software-markdown.js`:
+  - 将已去重的软件端 CSV 转换成同字段 Markdown 表格。
+  - 保留软件端 17 列固定表头。
+  - 输出原始行数、去重后行数、删除重复行数、去重口径和排名规则。
+- 更新 `package.json`:
+  - 新增 `npm run software:markdown`。
+- 已基于 `outputs/overnight-quality-1780682025651/manual-review-sample.csv` 输出软件端去重表:
+  - CSV:`outputs/overnight-quality-1780682025651/software-client-list.dedup.csv`
+  - Markdown:`outputs/overnight-quality-1780682025651/software-client-list.dedup.md`
+  - 摘要:`outputs/overnight-quality-1780682025651/software-client-list.dedup.summary.json`
+
+### 去重表结果
+
+- 原始行数:60
+- 去重后行数:28
+- 删除重复行数:32
+- 去重口径:同一 `brief编号 + 平台 + 主页链接`;没有主页链接时使用 `brief编号 + 平台 + 博主名称`。
+- 排名规则:去重后按每个 brief 重新从 1 排名。
+- 排名连续:`true`
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check mcp\src\features\tihao-sourcing\ranker.js
+node --check scripts\reference-rules-smoke.js
+node --check scripts\overnight-quality-campaign.js
+node --check scripts\optimization-status-audit.js
+node --check scripts\export-software-markdown.js
+npm run reference-rules:smoke
+npm run acceptance:pm
+npm run optimization:status:smoke
+npm run software:smoke
+npm run acceptance
+npm run optimization:status
+```
+
+状态审计结果:
+
+- `passed=10`
+- `ready_not_proven=2`
+- `blocked_by_external_data=2`
+- `complete=false`
+
+精确泄密/乱码扫描:
+
+- 范围:`docs`、`README.md`、`scripts`、`mcp/src`、`skills`、`fixtures`
+- 结果:`COUNT=0`
+
+### 当前边界
+
+- 本轮证明的是参考占位证据不会误当真实强推荐证据,且软件端去重表可导出为 CSV 和 Markdown 表格版。
+- 本轮不新增真实 provider/live/客户选择证据。
+- 视频分析提升命中率、客户选中率和人工补号量减少仍需要真实 live 矩阵与商务/客户标注数据证明。
+
+## 2026-06-07 泄密/乱码自动化门禁
+
+### 已完成
+
+- 新增 `scripts/leak-mojibake-audit.js`:
+  - 扫描范围:`docs`、`README.md`、`scripts`、`mcp/src`、`skills`、`fixtures`。
+  - 检查 OpenAI 风格 key、Parse sessionToken、Authorization Bearer 等 token-like secret。
+  - 检查常见中文 mojibake 和替换字符。
+  - 命中时只输出脱敏样例,不在报告里暴露完整密钥。
+  - 脚本自身跳过扫描,避免因检测规则文本造成误报。
+- 更新 `package.json`:
+  - 新增 `npm run hygiene:smoke`。
+- 更新 `scripts/acceptance.js`:
+  - 默认 `npm run acceptance` 纳入 `hygiene:smoke`。
+- 更新 `scripts/optimization-status-audit.js`:
+  - 新增 `output-hygiene` 状态项。
+  - 状态审计现在检查 `hygiene:smoke` 是否存在、是否覆盖 sessionToken 和乱码模式、是否进入默认 acceptance。
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check scripts\leak-mojibake-audit.js
+node --check scripts\optimization-status-audit.js
+npm run hygiene:smoke
+npm run optimization:status:smoke
+npm run acceptance
+npm run optimization:status
+```
+
+`hygiene:smoke` 结果:
+
+- `scannedFiles=80`
+- `hitCount=0`
+
+状态审计结果:
+
+- `passed=11`
+- `ready_not_proven=2`
+- `blocked_by_external_data=2`
+- `complete=false`
+
+### 当前边界
+
+- 本轮把泄密和乱码检查从人工抽检升级为发布前自动门禁。
+- 本轮仍不新增真实 live/provider/客户选择数据。
+- 长期目标仍需要真实视频 A/B、真实历史 Brief、真实 live/provider 和客户选择数据证明。
+
+## 2026-06-07 参考链路业务效果对照指标
+
+### 已完成
+
+- 更新 `scripts/review-metrics.js`:
+  - 新增分策略指标 `byStrategy`。
+  - 新增 `strategyComparison`,把 `reference-account`、`homepage-evidence`、`video-enhanced` 归为参考/证据增强链路,把 `baseline-live`、`brief-only`、`keyword-only` 归为关键词/基础基线链路。
+  - 新增 `referenceCustomerSelectedRatePass`:当参考/证据增强链路有客户选择数据时,要求客户选中率 >= 40%。
+  - 新增 `referencePassRateHigherThanKeyword`:当参考链路和关键词基线都有标注样本时,要求参考链路商务可用率高于关键词/基础基线。
+  - 没有客户选择或缺少对照组时,相关指标标记为待补,不宣称达标。
+  - `review-metrics-report.md` 新增“分策略指标”和“参考链路对照”章节。
+- 更新 `scripts/review-metrics-smoke.js`:
+  - 验证无参考/关键词对照组时指标保持待补。
+  - 验证有 `baseline-live` 与 `reference-account/homepage-evidence` 标注样本时,参考链路通过率对照能正确计算并通过。
+- 更新 `scripts/optimization-status-audit.js`:
+  - `review-metrics` 状态项现在检查参考链路对照逻辑和 smoke 覆盖。
+- 更新文档:
+  - `docs/manual-review-handoff.md`
+  - `docs/tihao-experience-optimization-plan.md`
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check scripts\review-metrics.js
+node --check scripts\review-metrics-smoke.js
+npm run review:metrics:smoke
+npm run optimization:status:smoke
+npm run acceptance
+npm run optimization:status
+```
+
+状态审计结果:
+
+- `passed=11`
+- `ready_not_proven=2`
+- `blocked_by_external_data=2`
+- `complete=false`
+
+### 当前边界
+
+- 本轮把中期业务目标“参考账号链路通过率高于纯关键词召回”“有参考账号 Brief 客户选中率 >= 40%”转成可计算指标。
+- 本轮仍没有真实商务/客户标注 CSV,因此不能宣称参考链路已经真实优于关键词召回。
+- 真实达标需要商务回填 `人工复核标签`、`客户选择`、`归因类型` 后运行 `npm run review:metrics -- --input <已标注CSV> --strict`。
+
+## 2026-06-07 长跑启动就绪度审计
+
+### 已完成
+
+- 新增 `scripts/long-run-readiness-audit.js`:
+  - 支持 `smoke`、`live-subset`、`full-matrix`、`video-ab`、`customer-effect` 模式。
+  - 读取 `live-preflight-summary.json`,检查 `readyForLiveAcceptance`、sessionToken、company、Parse 用户解析和视频模型。
+  - 读取 `historical-dataset-audit.json`,检查真实历史 Brief 是否可用于完整矩阵和客户效果证明。
+  - `video-ab` 模式要求 `readyForVideoAb=true` 且模型为 `doubao-seed-2-0-pro`。
+  - `customer-effect` 模式要求历史数据达到 `readyForCustomerEffectProof=true`,并具备人工补号量基线。
+  - 输出 `long-run-readiness-summary.json` 和 `long-run-readiness-report.md`。
+  - 严格模式下未就绪会返回非 0,阻止启动真实长跑。
+- 新增 `scripts/long-run-readiness-smoke.js`:
+  - 验证完整矩阵、视频 A/B、客户效果证明缺历史数据、视频模型错误等场景。
+- 更新 `package.json`:
+  - 新增 `npm run longrun:readiness`。
+  - 新增 `npm run longrun:readiness:smoke`。
+- 更新 `scripts/acceptance.js`:
+  - 默认验收纳入 `longrun:readiness:smoke`。
+- 更新 `scripts/optimization-status-audit.js`:
+  - 新增 `long-run-readiness` 状态项。
+- 更新文档:
+  - `docs/ai-overnight-optimization-task.md`
+  - `docs/overnight-quality-runbook.md`
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check scripts\long-run-readiness-audit.js
+node --check scripts\long-run-readiness-smoke.js
+node --check scripts\optimization-status-audit.js
+npm run longrun:readiness:smoke
+npm run longrun:readiness -- --mode smoke --output outputs\long-run-readiness-smoke-check
+npm run acceptance
+npm run optimization:status
+```
+
+长跑就绪度 smoke 报告:
+
+- `outputs/long-run-readiness-smoke-check/long-run-readiness-summary.json`
+- `outputs/long-run-readiness-smoke-check/long-run-readiness-report.md`
+
+状态审计结果:
+
+- `passed=12`
+- `ready_not_proven=2`
+- `blocked_by_external_data=2`
+- `complete=false`
+
+### 当前边界
+
+- 本轮把真实长跑前置条件做成了独立审计,不发起扣费 live。
+- 没有真实 `live-preflight-summary.json`、`historical-dataset-audit.json` 和客户标注结果时,仍不能证明真实命中率、客户选中率或人工补号量改善。
+
+## 2026-06-07 长跑产物证据台账
+
+### 已完成
+
+- 新增 `scripts/evidence-index.js`:
+  - 自动扫描 `outputs` 下的关键 summary。
+  - 支持分类:
+    - `overnight-quality`
+    - `optimization-status`
+    - `long-run-readiness`
+    - `review-metrics`
+    - `historical-dataset`
+    - `video-ab`
+  - 给每条产物标记证明等级:
+    - `real_evidence`
+    - `smoke_or_local`
+    - `not_business_proof`
+  - 输出 `evidence-index-summary.json` 和 `evidence-index-report.md`。
+- 新增 `scripts/evidence-index-smoke.js`:
+  - 验证 live overnight、review metrics、未完成状态审计能被正确分类。
+- 更新 `package.json`:
+  - 新增 `npm run evidence:index`。
+  - 新增 `npm run evidence:index:smoke`。
+- 更新 `scripts/acceptance.js`:
+  - 默认验收纳入 `evidence:index:smoke`。
+- 更新 `scripts/optimization-status-audit.js`:
+  - 新增 `evidence-index` 状态项。
+- 更新文档:
+  - `docs/ai-overnight-optimization-task.md`
+  - `docs/overnight-quality-runbook.md`
+
+### 产物索引结果
+
+已生成当前 outputs 证据台账:
+
+- `outputs/evidence-index-latest/evidence-index-summary.json`
+- `outputs/evidence-index-latest/evidence-index-report.md`
+
+本次扫描结果:
+
+- `total=74`
+- `realEvidence=15`
+- `smokeOrLocal=17`
+- `notProof=42`
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check scripts\evidence-index.js
+node --check scripts\evidence-index-smoke.js
+node --check scripts\optimization-status-audit.js
+npm run evidence:index:smoke
+npm run evidence:index -- --output outputs\evidence-index-latest
+npm run acceptance
+npm run optimization:status
+```
+
+状态审计结果:
+
+- `passed=13`
+- `ready_not_proven=2`
+- `blocked_by_external_data=2`
+- `complete=false`
+
+### 当前边界
+
+- 本轮解决的是产物证据管理,避免把 smoke、本地门禁或未完成状态误当真实业务证明。
+- 证据台账中 `real_evidence` 只表示有真实证明入口,不自动等同于客户选中率、人工补号量减少或视频 A/B 已达标。
+- 长期目标仍需要真实客户选择数据、人工补号量基线和视频 A/B 通过结果来补齐。
+
+## 2026-06-07 证据台账真实证明等级收紧
+
+### 已完成
+
+- 更新 `scripts/evidence-index.js`:
+  - `overnight-quality` 只有同时满足以下条件才标为 `real_evidence`:
+    - `manifest.liveEnabled=true`
+    - `acceptance.overallPass=true`
+    - `failureCount=0`
+    - `failedGateCount=0`
+  - live 产物如果没有通过全部门禁,会标为 `not_business_proof`。
+  - `video-ab` 只有同时满足以下关键门禁才标为 `real_evidence`:
+    - `acceptance.passed=true`
+    - 已加载真实参考资源
+    - 已加载真实视频 URL
+    - 已加载封面/ASR/帧图至少一类
+    - 证据卡不是待补占位
+    - 证据卡包含可解释信号
+    - 强推荐数不下降
+    - top10 证据命中提升且分数不下降
+  - 报告说明从“真实证明入口”收紧为“真实数据且关键门禁通过”。
+- 更新 `scripts/evidence-index-smoke.js`:
+  - 新增 live 门禁失败样本,验证不能算真实业务证明。
+  - 新增严格 video A/B 样本,验证关键门禁全过才算 `real_evidence`。
+  - 新增弱 video A/B 样本,验证缺真实视频 URL 时不能算证明。
+- 更新 `scripts/optimization-status-audit.js`:
+  - `evidence-index` 状态项同步检查新的严格 smoke 文案。
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check scripts\evidence-index.js
+node --check scripts\evidence-index-smoke.js
+node --check scripts\optimization-status-audit.js
+npm run evidence:index:smoke
+npm run evidence:index -- --output outputs\evidence-index-latest
+npm run acceptance
+npm run optimization:status
+npm run hygiene:smoke
+```
+
+最新证据台账结果:
+
+- `total=76`
+- `realEvidence=5`
+- `smokeOrLocal=17`
+- `notProof=54`
+
+状态审计结果:
+
+- `passed=13`
+- `ready_not_proven=2`
+- `blocked_by_external_data=2`
+- `complete=false`
+
+### 当前边界
+
+- 本轮进一步降低误判风险:有真实调用但门禁未过的产物不能进入 `real_evidence`。
+- `real_evidence` 仍不是“长期目标完成”的同义词;客户选中率、人工补号量减少和真实业务效果仍要看 `review:metrics`、历史数据和客户选择字段。
+## 2026-06-07 客户效果证明审计入口
+
+### 已完成
+
+- 新增 `scripts/customer-effect-audit.js`:
+  - 输入商务标注后的软件端 CSV。
+  - 可读取 `historical-dataset-audit.json` 中的历史人工补号量基线。
+  - 支持通过 `--current-manual-supplement-count` 传入本轮人工补号量。
+  - 也支持从 CSV 的 `本轮人工补号量`、`当前人工补号量`、`人工补号量`、`manualSupplementCount` 等列读取本轮补号量。
+  - 输出 `customer-effect-summary.json` 和 `customer-effect-report.md`。
+- 新增 `scripts/customer-effect-audit-smoke.js`:
+  - 验证客户选中率 30%/50%。
+  - 验证参考链路客户选中率 40%。
+  - 验证历史人工补号基线 6、本轮人工补号量 3 时减少率为 50%。
+  - 验证缺本轮人工补号量不得通过。
+  - 验证缺历史人工补号基线不得通过。
+- 更新 `package.json`:
+  - 新增 `customer-effect:audit`。
+  - 新增 `customer-effect:smoke`。
+- 更新 `scripts/acceptance.js`:
+  - 默认验收纳入 `customer-effect:smoke`。
+- 更新 `scripts/optimization-status-audit.js`:
+  - 新增 `customer-effect-audit-entry` 状态项,用于区分“审计入口已具备”和“真实客户效果已证明”。
+  - 收紧 `customer-effect-proof`,必须有 `customer-effect-summary.json` 且 `overallPass=true` 才能算证明。
+- 更新文档:
+  - `docs/manual-review-handoff.md`
+  - `docs/historical-dataset-guide.md`
+  - `docs/tihao-experience-optimization-plan.md`
+
+### 当前边界
+
+- 本轮新增的是客户效果证明的自动化入口,不新增真实客户数据。
+- 没有客户选择、历史人工补号基线或本轮人工补号量时,报告会显示待补或未通过,不会把本地 smoke 当成真实业务效果。
+
+## 2026-06-07 客户效果证据台账闭环
+
+### 已完成
+
+- 更新 `scripts/evidence-index.js`:
+  - 新增 `customer-effect` 证据类型。
+  - 自动识别 `customer-effect-summary.json`。
+  - 兼容 `customer-effect-audit/customer-effect-summary.json` 子目录。
+  - 只有同时满足客户选中率、参考链路、历史补号基线、本轮补号量和人工补号减少 50% 门禁时,才标记为 `real_evidence`。
+  - 缺本轮补号量或门禁未过时,标记为 `not_business_proof`。
+- 更新 `scripts/evidence-index-smoke.js`:
+  - 新增完整客户效果证明样本。
+  - 新增缺本轮人工补号量样本。
+  - 验证完整客户效果证明进入 `real_evidence`,缺证据样本不能进入真实证明。
+- 更新 `scripts/long-run-readiness-audit.js`:
+  - `customer-effect` 模式下一步命令补充 `customer-effect:audit`。
+  - 补充 `evidence:index` 命令,确保客户效果产物进入证据台账。
+- 更新 `scripts/optimization-status-audit.js`:
+  - `evidence-index` 状态项同步检查 `customer-effect` 类型覆盖。
+
+### 验收结果
+
+已通过:
+
+```powershell
+node --check scripts\evidence-index.js
+node --check scripts\evidence-index-smoke.js
+node --check scripts\long-run-readiness-audit.js
+node --check scripts\optimization-status-audit.js
+npm run evidence:index:smoke
+npm run longrun:readiness:smoke
+npm run optimization:status:smoke
+```
+
+### 当前边界
+
+- 本轮补齐的是“客户效果审计产物进入证据台账”的闭环。
+- 没有真实 `customer-effect-summary.json` 且 `overallPass=true` 时,长期目标仍不会被判定完成。
+
+## 2026-06-07 live preflight 证据台账闭环
+
+### 已完成
+
+- 更新 `scripts/evidence-index.js`:
+  - 新增 `live-preflight` 证据类型。
+  - 自动识别 `live-preflight-summary.json`。
+  - 读取 `readyForLiveAcceptance`、`readyForVideoAb` 和检查项 fail/warn 数量。
+  - `readyForLiveAcceptance=true` 时标记为 `smoke_or_local`,表示只证明 live 启动前置条件。
+  - 缺 token、company 或关键配置时标记为 `not_business_proof`。
+- 更新 `scripts/evidence-index-smoke.js`:
+  - 新增 ready live preflight 样本。
+  - 新增 missing live preflight 样本。
+  - 验证 ready preflight 只能作为启动证据,不能当业务证明。
+- 更新 `scripts/optimization-status-audit.js`:
+  - `evidence-index` 状态项同步检查 `live-preflight` 类型覆盖。
+- 更新 `docs/overnight-quality-runbook.md`:
+  - 说明 `live-preflight-summary.json` 会进入证据台账。
+  - 明确 preflight 只证明 sessionToken、company、provider 和视频模型等启动前置条件,不等于 live 长跑结果或客户效果证明。
+
+### 当前边界
+
+- 本轮补齐的是 live 长跑启动前置证据管理。
+- 真实 live/provider 效果仍必须看 overnight live 矩阵、视频 A/B 和客户效果审计结果。
+
+## 2026-06-07 历史数据 CSV 导入
+
+### 已完成
+
+- 新增 `scripts/history-dataset-from-csv.js`:
+  - 将商务 CSV 按 `brief编号` 合并为 per-brief JSON 历史样本。
+  - 保留客户原始 Brief、类目、参考账号/视频、人工最终名单、客户选择、拒绝原因和历史人工补号量基线。
+  - 多个参考链接支持用中英文分号分隔。
+  - 输出 `history-dataset-from-csv-summary.json`。
+- 新增 `scripts/history-dataset-from-csv-smoke.js`:
+  - 验证 3 个 Brief、6 行历史提报账号能转换为 3 个 JSON 样本。
+  - 转换后直接调用 `history:audit` 逻辑,验证可用于长期优化长跑和客户效果证明。
+  - 验证缺历史人工补号量基线时,仍可用于基础长跑,但不能用于客户效果证明。
+- 更新 `package.json`:
+  - 新增 `history:from-csv`。
+  - 新增 `history:from-csv:smoke`。
+- 更新 `scripts/acceptance.js`:
+  - 默认验收纳入 `history:from-csv:smoke`。
+- 更新 `scripts/optimization-status-audit.js`:
+  - 新增 `history-csv-import` 状态项。
+- 更新文档:
+  - `docs/historical-dataset-guide.md`
+  - `docs/tihao-experience-optimization-plan.md`
+
+### 当前边界
+
+- CSV 转换只解决真实历史数据整理成本,不替代真实客户选择和人工补号量基线。
+- CSV 中不要写 token、鉴权密钥或客户敏感报价。
+
+## 2026-06-07 端到端优化 Pipeline
+
+### 已完成
+
+- 新增 `scripts/optimization-pipeline.js`:
+  - 支持 `--history-csv` 自动运行 `history:from-csv`。
+  - 自动运行 `history:audit`。
+  - 自动运行 `longrun:readiness`。
+  - 有 `--review-csv` 时自动运行 `review:metrics` 和 `customer-effect:audit`。
+  - 最后自动运行 `evidence:index`。
+  - 支持 `--dry-run` 只生成计划。
+  - 支持 `--strict` 遇到失败即中止并返回非 0。
+- 新增 `scripts/optimization-pipeline-smoke.js`:
+  - 构造历史 CSV 和商务复核 CSV。
+  - 验证 dry-run 只输出 planned 步骤。
+  - 验证真实本地 pipeline 能串起 history import、history audit、review metrics、customer effect audit 和 evidence index。
+- 更新 `package.json`:
+  - 新增 `optimization:pipeline`。
+  - 新增 `optimization:pipeline:smoke`。
+- 更新 `scripts/acceptance.js`:
+  - 默认验收纳入 `optimization:pipeline:smoke`。
+- 更新 `scripts/optimization-status-audit.js`:
+  - 新增 `optimization-pipeline` 状态项。
+- 更新文档:
+  - `docs/overnight-quality-runbook.md`
+  - `docs/tihao-experience-optimization-plan.md`
+
+### 当前边界
+
+- pipeline 只编排本地审计链路,不自动注入 token,也不默认启动真实 live 长跑。
+- 真实 live 长跑仍必须先通过 `live:preflight` 和 `longrun:readiness`。
+- 没有真实客户选择、历史人工补号基线和本轮人工补号量时,pipeline 不会宣称业务效果完成。
+## 2026-06-07 软件端表格去重与中文格式修复
+
+### 已完成
+
+- 重写 `scripts/export-software-table.js`:
+  - 固定输出中文软件端表头。
+  - 平台字段统一输出为 `小红书`、`抖音` 等中文展示名。
+  - URL 去重升级为规范化口径:统一 http/https、小红书移动端/桌面端域名、去掉 query、fragment 和尾斜杠。
+  - 无主页链接时按 `brief编号 + 平台 + 规范化博主名称` 去重。
+  - 博主名称规范化会去空格、符号和括号补充信息,用于识别展示层重复。
+  - 摘要文件新增四类重复审计:同 brief URL、同 brief 名称、全局 URL、全局平台+名称。
+- 重写 `scripts/export-software-markdown.js`:
+  - Markdown 标题、摘要和去重说明全部中文输出。
+  - 表格仍保持软件端固定字段。
+- 重写 `scripts/software-table-smoke.js`:
+  - 覆盖中文表头、平台中文显示、重复标签清理、URL 参数归一化、同名无链接去重、重复审计为 0。
+- 修复 `docs/software-client-table-format.md`:
+  - 改为完整中文说明。
+  - 明确软件端字段、去重规则、人工复核标签和验收标准。
+- 基于真实样本重新导出:
+  - `outputs/overnight-quality-1780682025651/software-client-list.final.csv`
+  - `outputs/overnight-quality-1780682025651/software-client-list.final.md`
+  - `outputs/overnight-quality-1780682025651/software-client-list.final.summary.json`
+
+### 本轮验证结果
+
+- 原始行数:60
+- 去重后行数:28
+- 删除重复行数:32
+- 同 brief URL 重复组:0
+- 同 brief 名称重复组:0
+- 全局 URL 重复组:0
+- 全局平台+名称重复组:0
+- 每个 brief 内排名连续:true
+
+### 当前边界
+
+- 本轮只证明软件端交付表格式和去重链路通过,不证明客户命中率已经提升。
+- 客户选择率、人工补号减少和视频分析带来的真实业务提升,仍需依赖真实历史数据、商务复核 CSV 和 live A/B 证据。
+## 2026-06-07 长期优化任务文档中文化
+
+### 已完成
+
+- 重写 `docs/tihao-experience-optimization-plan.md`:
+  - 改为完整中文长期优化计划。
+  - 保留核心判断、已具备能力、关键差距、长跑任务、软件端表格验收、视频 A/B、客户效果审计和给 AI 的执行任务。
+  - 明确不得把 sample/local/provider fallback 当成真实业务效果。
+- 重写 `docs/ai-optimization-task-software-table.md`:
+  - 改为软件端可引用的中文表格任务书。
+  - 明确每个阶段的输入、AI 动作、输出文件、验收标准和不通过处理。
+  - 保留软件端固定字段、人工复核标签、去重验收和 AI 执行指令。
+- 重写 `docs/ai-overnight-optimization-task.md`:
+  - 改为中文长跑任务说明。
+  - 保留运行前检查、live 预检、受控 live 子集、完整 live 矩阵、视频 A/B、人工复核和沉淀要求。
+
+### 当前边界
+
+- 本轮修复的是直接给 AI 和软件端引用的三份核心任务文档。
+- 历史实施日志中仍可能有早期乱码段落;这些旧段落不作为本轮新验收依据,后续可单独做日志归档和清理。
+- 真实业务效果仍需真实历史数据、商务复核和客户选择数据证明。
+## 2026-06-07 乱码审计规则补强
+
+### 已完成
+
+- 检查核心中文文档实际内容:
+  - `docs/tihao-experience-implementation-log.md`
+  - `docs/tihao-experience-optimization-plan.md`
+  - `docs/ai-overnight-optimization-task.md`
+  - Node 以 UTF-8 读取均为正常中文。
+- 补强 `scripts/leak-mojibake-audit.js`:
+  - 保留 token、sessionToken、Authorization 泄露扫描。
+  - 增加常见 UTF-8 被错读为 GBK 的短片段扫描。
+  - 增加典型乱码标点扫描。
+- 运行 `npm.cmd run hygiene:smoke`:
+  - `scannedFiles=90`
+  - `hitCount=0`
+
+### 当前边界
+
+- PowerShell 未显式指定 `-Encoding UTF8` 时,终端预览可能显示乱码;这不代表文件实际内容损坏。
+- 后续判断文档是否乱码,以 `hygiene:smoke` 和 UTF-8 读取结果为准。
+## 2026-06-07 Pipeline 产物纳入证据台账
+
+### 已完成
+
+- 重写 `scripts/evidence-index.js`:
+  - 报告模板改为可读中文。
+  - 新增 `optimization-pipeline-summary.json` 分类。
+  - pipeline 产物通过时标记为 `smoke_or_local`,失败时标记为 `not_business_proof`。
+  - 即使 `readyForClaim=true`,pipeline 本身也不直接标为 `real_evidence`;客户效果仍以 `customer-effect` 产物单独判定。
+- 更新 `scripts/evidence-index-smoke.js`:
+  - 增加 pipeline pass/fail 两类样本。
+  - 验证 pipeline pass 只进入 `smoke_or_local`。
+  - 验证 pipeline fail 进入 `not_business_proof`。
+- 重写 `scripts/optimization-pipeline.js` 报告模板:
+  - `optimization-pipeline-report.md` 改为可读中文。
+  - 保留不自动写入 token、不默认启动真实 live、不单独证明业务效果的边界。
+- 更新 `scripts/optimization-status-audit.js`:
+  - 证据台账状态项现在要求覆盖 `optimization-pipeline` 类型。
+
+### 本轮验证结果
+
+- `npm.cmd run evidence:index:smoke`:
+  - `total=12`
+  - `real_evidence=4`
+  - `smoke_or_local=2`
+  - `not_business_proof=6`
+- `npm.cmd run optimization:pipeline:smoke` 通过。
+- `npm.cmd run hygiene:smoke` 通过,`hitCount=0`。
+- `npm.cmd run optimization:status`:
+  - `passed=16`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+
+### 当前边界
+
+- pipeline 进入证据台账只证明编排链路可追踪,不证明客户效果。
+- 客户选中率、视频分析真实提升、人工补号减少仍需真实数据和独立审计产物证明。
+## 2026-06-07 真实数据收集包入口
+
+### 已完成
+
+- 新增 `scripts/create-data-intake-pack.js`:
+  - 生成 `history-data-template.csv`,用于整理真实历史 Brief、人工名单、客户选择、拒绝原因和历史人工补号量基线。
+  - 生成 `manual-review-template.csv`,用于整理本轮 AI 候选、商务复核、客户选择、归因类型、反馈原因和本轮人工补号量。
+  - 生成 `README.md`,说明填写规则、审计命令和客户效果证明边界。
+- 新增 `scripts/create-data-intake-pack-smoke.js`:
+  - 验证两个 CSV 表头。
+  - 验证客户选择、历史人工补号量基线、本轮人工补号量字段存在。
+  - 验证模板不包含 token/sessionToken/Authorization 类敏感值。
+- 新增 npm scripts:
+  - `data:intake-template`
+  - `data:intake-template:smoke`
+- `npm run acceptance` 已纳入 `data:intake-template:smoke`。
+- `optimization:status` 新增 `data-intake-pack` 状态项,并显示为 `passed`。
+- 更新文档:
+  - `docs/historical-dataset-guide.md`
+  - `docs/manual-review-handoff.md`
+
+### 本轮输出
+
+- `outputs/data-intake-pack-latest/history-data-template.csv`
+- `outputs/data-intake-pack-latest/manual-review-template.csv`
+- `outputs/data-intake-pack-latest/README.md`
+
+### 当前边界
+
+- 数据收集包只降低真实数据交接成本,不证明客户效果。
+- 客户效果证明仍必须等待真实客户选择、历史人工补号基线和本轮人工补号量通过 `customer-effect:audit`。
+## 2026-06-07 数据收集包进入证据台账
+
+### 已完成
+
+- 更新 `scripts/evidence-index.js`:
+  - 新增识别 `data-intake-pack`。
+  - 当输出目录同时包含 `history-data-template.csv`、`manual-review-template.csv` 和 `README.md` 时,登记为数据收集包。
+  - 证明等级固定为 `smoke_or_local`,不作为业务效果证明。
+- 更新 `scripts/evidence-index-smoke.js`:
+  - 增加数据收集包样本。
+  - 验证数据收集包进入 `smoke_or_local`。
+  - 验证不会进入 `real_evidence`。
+- 更新 `scripts/optimization-status-audit.js`:
+  - `evidence-index` 状态项要求覆盖 `data-intake-pack` 类型。
+
+### 本轮验证结果
+
+- `npm.cmd run evidence:index:smoke`:
+  - `total=13`
+  - `real_evidence=4`
+  - `smoke_or_local=3`
+  - `not_business_proof=6`
+- `npm.cmd run optimization:status`:
+  - `passed=17`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+- `npm.cmd run acceptance` 通过。
+- 最新证据台账:
+  - `total=99`
+  - `real_evidence=5`
+  - `smoke_or_local=19`
+  - `not_business_proof=75`
+  - `data-intake-pack=1`
+
+### 当前边界
+
+- 数据收集包入账只说明真实数据交接入口已准备好。
+- 真实客户效果仍必须等待商务填写后的历史数据、客户选择和人工补号量审计。
+## 2026-06-07 长期优化交接摘要
+
+### 已完成
+
+- 新增 `scripts/generate-optimization-handoff.js`:
+  - 汇总 `optimization:status`、`evidence:index`、数据收集包路径和下一轮命令。
+  - 输出中文 `optimization-handoff-report.md` 和结构化 `optimization-handoff-summary.json`。
+  - 明确区分已通过能力、已具备入口但未真实证明、仍需真实外部数据的阻塞项。
+  - 明确 sample、smoke、dry-run、provider fallback 不能作为客户效果证明。
+- 新增 `scripts/generate-optimization-handoff-smoke.js`:
+  - 验证交接报告是中文。
+  - 验证长期目标在缺少真实证明时不会被标记为完成。
+  - 验证报告包含 `customer-effect:audit` 等下一步命令。
+  - 验证报告不包含 sessionToken、模型 token 或鉴权头等敏感信息。
+- 新增 npm scripts:
+  - `handoff:summary`
+  - `handoff:summary:smoke`
+- `npm run acceptance` 已纳入 `handoff:summary:smoke`。
+- 更新 `scripts/evidence-index.js`:
+  - 新增识别 `optimization-handoff-summary.json`。
+  - 交接摘要固定归为 `smoke_or_local`,不作为真实业务证明。
+- 更新 `scripts/optimization-status-audit.js`:
+  - 新增 `optimization-handoff` 状态项。
+  - 当前本地可通过能力从 17 项提升到 18 项。
+
+### 本轮输出
+
+- `outputs/optimization-handoff-latest/optimization-handoff-summary.json`
+- `outputs/optimization-handoff-latest/optimization-handoff-report.md`
+
+### 本轮验证结果
+
+- `npm.cmd run handoff:summary:smoke` 通过:
+  - `readyCapabilities=18`
+  - `readyNotProven=2`
+  - `externalBlockers=2`
+- `npm.cmd run evidence:index:smoke` 通过:
+  - `total=14`
+  - `real_evidence=4`
+  - `smoke_or_local=4`
+  - `not_business_proof=6`
+- `npm.cmd run optimization:status:smoke` 通过。
+- `npm.cmd run hygiene:smoke` 通过:
+  - `scannedFiles=94`
+  - `hitCount=0`
+- 最新 `npm.cmd run optimization:status`:
+  - `passed=18`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+- 最新 `npm.cmd run evidence:index -- --output outputs\evidence-index-latest`:
+  - `total=102`
+  - `realEvidence=5`
+  - `smokeOrLocal=20`
+  - `notProof=77`
+- `npm.cmd run acceptance` 通过,新增 `handoff:summary:smoke` 已进入默认发布验收。
+
+### 当前边界
+
+- 交接摘要只证明长跑进度可交接、可追踪,不证明提号命中率已经提升。
+- 视频 A/B、真实 live/provider 长跑、客户选中率和人工补号减少仍需真实 token、provider、历史数据、商务复核和客户选择数据。
+- 长期目标仍未完成,不能调用完成状态。
+## 2026-06-07 当前规范口径一致性门禁
+
+### 已完成
+
+- 修正当前可执行规范中的软件端去重口径:
+  - `docs/tihao-experience-optimization-plan.md`
+  - `docs/ai-optimization-task-software-table.md`
+  - `skills/tihao-creator-sourcing/references/output-format.md`
+- 当前统一口径:
+  - 软件端交付表按“全局博主唯一”去重。
+  - 同一 `平台 + 规范化主页链接` 不重复。
+  - 没有主页链接时,同一 `平台 + 规范化博主名称` 不重复。
+  - 同一博主被多个 brief 或多个策略召回时,只保留证据更完整、综合分更高的一条进入交付表。
+  - 每个 brief 内排名仍需从 1 开始连续。
+- 新增 `scripts/plan-consistency-smoke.js`:
+  - 扫描当前执行规范文件,不扫描历史实施日志。
+  - 要求计划、软件端格式、AI 任务表和 skill 输出规范都包含全局去重口径。
+  - 阻止旧的 `brief编号 + 平台 + 主页链接/博主名称` 去重口径重新进入当前规范。
+- 新增 npm script:
+  - `plan:consistency:smoke`
+- `npm run acceptance` 已纳入 `plan:consistency:smoke`。
+- `optimization:status` 新增 `plan-consistency` 状态项。
+
+### 本轮验证结果
+
+- `npm.cmd run plan:consistency:smoke` 通过:
+  - `scannedFiles=4`
+  - `hits=[]`
+- `npm.cmd run software:smoke` 通过:
+  - `duplicateCount=3`
+  - `outputRows=4`
+- `npm.cmd run optimization:status:smoke` 通过。
+- 最新 `npm.cmd run optimization:status`:
+  - `passed=19`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+- 最新 `npm.cmd run evidence:index -- --output outputs\evidence-index-latest`:
+  - `total=104`
+  - `realEvidence=5`
+  - `smokeOrLocal=20`
+  - `notProof=79`
+
+### 当前边界
+
+- 本轮只证明当前规范文件不会继续指导 AI 生成 brief 内去重的重复表。
+- 历史日志里的旧口径保留为历史记录,不作为当前验收标准。
+- 视频 A/B、真实 live/provider 长跑、客户效果证明仍需真实外部数据。
+
+## 2026-06-07 每轮沉淀审计与交接摘要中文修复
+
+### 已完成
+
+- 修复 `scripts/generate-optimization-handoff.js` 和 `scripts/generate-optimization-handoff-smoke.js`:
+  - 交接摘要标题、状态、下一轮命令、边界说明恢复为可读中文。
+  - 继续校验不得写入 sessionToken、模型 token、鉴权头或 npm token。
+  - 继续保持缺少真实客户效果证明时 `complete=false`。
+- 新增 `scripts/round-deposition-audit.js`:
+  - 检查实施日志、最新 `optimization-status-report.md`、最新证据台账和最新交接摘要是否齐全。
+  - 检查每轮是否记录命令、输出目录、live 标记、严格 provider 门禁、视频资源真实性、`failureCount`、`failedGateCount`、`overallPass`、软件端重复键和排名连续性、人工复核或客户指标、仍未证明边界。
+  - 输出 `round-deposition-summary.json` 和 UTF-8 BOM 中文 `round-deposition-report.md`。
+  - 该产物只证明本轮沉淀完整,不证明客户效果或命中率提升。
+- 新增 `scripts/round-deposition-smoke.js`:
+  - 用安全 fake 数据验证沉淀审计可通过。
+  - 验证报告为中文且不包含 token/sessionToken 模式。
+- 新增 npm scripts:
+  - `round:deposition`
+  - `round:deposition:smoke`
+- `npm run acceptance` 已纳入 `round:deposition:smoke`。
+- `scripts/evidence-index.js` 已识别 `round-deposition-summary.json`:
+  - 通过时固定归为 `smoke_or_local`。
+  - 失败时归为 `not_business_proof`。
+- `scripts/optimization-status-audit.js` 已新增 `round-deposition` 状态项,并要求 acceptance 覆盖该 smoke gate。
+
+### 本轮验证结果
+
+- `npm.cmd run handoff:summary:smoke` 通过:
+  - `readyCapabilities=20`
+  - `readyNotProven=2`
+  - `externalBlockers=2`
+- `npm.cmd run round:deposition:smoke` 通过:
+  - `passCount=24`
+  - `failCount=0`
+- `npm.cmd run evidence:index:smoke` 通过:
+  - `total=15`
+  - `real_evidence=4`
+  - `smoke_or_local=5`
+  - `not_business_proof=6`
+- `npm.cmd run optimization:status:smoke` 通过。
+- 最新 `npm.cmd run optimization:status`:
+  - `passed=20`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+- 最新 `npm.cmd run evidence:index -- --output outputs\evidence-index-latest`:
+  - `total=107`
+  - `realEvidence=5`
+  - `smokeOrLocal=21`
+  - `notProof=81`
+- 最新 `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest`:
+  - `complete=false`
+  - `readyCapabilities=20`
+  - `externalBlockers=2`
+- 最新 `npm.cmd run round:deposition -- --output outputs\round-deposition-latest`:
+  - `passed=true`
+  - `passCount=24`
+  - `warnCount=0`
+  - `failCount=0`
+- `npm.cmd run hygiene:smoke` 通过:
+  - `scannedFiles=97`
+  - `hitCount=0`
+- `npm.cmd run acceptance` 通过,新增 `round:deposition:smoke` 已进入默认发布前验收。
+
+### 本轮输出
+
+- `outputs/optimization-handoff-latest/optimization-handoff-summary.json`
+- `outputs/optimization-handoff-latest/optimization-handoff-report.md`
+- `outputs/round-deposition-latest/round-deposition-summary.json`
+- `outputs/round-deposition-latest/round-deposition-report.md`
+
+### 当前边界
+
+- `round-deposition` 只证明每轮沉淀和交接材料完整,不证明真实客户效果。
+- 当前 live 标记仍不能替代真实 live/provider 长跑;严格 provider 门禁需要真实 provider 配置后再开启。
+- 视频资源真实性仍需真实视频 URL、封面、字幕、ASR、帧图或正文证据通过 `acceptance:video-ab`。
+- 客户选中率、人工复核通过率和人工补号减少仍需真实商务复核和客户选择数据通过 `review:metrics` 与 `customer-effect:audit`。
+
+## 2026-06-07 视频资源收集包进入长跑交接
+
+### 已完成
+
+- 新增 `scripts/create-video-intake-pack.js`:
+  - 生成 `video-resource-template.csv`。
+  - 生成 UTF-8 BOM 中文 `README.md`。
+  - 字段覆盖 `视频链接`、`封面链接`、`字幕或ASR文本`、`帧图链接`、`是否真实资源`、`资源角色`、`风格调性标签`、`画面人设场景信号` 和 `风险提示`。
+  - 明确没有真实视频 URL、封面、字幕、ASR、帧图或正文证据时,不得声明视频分析完成。
+- 新增 `scripts/create-video-intake-pack-smoke.js`:
+  - 验证 CSV 表头固定。
+  - 验证视频 URL、封面、ASR、帧图和真实资源字段存在。
+  - 验证 README 包含 `acceptance:video-ab` 和视频证明边界。
+  - 验证不包含 token/sessionToken/Authorization 模式。
+- 新增 npm scripts:
+  - `video:intake-template`
+  - `video:intake-template:smoke`
+- `npm run acceptance` 已纳入 `video:intake-template:smoke`。
+- `scripts/evidence-index.js` 已识别 `video-resource-template.csv`:
+  - 通过时固定归为 `smoke_or_local`。
+  - 不作为真实视频 A/B 或客户效果证明。
+- `scripts/optimization-status-audit.js` 已新增 `video-intake-pack` 状态项。
+- `scripts/generate-optimization-handoff.js` 已在下一轮命令中加入:
+  - `npm run video:intake-template -- --output outputs\video-intake-pack-latest`
+  - `npm run acceptance:video-ab`
+
+### 本轮验证结果
+
+- `npm.cmd run video:intake-template:smoke` 通过:
+  - `files=2`
+- `npm.cmd run evidence:index:smoke` 通过:
+  - `total=16`
+  - `real_evidence=4`
+  - `smoke_or_local=6`
+  - `not_business_proof=6`
+- `npm.cmd run optimization:status:smoke` 通过。
+- 最新 `npm.cmd run optimization:status`:
+  - `passed=21`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+- 最新 `npm.cmd run video:intake-template -- --output outputs\video-intake-pack-latest` 已生成:
+  - `outputs/video-intake-pack-latest/video-resource-template.csv`
+  - `outputs/video-intake-pack-latest/README.md`
+- 最新 `npm.cmd run evidence:index -- --output outputs\evidence-index-latest`:
+  - `total=110`
+  - `realEvidence=5`
+  - `smokeOrLocal=22`
+  - `notProof=83`
+- 最新 `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest`:
+  - `complete=false`
+  - `readyCapabilities=21`
+  - `externalBlockers=2`
+- 最新 `npm.cmd run round:deposition -- --output outputs\round-deposition-latest`:
+  - `passed=true`
+  - `passCount=24`
+  - `warnCount=0`
+  - `failCount=0`
+- `npm.cmd run hygiene:smoke` 通过:
+  - `scannedFiles=99`
+  - `hitCount=0`
+- `npm.cmd run acceptance` 通过,新增 `video:intake-template:smoke` 已进入默认发布前验收。
+
+### 本轮输出
+
+- `outputs/video-intake-pack-latest/video-resource-template.csv`
+- `outputs/video-intake-pack-latest/README.md`
+- `outputs/evidence-index-latest/evidence-index-summary.json`
+- `outputs/evidence-index-latest/evidence-index-report.md`
+- `outputs/optimization-handoff-latest/optimization-handoff-summary.json`
+- `outputs/optimization-handoff-latest/optimization-handoff-report.md`
+- `outputs/round-deposition-latest/round-deposition-summary.json`
+- `outputs/round-deposition-latest/round-deposition-report.md`
+
+### 当前边界
+
+- 视频资源收集包只降低真实视频证据交接成本,不证明视频 A/B 通过。
+- 当前 live 标记仍不能替代真实 live/provider 长跑;严格 provider 门禁需要真实 provider 配置后再开启。
+- 真实视频 A/B 仍需真实 sessionToken、company、VOC social provider、视频分析 provider、真实参考视频/候选视频资源后运行 `acceptance:video-ab`。
+- 客户选中率和人工补号减少仍需真实商务复核、客户最终选择和历史人工补号基线通过 `customer-effect:audit`。
+
+## 2026-06-07 视频 A/B 前置资源就绪审计
+
+### 已完成
+
+- 新增 `scripts/video-resource-readiness-audit.js`:
+  - 读取商务填写后的 `video-resource-template.csv`。
+  - 检查固定表头、真实参考视频、真实候选视频、真实视频 URL、封面/ASR/帧图/正文证据。
+  - 检查标记为真实资源的行不能没有任何视频/封面/ASR/帧图/正文证据。
+  - 检查不包含 sessionToken、模型 token、Authorization 等敏感模式。
+  - 输出 `video-resource-readiness-summary.json` 和 UTF-8 BOM 中文 `video-resource-readiness-report.md`。
+  - 明确该审计只证明视频 A/B 前置资源是否齐备,不证明 provider、证据卡、Top10 非退化或客户效果。
+- 新增 `scripts/video-resource-readiness-smoke.js`:
+  - 覆盖完整资源通过样本。
+  - 覆盖缺少真实参考/候选/视频 URL 的失败样本。
+  - 验证严格模式下失败样本会非 0 退出。
+- 新增 npm scripts:
+  - `video:resource-readiness`
+  - `video:resource-readiness:smoke`
+- `npm run acceptance` 已纳入 `video:resource-readiness:smoke`。
+- `scripts/evidence-index.js` 已识别 `video-resource-readiness-summary.json`:
+  - `readyForVideoAbPreflight=true` 时归为 `smoke_or_local`。
+  - 未就绪时归为 `not_business_proof`。
+- `scripts/optimization-status-audit.js` 已新增 `video-resource-readiness` 状态项。
+- `scripts/generate-optimization-handoff.js` 已在下一轮命令中加入 `video:resource-readiness`。
+
+### 本轮验证结果
+
+- `npm.cmd run video:resource-readiness:smoke` 通过:
+  - 通过样本 `passFailureCount=0`
+  - 失败样本 `failFailureCount=4`
+- `npm.cmd run evidence:index:smoke` 通过:
+  - `total=18`
+  - `real_evidence=4`
+  - `smoke_or_local=7`
+  - `not_business_proof=7`
+- `npm.cmd run optimization:status:smoke` 通过。
+- `npm.cmd run handoff:summary:smoke` 通过:
+  - `readyCapabilities=22`
+  - `readyNotProven=2`
+  - `externalBlockers=2`
+- 最新 `npm.cmd run video:resource-readiness -- --input outputs\video-intake-pack-latest\video-resource-template.csv --output outputs\video-resource-readiness-latest`:
+  - `readyForVideoAbPreflight=false`
+  - `failureCount=1`
+  - `realReferenceRows=1`
+  - `realCandidateRows=0`
+  - `videoUrlRows=1`
+  - 原因:当前模板样例只有真实参考视频,候选视频示例标记为 `否`,因此不具备真实 A/B 前置资源。
+- 最新 `npm.cmd run optimization:status`:
+  - `passed=22`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+- 最新 `npm.cmd run evidence:index -- --output outputs\evidence-index-latest`:
+  - `total=111`
+  - `realEvidence=5`
+  - `smokeOrLocal=22`
+  - `notProof=84`
+- `npm.cmd run hygiene:smoke` 通过:
+  - `scannedFiles=101`
+  - `hitCount=0`
+- `npm.cmd run acceptance` 通过,新增 `video:resource-readiness:smoke` 已进入默认发布前验收。
+
+### 本轮输出
+
+- `outputs/video-resource-readiness-latest/video-resource-readiness-summary.json`
+- `outputs/video-resource-readiness-latest/video-resource-readiness-report.md`
+- `outputs/evidence-index-latest/evidence-index-summary.json`
+- `outputs/evidence-index-latest/evidence-index-report.md`
+
+### 当前边界
+
+- 当前视频资源 readiness 产物是 `not_business_proof`,因为模板样例缺真实候选视频。
+- readiness 通过也只代表可启动视频 A/B 前置资源齐备,不代表视频分析 provider 正常、证据卡有效或命中率提升。
+- 真实视频 A/B 仍需真实 sessionToken、company、VOC social provider、视频分析 provider,并运行 `acceptance:video-ab`。
+- 客户效果仍需真实商务复核、客户最终选择和历史人工补号基线通过 `customer-effect:audit`。
+
+## 2026-06-07 真实证明缺口请求包
+
+### 已完成
+
+- 新增 `scripts/generate-proof-gap-request.js`:
+  - 汇总最新 handoff、evidence index 和 video-resource-readiness。
+  - 输出 `proof-gap-request-summary.json`、UTF-8 BOM 中文 `proof-gap-request-report.md` 和 `proof-gap-request-table.csv`。
+  - 请求项覆盖真实历史数据、真实候选视频资源、视频 A/B live proof、真实 live/provider 长跑、商务复核/客户效果证明。
+  - 每项都包含负责人、当前缺口、需要补齐、验证命令和通过标准。
+  - 明确不得写入 sessionToken、模型 token、Authorization、npm token 或内部鉴权参数。
+- 新增 `scripts/generate-proof-gap-request-smoke.js`:
+  - 验证请求包不会把长期目标标为完成。
+  - 验证会请求真实候选视频资源和客户效果证明数据。
+  - 验证中文 Markdown 和商务 CSV 表头。
+  - 验证不包含 token/sessionToken/Authorization 模式。
+- 新增 npm scripts:
+  - `proof-gap:request`
+  - `proof-gap:request:smoke`
+- `npm run acceptance` 已纳入 `proof-gap:request:smoke`。
+- `scripts/evidence-index.js` 已识别 `proof-gap-request-summary.json`:
+  - 固定归为 `smoke_or_local`。
+  - 不作为业务效果证明。
+- `scripts/optimization-status-audit.js` 已新增 `proof-gap-request` 状态项。
+- `scripts/generate-optimization-handoff.js` 已在下一轮命令中加入 `proof-gap:request`。
+
+### 本轮验证结果
+
+- `npm.cmd run proof-gap:request:smoke` 通过:
+  - `gapCount=5`
+  - `unresolvedCount=5`
+- `npm.cmd run evidence:index:smoke` 通过:
+  - `total=19`
+  - `real_evidence=4`
+  - `smoke_or_local=8`
+  - `not_business_proof=7`
+- `npm.cmd run optimization:status:smoke` 通过。
+- `npm.cmd run handoff:summary:smoke` 通过:
+  - `readyCapabilities=23`
+  - `readyNotProven=2`
+  - `externalBlockers=2`
+- 最新 `npm.cmd run proof-gap:request -- --output outputs\proof-gap-request-latest`:
+  - `gapCount=5`
+  - `unresolvedCount=5`
+  - `complete=false`
+- 最新 `npm.cmd run optimization:status`:
+  - `passed=23`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+- 最新 `npm.cmd run evidence:index -- --output outputs\evidence-index-latest`:
+  - `total=114`
+  - `realEvidence=5`
+  - `smokeOrLocal=23`
+  - `notProof=86`
+- `npm.cmd run hygiene:smoke` 通过:
+  - `scannedFiles=103`
+  - `hitCount=0`
+- `npm.cmd run acceptance` 通过,新增 `proof-gap:request:smoke` 已进入默认发布前验收。
+
+### 本轮输出
+
+- `outputs/proof-gap-request-latest/proof-gap-request-summary.json`
+- `outputs/proof-gap-request-latest/proof-gap-request-report.md`
+- `outputs/proof-gap-request-latest/proof-gap-request-table.csv`
+
+### 当前边界
+
+- 证明缺口请求包只说明还需要补哪些真实材料,不证明业务效果。
+- 当前 5 个缺口仍未解决:真实历史数据、真实候选视频资源、视频 A/B live proof、真实 live/provider 长跑、商务复核和客户效果证明。
+- 长期目标仍不能标完成;必须等真实视频 A/B、真实 live/provider 和客户效果审计全部通过。
+
+## 2026-06-07 长跑启动前就绪度审计增强
+
+### 已完成
+
+- 重写 `scripts/long-run-readiness-audit.js`:
+  - 保留 `smoke`、`live-subset`、`full-matrix`、`video-ab`、`customer-effect` 模式。
+  - 中文化 `long-run-readiness-report.md`。
+  - 新增 `--video-readiness <video-resource-readiness-summary.json>` 参数。
+  - 新增 `--proof-gap <proof-gap-request-summary.json>` 参数。
+  - `video-ab` 模式现在会检查:
+    - live preflight 是否 ready。
+    - 视频模型是否为 `doubao-seed-2-0-pro`。
+    - 是否提供 video-resource-readiness summary。
+    - 是否有真实参考视频、真实候选视频和真实视频 URL。
+  - `full-matrix` / `customer-effect` 模式现在会检查:
+    - history audit 是否存在且满足长跑条件。
+    - proof-gap 是否存在。
+    - proof-gap 未解决项是否为 0。
+  - 新增边界:proof-gap 仍有未解决项时,只能继续补真实材料,不能宣称长期优化目标完成。
+- 重写 `scripts/long-run-readiness-smoke.js`:
+  - 覆盖 full-matrix 在 proof-gap=0 时通过。
+  - 覆盖 proof-gap 未解决会拦住 full-matrix。
+  - 覆盖 video-ab 缺 video-resource-readiness 会失败。
+  - 覆盖缺真实候选视频会拦住 video-ab。
+  - 保留 customer-effect 缺 history audit 会失败、视频模型错误会失败的断言。
+- `scripts/optimization-status-audit.js` 已更新 long-run-readiness 状态项,检查 proof-gap 和 video readiness 门禁。
+
+### 本轮验证结果
+
+- `npm.cmd run longrun:readiness:smoke` 通过。
+- `npm.cmd run optimization:status:smoke` 通过。
+- 最新 `npm.cmd run longrun:readiness -- --mode video-ab --video-readiness outputs\video-resource-readiness-latest\video-resource-readiness-summary.json --output outputs\long-run-readiness-video-ab-latest`:
+  - `ready=false`
+  - `failCount=5`
+  - 原因:当前缺 live preflight,且视频资源 readiness 未通过,缺真实候选视频。
+- 最新 `npm.cmd run longrun:readiness -- --mode full-matrix --proof-gap outputs\proof-gap-request-latest\proof-gap-request-summary.json --output outputs\long-run-readiness-full-matrix-latest`:
+  - `ready=false`
+  - `failCount=3`
+  - 原因:当前缺 live preflight、history audit,且 proof-gap 仍有 5 个未解决项。
+- `npm.cmd run hygiene:smoke` 通过:
+  - `scannedFiles=103`
+  - `hitCount=0`
+- `npm.cmd run acceptance` 通过。
+- 最新 `npm.cmd run optimization:status`:
+  - `passed=23`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+
+### 本轮输出
+
+- `outputs/long-run-readiness-video-ab-latest/long-run-readiness-summary.json`
+- `outputs/long-run-readiness-video-ab-latest/long-run-readiness-report.md`
+- `outputs/long-run-readiness-full-matrix-latest/long-run-readiness-summary.json`
+- `outputs/long-run-readiness-full-matrix-latest/long-run-readiness-report.md`
+
+### 当前边界
+
+- longrun readiness 现在会明确阻止未补齐真实 proof-gap 的 full-matrix/customer-effect 长跑。
+- video-ab readiness 现在会明确阻止缺真实候选视频的真实视频 A/B。
+- 当前 readiness 失败是正确结果,不是实现失败;说明还需要真实外部数据。
+
+## 2026-06-07 最终证据刷新与软件端任务表去重
+
+### 已完成
+
+- 新增 `docs/ai-optimization-job-dedup-software-format.md`:
+  - 按 `docs/software-client-table-format.md` 的固定 17 列输出 AI 长跑优化任务。
+  - 将原计划中重复出现的 `token 泄露为 0`、`软件端重复键为 0`、`排名连续`、`sample/smoke 不算业务证明`、`客户效果不可宣称` 等要求合并到唯一任务项。
+  - 使用 `brief编号` 承载任务编号,`博主名称` 承载任务项名称,`主页链接` 承载文档或命令入口,便于软件端或任务系统直接展示。
+- 按顺序刷新本轮证据:
+  - `npm.cmd run optimization:status`
+  - `npm.cmd run evidence:index -- --output outputs\evidence-index-latest`
+  - `npm.cmd run proof-gap:request -- --output outputs\proof-gap-request-latest`
+  - `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest`
+  - `npm.cmd run round:deposition -- --output outputs\round-deposition-latest`
+- 修正并行刷新导致的临时计数不一致,最终 handoff、round deposition 和 evidence index 计数已一致。
+
+### 本轮验证结果
+
+- 最新 `npm.cmd run optimization:status`:
+  - `passed=23`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+- 最新 `npm.cmd run evidence:index -- --output outputs\evidence-index-latest`:
+  - `total=119`
+  - `realEvidence=5`
+  - `smokeOrLocal=23`
+  - `notProof=91`
+- 最新 `npm.cmd run proof-gap:request -- --output outputs\proof-gap-request-latest`:
+  - `gapCount=5`
+  - `unresolvedCount=5`
+  - `complete=false`
+- 最新 `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest`:
+  - `complete=false`
+  - `readyCapabilities=23`
+  - `externalBlockers=2`
+- 最新 `npm.cmd run round:deposition -- --output outputs\round-deposition-latest`:
+  - `passed=true`
+  - `passCount=29`
+  - `failCount=0`
+  - `warnCount=0`
+  - `proofGapUnresolvedCount=5`
+
+### 本轮输出
+
+- `docs/ai-optimization-job-dedup-software-format.md`
+- `outputs/evidence-index-latest/evidence-index-summary.json`
+- `outputs/evidence-index-latest/evidence-index-report.md`
+- `outputs/proof-gap-request-latest/proof-gap-request-summary.json`
+- `outputs/proof-gap-request-latest/proof-gap-request-report.md`
+- `outputs/proof-gap-request-latest/proof-gap-request-table.csv`
+- `outputs/optimization-handoff-latest/optimization-handoff-summary.json`
+- `outputs/optimization-handoff-latest/optimization-handoff-report.md`
+- `outputs/round-deposition-latest/round-deposition-summary.json`
+- `outputs/round-deposition-latest/round-deposition-report.md`
+
+### 当前边界
+
+- 本轮是任务表去重和本地证据沉淀刷新,没有新增真实客户复核数据。
+- 仍有 5 个真实证明缺口:
+  - 真实历史 Brief 数据集。
+  - 真实候选视频资源。
+  - 真实 provider + 真实视频资源下的视频 A/B live proof。
+  - 真实 live/provider 长跑完整通过证明。
+  - 商务复核、客户最终选择和人工补号减少证明。
+- 长期优化目标仍不能标完成;必须等 `history:audit`、`video:resource-readiness`、`acceptance:video-ab`、真实 live/provider 长跑和 `customer-effect:audit` 全部用真实数据通过。
+
+## 2026-06-07 软件端去重任务表纳入自动化门禁
+
+### 已完成
+
+- 更新 `scripts/plan-consistency-smoke.js`:
+  - 新增扫描 `docs/ai-optimization-job-dedup-software-format.md`。
+  - 校验去重任务表必须包含软件端固定 17 列表头。
+  - 校验 `OPT-001` 到 `OPT-012` 共 12 个任务项必须存在且不重复。
+  - 校验去重说明中必须保留:
+    - `软件端重复键为 0`
+    - `sample/smoke/provider fallback 不算业务证明`
+    - `客户效果不可宣称`
+  - 继续拦截旧版 brief 维度去重口径,避免回退到 `brief编号 + 平台 + 主页链接` 这类错误去重方式。
+- 刷新本轮证据和交接沉淀:
+  - `npm.cmd run optimization:status`
+  - `npm.cmd run evidence:index -- --output outputs\evidence-index-latest`
+  - `npm.cmd run proof-gap:request -- --output outputs\proof-gap-request-latest`
+  - `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest`
+  - `npm.cmd run round:deposition -- --output outputs\round-deposition-latest`
+
+### 本轮验证结果
+
+- `npm.cmd run plan:consistency:smoke` 通过:
+  - `ok=true`
+  - `scannedFiles=5`
+  - `hits=[]`
+- `npm.cmd run hygiene:smoke` 通过:
+  - `scannedFiles=104`
+  - `hitCount=0`
+- `npm.cmd run optimization:status:smoke` 通过。
+- `npm.cmd run round:deposition:smoke` 通过:
+  - `passCount=29`
+  - `failCount=0`
+  - `proofGapUnresolvedCount=5`
+- 最新 `npm.cmd run optimization:status`:
+  - `passed=23`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+- 最新 `npm.cmd run evidence:index -- --output outputs\evidence-index-latest`:
+  - `total=120`
+  - `realEvidence=5`
+  - `smokeOrLocal=23`
+  - `notProof=92`
+- 最新 `npm.cmd run proof-gap:request -- --output outputs\proof-gap-request-latest`:
+  - `gapCount=5`
+  - `unresolvedCount=5`
+  - `complete=false`
+- 最新 `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest`:
+  - `complete=false`
+  - `readyCapabilities=23`
+  - `readyNotProven=2`
+  - `externalBlockers=2`
+- 最新 `npm.cmd run round:deposition -- --output outputs\round-deposition-latest`:
+  - `passed=true`
+  - `passCount=29`
+  - `failCount=0`
+  - `warnCount=0`
+
+### 当前边界
+
+- 本轮增强的是文档一致性和交付格式门禁,不是新增真实业务效果证明。
+- 真实证据仍为 5 条,5 个 proof gap 仍未解决。
+- 长期优化目标仍不能标完成;下一步需要真实历史数据、真实候选视频资源、视频 A/B live proof、真实 live/provider 长跑和客户效果审计。
+
+## 2026-06-07 状态审计补充软件端任务表证据
+
+### 已完成
+
+- 更新 `scripts/optimization-status-audit.js`:
+  - 将 `docs/ai-optimization-job-dedup-software-format.md` 加入状态审计文件存在性检查。
+  - `plan-consistency` 状态项现在会检查:
+    - 去重软件端任务表已存在。
+    - `plan-consistency-smoke` 包含固定 17 列表头校验。
+    - `plan-consistency-smoke` 包含 12 个 `OPT` 任务项数量校验。
+    - `plan-consistency-smoke` 包含新去重任务表路径。
+  - 状态报告中的证据描述已更新为:同时覆盖长期计划、软件端表格规范、AI 任务表、去重软件端任务表和 skill 输出规范。
+
+### 本轮验证结果
+
+- `npm.cmd run plan:consistency:smoke` 通过:
+  - `ok=true`
+  - `scannedFiles=5`
+  - `hits=[]`
+- `npm.cmd run optimization:status:smoke` 通过。
+- 最新 `npm.cmd run optimization:status`:
+  - `passed=23`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+- 最新 `npm.cmd run evidence:index -- --output outputs\evidence-index-latest`:
+  - `total=121`
+  - `realEvidence=5`
+  - `smokeOrLocal=23`
+  - `notProof=93`
+- 最新 `npm.cmd run proof-gap:request -- --output outputs\proof-gap-request-latest`:
+  - `gapCount=5`
+  - `unresolvedCount=5`
+  - `complete=false`
+- 最新 `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest`:
+  - `complete=false`
+  - `readyCapabilities=23`
+  - `externalBlockers=2`
+- 最新 `npm.cmd run round:deposition -- --output outputs\round-deposition-latest`:
+  - `passed=true`
+  - `passCount=29`
+  - `failCount=0`
+  - `warnCount=0`
+
+### 当前边界
+
+- 本轮提升的是状态报告的可读性和审计覆盖面,不新增真实客户效果证明。
+- 真实证据仍为 5 条;5 个 proof gap 仍未关闭。
+- 仍不得宣称视频分析已提升命中率,也不得宣称长期优化目标完成。
+
+## 2026-06-07 真实证明缺口关闭审计
+
+### 已完成
+
+- 新增 `scripts/proof-gap-closure-audit.js`:
+  - 读取 `historical-dataset-audit.json`、`video-resource-readiness-summary.json`、`video-hit-rate-summary.json`、`aggregate-summary.json`、`customer-effect-summary.json`。
+  - 逐项判断 5 个真实证明缺口是否关闭:
+    - `historical-dataset`
+    - `video-real-candidate`
+    - `video-ab-live-proof`
+    - `live-provider-overnight-proof`
+    - `manual-review-and-customer-effect`
+  - 输出 `proof-gap-closure-summary.json` 和 `proof-gap-closure-report.md`。
+  - 任一缺口为 `open` 时,`complete=false`,不得宣布长期目标完成。
+- 新增 `scripts/proof-gap-closure-smoke.js`:
+  - 覆盖全部 open 和全部 closed 两类情况。
+  - 验证报告不包含 secret-like 值。
+- 更新 `package.json`:
+  - 新增 `proof-gap:closure`
+  - 新增 `proof-gap:closure:smoke`
+- 更新 `scripts/acceptance.js`:
+  - 默认发布前验收已纳入 `proof-gap:closure:smoke`。
+- 更新 `scripts/evidence-index.js` 和 `scripts/evidence-index-smoke.js`:
+  - 新增识别 `proof-gap-closure-summary.json`。
+  - `complete=true` 时归类为 `real_evidence`。
+  - 仍有 open 缺口时归类为 `not_business_proof`。
+- 更新 `scripts/optimization-status-audit.js`:
+  - 新增 `proof-gap-closure` 状态项。
+  - `evidence-index` 状态项同步检查 proof-gap closure 分类。
+
+### 本轮验证结果
+
+- `npm.cmd run proof-gap:closure:smoke` 通过:
+  - `openCount=5`
+  - `closedCount=5`
+- 当前真实输出下 `npm.cmd run proof-gap:closure -- --output outputs\proof-gap-closure-latest`:
+  - `complete=false`
+  - `closedCount=1`
+  - `openCount=4`
+- `npm.cmd run evidence:index:smoke` 通过:
+  - `total=21`
+  - `real_evidence=5`
+  - `smoke_or_local=8`
+  - `not_business_proof=8`
+- `npm.cmd run optimization:status:smoke` 通过。
+- `npm.cmd run hygiene:smoke` 通过:
+  - `scannedFiles=106`
+  - `hitCount=0`
+- 最新 `npm.cmd run optimization:status`:
+  - `passed=24`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+- 最新 `npm.cmd run evidence:index -- --output outputs\evidence-index-latest`:
+  - `total=124`
+  - `realEvidence=5`
+  - `smokeOrLocal=23`
+  - `notProof=96`
+- 最新 `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest`:
+  - `complete=false`
+  - `readyCapabilities=24`
+  - `externalBlockers=2`
+- 最新 `npm.cmd run round:deposition -- --output outputs\round-deposition-latest`:
+  - `passed=true`
+  - `passCount=29`
+  - `failCount=0`
+  - `warnCount=0`
+
+### 当前边界
+
+- 当前 closure 审计只关闭了 1 个真实证明缺口,仍有 4 个缺口 open。
+- `proof-gap:request` 仍会继续请求 5 类材料;`proof-gap:closure` 用于判断这些材料是否已被真实产物关闭。
+- 长期目标仍不能标完成;至少需要 closure `openCount=0`,并且 `optimization:status complete=true`。
+
+## 2026-06-07 closure 纳入交接摘要和每轮沉淀审计
+
+### 已完成
+
+- 更新 `scripts/generate-optimization-handoff.js`:
+  - `nextCommands` 新增 `npm run proof-gap:closure -- --output outputs\proof-gap-closure-latest`。
+  - 使用时机说明为:真实历史数据、视频资源、视频 A/B、live 长跑或客户效果产物更新后运行;只有 `openCount=0` 才能进入长期完成判断。
+- 更新 `scripts/generate-optimization-handoff-smoke.js`:
+  - 新增断言:handoff 必须包含 `proof-gap:closure` 命令。
+- 更新 `scripts/round-deposition-audit.js`:
+  - 新增读取 `proof-gap-closure-summary.json`。
+  - 新增检查:
+    - closure summary 存在。
+    - closure report 存在。
+    - 未完成时记录 `proofGapClosure.openCount`。
+    - handoff 记录 `proof-gap:closure` 命令。
+  - `observed` 中新增:
+    - `proofGapClosureComplete`
+    - `proofGapClosureOpenCount`
+    - `proofGapClosureRows`
+- 更新 `scripts/round-deposition-smoke.js`:
+  - 新增 proof-gap closure fixture。
+  - 新增断言 round deposition 记录 closure open count 和 closure 命令。
+
+### 本轮验证结果
+
+- `npm.cmd run handoff:summary:smoke` 通过:
+  - `readyCapabilities=24`
+  - `readyNotProven=2`
+  - `externalBlockers=2`
+- `npm.cmd run round:deposition:smoke` 通过:
+  - `passCount=33`
+  - `failCount=0`
+  - `proofGapUnresolvedCount=5`
+- `npm.cmd run optimization:status:smoke` 通过。
+- `npm.cmd run proof-gap:closure:smoke` 通过。
+- `npm.cmd run hygiene:smoke` 通过:
+  - `scannedFiles=106`
+  - `hitCount=0`
+- 最新 `npm.cmd run optimization:status`:
+  - `passed=24`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+- 最新 `npm.cmd run evidence:index -- --output outputs\evidence-index-latest`:
+  - `total=125`
+  - `realEvidence=5`
+  - `smokeOrLocal=23`
+  - `notProof=97`
+- 最新 `npm.cmd run proof-gap:request -- --output outputs\proof-gap-request-latest`:
+  - `gapCount=5`
+  - `unresolvedCount=5`
+  - `complete=false`
+- 最新 `npm.cmd run proof-gap:closure -- --output outputs\proof-gap-closure-latest`:
+  - `complete=false`
+  - `closedCount=1`
+  - `openCount=4`
+- 最新 `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest`:
+  - `complete=false`
+  - `readyCapabilities=24`
+  - `externalBlockers=2`
+- 最新 `npm.cmd run round:deposition -- --output outputs\round-deposition-latest`:
+  - `passed=true`
+  - `passCount=33`
+  - `failCount=0`
+  - `warnCount=0`
+
+### 注意事项
+
+- handoff 和 round deposition 不能并行刷新;round deposition 会读取最新 handoff。若并行运行,可能暂时读到旧 handoff 并触发 `closure-command-recorded` 失败。
+- 本轮已按顺序重跑后通过。
+- 当前 closure 仍为 `openCount=4`,长期目标仍不能标完成。
+
+## 2026-06-07 proof-gap closure 可执行化字段验证和最新沉淀
+
+### 已完成
+
+- 更新并验证 `scripts/proof-gap-closure-audit.js` 的关闭报告可执行化字段:
+  - 每个缺口行输出 `验证命令`。
+  - 每个缺口行输出 `期望产物`。
+  - open 缺口的 `下一步` 同时包含命令和期望产物,便于下一轮 AI 或商务直接补证。
+- 更新并验证 `scripts/proof-gap-closure-smoke.js`:
+  - smoke 断言每个 closure row 都有 command 和 expectedArtifact。
+  - smoke 断言 Markdown 报告包含 `验证命令` 和 `期望产物` 两列。
+- 重写 `docs/ai-optimization-job-dedup-software-format.md`:
+  - 将长期优化任务从 12 项去重合并为 9 项。
+  - 使用软件端固定表头输出。
+  - 增加字段映射、合并去重说明和软件端验收口径。
+
+### 本轮验证结果
+
+- `npm.cmd run proof-gap:closure:smoke` 通过:
+  - `ok=true`
+  - smoke fixture 中 `openCount=5`
+  - smoke fixture 中 `closedCount=5`
+- `npm.cmd run proof-gap:closure -- --output outputs\proof-gap-closure-latest` 通过生成产物:
+  - `complete=false`
+  - `closedCount=1`
+  - `openCount=4`
+  - 报告已包含 `验证命令` 和 `期望产物` 两列。
+- `npm.cmd run hygiene:smoke` 通过:
+  - `scannedFiles=106`
+  - `hitCount=0`
+- `npm.cmd run evidence:index:smoke` 通过:
+  - `total=21`
+  - `real_evidence=5`
+  - `smoke_or_local=8`
+  - `not_business_proof=8`
+- `npm.cmd run optimization:status:smoke` 通过。
+- `npm.cmd run handoff:summary:smoke` 通过:
+  - `readyCapabilities=24`
+  - `readyNotProven=2`
+  - `externalBlockers=2`
+- `npm.cmd run round:deposition:smoke` 通过:
+  - `passCount=33`
+  - `failCount=0`
+  - `proofGapUnresolvedCount=5`
+
+### 最新正式产物
+
+- `npm.cmd run optimization:status`:
+  - 输出目录:`outputs/optimization-status-1780809383717`
+  - `passed=24`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+- `npm.cmd run evidence:index -- --output outputs\evidence-index-latest`:
+  - `total=125`
+  - `realEvidence=5`
+  - `smokeOrLocal=23`
+  - `notProof=97`
+- `npm.cmd run proof-gap:request -- --output outputs\proof-gap-request-latest`:
+  - `complete=false`
+  - `unresolvedCount=5`
+- `npm.cmd run proof-gap:closure -- --output outputs\proof-gap-closure-latest`:
+  - `complete=false`
+  - `closedCount=1`
+  - `openCount=4`
+- `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest`:
+  - `complete=false`
+  - `readyCapabilities=24`
+  - `externalBlockers=2`
+  - `nextCommands` 已包含 `proof-gap:closure`。
+- `npm.cmd run round:deposition -- --output outputs\round-deposition-latest`:
+  - `passed=true`
+  - `passCount=33`
+  - `failCount=0`
+  - `warnCount=0`
+  - `proofGap.unresolvedCount=5`
+  - `proofGapClosure.openCount=4`
+
+### 当前仍未完成的真实证明
+
+- 真实历史 Brief 数据集:缺 `historical-dataset-audit.json`,需要 `history:audit` 显示 `readyForCustomerEffectProof=true`。
+- 真实候选视频资源:当前 `realReference=1`、`realCandidate=0`、`failureCount=1`,需要 `video:resource-readiness` 严格通过。
+- 真实 live/provider 长跑证明:当前 `liveEnabled=false`、`overallPass=false`,需要真实 provider 下 `overnight:quality` 产物显示 `liveEnabled=true`、`overallPass=true`、`failureCount=0`、`failedGateCount=0`。
+- 商务复核和客户效果证明:缺 `customer-effect-summary.json`,需要 `customer-effect:audit` 显示 `overallPass=true`。
+
+### 边界
+
+- 当前长期优化目标仍未完成。
+- `acceptance:video-ab` 已有通过产物,但 closure 报告仍保留其他真实证明缺口;不得用视频 A/B 单项关闭替代完整客户效果证明。
+- sample、smoke、provider fallback、模板和接口 200 仍不得作为真实业务效果证明。
+
+## 2026-06-07 proof-gap 请求包补齐模板入口
+
+### 已完成
+
+- 更新 `scripts/generate-proof-gap-request.js`:
+  - `proof-gap-request-summary.json` 每个缺口新增 `templateCommand` 和 `templateArtifact`。
+  - `proof-gap-request-report.md` 的请求明细表新增 `模板命令` 和 `模板产物` 两列。
+  - `proof-gap-request-table.csv` 新增 `模板命令` 和 `模板产物` 两列。
+  - 历史数据、live 长跑和客户效果缺口指向 `data:intake-template`。
+  - 视频资源和视频 A/B 缺口指向 `video:intake-template`。
+- 更新 `scripts/generate-proof-gap-request-smoke.js`:
+  - 断言每个缺口行都有模板命令和模板产物。
+  - 断言历史数据缺口指向 `data:intake-template`。
+  - 断言视频资源缺口指向 `video:intake-template`。
+  - 断言 Markdown 和 CSV 都输出 `模板命令`、`模板产物` 字段。
+- 生成最新真实材料收集模板包:
+  - `outputs/data-intake-pack-latest/history-data-template.csv`
+  - `outputs/data-intake-pack-latest/manual-review-template.csv`
+  - `outputs/data-intake-pack-latest/README.md`
+  - `outputs/video-intake-pack-latest/video-resource-template.csv`
+  - `outputs/video-intake-pack-latest/README.md`
+
+### 本轮验证结果
+
+- `npm.cmd run proof-gap:request:smoke` 通过:
+  - `gapCount=5`
+  - `unresolvedCount=5`
+- `npm.cmd run data:intake-template:smoke` 通过:
+  - `files=3`
+- `npm.cmd run video:intake-template:smoke` 通过:
+  - `files=2`
+- `npm.cmd run data:intake-template -- --output outputs\data-intake-pack-latest` 通过,生成历史数据和人工复核模板。
+- `npm.cmd run video:intake-template -- --output outputs\video-intake-pack-latest` 通过,生成视频资源模板。
+- `npm.cmd run proof-gap:request -- --output outputs\proof-gap-request-latest` 通过:
+  - `complete=false`
+  - `gapCount=5`
+  - `unresolvedCount=5`
+  - 报告已包含 `模板命令` 和 `模板产物` 两列。
+- `npm.cmd run hygiene:smoke` 通过:
+  - `scannedFiles=106`
+  - `hitCount=0`
+- `npm.cmd run evidence:index -- --output outputs\evidence-index-latest`:
+  - `total=126`
+  - `realEvidence=5`
+  - `smokeOrLocal=23`
+  - `notProof=98`
+- `npm.cmd run proof-gap:closure -- --output outputs\proof-gap-closure-latest`:
+  - `complete=false`
+  - `closedCount=1`
+  - `openCount=4`
+- `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest`:
+  - `complete=false`
+  - `readyCapabilities=24`
+  - `externalBlockers=2`
+
+### 当前边界
+
+- 本轮生成的是真实材料收集入口和模板,不是业务效果证明。
+- `proof-gap:request` 仍有 5 个 unresolved 缺口,`proof-gap:closure` 仍有 4 个 open 缺口。
+- `outputs/data-intake-pack-latest` 和 `outputs/video-intake-pack-latest` 可交给商务/投放补真实数据;补完后必须重新跑对应审计命令。
+
+## 2026-06-07 模板入口纳入每轮沉淀审计
+
+### 已完成
+
+- 更新 `scripts/round-deposition-audit.js`:
+  - 新增检查 `proof-gap-template-entry-recorded`,要求 `proof-gap-request-summary.json` 每个缺口行都包含 `templateCommand` 和 `templateArtifact`。
+  - 新增检查 `proof-gap-template-columns-exported`,要求 `proof-gap-request-report.md` 和 `proof-gap-request-table.csv` 都导出 `模板命令`、`模板产物` 两列。
+  - `observed` 新增 `proofGapTemplateRowsReady` 和 `proofGapTemplateColumnsReady`。
+  - `proofGapRows` 记录每个缺口是否有模板命令和模板产物。
+- 更新 `scripts/round-deposition-smoke.js`:
+  - smoke fixture 补充模板命令和模板产物。
+  - 断言沉淀审计包含模板入口检查。
+  - 断言报告展示 `proofGap.templateRowsReady` 和 `proofGap.templateColumnsReady`。
+
+### 本轮验证结果
+
+- `npm.cmd run round:deposition:smoke` 通过:
+  - `passCount=35`
+  - `failCount=0`
+  - `proofGapUnresolvedCount=5`
+- `npm.cmd run proof-gap:request:smoke` 通过:
+  - `gapCount=5`
+  - `unresolvedCount=5`
+- `npm.cmd run proof-gap:request -- --output outputs\proof-gap-request-latest`:
+  - `complete=false`
+  - `gapCount=5`
+  - `unresolvedCount=5`
+- `npm.cmd run hygiene:smoke` 通过:
+  - `scannedFiles=106`
+  - `hitCount=0`
+- `npm.cmd run evidence:index -- --output outputs\evidence-index-latest`:
+  - `total=126`
+  - `realEvidence=5`
+  - `smokeOrLocal=23`
+  - `notProof=98`
+- `npm.cmd run proof-gap:closure -- --output outputs\proof-gap-closure-latest`:
+  - `complete=false`
+  - `closedCount=1`
+  - `openCount=4`
+- `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest`:
+  - `complete=false`
+  - `readyCapabilities=24`
+  - `externalBlockers=2`
+
+### 当前边界
+
+- 每轮沉淀现在会拦截 proof-gap 请求包缺少模板入口的情况。
+- 这只提升补证流程的可执行性,不改变真实业务证明状态。
+- 长期优化目标仍需真实历史 Brief、真实候选视频、真实 live/provider 长跑、商务复核和客户效果审计全部闭环。
+
+## 2026-06-07 新增真实材料 Intake Readiness 预审
+
+### 已完成
+
+- 新增 `scripts/intake-readiness-audit.js`:
+  - 支持 `--data-pack <目录>` 和 `--video-pack <目录>`。
+  - 同时检查 `history-data-template.csv`、`manual-review-template.csv`、`video-resource-template.csv`。
+  - 拦截 `example.com`、`示例`、`候选博主`、`待确认`、`人工待补` 等模板占位内容。
+  - 检查历史 Brief 数、客户原始 Brief、历史人工补号量基线、参考账号/视频、人工最终名单、客户选择和拒绝原因。
+  - 检查人工复核标签、负样本归因、客户最终选择和本轮人工补号量。
+  - 检查真实参考视频、真实候选视频、真实视频 URL 和视频证据。
+  - 输出 `intake-readiness-summary.json` 和 `intake-readiness-report.md`。
+- 新增 `scripts/intake-readiness-smoke.js`:
+  - 验证刚生成的模板包会被判定为未就绪。
+  - 验证一组模拟真实 CSV 会通过 readiness。
+- 更新 `package.json`:
+  - 新增 `npm run intake:readiness`。
+  - 新增 `npm run intake:readiness:smoke`。
+- 更新 `scripts/acceptance.js`:
+  - 总验收纳入 `intake:readiness:smoke`。
+- 更新 `scripts/evidence-index.js` 和 `scripts/evidence-index-smoke.js`:
+  - 证据台账新增 `intake-readiness` 类型。
+  - `overallReady=true` 只记为 `smoke_or_local` 前置证明。
+  - `overallReady=false` 记为 `not_business_proof`。
+- 更新 `scripts/generate-optimization-handoff.js` 和 smoke:
+  - 下一轮命令新增 `npm run intake:readiness -- --data-pack outputs\data-intake-pack-latest --video-pack outputs\video-intake-pack-latest --output outputs\intake-readiness-latest`。
+
+### 本轮验证结果
+
+- `npm.cmd run intake:readiness:smoke` 通过:
+  - 新模板包 `templateFailureCount=9`
+  - 模拟真实数据 `realFailureCount=0`
+- `npm.cmd run data:intake-template:smoke` 通过。
+- `npm.cmd run video:intake-template:smoke` 通过。
+- `npm.cmd run intake:readiness -- --data-pack outputs\data-intake-pack-latest --video-pack outputs\video-intake-pack-latest --output outputs\intake-readiness-latest`:
+  - `overallReady=false`
+  - `readyForHistoryAudit=false`
+  - `readyForVideoResourceAudit=false`
+  - `readyForReviewMetrics=false`
+  - `readyForCustomerEffectAudit=false`
+  - `failureCount=9`
+- `npm.cmd run evidence:index:smoke` 通过:
+  - `total=23`
+  - `real_evidence=5`
+  - `smoke_or_local=9`
+  - `not_business_proof=9`
+- `npm.cmd run hygiene:smoke` 通过:
+  - `scannedFiles=108`
+  - `hitCount=0`
+- `npm.cmd run handoff:summary:smoke` 通过。
+- `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest`:
+  - `complete=false`
+  - `readyCapabilities=24`
+  - `externalBlockers=2`
+- `npm.cmd run evidence:index -- --output outputs\evidence-index-latest`:
+  - `total=127`
+  - `realEvidence=5`
+  - `smokeOrLocal=23`
+  - `notProof=99`
+- `npm.cmd run proof-gap:closure -- --output outputs\proof-gap-closure-latest`:
+  - `complete=false`
+  - `closedCount=1`
+  - `openCount=4`
+
+### 当前边界
+
+- `intake:readiness` 是正式审计前的“半成品拦截器”,不能替代 `history:audit`、`video:resource-readiness`、`review:metrics` 或 `customer-effect:audit`。
+- 当前 latest 模板包仍未被真实数据替换,因此 readiness 为未就绪是正确结果。
+- 长期目标仍不能标完成;真实业务证明仍依赖商务/投放补齐真实历史 Brief、真实候选视频、客户选择和本轮人工补号量。
+
+## 2026-06-07 intake readiness 纳入状态审计
+
+### 已完成
+
+- 更新 `scripts/optimization-status-audit.js`:
+  - 文件清单新增 `scripts/intake-readiness-audit.js` 和 `scripts/intake-readiness-smoke.js`。
+  - 状态项新增 `intake-readiness`,用于确认真实材料半成品预审能力已接入。
+  - `evidence-index` 状态项同步检查 `intake-readiness-summary.json` 和 `type: 'intake-readiness'`。
+  - `plan-consistency` 状态项文案从 12 个 OPT 行更新为 9 个去重后 OPT 行。
+- 更新 `scripts/optimization-status-smoke.js`:
+  - 断言状态审计暴露稳定 ID:`intake-readiness`。
+- 更新 `scripts/plan-consistency-smoke.js`:
+  - 去重任务数从 12 项改为 9 项。
+  - 软件端表头识别改为完整匹配 17 列固定表头,避免误抓字段映射表。
+- 更新 `docs/ai-optimization-job-dedup-software-format.md`:
+  - 补回 `软件端重复键为 0`。
+  - 补回 `sample/smoke/provider fallback 不算业务证明`。
+  - 补回 `客户效果不可宣称`。
+
+### 本轮验证结果
+
+- `npm.cmd run plan:consistency:smoke` 通过:
+  - `scannedFiles=5`
+  - `hits=[]`
+- `npm.cmd run optimization:status:smoke` 通过。
+- `npm.cmd run evidence:index:smoke` 通过:
+  - `total=23`
+  - `real_evidence=5`
+  - `smoke_or_local=9`
+  - `not_business_proof=9`
+- `npm.cmd run intake:readiness:smoke` 通过:
+  - `templateFailureCount=9`
+  - `realFailureCount=0`
+- `npm.cmd run hygiene:smoke` 通过:
+  - `scannedFiles=108`
+  - `hitCount=0`
+- `npm.cmd run optimization:status`:
+  - 输出目录:`outputs/optimization-status-1780810669737`
+  - `passed=25`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+- `npm.cmd run evidence:index -- --output outputs\evidence-index-latest`:
+  - `total=128`
+  - `realEvidence=5`
+  - `smokeOrLocal=23`
+  - `notProof=100`
+- `npm.cmd run proof-gap:request -- --output outputs\proof-gap-request-latest`:
+  - `complete=false`
+  - `gapCount=5`
+  - `unresolvedCount=5`
+- `npm.cmd run proof-gap:closure -- --output outputs\proof-gap-closure-latest`:
+  - `complete=false`
+  - `closedCount=1`
+  - `openCount=4`
+- `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest`:
+  - `complete=false`
+  - `readyCapabilities=25`
+  - `externalBlockers=2`
+
+### 当前边界
+
+- `intake-readiness` 是能力项通过,不代表当前模板已具备真实业务数据。
+- 当前 `outputs/intake-readiness-latest` 仍显示 `overallReady=false`,所以不能进入真实长跑证明或客户效果证明。
+- 长期优化目标仍未完成,仍需关闭 4 个 proof-gap closure open 项。
+
+## 2026-06-07 intake readiness 接入 closure 和 longrun readiness
+
+### 已完成
+
+- 更新 `scripts/proof-gap-closure-audit.js`:
+  - 新增读取 `--intake-readiness` / `TIHAO_INTAKE_READINESS_SUMMARY` / 最新 `intake-readiness-summary.json`。
+  - 关闭审计新增 `intake-readiness` 行,展示 `overallReady`、`failureCount`、history/video/review/customerEffect readiness。
+  - `intake-readiness` 作为补证预审行展示,不计入 5 类真实业务缺口的 `openCount`。
+  - 报告摘要新增 `Intake 预审是否就绪`。
+- 更新 `scripts/proof-gap-closure-smoke.js`:
+  - smoke fixture 覆盖 intake 未就绪和 intake 已就绪两种情况。
+- 更新 `scripts/long-run-readiness-audit.js`:
+  - 新增可选参数 `--intake-readiness`。
+  - `full-matrix` 和 `customer-effect` 模式下,如果提供 intake readiness,则要求 `overallReady=true` 且 `failureCount=0`。
+  - `video-ab` 模式下,如果提供 intake readiness,则要求 `videoReady=true`。
+  - 下一步命令中加入 `npm run intake:readiness`。
+- 更新 `scripts/long-run-readiness-smoke.js`:
+  - 验证 failing intake readiness 会阻断 full matrix readiness。
+- 更新 `scripts/optimization-status-audit.js`:
+  - `long-run-readiness` 状态项检查 intake readiness 门禁。
+  - `proof-gap-closure` 状态项检查 closure 已包含 intake readiness 行和 `intakeReady` 字段。
+
+### 本轮验证结果
+
+- `npm.cmd run proof-gap:closure:smoke` 通过:
+  - `openCount=5`
+  - `closedCount=5`
+- `npm.cmd run longrun:readiness:smoke` 通过。
+- `npm.cmd run optimization:status:smoke` 通过。
+- `npm.cmd run hygiene:smoke` 通过:
+  - `scannedFiles=108`
+  - `hitCount=0`
+- `npm.cmd run longrun:readiness -- --mode full-matrix --intake-readiness outputs\intake-readiness-latest\intake-readiness-summary.json --proof-gap outputs\proof-gap-request-latest\proof-gap-request-summary.json --output outputs\long-run-readiness-latest`:
+  - `ready=false`
+  - `failCount=5`
+  - `warnCount=0`
+- `npm.cmd run optimization:status`:
+  - 输出目录:`outputs/optimization-status-1780810937378`
+  - `passed=25`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+- `npm.cmd run proof-gap:closure -- --intake-readiness outputs\intake-readiness-latest\intake-readiness-summary.json --output outputs\proof-gap-closure-latest`:
+  - `complete=false`
+  - `intakeReady=false`
+  - `closedCount=1`
+  - `openCount=4`
+- `npm.cmd run evidence:index -- --output outputs\evidence-index-latest`:
+  - `total=130`
+  - `realEvidence=5`
+  - `smokeOrLocal=23`
+  - `notProof=102`
+- `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest`:
+  - `complete=false`
+  - `readyCapabilities=25`
+  - `externalBlockers=2`
+
+### 当前边界
+
+- longrun readiness 现在会明确拒绝半成品模板进入 full matrix/customer-effect 路径。
+- proof-gap closure 现在会显示 intake 预审是否就绪,但 `openCount` 仍只统计真实业务证明缺口。
+- 当前 `intakeReady=false`、`longrun ready=false`、`proof-gap closure openCount=4`,长期目标仍不能标完成。
+
+## 2026-06-07 intake/longrun 纳入每轮沉淀审计
+
+### 已完成
+
+- 更新 `scripts/round-deposition-audit.js`:
+  - 新增读取 `intake-readiness-summary.json`。
+  - 新增读取 `long-run-readiness-summary.json`。
+  - 新增检查 `intake-readiness-summary`。
+  - 新增检查 `intake-readiness-boundary-recorded`。
+  - 新增检查 `longrun-readiness-summary`。
+  - 新增检查 `longrun-readiness-intake-gate`。
+  - `sources` 新增 intake readiness 和 longrun readiness 两个来源。
+  - `observed` 新增 `intakeReadinessReady`、`intakeReadinessFailureCount`、`longRunReadinessMode`、`longRunReadinessReady`、`longRunReadinessFailCount`。
+  - 报告“当前观测”新增 intake/longrun readiness 状态。
+- 更新 `scripts/round-deposition-smoke.js`:
+  - smoke fixture 增加 intake readiness 和 longrun readiness 产物。
+  - 断言沉淀审计包含 intake/longrun 检查项。
+  - 断言报告展示 intake/longrun 状态。
+
+### 本轮验证结果
+
+- `npm.cmd run round:deposition:smoke` 通过:
+  - `passCount=39`
+  - `failCount=0`
+  - `proofGapUnresolvedCount=5`
+- `npm.cmd run hygiene:smoke` 通过:
+  - `scannedFiles=108`
+  - `hitCount=0`
+- `npm.cmd run round:deposition -- --output outputs\round-deposition-latest`:
+  - `passed=true`
+  - `passCount=39`
+  - `failCount=0`
+  - `warnCount=0`
+- `npm.cmd run evidence:index:smoke` 通过:
+  - `total=23`
+  - `real_evidence=5`
+  - `smoke_or_local=9`
+  - `not_business_proof=9`
+- `npm.cmd run optimization:status:smoke` 通过。
+
+### 最新沉淀观测
+
+- `outputs/round-deposition-latest/round-deposition-summary.json`:
+  - `passed=true`
+  - `pass=39`
+  - `fail=0`
+  - `proofGap.unresolvedCount=5`
+  - `intakeReadiness.ready=false`
+  - `intakeReadiness.failureCount=9`
+  - `longRunReadiness.mode=full-matrix`
+  - `longRunReadiness.ready=false`
+  - `longRunReadiness.failCount=5`
+  - `proofGapClosure.openCount=4`
+
+### 当前边界
+
+- 每轮沉淀现在会强制要求 intake readiness 和 longrun readiness 产物存在。
+- 当前沉淀通过只证明本轮证据链完整,不证明 longrun 就绪,也不证明客户效果。
+- `intakeReadiness.ready=false` 和 `longRunReadiness.ready=false` 是当前真实边界,长期优化目标仍未完成。
+
+## 2026-06-07 交接摘要补充负责人行动文件
+
+### 已完成
+
+- 更新 `scripts/generate-optimization-handoff.js`:
+  - 读取 `outputs/business-proof-next-actions-latest/business-proof-next-actions-summary.json` 中的 `ownerGroups` 和 `ownerArtifacts`。
+  - 在 handoff JSON 的 `businessProofNextActions` 下输出负责人分组和负责人专属文件。
+  - 在 handoff Markdown 报告中新增“负责人行动文件”表格。
+- 更新 `scripts/generate-optimization-handoff-smoke.js`:
+  - 强制断言 handoff JSON 包含 `ownerArtifacts`。
+  - 强制断言报告包含“负责人行动文件”和 `by-owner/` 文件入口。
+- 更新 `docs/ai-optimization-job-dedup-software-format.md`:
+  - 按软件端固定字段输出 9 条去重任务。
+  - 保留固定边界短语:`sample/smoke/provider fallback 不算业务证明`、`客户效果不可宣称`。
+
+### 本轮验证结果
+
+- `node --check scripts\generate-optimization-handoff.js` 通过。
+- `node --check scripts\generate-optimization-handoff-smoke.js` 通过。
+- `npm.cmd run handoff:summary:smoke` 通过:
+  - `readyCapabilities=27`
+  - `readyNotProven=2`
+  - `externalBlockers=2`
+- `npm.cmd run business-proof:next-actions:smoke` 通过:
+  - `actionCount=8`
+  - `topAction=填真实历史 Brief`
+- `npm.cmd run optimization:status:smoke` 通过。
+- `npm.cmd run plan:consistency:smoke` 通过:
+  - `scannedFiles=6`
+  - `hits=0`
+- `npm.cmd run hygiene:smoke` 通过:
+  - `scannedFiles=113`
+  - `hitCount=0`
+- `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest` 已刷新:
+  - `complete=false`
+  - `readyCapabilities=27`
+  - `externalBlockers=2`
+- `npm.cmd run acceptance` 通过:
+  - package smoke、MCP smoke、provider mock、软件端表、视频 A/B smoke、business proof、round deposition smoke 全部通过。
+- `npm.cmd run evidence:index -- --output outputs\evidence-index-latest` 已刷新:
+  - `total=137`
+  - `realEvidence=5`
+  - `smokeOrLocal=25`
+  - `notProof=107`
+- `npm.cmd run round:deposition -- --output outputs\round-deposition-latest` 已刷新:
+  - `passed=true`
+  - `passCount=44`
+  - `failCount=0`
+  - `warnCount=0`
+
+### 最新负责人行动文件
+
+- 商务:
+  - `outputs/business-proof-next-actions-latest/by-owner/business.md`
+  - `outputs/business-proof-next-actions-latest/by-owner/business.csv`
+- 投放/商务:
+  - `outputs/business-proof-next-actions-latest/by-owner/media-business.md`
+  - `outputs/business-proof-next-actions-latest/by-owner/media-business.csv`
+- 商务/投放:
+  - `outputs/business-proof-next-actions-latest/by-owner/business-media.md`
+  - `outputs/business-proof-next-actions-latest/by-owner/business-media.csv`
+
+### 当前边界
+
+- handoff 现在可以直接分派给负责人,但行动队列仍只证明“下一步补证可执行”,不证明客户效果完成。
+- 当前长期目标仍未完成:真实历史 Brief、真实视频资源、商务复核标注、客户选择和人工补号减少证明仍需外部真实数据闭环。
+
+## 2026-06-07 下一步行动队列去重合并
+
+### 已完成
+
+- 更新 `scripts/business-proof-next-actions.js`:
+  - 同一份业务材料的多个 intake 字段缺口不再拆成多条重复任务。
+  - `history` 字段缺口合并到“填真实历史 Brief”。
+  - `video` 字段缺口合并到“填真实视频资源”。
+  - `review` 字段缺口合并到“人工复核标注”。
+  - 当 15 步进度中已有对应主动作时,`proof-gap-closure` 的同类 open 项不再重复进入行动队列。
+  - 总报告和负责人专属 Markdown 均新增“下一步说明”列,确保合并后的具体缺口可见。
+- 更新 `scripts/business-proof-next-actions-smoke.js`:
+  - 断言 history/video/review intake issue 必须合并到主动作。
+  - 断言 proof-gap 的历史数据项不重复覆盖 15 步主动作。
+  - 断言报告展示“下一步说明”和合并后的缺口明细。
+
+### 本轮验证结果
+
+- `node --check scripts\business-proof-next-actions.js` 通过。
+- `node --check scripts\business-proof-next-actions-smoke.js` 通过。
+- `npm.cmd run business-proof:next-actions:smoke` 通过:
+  - `actionCount=5`
+  - `topAction=填真实历史 Brief`
+- `npm.cmd run business-proof:next-actions -- --output outputs\business-proof-next-actions-latest` 已刷新:
+  - `actionCount=8`
+  - `ownerArtifacts=3`
+  - `topAction=填真实历史 Brief`
+- `npm.cmd run handoff:summary:smoke` 通过:
+  - `readyCapabilities=27`
+  - `readyNotProven=2`
+  - `externalBlockers=2`
+- `npm.cmd run plan:consistency:smoke` 通过:
+  - `hits=0`
+- `npm.cmd run hygiene:smoke` 通过:
+  - `scannedFiles=113`
+  - `hitCount=0`
+- `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest` 已刷新:
+  - `complete=false`
+  - `readyCapabilities=27`
+  - `externalBlockers=2`
+- `npm.cmd run evidence:index -- --output outputs\evidence-index-latest` 已刷新:
+  - `total=138`
+  - `realEvidence=5`
+  - `smokeOrLocal=25`
+  - `notProof=108`
+- `npm.cmd run round:deposition -- --output outputs\round-deposition-latest` 已刷新:
+  - `passed=true`
+  - `passCount=44`
+  - `failCount=0`
+  - `warnCount=0`
+
+### 最新负责人视图
+
+- 商务行动已收敛为 2 个主动作:
+  - `填真实历史 Brief`
+  - `人工复核标注`
+- 商务/投放行动已收敛为 1 个主动作:
+  - `填真实视频资源`
+- 每个主动作的“下一步说明”保留具体缺口,例如真实 Brief 数不足、模板占位、缺参考链接、缺人工名单、缺真实候选视频、缺客户最终选中/拒绝记录等。
+
+### 当前边界
+
+- 去重后的行动队列提升了商务执行清晰度,但仍不证明真实业务效果。
+- 当前长期目标仍未完成:`intakeReadiness.ready=false`、真实历史数据和真实视频资源未闭合,客户效果审计仍缺真实客户选择和人工补号量证明。
+
+## 2026-06-07 收集包一键审计 Pipeline
+
+### 已完成
+
+- 更新 `scripts/optimization-pipeline.js`:
+  - 新增 `--data-pack` 和 `--video-pack` 入口。
+  - 从 data pack 自动识别 `history-data-template.csv` 和 `manual-review-template.csv`。
+  - 从 video pack 自动识别 `video-resource-template.csv`。
+  - 当提供 data/video pack 时,pipeline 会先运行 `intake:readiness`。
+  - 当提供视频资源 CSV 时,pipeline 会运行 `video:resource-readiness`。
+  - `longrun:readiness` 会接收 pipeline 生成的 `intake-readiness-summary.json` 和 `video-resource-readiness-summary.json`。
+- 更新 `scripts/optimization-pipeline-smoke.js`:
+  - 保留旧的 `--history-csv/--review-csv` smoke。
+  - 新增 data/video pack dry-run smoke,验证会规划 intake readiness、video resource readiness,并把 intake summary 传给 longrun readiness。
+- 更新 `docs/tihao-experience-optimization-plan.md`:
+  - 在 pipeline 章节新增 data-pack/video-pack 一键入口命令。
+- 更新 `scripts/generate-optimization-handoff.js` 和 `scripts/generate-optimization-handoff-smoke.js`:
+  - handoff “下一轮命令”新增“收集包一键审计 Pipeline”。
+  - smoke 断言 handoff 必须暴露 `optimization:pipeline --data-pack --video-pack` 命令。
+
+### 本轮验证结果
+
+- `node --check scripts\optimization-pipeline.js` 通过。
+- `node --check scripts\optimization-pipeline-smoke.js` 通过。
+- `npm.cmd run optimization:pipeline:smoke` 通过:
+  - `steps=6`
+- `npm.cmd run handoff:summary:smoke` 通过:
+  - `readyCapabilities=27`
+  - `readyNotProven=2`
+  - `externalBlockers=2`
+- `npm.cmd run plan:consistency:smoke` 通过:
+  - `hits=0`
+- `npm.cmd run hygiene:smoke` 通过:
+  - `scannedFiles=113`
+  - `hitCount=0`
+- `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest` 已刷新:
+  - `complete=false`
+  - `readyCapabilities=27`
+  - `externalBlockers=2`
+- `npm.cmd run evidence:index -- --output E:\workspace\tihao-ai\claude-code-tihao-sourcing\outputs\evidence-index-latest` 已刷新:
+  - `total=140`
+  - `realEvidence=5`
+  - `smokeOrLocal=25`
+  - `notProof=110`
+- `npm.cmd run round:deposition -- --output outputs\round-deposition-latest` 已刷新:
+  - `passed=true`
+  - `passCount=44`
+  - `failCount=0`
+  - `warnCount=0`
+- `npm.cmd run optimization:status -- --output outputs\optimization-status-latest` 已刷新:
+  - `passed=27`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+
+### 当前边界
+
+- pipeline 新入口只降低真实材料补齐后的执行门槛,不替代真实数据本身。
+- 当前仍不能宣称客户效果完成:真实历史 Brief、真实视频资源、商务复核、客户选择和人工补号量证明仍需补齐并通过 strict 审计。
+
+## 2026-06-07 Pipeline 下一步动作沉淀
+
+### 已完成
+
+- 更新 `scripts/optimization-pipeline.js`:
+  - `optimization-pipeline-summary.json` 新增 `nextActions`。
+  - `optimization-pipeline-report.md` 新增“下一步动作”章节。
+  - dry-run 会提示确认输入后去掉 `--dry-run` 执行真实 pipeline。
+  - 失败步骤会映射到可分派动作,例如修复真实材料收集包、补齐历史数据审计字段、补齐真实视频资源、修复长跑前门禁、补齐人工复核标注和补齐客户效果证明。
+  - Windows 子进程执行改为 `cmd.exe /d /s /c` 包装,避免 `shell:true + args` 的弃用提醒,同时保留 `npm.cmd` 兼容性。
+- 更新 `scripts/optimization-pipeline-smoke.js`:
+  - 断言 dry-run summary 必须输出 `nextActions`。
+  - 断言 pipeline report 必须包含“下一步动作”章节。
+  - 断言 data/video pack dry-run 会提示去掉 dry-run 后重跑。
+- 更新 `scripts/evidence-index.js`:
+  - `optimization-pipeline` 证据摘要新增 `nextActions=<数量>`。
+
+### 本轮验证结果
+
+- `node --check scripts\optimization-pipeline.js` 通过。
+- `node --check scripts\optimization-pipeline-smoke.js` 通过。
+- `npm.cmd run optimization:pipeline:smoke` 通过:
+  - `steps=6`
+- `npm.cmd run optimization:pipeline -- --data-pack outputs\data-intake-pack-latest --video-pack outputs\video-intake-pack-latest --current-manual-supplement-count 0 --output outputs\optimization-pipeline-latest` 已刷新当前 pipeline:
+  - `pass=5`
+  - `fail=3`
+  - `readyForClaim=false`
+  - `nextActionCount=3`
+- 当前 pipeline 下一步动作:
+  - 商务:补齐历史数据审计字段。
+  - 商务/投放:补齐真实视频资源。
+  - 商务:补齐客户效果证明。
+- `npm.cmd run evidence:index:smoke` 通过:
+  - `total=25`
+  - `real_evidence=5`
+  - `smoke_or_local=11`
+  - `not_business_proof=9`
+- `npm.cmd run evidence:index -- --output E:\workspace\tihao-ai\claude-code-tihao-sourcing\outputs\evidence-index-latest` 已刷新:
+  - `total=142`
+  - `realEvidence=5`
+  - `smokeOrLocal=24`
+  - `notProof=113`
+- `npm.cmd run round:deposition -- --output E:\workspace\tihao-ai\claude-code-tihao-sourcing\outputs\round-deposition-latest` 已刷新:
+  - `passed=true`
+  - `passCount=44`
+  - `failCount=0`
+  - `warnCount=0`
+- `npm.cmd run optimization:status -- --output outputs\optimization-status-latest` 已刷新:
+  - `passed=27`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+- `npm.cmd run plan:consistency:smoke` 通过:
+  - `hits=0`
+
+### 当前边界
+
+- pipeline 现在能把失败结果转成下一步动作,但这些动作仍依赖真实外部材料。
+- 当前 `readyForClaim=false`,不能宣称客户效果完成;必须补齐真实历史数据、真实候选视频、客户最终选择和人工补号量证明后再跑 strict 审计。
+
+## 2026-06-07 Handoff 接入 Pipeline 下一步动作
+
+### 已完成
+
+- 更新 `scripts/generate-optimization-handoff.js`:
+  - 读取最新 `outputs/optimization-pipeline-latest/optimization-pipeline-summary.json`。
+  - 在 handoff JSON 中新增 `optimizationPipeline`,包含 summary/report 路径、pass/fail 计数、`readyForClaim` 和 `nextActions`。
+  - 在 handoff Markdown 中新增“Pipeline 下一步动作”章节。
+- 更新 `scripts/generate-optimization-handoff-smoke.js`:
+  - 断言 handoff JSON 必须包含 pipeline summary 和 `nextActions` 数组。
+  - 断言 handoff 报告必须包含“Pipeline 下一步动作”和 `readyForClaim`。
+
+### 本轮验证结果
+
+- `node --check scripts\generate-optimization-handoff.js` 通过。
+- `node --check scripts\generate-optimization-handoff-smoke.js` 通过。
+- `npm.cmd run handoff:summary:smoke` 通过:
+  - `readyCapabilities=27`
+  - `readyNotProven=2`
+  - `externalBlockers=2`
+- `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest` 已刷新:
+  - `complete=false`
+  - `readyCapabilities=27`
+  - `externalBlockers=2`
+- `npm.cmd run evidence:index -- --output E:\workspace\tihao-ai\claude-code-tihao-sourcing\outputs\evidence-index-latest` 已刷新:
+  - `total=144`
+  - `realEvidence=5`
+  - `smokeOrLocal=24`
+  - `notProof=115`
+- `npm.cmd run hygiene:smoke` 通过:
+  - `scannedFiles=113`
+  - `hitCount=0`
+
+### 当前 Pipeline 下一步动作
+
+- 商务:补齐历史数据审计字段。
+- 商务/投放:补齐真实视频资源。
+- 商务:补齐客户效果证明。
+
+### 当前边界
+
+- handoff 现在能直接暴露 pipeline 失败后的动作,但动作本身仍依赖真实外部材料。
+- 当前 `readyForClaim=false`、pipeline `fail=3`、`nextActions=3`,长期目标仍不能标完成。
+
+## 2026-06-07 Pipeline 动作并入负责人补证队列
+
+### 已完成
+
+- 更新 `scripts/business-proof-next-actions.js`:
+  - 读取最新 `optimization-pipeline-summary.json`。
+  - `sourceState` 新增 `pipelineReadyForClaim`、`pipelineFailCount`、`pipelineNextActionCount`。
+  - 将 pipeline `nextActions` 合并进补证行动队列。
+  - 对同类步骤做映射去重:
+    - `history-audit` 合并到 15 步的历史数据审计动作,并按 pipeline owner 改为商务。
+    - `video-resource-readiness` 合并到视频资源就绪审计动作,并按 pipeline owner 改为商务/投放。
+    - `customer-effect-audit` 合并到客户效果审计动作,并按 pipeline owner 改为商务。
+  - 合并后保留原 15 步上下文,并把 pipeline 当前动作写入“下一步说明”。
+- 更新 `scripts/business-proof-next-actions-smoke.js`:
+  - 增加 pipeline fixture。
+  - 断言 pipeline nextActions 被吸收到行动队列。
+  - 断言负责人分组能看到 pipeline 的商务动作。
+  - 断言报告展示 `pipeline.nextActionCount` 和合并后的动作标题。
+
+### 本轮验证结果
+
+- `node --check scripts\business-proof-next-actions.js` 通过。
+- `node --check scripts\business-proof-next-actions-smoke.js` 通过。
+- `npm.cmd run business-proof:next-actions:smoke` 通过:
+  - `actionCount=6`
+  - `topAction=填真实历史 Brief`
+- `npm.cmd run business-proof:next-actions -- --output outputs\business-proof-next-actions-latest` 已刷新:
+  - `actionCount=8`
+  - `ownerArtifacts=3`
+  - `topAction=填真实历史 Brief`
+- `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest` 已刷新:
+  - `complete=false`
+  - `readyCapabilities=27`
+  - `externalBlockers=2`
+- `npm.cmd run evidence:index -- --output E:\workspace\tihao-ai\claude-code-tihao-sourcing\outputs\evidence-index-latest` 已刷新:
+  - `total=145`
+  - `realEvidence=5`
+  - `smokeOrLocal=24`
+  - `notProof=116`
+- `npm.cmd run hygiene:smoke` 通过:
+  - `scannedFiles=113`
+  - `hitCount=0`
+- `npm.cmd run handoff:summary:smoke` 通过:
+  - `readyCapabilities=27`
+  - `readyNotProven=2`
+  - `externalBlockers=2`
+- `npm.cmd run round:deposition -- --output E:\workspace\tihao-ai\claude-code-tihao-sourcing\outputs\round-deposition-latest` 已刷新:
+  - `passed=true`
+  - `passCount=44`
+  - `failCount=0`
+  - `warnCount=0`
+- `npm.cmd run optimization:status -- --output outputs\optimization-status-latest` 已刷新:
+  - `passed=27`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+
+### 最新负责人视图
+
+- 商务负责人文件现在包含:
+  - `填真实历史 Brief`
+  - `补齐历史数据审计字段`
+  - `人工复核标注`
+  - `补齐客户效果证明`
+- 商务/投放负责人文件现在包含:
+  - `填真实视频资源`
+  - `补齐真实视频资源`
+
+### 当前边界
+
+- pipeline 下一步动作已经进入负责人行动文件,但仍只代表“补证任务可执行”,不证明业务效果。
+- 当前 `pipelineReadyForClaim=false`、`pipelineFailCount=3`、`pipelineNextActionCount=3`,长期目标仍未完成。
+
+## 2026-06-07 Pipeline 动作收敛到主补证动作
+
+### 已完成
+
+- 更新 `scripts/business-proof-next-actions.js`:
+  - 当 `history` intake 仍未就绪时,pipeline 的 `history-audit` 动作合并到“填真实历史 Brief”,不再单独生成“补齐历史数据审计字段”。
+  - 当 `video` intake 仍未就绪时,pipeline 的 `video-resource-readiness` 动作合并到“填真实视频资源”,不再单独生成“补齐真实视频资源”。
+  - 当 `review` intake 仍未就绪时,pipeline 的 `customer-effect-audit` 动作合并到“人工复核标注”,不再单独生成“补齐客户效果证明”。
+  - 主动作保留原负责人、标题和优先级,只吸收 pipeline 的命令、预期产物和通过标准。
+- 更新 `scripts/business-proof-next-actions-smoke.js`:
+  - 新增断言:pipeline 后续动作必须并入主补证动作。
+  - 新增断言:主动作必须保留 `pipeline 当前动作` 说明。
+
+### 本轮验证结果
+
+- `node --check scripts\business-proof-next-actions.js` 通过。
+- `node --check scripts\business-proof-next-actions-smoke.js` 通过。
+- `npm.cmd run business-proof:next-actions:smoke` 通过:
+  - `actionCount=5`
+  - `topAction=填真实历史 Brief`
+- `npm.cmd run business-proof:next-actions -- --output outputs\business-proof-next-actions-latest` 已刷新:
+  - `actionCount=8`
+  - `ownerArtifacts=3`
+  - `topAction=填真实历史 Brief`
+- `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest` 已刷新:
+  - `complete=false`
+  - `readyCapabilities=27`
+  - `externalBlockers=2`
+- `npm.cmd run evidence:index -- --output E:\workspace\tihao-ai\claude-code-tihao-sourcing\outputs\evidence-index-latest` 已刷新:
+  - `total=147`
+  - `realEvidence=5`
+  - `smokeOrLocal=24`
+  - `notProof=118`
+- `npm.cmd run handoff:summary:smoke` 通过:
+  - `readyCapabilities=27`
+  - `readyNotProven=2`
+  - `externalBlockers=2`
+- `npm.cmd run round:deposition -- --output E:\workspace\tihao-ai\claude-code-tihao-sourcing\outputs\round-deposition-latest` 已刷新:
+  - `passed=true`
+  - `passCount=44`
+  - `failCount=0`
+  - `warnCount=0`
+- `npm.cmd run optimization:status -- --output outputs\optimization-status-latest` 已刷新:
+  - `passed=27`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+- `npm.cmd run hygiene:smoke` 通过:
+  - `scannedFiles=113`
+  - `hitCount=0`
+
+### 最新负责人视图
+
+- 商务负责人文件收敛为 2 个主动作:
+  - `填真实历史 Brief`
+  - `人工复核标注`
+- 商务/投放负责人文件收敛为 1 个主动作:
+  - `填真实视频资源`
+- 每个主动作的“下一步说明”保留 pipeline 审计动作和通过标准。
+
+### 当前边界
+
+- 行动文件更清晰,但仍只证明补证路径可执行,不证明客户效果完成。
+- 当前 pipeline 仍为 `readyForClaim=false`,真实历史数据、真实视频资源、客户最终选择和人工补号量证明仍需补齐。
+
+## 2026-06-07 负责人行动表去重与软件端格式修正
+
+### 本轮修正
+
+- 修复 `scripts/business-proof-next-actions.js`:
+  - pipeline 下一步动作合并到 `填真实历史 Brief`、`填真实视频资源`、`人工复核标注` 时,不再覆盖主动作的 `执行命令`、`预期产物`、`通过标准`。
+  - 主动作继续保留软件/商务可执行的填表入口:
+    - `填写 outputs\data-intake-pack-latest\history-data-template.csv`
+    - `填写 outputs\video-intake-pack-latest\video-resource-template.csv`
+    - `填写 outputs\data-intake-pack-latest\manual-review-template.csv`
+  - 视频类负责人统一归一为 `商务/投放`,避免同时出现 `商务/投放` 和 `投放/商务` 两组重复负责人文件。
+- 删除 latest 输出目录中的旧重复生成物:
+  - `outputs\business-proof-next-actions-latest\by-owner\media-business.md`
+  - `outputs\business-proof-next-actions-latest\by-owner\media-business.csv`
+- 更新 `scripts/business-proof-next-actions-smoke.js`:
+  - 断言三条主补证动作必须保留填表命令。
+  - 断言 `商务/投放` 只生成 `by-owner/business-media.md`,不再生成 `by-owner/media-business.md`。
+
+### 已验证
+
+- `node --check scripts\business-proof-next-actions.js` 通过。
+- `node --check scripts\business-proof-next-actions-smoke.js` 通过。
+- `npm.cmd run business-proof:next-actions:smoke` 通过:
+  - `actionCount=5`
+  - `topAction=填真实历史 Brief`
+- `npm.cmd run business-proof:next-actions -- --output outputs\business-proof-next-actions-latest` 已刷新:
+  - `ownerArtifacts=3`
+  - `actionCount=8`
+  - `complete=false`
+- latest 负责人文件已收敛为:
+  - `business.csv/md`
+  - `business-media.csv/md`
+  - `tech-ai.csv/md`
+
+### 当前边界
+
+- 本轮解决的是重复行动和软件端可执行字段问题。
+- 当前行动表仍不证明客户效果完成;真实历史 Brief、真实视频资源、客户选择/拒绝和人工补号量基线仍需补齐。
+
+## 2026-06-07 状态 latest 输出链路修复
+
+### 问题
+
+- 长期优化计划要求每轮刷新 `optimization-status-report.md`。
+- 原 `npm run optimization:status -- --output outputs\optimization-status-latest` 命令不会真正使用 `--output`,脚本只读取 `TIHAO_STATUS_OUTPUT`。
+- 结果是状态审计会生成时间戳目录,但不会稳定生成 `outputs\optimization-status-latest`,不利于每轮沉淀、交接摘要和 round deposition 查证。
+
+### 本轮修正
+
+- 修复 `scripts/optimization-status-audit.js`:
+  - 增加 `--output` 参数解析。
+  - 保留 `TIHAO_STATUS_OUTPUT` 环境变量兼容。
+  - 未传入输出目录时仍按原逻辑生成时间戳目录。
+- 更新 `scripts/optimization-status-smoke.js`:
+  - smoke 不再只检查内存 summary。
+  - 真实调用 `optimization-status-audit.js --output <临时目录>`。
+  - 断言 `optimization-status-summary.json` 和 `optimization-status-report.md` 都写入指定目录。
+
+### 已验证
+
+- `node --check scripts\optimization-status-audit.js` 通过。
+- `node --check scripts\optimization-status-smoke.js` 通过。
+- `npm.cmd run optimization:status:smoke` 通过。
+- `npm.cmd run optimization:status -- --output outputs\optimization-status-latest` 已刷新:
+  - `passed=27`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+- `npm.cmd run evidence:index -- --output outputs\evidence-index-latest` 已刷新:
+  - `total=150`
+  - `realEvidence=5`
+  - `smokeOrLocal=24`
+  - `notProof=121`
+- `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest` 已刷新:
+  - `complete=false`
+  - `readyCapabilities=27`
+  - `externalBlockers=2`
+- `npm.cmd run round:deposition -- --output outputs\round-deposition-latest` 已刷新:
+  - `passed=true`
+  - `passCount=44`
+  - `failCount=0`
+  - `warnCount=0`
+- `npm.cmd run acceptance` 通过。
+
+### 当前边界
+
+- 状态沉淀链路已补稳,但长期优化目标仍未完成。
+- 当前剩余真实证明缺口仍是:
+  - 5-10 个真实历史 Brief 未齐。
+  - 真实候选视频资源未齐。
+  - 客户最终选择/拒绝、拒绝原因和人工补号量基线未齐。
+  - `customer-effect:audit` 仍未通过,不能宣称客户效果完成。
+
+## 2026-06-07 视频 A/B 真实证明误闭环修正
+
+### 问题
+
+- `proof-gap:closure` 只用 `video-hit-rate-summary.json.acceptance.passed=true` 判断 `video-ab-live-proof` 是否 closed。
+- 旧的本地/示例 video-hit-rate 产物没有明确 `proofContext`,但仍可能被 latest 扫描命中,导致 `video-ab-live-proof=closed`。
+- `business-proof:progress` 又依据 closure 行把第 11 步“视频 A/B 验收”标成 `pass`。
+- 这会和 `optimization:status` 中 `video-ab-proof=ready_not_proven` 发生冲突,并可能误导为“视频分析已经证明提升提号率”。
+
+### 本轮修正
+
+- 修复 `scripts/video-hit-rate-audit.js`:
+  - 真实 `acceptance:video-ab` 产物新增 `proofContext`。
+  - 记录 `mode=live`、`collectionMode=live`、`generatedBy=acceptance:video-ab`、是否需要 runtime credential、VOC social provider、video analysis provider、视频分析模型等上下文。
+- 修复 `scripts/proof-gap-closure-audit.js`:
+  - `video-ab-live-proof` 只有同时满足以下条件才 closed:
+    - `acceptance.passed=true`
+    - `proofContext.mode=live`
+    - `proofContext.collectionMode=live`
+    - `proofContext.generatedBy=acceptance:video-ab`
+    - `requiresRuntimeCredential=true`
+    - `requiresVocSocialProvider=true`
+    - `requiresVideoAnalysisProvider=true`
+    - `enhanced.provider.evidence.providerStatus=ok`
+  - 旧产物或缺 proofContext 的 passed summary 一律不关闭真实视频 A/B 缺口。
+- 修复 `scripts/business-proof-progress.js`:
+  - 第 11 步“视频 A/B 验收”不再只看 closure。
+  - 必须同时满足 `optimization-status` 中 `video-ab-proof=passed` 且 closure 为 closed,才标记为 `pass`。
+  - 当前 `video-ab-proof=ready_not_proven` 时,第 11 步保持 `pending`。
+  - 负责人 `投放/商务` 统一为 `商务/投放`,避免上游进度表再次制造负责人重复。
+- 更新 smoke:
+  - `proof-gap:closure:smoke` 增加“passed 但没有 live proofContext 不得 closed”的断言。
+  - `business-proof:progress:smoke` 增加“status ready_not_proven 时视频 A/B 必须 pending”的断言。
+
+### 已验证
+
+- `node --check scripts\video-hit-rate-audit.js` 通过。
+- `node --check scripts\proof-gap-closure-audit.js` 通过。
+- `node --check scripts\business-proof-progress.js` 通过。
+- `node --check scripts\proof-gap-closure-smoke.js` 通过。
+- `node --check scripts\business-proof-progress-smoke.js` 通过。
+- `npm.cmd run proof-gap:closure:smoke` 通过。
+- `npm.cmd run business-proof:progress:smoke` 通过:
+  - `pass=3`
+  - `blocked_by_external_data=7`
+  - `fail=2`
+  - `pending=3`
+- `npm.cmd run acceptance:video-ab:smoke` 通过。
+- `npm.cmd run proof-gap:closure -- --output outputs\proof-gap-closure-latest` 已刷新:
+  - `complete=false`
+  - `closedCount=0`
+  - `openCount=5`
+- `npm.cmd run business-proof:progress -- --output outputs\business-proof-progress-latest` 已刷新:
+  - `complete=false`
+  - `pass=3`
+  - `blocked=7`
+  - `pending=3`
+  - `fail=2`
+- `npm.cmd run business-proof:next-actions -- --output outputs\business-proof-next-actions-latest` 已刷新:
+  - `complete=false`
+  - `actionCount=8`
+  - `topAction=填真实历史 Brief`
+- `npm.cmd run optimization:status -- --output outputs\optimization-status-latest` 已刷新:
+  - `passed=27`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+- `npm.cmd run evidence:index -- --output outputs\evidence-index-latest` 已刷新:
+  - `realEvidence=5`
+  - `smokeOrLocal=24`
+  - `notProof=121`
+- `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest` 已刷新:
+  - `complete=false`
+  - `readyCapabilities=27`
+  - `externalBlockers=2`
+- `npm.cmd run round:deposition -- --output outputs\round-deposition-latest` 已刷新:
+  - `passed=true`
+  - `passCount=44`
+  - `failCount=0`
+  - `warnCount=0`
+- `npm.cmd run acceptance` 通过。
+
+### 当前边界
+
+- 当前 `closedCount=0, openCount=5` 是更准确的状态,不是倒退。
+- 旧视频 A/B 产物没有 `proofContext`,不能支撑真实视频提升结论。
+- 要关闭 `video-ab-live-proof`,必须重新用真实 token/provider/真实视频资源运行 `npm run acceptance:video-ab`,生成带 `proofContext` 的新 summary。
+- 长期优化目标仍未完成,仍需要真实历史数据、真实候选视频、客户选择/拒绝、拒绝原因和人工补号量基线。
+
+## 2026-06-07 Proof Gap 请求包同步 closure 和负责人归一
+
+### 问题
+
+- `outputs\proof-gap-request-latest` 仍是较早生成的请求包,里面的 status/evidence 计数和最新 closure/progress 不一致。
+- proof-gap 请求包里仍有 `技术/投放`、`商务/客户` 等负责人标签,与行动队列中的 `商务`、`商务/投放`、`技术/AI` 三类口径不一致。
+- 请求包没有直接展示最新 `proof-gap:closure` 的 openCount,商务或下一轮 AI 需要额外打开 closure 文件才能确认当前真实证明缺口是否已关闭。
+
+### 本轮修正
+
+- 修复 `scripts/generate-proof-gap-request.js`:
+  - 增加 `--closure` 输入,默认读取 latest `proof-gap-closure-summary.json`。
+  - 在 `observed` 和报告中展示:
+    - `closure.complete`
+    - `closure.openCount`
+  - 负责人归一:
+    - `video-ab-live-proof`:`技术/AI`
+    - `live-provider-overnight-proof`:`技术/AI`
+    - `manual-review-and-customer-effect`:`商务`
+- 更新 `scripts/generate-proof-gap-request-smoke.js`:
+  - 构造 closure fixture。
+  - 断言请求包展示 `proofGapClosureOpenCount=5`。
+  - 断言不会出现 `技术/投放`、`商务/客户`、`投放/商务` 旧负责人标签。
+  - 断言报告包含 `closure.openCount:5`。
+
+### 已验证
+
+- `node --check scripts\generate-proof-gap-request.js` 通过。
+- `node --check scripts\generate-proof-gap-request-smoke.js` 通过。
+- `npm.cmd run proof-gap:request:smoke` 通过:
+  - `gapCount=5`
+  - `unresolvedCount=5`
+- `npm.cmd run proof-gap:request -- --output outputs\proof-gap-request-latest` 已刷新:
+  - `gapCount=5`
+  - `unresolvedCount=5`
+- `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest` 已刷新:
+  - `complete=false`
+  - `readyCapabilities=27`
+  - `externalBlockers=2`
+- `npm.cmd run evidence:index -- --output outputs\evidence-index-latest` 已刷新:
+  - `realEvidence=5`
+  - `smokeOrLocal=24`
+  - `notProof=121`
+- `npm.cmd run round:deposition -- --output outputs\round-deposition-latest` 已刷新:
+  - `passed=true`
+  - `passCount=44`
+  - `failCount=0`
+  - `warnCount=0`
+- `npm.cmd run acceptance` 通过。
+
+### 当前边界
+
+- proof-gap 请求包现在能直接看到最新 closure 口径,但它仍只是“补证请求”,不证明客户效果完成。
+- 当前 `unresolvedCount=5`,`closure.openCount=5`,长期目标仍未完成。
+
+## 2026-06-07 Pipeline 纳入 Proof Gap Closure 作为宣称门禁
+
+### 问题
+
+- `optimization:pipeline` 原本只用“所有步骤 pass + 存在 reviewCsv”判断 `readyForClaim`。
+- 这会让本地完整假数据在 `history/review/customer-effect` 都通过时,可能过早出现 `readyForClaim=true`。
+- 但长期优化计划要求客户效果和真实业务证明必须经过独立 proof gap closure,不能只靠本地 customer-effect smoke 或局部审计判断。
+
+### 本轮修正
+
+- 修复 `scripts/optimization-pipeline.js`:
+  - Pipeline 新增 `proof-gap:closure` 步骤。
+  - closure 会接收本轮 pipeline 产出的:
+    - `historical-dataset-audit.json`
+    - `intake-readiness-summary.json`
+    - `video-resource-readiness-summary.json`
+    - `customer-effect-summary.json`
+  - `readyForClaim` 改为必须同时满足:
+    - 所有 pipeline 步骤 `status=pass`
+    - 存在 `reviewCsv`
+    - `proof-gap:closure complete=true`
+  - summary 新增 `proofGapClosure`:
+    - `complete`
+    - `openCount`
+    - `closedCount`
+  - 报告新增:
+    - `proofGapClosure.complete`
+    - `proofGapClosure.openCount`
+  - 当步骤都 pass 但 closure 仍未 complete 时,`nextActions` 会输出“关闭真实证明缺口”。
+- 更新 `scripts/optimization-pipeline-smoke.js`:
+  - dry-run 必须规划 `proof-gap:closure`。
+  - 完整本地数据可以跑通 history/review/customer-effect,但只要 proof gap 未关闭,`readyForClaim` 必须为 `false`。
+  - 必须生成 `<输出目录>/proof-gap-closure/proof-gap-closure-summary.json`。
+
+### 已验证
+
+- `node --check scripts\optimization-pipeline.js` 通过。
+- `node --check scripts\optimization-pipeline-smoke.js` 通过。
+- `npm.cmd run optimization:pipeline:smoke` 通过:
+  - `steps=7`
+- `npm.cmd run optimization:pipeline -- --data-pack outputs\data-intake-pack-latest --video-pack outputs\video-intake-pack-latest --current-manual-supplement-count 0 --output outputs\optimization-pipeline-latest` 已刷新:
+  - `pass=6`
+  - `fail=3`
+  - `readyForClaim=false`
+  - `proofGapClosure.complete=false`
+  - `proofGapClosure.openCount=5`
+  - `proofGapClosure.closedCount=0`
+- `npm.cmd run business-proof:next-actions -- --output outputs\business-proof-next-actions-latest` 已刷新:
+  - `actionCount=8`
+  - `topAction=填真实历史 Brief`
+- `npm.cmd run proof-gap:closure -- --output outputs\proof-gap-closure-latest` 已刷新:
+  - `closedCount=0`
+  - `openCount=5`
+- `npm.cmd run proof-gap:request -- --output E:\workspace\tihao-ai\claude-code-tihao-sourcing\outputs\proof-gap-request-latest` 已刷新:
+  - `gapCount=5`
+  - `unresolvedCount=5`
+- `npm.cmd run optimization:status -- --output outputs\optimization-status-latest` 已刷新:
+  - `passed=27`
+  - `ready_not_proven=2`
+  - `blocked_by_external_data=2`
+  - `complete=false`
+- `npm.cmd run evidence:index -- --output outputs\evidence-index-latest` 已刷新:
+  - `realEvidence=5`
+  - `smokeOrLocal=24`
+  - `notProof=121`
+- `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest` 已刷新:
+  - `complete=false`
+  - `readyCapabilities=27`
+  - `externalBlockers=2`
+- `npm.cmd run round:deposition -- --output outputs\round-deposition-latest` 已刷新:
+  - `passed=true`
+  - `passCount=44`
+  - `failCount=0`
+  - `warnCount=0`
+- `npm.cmd run acceptance` 通过。
+
+### 当前边界
+
+- Pipeline 现在更接近真实验收闭环,但仍不会自动创造真实业务证明。
+- 当前 `proofGapClosure.openCount=5`,因此 `readyForClaim=false` 是正确状态。
+- 仍需真实历史 Brief、真实候选视频、视频 A/B live proof、live/provider 长跑证明、客户选择和人工补号量基线。
+
+## 2026-06-07 证据台账同步 Pipeline Closure 口径
+
+### 问题
+
+- `optimization:pipeline` 已经把 `proof-gap:closure complete=true` 纳入 `readyForClaim` 门禁。
+- 但 `evidence:index` 里的 pipeline 分类仍是旧说明:
+  - `readyForClaim=true` 时写“客户效果仍以 customer-effect 产物单独判定”。
+  - summary 不显示 `proofGapClosure.complete/openCount`。
+- 这会让证据台账和 pipeline 最新门禁不一致。
+
+### 本轮修正
+
+- 修复 `scripts/evidence-index.js`:
+  - `classifyPipeline` 读取 `pipeline.proofGapClosure`。
+  - summary 新增:
+    - `closureComplete`
+    - `closureOpen`
+  - `readyForClaim` 展示值改为必须同时满足:
+    - `pipeline.readyForClaim=true`
+    - `proofGapClosure.complete=true`
+  - next 文案改为:
+    - closure complete 时:pipeline 已跑通且 proof-gap closure complete。
+    - closure 未完成时:不能宣称业务效果。
+- 更新 `scripts/evidence-index-smoke.js`:
+  - pipeline pass fixture 增加 `proofGapClosure.complete=true`。
+  - 新增 `pipeline-claim-with-open-closure` fixture:即使原始 `readyForClaim=true`,只要 closure open,证据台账 summary 必须显示 `readyForClaim=false`。
+  - 断言 summary 包含 `closureOpen=1`。
+
+### 已验证
+
+- `node --check scripts\evidence-index.js` 通过。
+- `node --check scripts\evidence-index-smoke.js` 通过。
+- `npm.cmd run evidence:index:smoke` 通过:
+  - `total=26`
+  - `smoke_or_local=12`
+  - `not_business_proof=9`
+  - `real_evidence=5`
+- `npm.cmd run evidence:index -- --output outputs\evidence-index-latest` 已刷新:
+  - `total=150`
+  - `realEvidence=5`
+  - `smokeOrLocal=24`
+  - `notProof=121`
+- `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest` 已刷新:
+  - `complete=false`
+  - `readyCapabilities=27`
+  - `externalBlockers=2`
+- `npm.cmd run round:deposition -- --output outputs\round-deposition-latest` 已刷新:
+  - `passed=true`
+  - `passCount=44`
+  - `failCount=0`
+  - `warnCount=0`
+- `npm.cmd run acceptance` 通过。
+
+### 当前边界
+
+- 证据台账现在和 pipeline 最新门禁一致。
+- 当前 pipeline 仍为:
+  - `readyForClaim=false`
+  - `proofGapClosure.complete=false`
+  - `proofGapClosure.openCount=5`
+- 长期目标仍未完成。
+
+## 2026-06-07 Longrun Readiness 接入 Proof-Gap Closure 门禁
+
+### 问题
+
+- `proof-gap:request` 只能说明当前请求包里还有哪些缺口,不能证明缺口已真实关闭。
+- 如果 `longrun:readiness` 只看 request 的 unresolved 数,可能出现“请求表同步了,但 closure 仍有 open 项”的误判。
+- 这会让 full-matrix 或 customer-effect 长跑在真实证明缺口未关闭时被误认为可启动或可宣称完成。
+
+### 本轮修正
+
+- 修复 `scripts/long-run-readiness-audit.js`:
+  - 新增 `--proof-gap-closure` 参数和 `TIHAO_PROOF_GAP_CLOSURE_SUMMARY` 环境变量。
+  - `full-matrix` 和 `customer-effect` 模式会检查 `proofGapClosure.complete=true` 且 `openCount=0`。
+  - `nextCommands` 增加 `npm run proof-gap:closure -- --output outputs\proof-gap-closure-latest`。
+  - 边界增加:`proof-gap:closure` 仍有 open 项时,不能宣称长期优化目标完成。
+- 更新 `scripts/long-run-readiness-smoke.js`:
+  - happy path 必须传入已关闭的 proof-gap closure。
+  - 新增 request unresolved=0 但 closure openCount=5 的用例,必须判定 `ready=false`。
+  - 断言 full-matrix 下一步命令包含 `proof-gap:closure`。
+- 新增软件端格式补证表:
+  - `docs/ai-optimization-proof-gap-software-table.md`
+  - 按软件端固定 17 列输出。
+  - 将重复补证动作收敛为 5 个唯一任务:真实历史 Brief 数据集、真实视频资源表、真实视频证据 A/B、真实 provider 长跑、商务复核和客户效果。
+
+### 已验证
+
+- `node --check scripts\long-run-readiness-audit.js` 通过。
+- `node --check scripts\long-run-readiness-smoke.js` 通过。
+- `npm.cmd run longrun:readiness:smoke` 通过。
+- `npm.cmd run longrun:readiness -- --mode full-matrix --intake-readiness outputs\intake-readiness-latest\intake-readiness-summary.json --proof-gap outputs\proof-gap-request-latest\proof-gap-request-summary.json --proof-gap-closure outputs\proof-gap-closure-latest\proof-gap-closure-summary.json --history outputs\optimization-pipeline-latest\history-audit\historical-dataset-audit.json --output outputs\long-run-readiness-latest` 已刷新:
+  - `ready=false`
+  - `failCount=7`
+  - `warnCount=0`
+- `npm.cmd run business-proof:progress -- --output outputs\business-proof-progress-latest` 已刷新:
+  - `complete=false`
+  - `pass=3`
+  - `blocked=7`
+  - `pending=3`
+  - `fail=2`
+- `npm.cmd run business-proof:next-actions -- --output outputs\business-proof-next-actions-latest` 已刷新:
+  - `complete=false`
+  - `actionCount=8`
+  - `ownerArtifacts=3`
+  - `topAction=填真实历史 Brief`
+- `npm.cmd run evidence:index -- --output outputs\evidence-index-latest` 已刷新:
+  - `total=150`
+  - `realEvidence=5`
+  - `smokeOrLocal=24`
+  - `notProof=121`
+- `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest` 已刷新:
+  - `complete=false`
+  - `readyCapabilities=27`
+  - `externalBlockers=2`
+- `npm.cmd run round:deposition -- --output outputs\round-deposition-latest` 已刷新:
+  - `passed=true`
+  - `passCount=44`
+  - `failCount=0`
+  - `warnCount=0`
+- `npm.cmd run acceptance` 通过。
+
+### 当前边界
+
+- 当前 `longrun:readiness` 已经受 `proof-gap:closure` 约束,不能绕过真实关闭证据。
+- 当前 `proofGapClosure.complete=false` 且 `openCount=5`,长期优化目标仍未完成。
+- 当前仍缺:
+  - 5-10 个真实历史 Brief、人工最终名单、客户选择/拒绝记录、拒绝原因和历史人工补号量基线。
+  - 真实候选视频资源。
+  - 真实视频 A/B live proof。
+  - 真实 live/provider 长跑证明。
+  - 商务复核标注、客户最终选择和本轮人工补号量。
+
+## 2026-06-07 补证行动队列按业务缺口去重
+
+### 问题
+
+- `docs/ai-optimization-proof-gap-software-table.md` 已经按软件端格式收敛为 5 个唯一补证任务。
+- 但 `business-proof:next-actions` 的真实 latest 仍输出 8 条行动:
+  - 历史数据填写、历史数据导入、历史数据审计被拆成多行。
+  - 视频资源填写、视频资源就绪审计被拆成多行。
+  - 长跑前门禁、运行策略矩阵、真实 live/provider 长跑证明被拆成多行。
+  - 视频 A/B 验收和真实视频 A/B live proof 没有合并。
+- 这会让商务或技术看板误以为需要领取更多独立任务,实际只是同一业务缺口的填写、转换、审计和验收动作。
+
+### 本轮修正
+
+- 修复 `scripts/business-proof-next-actions.js`:
+  - 新增 `compactBusinessGapActions`,把技术验证动作合并到对应业务缺口。
+  - 历史链路合并为 `填真实历史 Brief`,保留 intake、history:from-csv、history:audit 的验证说明和预期产物。
+  - 视频资源链路合并为 `填真实视频资源`,保留 video:resource-readiness 的验证说明。
+  - 商务复核链路合并为 `人工复核标注`,保留 review:metrics 和 customer-effect:audit 的验证说明。
+  - 视频验收链路合并为 `真实视频 A/B 验收`。
+  - live 长跑链路合并为 `真实 live/provider 长跑证明`,保留 longrun readiness 和 optimization:pipeline 的前置说明。
+  - `longrun:readiness` 推荐命令同步增加 `--proof-gap-closure outputs\proof-gap-closure-latest\proof-gap-closure-summary.json`。
+  - closure 标题统一中文化,避免输出 `live proof` 等半英文标题。
+- 更新 `scripts/business-proof-next-actions-smoke.js`:
+  - 验证重复技术动作不会单独出现。
+  - 验证合并后仍保留 intake/history/video/review/customer-effect 的验证说明。
+  - 不再用旧的固定行动数作为主断言,而是以“核心缺口存在、重复技术动作不存在”为验收口径。
+
+### 已验证
+
+- `node --check scripts\business-proof-next-actions.js` 通过。
+- `node --check scripts\business-proof-next-actions-smoke.js` 通过。
+- `npm.cmd run business-proof:next-actions:smoke` 通过:
+  - fixture `actionCount=3`
+  - `topAction=填真实历史 Brief`
+- `npm.cmd run business-proof:next-actions -- --output outputs\business-proof-next-actions-latest` 已刷新:
+  - `complete=false`
+  - `actionCount=5`
+  - `ownerArtifacts=3`
+  - `topAction=填真实历史 Brief`
+- 最新行动队列 5 条:
+  - `填真实历史 Brief`
+  - `填真实视频资源`
+  - `人工复核标注`
+  - `真实视频 A/B 验收`
+  - `真实 live/provider 长跑证明`
+- `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest` 已刷新:
+  - `complete=false`
+  - `readyCapabilities=27`
+  - `externalBlockers=2`
+- `npm.cmd run evidence:index -- --output outputs\evidence-index-latest` 已刷新:
+  - `total=150`
+  - `realEvidence=5`
+  - `smokeOrLocal=24`
+  - `notProof=121`
+- `npm.cmd run round:deposition -- --output outputs\round-deposition-latest` 已刷新:
+  - `passed=true`
+  - `passCount=44`
+  - `failCount=0`
+  - `warnCount=0`
+- `npm.cmd run acceptance` 通过。
+
+### 当前边界
+
+- 行动队列已和软件端去重补证表一致,但这只是补证安排,不是客户效果证明。
+- 当前仍是:
+  - `proofGap.openCount=5`
+  - `intake.failureCount=9`
+  - `video.failureCount=1`
+  - `pipeline.readyForClaim=false`
+- 继续不得宣称长期优化目标完成。
+
+## 2026-06-07 沉淀审计固化行动队列去重门禁
+
+### 问题
+
+- 上一轮已经把 `business-proof:next-actions` 真实 latest 从 8 条压成 5 条。
+- 但 `round:deposition` 只记录 `businessProofNextActionsCount=5`,没有把“必须按业务缺口去重”作为硬性检查。
+- 如果后续生成器回归到 7/8 条重复动作,沉淀审计仍可能通过,商务端又会看到重复任务。
+
+### 本轮修正
+
+- 修复 `scripts/round-deposition-audit.js`:
+  - 新增 `business-proof-next-actions-deduped` 检查项。
+  - 要求下一步行动队列数量与 `proofGap.unresolvedCount` 对齐。
+  - 当 unresolvedCount >= 5 时,必须包含 5 个核心动作:
+    - `填真实历史 Brief`
+    - `填真实视频资源`
+    - `人工复核标注`
+    - `真实视频 A/B 验收`
+    - `真实 live/provider 长跑证明`
+  - 禁止把以下技术动作重新拆成独立业务行动:
+    - `真实材料预审`
+    - `历史数据导入`
+    - `历史数据审计`
+    - `视频资源就绪审计`
+    - `长跑前门禁`
+    - `运行策略矩阵`
+    - `视频 A/B 验收`
+    - `商务复核指标`
+    - `客户效果审计`
+  - 要求行动队列覆盖 `商务`、`商务/投放`、`技术/AI` 三类负责人。
+- 更新 `scripts/round-deposition-smoke.js`:
+  - smoke fixture 的 next actions 从 1 条扩展为 5 条核心业务缺口。
+  - 断言沉淀审计包含 `business-proof-next-actions-deduped`。
+  - 断言 `businessProofNextActionsCount=5`。
+
+### 已验证
+
+- `node --check scripts\round-deposition-audit.js` 通过。
+- `node --check scripts\round-deposition-smoke.js` 通过。
+- `npm.cmd run round:deposition:smoke` 通过:
+  - `passCount=45`
+  - `failCount=0`
+  - `proofGapUnresolvedCount=5`
+- `npm.cmd run round:deposition -- --output outputs\round-deposition-latest` 已刷新:
+  - `passed=true`
+  - `passCount=45`
+  - `failCount=0`
+  - `warnCount=0`
+  - `businessProofNextActionsCount=5`
+- `npm.cmd run acceptance` 通过。
+
+### 当前边界
+
+- 行动队列去重现在有生成器 smoke 和 round deposition 双层保护。
+- 当前仍是补证阶段:
+  - `proofGapUnresolvedCount=5`
+  - `proofGapClosure.openCount=5`
+  - `intakeReadinessFailureCount=9`
+  - `longRunReadinessReady=false`
+- 仍不得宣称客户效果、视频提升或长期优化目标完成。
+
+## 2026-06-07 软件端补证表与 Next Actions 一致性门禁
+
+### 问题
+
+- `docs/ai-optimization-proof-gap-software-table.md` 是给软件端和商务看的 5 条补证表。
+- `outputs/business-proof-next-actions-latest/business-proof-next-actions-summary.json` 是自动生成的下一步行动队列。
+- 上一轮两者都已经是 5 条,但此前没有自动检查两者是否一致。
+- 如果文档表被人工改成另一套标题,或 next-actions 又拆出重复技术动作,acceptance 可能仍然通过。
+
+### 本轮修正
+
+- 修复 `scripts/plan-consistency-smoke.js`:
+  - 扫描文件从 6 个增加到 7 个,新增 `docs/ai-optimization-proof-gap-software-table.md`。
+  - 检查该文档必须使用软件端固定 17 列表头。
+  - 检查该文档必须只有 5 个 `GAP-001` 到 `GAP-005` 行,且 ID 不重复。
+  - 检查 5 行核心补证任务:
+    - `真实历史 Brief 数据集`
+    - `真实视频资源表`
+    - `真实视频证据 A/B`
+    - `真实 live/provider 长跑证明`
+    - `商务复核和客户效果`
+  - 检查 latest next-actions 必须与该表数量一致。
+  - 检查 latest next-actions 必须包含:
+    - `填真实历史 Brief`
+    - `填真实视频资源`
+    - `人工复核标注`
+    - `真实视频 A/B 验收`
+    - `真实 live/provider 长跑证明`
+  - 检查 latest next-actions 不得重新出现重复技术动作:
+    - `真实材料预审`
+    - `历史数据导入`
+    - `历史数据审计`
+    - `视频资源就绪审计`
+    - `长跑前门禁`
+    - `运行策略矩阵`
+    - `视频 A/B 验收`
+    - `商务复核指标`
+    - `客户效果审计`
+- 统一 `docs/ai-optimization-proof-gap-software-table.md`:
+  - GAP-004 标题从 `真实 provider 长跑` 调整为 `真实 live/provider 长跑证明`,与 next-actions 保持一致。
+
+### 已验证
+
+- `node --check scripts\plan-consistency-smoke.js` 通过。
+- `npm.cmd run plan:consistency:smoke` 通过:
+  - `scannedFiles=7`
+  - `hits=[]`
+- `npm.cmd run acceptance` 通过。
+- `npm.cmd run round:deposition -- --output outputs\round-deposition-latest` 通过:
+  - `passed=true`
+  - `passCount=45`
+  - `failCount=0`
+  - `warnCount=0`
+
+### 当前边界
+
+- 软件端补证表、next-actions 和 round deposition 现在都锁定 5 条核心补证口径。
+- 当前仍未补齐真实业务证明:
+  - `proofGapUnresolvedCount=5`
+  - `proofGapClosure.openCount=5`
+  - `intakeReadinessFailureCount=9`
+  - `longRunReadinessReady=false`
+- 继续不得宣称长期优化目标完成。
+
+## 2026-06-07 最新提号补证表单导出
+
+### 问题
+
+- 此前最新补证表主要沉淀在 `docs/ai-optimization-proof-gap-software-table.md`。
+- 自动行动队列也有 `outputs/business-proof-next-actions-latest/business-proof-next-actions.csv`。
+- 但还缺一个明确的“最新提号表单”输出目录,方便商务或软件端直接拿 CSV/Markdown/summary。
+
+### 本轮修正
+
+- 新增 `scripts/export-proof-gap-software-form.js`:
+  - 从 `docs/ai-optimization-proof-gap-software-table.md` 读取软件端格式补证表。
+  - 导出 CSV、Markdown 和 summary 到 `outputs/tihao-proof-gap-software-form-latest`。
+  - CSV 使用 UTF-8 BOM,Excel 打开中文不乱码。
+  - 校验软件端固定 17 列、5 行、GAP ID 不重复、核心标题齐全。
+  - summary 记录 `rowCount`、`duplicateIdCount`、`headerMatches`、`rowWidthOk` 和 `passed`。
+- 新增 `scripts/export-proof-gap-software-form-smoke.js`:
+  - 校验导出的 CSV/Markdown/summary 文件存在。
+  - 校验 CSV 有 BOM、5 行、无重复 ID、无 token/sessionToken 形态。
+- 新增 npm 脚本:
+  - `proof-gap:software-form`
+  - `proof-gap:software-form:smoke`
+- `scripts/acceptance.js` 增加 `proof-gap:software-form:smoke`。
+- `scripts/plan-consistency-smoke.js` 增加 latest 表单 summary 检查:
+  - `outputs/tihao-proof-gap-software-form-latest/tihao-proof-gap-software-form-summary.json`
+  - 必须 `passed=true`、`headerMatches=true`、`rowWidthOk=true`、`rowCount=5`、`duplicateIdCount=0`。
+
+### 最新表单位置
+
+- CSV:
+  - `outputs/tihao-proof-gap-software-form-latest/tihao-proof-gap-software-form.csv`
+- Markdown:
+  - `outputs/tihao-proof-gap-software-form-latest/tihao-proof-gap-software-form.md`
+- Summary:
+  - `outputs/tihao-proof-gap-software-form-latest/tihao-proof-gap-software-form-summary.json`
+
+### 已验证
+
+- `node --check scripts\export-proof-gap-software-form.js` 通过。
+- `node --check scripts\export-proof-gap-software-form-smoke.js` 通过。
+- `npm.cmd run proof-gap:software-form -- --output outputs\tihao-proof-gap-software-form-latest --strict` 已生成最新表单:
+  - `rowCount=5`
+  - `duplicateIdCount=0`
+  - `headerMatches=true`
+- `npm.cmd run proof-gap:software-form:smoke` 通过:
+  - `rowCount=5`
+  - `duplicateIdCount=0`
+- `npm.cmd run plan:consistency:smoke` 通过:
+  - `scannedFiles=7`
+  - `hits=[]`
+- `npm.cmd run acceptance` 通过。
+- `npm.cmd run round:deposition -- --output outputs\round-deposition-latest` 通过:
+  - `passed=true`
+  - `passCount=45`
+  - `failCount=0`
+  - `warnCount=0`
+
+### 当前边界
+
+- 最新提号补证表单已可直接打开 CSV 使用,但它仍是补证任务表,不是客户提号结果名单。
+- 客户效果、视频提升和长期目标仍需真实历史 Brief、真实候选视频、真实 A/B、live/provider 长跑和客户复核数据证明。
+
+## 2026-06-07 沉淀审计记录最新提号表单
+
+### 问题
+
+- 最新提号补证表单已经导出到 `outputs/tihao-proof-gap-software-form-latest`。
+- acceptance 和 plan consistency 已经会检查表单。
+- 但 `round:deposition` 的来源、当前观测和检查项里还没有记录该表单,交接报告无法直接看到表单是否通过、行数和重复 ID 数。
+
+### 本轮修正
+
+- 修复 `scripts/round-deposition-audit.js`:
+  - 新增 `--proof-gap-software-form` 参数。
+  - 自动读取 latest `tihao-proof-gap-software-form-summary.json`。
+  - `sources` 增加 `proofGapSoftwareFormSummary`。
+  - `observed` 增加:
+    - `proofGapSoftwareFormPassed`
+    - `proofGapSoftwareFormRowCount`
+    - `proofGapSoftwareFormDuplicateIdCount`
+    - `proofGapSoftwareFormHeaderMatches`
+    - `proofGapSoftwareFormRowWidthOk`
+  - 检查项增加:
+    - `proof-gap-software-form-summary`
+    - `proof-gap-software-form-passed`
+  - 验收口径为:
+    - `passed=true`
+    - `headerMatches=true`
+    - `rowWidthOk=true`
+    - `rowCount` 与 `proofGap.unresolvedCount` 对齐
+    - `duplicateIdCount=0`
+    - 核心 5 个标题齐全。
+- 更新 `scripts/round-deposition-smoke.js`:
+  - fixture 增加 `outputs/tihao-proof-gap-software-form-latest/tihao-proof-gap-software-form-summary.json`。
+  - 断言 summary 和 report 都记录最新表单状态。
+
+### 已验证
+
+- `node --check scripts\round-deposition-audit.js` 通过。
+- `node --check scripts\round-deposition-smoke.js` 通过。
+- `npm.cmd run round:deposition:smoke` 通过:
+  - `passCount=47`
+  - `failCount=0`
+  - `proofGapUnresolvedCount=5`
+- `npm.cmd run round:deposition -- --output outputs\round-deposition-latest` 已刷新:
+  - `passed=true`
+  - `passCount=47`
+  - `failCount=0`
+  - `warnCount=0`
+- `npm.cmd run acceptance` 通过。
+
+### 当前边界
+
+- 每轮沉淀现在同时记录:
+  - 真实证明缺口请求包
+  - proof-gap closure
+  - business-proof next-actions
+  - 最新提号补证表单
+- 当前表单通过不代表客户效果完成;它只证明补证任务表格式和去重口径稳定。
+
+## 2026-06-07 交接摘要暴露最新提号表单
+
+### 问题
+
+- 最新提号补证表单已经导出并被 `round:deposition` 记录。
+- 但 `handoff:summary` 仍只列出数据模板、补证进度表、下一步行动队列和负责人文件。
+- 下一轮 AI 或商务打开交接摘要时,无法直接看到最新表单 CSV/Markdown/Summary,也看不到表单的 `passed`、`rowCount` 和 `duplicateIdCount`。
+
+### 本轮修正
+
+- 修复 `scripts/generate-optimization-handoff.js`:
+  - 自动发现 latest `outputs/tihao-proof-gap-software-form-latest`。
+  - 读取 `tihao-proof-gap-software-form-summary.json`。
+  - summary 新增 `proofGapSoftwareForm`:
+    - `csv`
+    - `markdown`
+    - `summary`
+    - `passed`
+    - `rowCount`
+    - `duplicateIdCount`
+    - `headerMatches`
+    - `rowWidthOk`
+  - Markdown 报告新增 `## 最新提号补证表单` 区块。
+  - `nextCommands` 新增:
+    - `npm run proof-gap:software-form -- --output outputs\tihao-proof-gap-software-form-latest --strict`
+- 更新 `scripts/generate-optimization-handoff-smoke.js`:
+  - 断言 handoff summary 包含最新表单 summary/csv/markdown。
+  - 断言表单 `passed=true`、`rowCount=5`、`duplicateIdCount=0`。
+  - 断言 handoff report 包含 `## 最新提号补证表单`、CSV 路径和表单状态。
+
+### 已验证
+
+- `node --check scripts\generate-optimization-handoff.js` 通过。
+- `node --check scripts\generate-optimization-handoff-smoke.js` 通过。
+- `npm.cmd run handoff:summary:smoke` 通过。
+- `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest` 已刷新:
+  - `complete=false`
+  - `readyCapabilities=27`
+  - `externalBlockers=2`
+- 最新交接摘要现在包含:
+  - `outputs/tihao-proof-gap-software-form-latest/tihao-proof-gap-software-form.csv`
+  - `passed=true`
+  - `rowCount=5`
+  - `duplicateIdCount=0`
+- `npm.cmd run acceptance` 通过。
+- `npm.cmd run round:deposition -- --output outputs\round-deposition-latest` 通过:
+  - `passed=true`
+  - `passCount=47`
+  - `failCount=0`
+  - `warnCount=0`
+
+### 当前边界
+
+- handoff 现在能直接交接最新表单,但该表单仍是补证任务表,不是最终客户博主名单。
+- 仍需真实历史 Brief、真实候选视频、真实视频 A/B、live/provider 长跑和客户复核数据来关闭 5 个 proof gap。
+
+## 2026-06-07 证据台账纳入最新提号补证表单
+
+### 问题
+
+- 最新提号补证表单已经生成、被 handoff 暴露,并被 round deposition 记录。
+- 但 `evidence:index` 仍只识别 proof-gap request/closure、handoff、round deposition 等产物。
+- 这会导致证据台账里看不到“软件端格式补证表单”这一项,下一轮审计时需要从 handoff 或 outputs 侧绕路确认。
+
+### 本轮修正
+
+- 修复 `scripts/evidence-index.js`:
+  - 自动读取 `tihao-proof-gap-software-form-summary.json`。
+  - 新增证据类型 `proof-gap-software-form`。
+  - `passed=true` 时只归类为 `smoke_or_local`,不归类为 `real_evidence`。
+  - 摘要输出 `rows`、`duplicateId`、`header`、`rowWidth`。
+  - next 明确写明:用于软件端/商务补证任务表,不证明客户效果或命中率提升。
+- 修复 `scripts/evidence-index-smoke.js`:
+  - fixture 增加最新补证表单 summary。
+  - 断言总条目从 26 增至 27。
+  - 断言 `smoke_or_local` 从 12 增至 13。
+  - 断言 `proof-gap-software-form=1`。
+
+### 已验证
+
+- `node --check scripts\evidence-index.js` 通过。
+- `node --check scripts\evidence-index-smoke.js` 通过。
+- `npm.cmd run evidence:index:smoke` 通过:
+  - `total=27`
+  - `smoke_or_local=13`
+  - `not_business_proof=9`
+  - `real_evidence=5`
+- `npm.cmd run evidence:index -- --output outputs\evidence-index-latest` 已刷新:
+  - `total=151`
+  - `realEvidence=5`
+  - `smokeOrLocal=25`
+  - `notProof=121`
+- `npm.cmd run handoff:summary -- --output outputs\optimization-handoff-latest` 已刷新:
+  - `complete=false`
+  - `readyCapabilities=27`
+  - `externalBlockers=2`
+- `npm.cmd run round:deposition -- --output outputs\round-deposition-latest` 已刷新:
+  - `passed=true`
+  - `passCount=47`
+  - `failCount=0`
+  - `warnCount=0`
+- `npm.cmd run acceptance` 通过。
+
+### 当前边界
+
+- 证据台账现在能直接识别最新提号补证表单,避免交接时遗漏该产物。
+- 该表单仍只是补证任务表,不是客户 Brief 上传后的真实博主名单。
+- 当前长期目标仍未完成:5 个 proof gap 仍需真实历史 Brief、真实视频资源、真实视频 A/B、真实 live/provider 长跑和客户复核效果数据来关闭。
+
+## 2026-06-07 固化每轮 latest 刷新顺序
+
+### 问题
+
+- 长期优化计划要求每轮结束后刷新:
+  - `outputs/optimization-status-latest/optimization-status-report.md`
+  - `outputs/evidence-index-latest/evidence-index-report.md`
+  - `outputs/optimization-handoff-latest/optimization-handoff-report.md`
+  - `outputs/round-deposition-latest/round-deposition-report.md`
+- 这些产物之间有顺序依赖:round deposition 应读取已经刷新的 evidence index,handoff 应暴露最新 proof-gap software form。
+- 手动并行运行时容易出现 latest 之间短暂不同步,例如 round 读到旧 evidence counts。
+
+### 本轮修正
+
+- 新增 `scripts/refresh-round-artifacts.js`:
+  - 固定按顺序运行:
+    1. `proof-gap:software-form`
+    2. `optimization:status`
+    3. `evidence:index`
+    4. `handoff:summary`
+    5. `round:deposition --strict`
+  - 输出 `outputs/round-refresh-summary.json`,记录每个步骤是否通过。
+  - 支持 `--output-root`,可用于真实 latest 或临时 smoke 目录。
+- 新增 `scripts/refresh-round-artifacts-smoke.js`:
+  - 在临时目录预置必要 proof-gap、intake、longrun、business-proof 依赖产物。
+  - 自动复制一份已有真实 evidence seed,确保 evidence counts 同时覆盖 `real_evidence`、`smoke_or_local`、`not_business_proof`。
+  - 断言 refresh 顺序稳定、最新表单通过、handoff 暴露表单、round 读取到刷新后的 evidence index。
+- 更新 `package.json`:
+  - 新增 `round:refresh`
+  - 新增 `round:refresh:smoke`
+- 更新 `scripts/acceptance.js`:
+  - 总验收纳入 `round:refresh:smoke`。
+
+### 已验证
+
+- `node --check scripts\refresh-round-artifacts.js` 通过。
+- `node --check scripts\refresh-round-artifacts-smoke.js` 通过。
+- `npm.cmd run round:refresh:smoke` 通过:
+  - `steps=proof-gap-software-form:pass, optimization-status:pass, evidence-index:pass, optimization-handoff:pass, round-deposition:pass`
+  - `roundPassCount=47`
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 已刷新真实 latest:
+  - `optimization-status-latest`: `passed=27`、`ready_not_proven=2`、`blocked_by_external_data=2`、`complete=false`
+  - `evidence-index-latest`: `total=151`、`realEvidence=5`、`smokeOrLocal=25`、`notProof=121`
+  - `optimization-handoff-latest`: `complete=false`、`readyCapabilities=27`、`externalBlockers=2`
+  - `round-deposition-latest`: `passed=true`、`passCount=47`、`failCount=0`
+- `npm.cmd run acceptance` 通过。
+
+### 当前边界
+
+- 现在每轮沉淀可以用一个命令刷新,避免 latest 产物之间顺序不同步。
+- 这仍不关闭真实业务证明缺口;当前 `complete=false`,`ready_not_proven=2`,`blocked_by_external_data=2`。
+- 5 个 proof gap 仍需真实历史 Brief、真实视频资源、真实视频 A/B、真实 live/provider 长跑和客户复核效果数据来关闭。
+
+## 2026-06-07 交接和 SOP 暴露一键刷新入口
+
+### 问题
+
+- `round:refresh` 已经能按固定顺序刷新每轮 latest 产物。
+- 但 `handoff:summary` 的 `nextCommands` 仍提示分开跑 `evidence:index` 和 `handoff:summary`。
+- `docs/business-proof-action-order.md` 第 15 步仍按旧方式列多个分散命令。
+- `docs/tihao-experience-optimization-plan.md` 的每轮沉淀章节也没有明确优先使用 `round:refresh`。
+
+### 本轮修正
+
+- 修复 `scripts/generate-optimization-handoff.js`:
+  - `nextCommands` 新增“每轮沉淀一键刷新”:
+    - `npm run round:refresh -- --output-root outputs --strict`
+  - 单独 `evidence:index` 和 `handoff:summary` 命令改为排查/单独刷新时使用。
+- 更新 `scripts/generate-optimization-handoff-smoke.js`:
+  - 断言 handoff 必须包含 `round:refresh` 且包含 `--strict`。
+- 更新 `docs/business-proof-action-order.md`:
+  - 第 15 步优先使用 `round:refresh`。
+  - 保留单独排查命令。
+  - 通过标准改为 `round-refresh-summary.json` 5 步全部通过,且 `round:deposition` 通过。
+- 更新 `docs/tihao-experience-optimization-plan.md`:
+  - 每轮沉淀章节新增优先命令:
+    - `npm run round:refresh -- --output-root outputs --strict`
+
+### 已验证
+
+- `node --check scripts\generate-optimization-handoff.js` 通过。
+- `node --check scripts\generate-optimization-handoff-smoke.js` 通过。
+- `npm.cmd run handoff:summary:smoke` 通过。
+- `npm.cmd run plan:consistency:smoke` 通过。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 已刷新真实 latest:
+  - `round-refresh-summary.json`: 5 步全部 `pass`
+  - `optimization-status-latest`: `complete=false`
+  - `evidence-index-latest`: `total=151`、`realEvidence=5`、`smokeOrLocal=25`、`notProof=121`
+  - `optimization-handoff-latest`: `readyCapabilities=27`、`externalBlockers=2`
+  - `round-deposition-latest`: `passed=true`、`passCount=47`、`failCount=0`
+
+### 当前边界
+
+- 下一轮 AI 或商务看 handoff/SOP 时,会优先看到一键沉淀入口。
+- 该入口只保证沉淀顺序和审计一致,不证明客户效果完成。
+- 长期目标仍未完成:`proofGapClosureOpenCount=5`,仍需真实业务材料关闭缺口。
+
+## 2026-06-07 状态审计 next 文案统一到一键刷新
+
+### 问题
+
+- handoff 的 `nextCommands` 已经优先暴露 `round:refresh`。
+- 但 `optimization-status` 的 readyCapabilities 里仍有旧 next 文案:
+  - `optimization-handoff` 仍提示单独跑 `handoff:summary`。
+  - `round-deposition` 仍提示先跑 `round:deposition`,再刷新 `evidence:index` 和 `handoff:summary`。
+  - `evidence-index` 仍提示单独跑 `evidence:index`。
+- 这些文案会进入 handoff 的 readyCapabilities,导致同一份交接摘要里前后口径不一致。
+
+### 本轮修正
+
+- 修复 `scripts/optimization-status-audit.js`:
+  - `optimization-handoff.next` 改为常规每轮优先 `round:refresh`,只在单独刷新交接摘要时跑 `handoff:summary`。
+  - `round-deposition.next` 改为常规每轮优先 `round:refresh`,确保状态、证据台账、交接摘要和沉淀审计按顺序刷新。
+  - `evidence-index.next` 改为长跑或复核后优先 `round:refresh`,只在单独刷新证明等级时跑 `evidence:index`。
+- 更新 `scripts/optimization-status-smoke.js`:
+  - 断言 `optimization-handoff.next` 必须包含 `round:refresh`。
+  - 断言 `round-deposition.next` 必须包含 `round:refresh`,且不得再包含旧的“再刷新 evidence:index”口径。
+  - 断言 `evidence-index.next` 必须包含 `round:refresh`。
+
+### 已验证
+
+- `node --check scripts\optimization-status-audit.js` 通过。
+- `node --check scripts\optimization-status-smoke.js` 通过。
+- `npm.cmd run optimization:status:smoke` 通过。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 已刷新真实 latest:
+  - `round-refresh-summary.json`: 5 步全部 `pass`
+  - `optimization-status-latest`: 三个 next 文案均指向 `round:refresh`
+  - `optimization-handoff-latest`: readyCapabilities 和 nextCommands 均指向 `round:refresh`
+  - `round-deposition-latest`: `passed=true`、`failCount=0`
+
+### 当前边界
+
+- 交接摘要现在前后口径一致:常规每轮沉淀优先使用 `round:refresh`。
+- 这仍只是流程沉淀优化,不关闭真实业务证明缺口。
+- 当前 `proofGapClosureOpenCount=5`,仍需真实历史 Brief、真实视频资源、真实 A/B、真实 live/provider 长跑和客户复核效果数据。
+
+## 2026-06-07 扩展 round:refresh 为完整每轮沉淀链路
+
+### 问题
+
+- 之前 `round:refresh` 只刷新提号补证表单、状态、证据台账、交接摘要和 round deposition。
+- `proof-gap:request`、`proof-gap:closure`、`business-proof:progress`、`business-proof:next-actions` 仍依赖旧 latest。
+- 扩展刷新链后,`business-proof:next-actions` 暴露出一个真实回归:模板生成步骤、视频资源收集包生成步骤和“刷新证据和交接”步骤混入了下一步业务补证队列,导致行动数从 5 条膨胀到 8 条。
+
+### 本轮修正
+
+- 修复 `scripts/refresh-round-artifacts.js`:
+  - 一键刷新顺序扩展为 9 步:`proof-gap-software-form`、`proof-gap-request`、`proof-gap-closure`、`business-proof-progress`、`business-proof-next-actions`、`optimization-status`、`evidence-index`、`optimization-handoff`、`round-deposition`。
+  - `round-refresh-summary.json` 现在记录 proof-gap 和 business-proof 两类产物路径。
+- 修复 `scripts/refresh-round-artifacts-smoke.js`:
+  - 断言 9 步顺序稳定。
+  - 断言 proof-gap request、proof-gap closure、business-proof progress、business-proof next-actions 都被刷新。
+- 修复 `scripts/business-proof-next-actions.js`:
+  - 将 `step-01`、`step-02`、`step-15` 作为准备/沉淀动作过滤,不再进入业务补证行动队列。
+  - guardrail 改为补齐真实材料后优先运行 `npm run round:refresh -- --output-root outputs --strict`。
+- 修复 `scripts/business-proof-next-actions-smoke.js`:
+  - 断言 guardrail 必须包含 `round:refresh`。
+  - 断言不能保留旧的手动分散刷新顺序。
+  - 断言模板生成和沉淀刷新步骤不得进入业务补证行动队列。
+- 更新 `docs/business-proof-action-order.md`:
+  - 第 15 步通过标准从 `round-refresh-summary.json` 5 步通过改为 9 步通过。
+- 更新 `scripts/generate-optimization-handoff.js`:
+  - handoff 中一键刷新说明补充 proof-gap 请求/关闭审计、业务补证进度和下一步行动队列。
+
+### 已验证
+
+- `node --check scripts\refresh-round-artifacts.js` 通过。
+- `node --check scripts\refresh-round-artifacts-smoke.js` 通过。
+- `node --check scripts\business-proof-next-actions.js` 通过。
+- `node --check scripts\business-proof-next-actions-smoke.js` 通过。
+- `npm.cmd run business-proof:next-actions:smoke` 通过。
+- `npm.cmd run round:refresh:smoke` 通过:
+  - `steps=proof-gap-software-form:pass, proof-gap-request:pass, proof-gap-closure:pass, business-proof-progress:pass, business-proof-next-actions:pass, optimization-status:pass, evidence-index:pass, optimization-handoff:pass, round-deposition:pass`
+  - `business-proof-next-actions.actionCount=5`
+  - `roundPassCount=47`
+- `npm.cmd run plan:consistency:smoke` 通过。
+- `npm.cmd run handoff:summary:smoke` 通过。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 已刷新真实 latest:
+  - `proof-gap-request-latest`: `unresolvedCount=5`
+  - `proof-gap-closure-latest`: `openCount=5`
+  - `business-proof-progress-latest`: `pass=3`、`blocked=7`、`pending=3`、`fail=2`
+  - `business-proof-next-actions-latest`: `actionCount=5`
+  - `evidence-index-latest`: `total=151`、`realEvidence=5`、`smokeOrLocal=25`、`notProof=121`
+  - `round-deposition-latest`: `passed=true`、`failCount=0`
+
+### 当前边界
+
+- 每轮沉淀现在会刷新 proof-gap、business-proof、status、evidence、handoff 和 round deposition,减少旧产物混入交接的风险。
+- 下一步行动队列重新收敛为 5 个业务缺口,不再把模板生成或沉淀刷新当成客户补证动作。
+- 当前 `proofGapClosureOpenCount=5`,真实业务证明仍未关闭,不能宣称客户效果或命中率提升已达标。
+
+## 2026-06-07 新增最新提号表单索引
+
+### 问题
+
+- 当前同时存在“最新提号补证表单”和“软件端候选名单”,商务询问“最新提号表单”时容易混淆。
+- 交接摘要能看到多个产物,但还缺一个轻量入口,直接告诉下一轮 AI 或商务:当前该打开哪份表、哪份只是候选名单、负责人行动文件在哪里。
+
+### 本轮修正
+
+- 新增 `scripts/latest-form-index.js`:
+  - 输出 `outputs/latest-form-index-latest/latest-form-index.md` 和 `latest-form-index-summary.json`。
+  - 集中索引最新提号补证表单、最新软件端候选名单、负责人行动 Markdown/CSV、交接摘要和 proof guardrail。
+  - 明确 `directCustomerProof=false`,避免把补证表单或候选名单误当成客户效果证明。
+- 新增 `scripts/latest-form-index-smoke.js`:
+  - 断言能找到 `tihao-proof-gap-software-form.csv` 和 `software-client-list.final.csv`。
+  - 断言负责人行动文件存在。
+  - 断言报告不包含 sessionToken、模型 token 或英文鉴权头敏感词。
+- 更新 `round:refresh`:
+  - 一键刷新链路从 9 步扩展为 10 步,新增 `latest-form-index`。
+  - `round-refresh-summary.json` 新增 `latestFormIndex` 产物路径。
+- 更新 `docs/business-proof-action-order.md` 和 `docs/tihao-experience-optimization-plan.md`:
+  - 第 15 步通过标准改为 `round-refresh-summary.json` 10 步通过。
+  - 每轮输出补充最新提号表单索引。
+
+### 已验证
+
+- `node --check scripts\latest-form-index.js` 通过。
+- `node --check scripts\latest-form-index-smoke.js` 通过。
+- `node --check scripts\refresh-round-artifacts.js` 通过。
+- `node --check scripts\refresh-round-artifacts-smoke.js` 通过。
+- `npm.cmd run latest-form:index:smoke` 通过。
+- `npm.cmd run round:refresh:smoke` 通过:
+  - `steps=proof-gap-software-form:pass, proof-gap-request:pass, proof-gap-closure:pass, business-proof-progress:pass, business-proof-next-actions:pass, optimization-status:pass, evidence-index:pass, optimization-handoff:pass, latest-form-index:pass, round-deposition:pass`
+
+### 当前边界
+
+- 最新表单入口现在更容易给商务使用,但它仍只解决“找到表单和行动文件”的问题。
+- 当前真实业务证明缺口仍是 5 个:真实历史 Brief、真实视频资源、人工复核标注、真实视频 A/B、真实 live/provider 长跑。
+
+## 2026-06-07 将最新表单索引纳入交接和沉淀审计
+
+### 问题
+
+- 上一轮已生成 `outputs/latest-form-index-latest/latest-form-index.md`,但 handoff 和 round deposition 还没有把它当成必须检查的一等产物。
+- 这会导致“最新表单入口”虽然存在,但下一轮 AI 或商务仍可能只看交接摘要,漏掉统一索引。
+
+### 本轮修正
+
+- 更新 `scripts/generate-optimization-handoff.js`:
+  - 新增 `latestFormIndex` 汇总字段。
+  - 报告新增“最新提号表单索引”区块,展示索引报告、summary、补证表单、候选名单、actionCount 和 proofOpenCount。
+  - 下一轮命令新增 `npm run latest-form:index -- --output outputs\latest-form-index-latest --strict`。
+- 更新 `scripts/refresh-round-artifacts.js`:
+  - 刷新顺序调整为先生成 `latest-form-index`,再生成 `optimization-handoff`,最后跑 `round-deposition`。
+  - 这样 handoff 能引用本轮最新索引。
+- 更新 `scripts/round-deposition-audit.js`:
+  - 新增 `latest-form-index-summary`、`latest-form-index-passed`、`handoff-latest-form-index` 三个检查项。
+  - 当前沉淀审计需要确认索引通过、指向补证表单、指向软件端候选名单、包含负责人行动文件,并且 handoff 已引用索引。
+- 更新 `scripts/evidence-index.js`:
+  - 新增 `latest-form-index` 证据分类,标记为 `smoke_or_local`,明确不证明客户效果或命中率提升。
+- 更新 smoke:
+  - `handoff:summary:smoke` 断言 handoff 包含最新表单索引。
+  - `round:deposition:smoke` 断言沉淀审计检查最新表单索引。
+  - `evidence:index:smoke` 断言证据台账识别 `latest-form-index`。
+  - `optimization:status:smoke` 断言状态审计暴露稳定 ID `latest-form-index`。
+  - `round:refresh:smoke` 在临时环境中种入 `software-client-latest`,保证索引必须指向候选名单。
+
+### 已验证
+
+- `node --check scripts\generate-optimization-handoff.js` 通过。
+- `node --check scripts\round-deposition-audit.js` 通过。
+- `node --check scripts\round-deposition-smoke.js` 通过。
+- `node --check scripts\evidence-index.js` 通过。
+- `node --check scripts\optimization-status-audit.js` 通过。
+- `npm.cmd run handoff:summary:smoke` 通过:`readyCapabilities=28`。
+- `npm.cmd run round:deposition:smoke` 通过:`passCount=50`、`failCount=0`。
+- `npm.cmd run evidence:index:smoke` 通过:`total=28`、`smoke_or_local=14`。
+- `npm.cmd run optimization:status:smoke` 通过。
+- `npm.cmd run round:refresh:smoke` 通过:
+  - `steps=proof-gap-software-form:pass, proof-gap-request:pass, proof-gap-closure:pass, business-proof-progress:pass, business-proof-next-actions:pass, optimization-status:pass, evidence-index:pass, latest-form-index:pass, optimization-handoff:pass, round-deposition:pass`
+  - `roundPassCount=50`
+
+### 当前边界
+
+- 最新表单索引现在进入了 handoff、status、evidence 和 round deposition 闭环。
+- 这仍是交付/沉淀能力,不关闭真实业务证明缺口。
+- 当前仍需补齐 5 个真实缺口后,才能验证客户效果或命中率提升。
+
+## 2026-06-07 补强真实材料收集包字段验收说明
+
+### 问题
+
+- 数据收集包和视频收集包已有基础填写规则,但缺少逐字段验收表。
+- 商务填表时容易把“客户选择”“历史人工补号量基线”“参考账号或视频”“是否真实资源”等字段按自然语言填成半成品,导致后续 `intake:readiness` 才整批退回。
+
+### 本轮修正
+
+- 更新 `scripts/create-data-intake-pack.js`:
+  - README 新增“字段填写验收表”。
+  - 历史数据表逐字段说明 `brief编号`、`客户原始Brief`、`历史人工补号量基线`、`参考账号或视频`、`博主名称/主页链接`、`客户选择`、`拒绝原因` 的必填规则和常见退回原因。
+  - 人工复核表逐字段说明 `人工复核标签`、`归因类型`、`反馈原因`、`客户选择`、`本轮人工补号量` 的验收规则。
+  - README 新增先跑 `npm run intake:readiness` 的预审命令。
+- 更新 `scripts/create-video-intake-pack.js`:
+  - README 新增“字段填写验收表”。
+  - 逐字段说明 `资源角色`、`博主名称/主页链接`、`视频链接`、`封面链接`、`字幕或ASR文本`、`帧图链接`、`内容摘要/风格调性标签/画面人设场景信号`、`是否真实资源` 的必填规则和常见退回原因。
+  - README 新增先跑 `npm run video:resource-readiness` 的预审命令。
+- 更新 smoke:
+  - `data:intake-template:smoke` 断言 README 包含字段验收表、常见退回原因、intake readiness 预审命令和客户拒绝原因要求。
+  - `video:intake-template:smoke` 断言 README 包含字段验收表、常见退回原因、video resource readiness 预审命令和防止占位行标成真实资源的提醒。
+- 重新生成 latest 收集包:
+  - `outputs/data-intake-pack-latest/README.md`
+  - `outputs/video-intake-pack-latest/README.md`
+
+### 已验证
+
+- `node --check scripts\create-data-intake-pack.js` 通过。
+- `node --check scripts\create-video-intake-pack.js` 通过。
+- `npm.cmd run data:intake-template:smoke` 通过。
+- `npm.cmd run video:intake-template:smoke` 通过。
+- `npm.cmd run data:intake-template -- --output outputs\data-intake-pack-latest` 已刷新数据收集包。
+- `npm.cmd run video:intake-template -- --output outputs\video-intake-pack-latest` 已刷新视频收集包。
+- `npm.cmd run intake:readiness -- --data-pack outputs\data-intake-pack-latest --video-pack outputs\video-intake-pack-latest --output outputs\intake-readiness-latest` 通过执行,当前模板数据正确被拦截:
+  - `overallReady=false`
+  - `failureCount=9`
+- `npm.cmd run video:resource-readiness -- --input outputs\video-intake-pack-latest\video-resource-template.csv --output outputs\video-resource-readiness-latest` 通过执行,当前模板数据正确被拦截:
+  - `readyForVideoAbPreflight=false`
+  - `failureCount=1`
+  - 主要问题是缺少真实候选视频资源。
+
+### 当前边界
+
+- 字段验收表降低商务填表返工,但模板仍不是业务证明。
+- 当前 latest 收集包仍包含示例/待补内容,预审保持不通过是正确结果。
+- 后续需要商务把模板替换为真实历史 Brief、真实视频资源、客户选择和人工补号量后,再重新运行预审和审计。
+
+## 2026-06-07 预审报告新增修复动作 CSV
+
+### 问题
+
+- `intake:readiness` 和 `video:resource-readiness` 之前能告诉用户有哪些问题,但商务/投放还需要人工把问题翻译成“改哪张表、哪一列、由谁改、通过标准是什么”。
+- 这会增加真实材料补齐的沟通成本,也容易让下一轮 AI 只看到 failureCount,却不知道如何派工。
+
+### 本轮修正
+
+- 更新 `scripts/intake-readiness-audit.js`:
+  - summary 新增 `repairActions`。
+  - 报告新增“修复清单”表。
+  - 输出新增 `intake-readiness-repair-actions.csv`,列包含优先级、负责人、区域、问题类型、文件、字段、修复动作和通过标准。
+  - 当前模板预审会把 9 个 issue 映射成 9 条修复动作。
+- 更新 `scripts/video-resource-readiness-audit.js`:
+  - summary 新增 `repairActions`。
+  - 报告新增“修复清单”表。
+  - 输出新增 `video-resource-repair-actions.csv`,列包含优先级、负责人、问题类型、行号、文件、字段、修复动作和通过标准。
+  - 当前模板视频资源预审会把缺真实候选视频映射成 1 条修复动作。
+- 更新 smoke:
+  - `intake:readiness:smoke` 断言模板审计为每个 issue 输出一条 repair action,并生成 CSV。
+  - `video:resource-readiness:smoke` 断言失败样本输出 repair actions 和 CSV,成功样本 repair actions 为 0。
+
+### 已验证
+
+- `node --check scripts\intake-readiness-audit.js` 通过。
+- `node --check scripts\video-resource-readiness-audit.js` 通过。
+- `npm.cmd run intake:readiness:smoke` 通过。
+- `npm.cmd run video:resource-readiness:smoke` 通过。
+- `npm.cmd run intake:readiness -- --data-pack outputs\data-intake-pack-latest --video-pack outputs\video-intake-pack-latest --output outputs\intake-readiness-latest` 已刷新:
+  - `failureCount=9`
+  - `repairActionCount=9`
+  - 新增 `outputs/intake-readiness-latest/intake-readiness-repair-actions.csv`
+- `npm.cmd run video:resource-readiness -- --input outputs\video-intake-pack-latest\video-resource-template.csv --output outputs\video-resource-readiness-latest` 已刷新:
+  - `failureCount=1`
+  - `repairActionCount=1`
+  - 新增 `outputs/video-resource-readiness-latest/video-resource-repair-actions.csv`
+
+### 当前边界
+
+- 修复动作 CSV 只帮助补齐真实材料,不证明业务效果。
+- 当前修复清单仍基于模板/占位数据生成,真实通过需要商务/投放替换为真实历史 Brief、真实视频和客户复核数据后重跑。
+
+## 2026-06-07 最新表单索引和交接摘要新增修复清单入口
+
+### 问题
+
+- 最新提号表单索引已经能定位补证表、候选名单和负责人行动文件,但没有直接暴露 `intake-readiness` 和 `video-resource-readiness` 生成的修复清单。
+- 商务或下一轮 AI 只看 latest form index / handoff 时,仍需要自己去找“具体改哪张表、哪一列、谁负责、通过标准是什么”,容易让修复动作和补证表割裂。
+
+### 本轮修正
+
+- 更新 `scripts/latest-form-index.js`:
+  - summary 新增 `repairActions.dataIntake` 和 `repairActions.videoResource`。
+  - 报告新增“修复清单入口”表格,展示修复 CSV、当前问题数、修复动作数和状态。
+  - 当前 latest 指向:
+    - `outputs/intake-readiness-latest/intake-readiness-repair-actions.csv`
+    - `outputs/video-resource-readiness-latest/video-resource-repair-actions.csv`
+- 更新 `scripts/generate-optimization-handoff.js`:
+  - `latestFormIndex` 汇总中同步携带 `repairActions`。
+  - 报告“最新提号表单索引”区块下新增“修复清单入口”。
+  - 增加兜底读取逻辑:即使 latest-form-index 产物还没刷新,handoff 也会尝试从 latest readiness 目录读取修复清单,避免单独运行 handoff 时漏入口。
+- 更新 smoke:
+  - `latest-form:index:smoke` 断言修复清单 CSV 和动作数存在。
+  - `handoff:summary:smoke` 断言交接摘要记录并展示修复清单入口。
+
+### 已验证
+
+- `node --check scripts/latest-form-index.js` 通过。
+- `node --check scripts/latest-form-index-smoke.js` 通过。
+- `node --check scripts/generate-optimization-handoff.js` 通过。
+- `node --check scripts/generate-optimization-handoff-smoke.js` 通过。
+- `npm.cmd run latest-form:index:smoke` 通过。
+- `npm.cmd run handoff:summary:smoke` 通过。
+- `npm.cmd run round:refresh:smoke` 通过:
+  - `roundPassCount=50`
+  - `failCount=0`
+- `npm.cmd run hygiene:smoke` 通过:
+  - `hitCount=0`
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 已刷新真实 latest:
+  - `proof-gap-closure.openCount=5`
+  - `business-proof-next-actions.actionCount=5`
+  - `optimization-status.passed=28`
+  - `optimization-status.ready_not_proven=2`
+  - `optimization-status.blocked_by_external_data=2`
+  - `evidence-index.total=152`
+  - `evidence-index.realEvidence=5`
+  - `evidence-index.smokeOrLocal=26`
+  - `evidence-index.notProof=121`
+  - `round-deposition.passed=true`
+  - `round-deposition.passCount=50`
+  - `round-deposition.failCount=0`
+- 抽查 latest 产物确认:
+  - `outputs/latest-form-index-latest/latest-form-index.md` 包含“修复清单入口”、两份 repair action CSV。
+  - `outputs/optimization-handoff-latest/optimization-handoff-report.md` 包含“修复清单入口”、两份 repair action CSV。
+  - `outputs/latest-form-index-latest/latest-form-index-summary.json` 和 `outputs/optimization-handoff-latest/optimization-handoff-summary.json` 均记录:
+    - 数据/复核修复:`failureCount=9`,`repairActionCount=9`
+    - 视频资源修复:`failureCount=1`,`repairActionCount=1`
+
+### 当前边界
+
+- 本轮提升的是商务补证入口的可用性,不是命中率或客户效果证明。
+- 当前真实证明缺口仍为 5 个:真实历史 Brief、真实视频资源、人工复核标注、真实视频 A/B、真实 live/provider 长跑。
+- 只要 `proof-gap-closure.openCount > 0` 或状态中仍有 `ready_not_proven` / `blocked_by_external_data`,就不能宣称长期优化目标完成。
+
+## 2026-06-07 将修复清单入口纳入每轮沉淀审计
+
+### 问题
+
+- 上一轮已经把修复清单入口写进 latest form index 和 handoff,但 `round-deposition` 仍只检查索引是否指向补证表、候选名单和负责人行动文件。
+- 如果后续某次刷新丢失 `intake-readiness-repair-actions.csv` 或 `video-resource-repair-actions.csv` 的入口,沉淀审计不会报警,商务又会回到“知道有缺口,但不知道改哪张表”的状态。
+
+### 本轮修正
+
+- 更新 `scripts/round-deposition-audit.js`:
+  - 新增检查项 `latest-form-index-repair-actions`。
+  - 新增检查项 `handoff-latest-form-repair-actions`。
+  - `observed` 新增:
+    - `latestFormIndexDataRepairCsv`
+    - `latestFormIndexDataRepairActionCount`
+    - `latestFormIndexVideoRepairCsv`
+    - `latestFormIndexVideoRepairActionCount`
+  - 报告“当前观测”区块新增 data/video repair CSV 和动作数。
+- 更新 `scripts/round-deposition-smoke.js`:
+  - fixture 中补齐 latestFormIndex 和 handoff 的 `repairActions`。
+  - 新增断言:沉淀审计必须看到两份 repair CSV 和动作数。
+- 更新 `scripts/refresh-round-artifacts-smoke.js`:
+  - 临时刷新环境新增 `video-resource-readiness-latest` 种子目录。
+  - 新增断言:latestForm、handoff、round deposition 三处均能看到 data/video repair CSV。
+
+### 已验证
+
+- `node --check scripts/round-deposition-audit.js` 通过。
+- `node --check scripts/round-deposition-smoke.js` 通过。
+- `node --check scripts/refresh-round-artifacts-smoke.js` 通过。
+- `npm.cmd run round:deposition:smoke` 通过:
+  - `passCount=52`
+  - `failCount=0`
+- `npm.cmd run round:refresh:smoke` 通过:
+  - `roundPassCount=52`
+  - `failCount=0`
+- `npm.cmd run hygiene:smoke` 通过:
+  - `hitCount=0`
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 已刷新真实 latest:
+  - 10 个 refresh 步骤全部 `pass`
+  - `round-deposition.passed=true`
+  - `round-deposition.passCount=52`
+  - `round-deposition.failCount=0`
+  - 新增两项 repair 检查均为 `pass`
+  - `latestFormIndexDataRepairCsv=outputs/intake-readiness-latest/intake-readiness-repair-actions.csv`
+  - `latestFormIndexDataRepairActionCount=9`
+  - `latestFormIndexVideoRepairCsv=outputs/video-resource-readiness-latest/video-resource-repair-actions.csv`
+  - `latestFormIndexVideoRepairActionCount=1`
+
+### 当前边界
+
+- 本轮完成的是沉淀审计闭环:以后修复清单入口丢失会被 round deposition 拦住。
+- 这仍然不是业务效果证明;当前 `proof-gap-closure.openCount=5`,真实历史 Brief、真实视频资源、人工复核标注、真实视频 A/B 和真实 live/provider 长跑仍未关闭。
+
+## 2026-06-07 负责人行动文件新增修复清单附件
+
+### 问题
+
+- 最新表单索引和 handoff 已经能看到修复清单入口,但商务实际执行时常常只打开自己的负责人行动文件。
+- 如果负责人文件只写“填写哪张模板”,却不直接附上 `intake-readiness-repair-actions.csv` 或 `video-resource-repair-actions.csv`,商务还需要回到索引里再找一次具体修复字段,执行链路仍不够短。
+
+### 本轮修正
+
+- 更新 `scripts/business-proof-next-actions.js`:
+  - 从 `intake-readiness-summary.json` 和 `video-resource-readiness-summary.json` 读取 `repairActions`。
+  - 新增 `ownerRepairArtifacts`,按 owner 聚合修复清单附件。
+  - `ownerArtifacts` 中同步写入对应负责人的 `repairArtifacts`。
+  - 总报告“负责人文件”表新增“修复清单附件”列。
+  - 每个负责人 Markdown 文件顶部新增“修复清单附件”区块。
+- 保持主行动队列去重逻辑不变:
+  - 修复清单作为附件展示,不额外拆成新的主行动,避免把“填一张表”重新拆成多条重复任务。
+
+### 已验证
+
+- `node --check scripts/business-proof-next-actions.js` 通过。
+- `node --check scripts/business-proof-next-actions-smoke.js` 通过。
+- `npm.cmd run business-proof:next-actions:smoke` 通过。
+- `npm.cmd run round:refresh:smoke` 通过。
+- `npm.cmd run handoff:summary:smoke` 通过。
+- `npm.cmd run hygiene:smoke` 通过:
+  - `hitCount=0`
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 已刷新真实 latest:
+  - 10 个 refresh 步骤全部 `pass`
+  - `business-proof-next-actions.actionCount=5`
+  - `round-deposition.passed=true`
+  - `round-deposition.passCount=52`
+  - `round-deposition.failCount=0`
+- 抽查最新负责人文件:
+  - `outputs/business-proof-next-actions-latest/by-owner/business.md` 包含“修复清单附件”和 `intake-readiness-repair-actions.csv`。
+  - `outputs/business-proof-next-actions-latest/by-owner/business-media.md` 包含“修复清单附件”、`video-resource-repair-actions.csv` 和归属投放的视频字段修复项。
+  - `outputs/business-proof-next-actions-latest/business-proof-next-actions-report.md` 包含两份 repair CSV。
+- `npm.cmd run acceptance` 通过,npm pack / workspace install smoke / MCP smoke 均正常。
+
+### 当前边界
+
+- 本轮缩短的是商务补证执行路径,不证明命中率提升。
+- 当前 `ownerRepairArtifacts` 仍基于模板/占位数据的预审失败项生成;真实通过需要商务/投放替换为真实历史 Brief、真实视频资源、人工复核和客户选择后重跑。
+- 当前真实业务证明缺口仍为 5 个,`optimization-status.complete=false`。
+
+## 2026-06-07 latest index 和 handoff 透传负责人修复附件
+
+### 问题
+
+- `business-proof-next-actions-summary.json` 已经有 owner 级 `repairArtifacts`,负责人 Markdown 也能直接展示修复清单附件。
+- 但 `latest-form-index` 和 `optimization-handoff` 在重新归一化负责人文件时只保留 Markdown/CSV,丢掉了 `repairArtifacts`。
+- 这会造成三层入口不一致:负责人文件里有修复附件,但最新索引和交接摘要看不到 owner 级修复附件。
+
+### 本轮修正
+
+- 更新 `scripts/latest-form-index.js`:
+  - `normalizeOwnerArtifacts` 优先读取 `business-proof-next-actions-summary.json` 的 `ownerArtifacts`。
+  - 保留每个 owner 的 `repairArtifacts`。
+  - “负责人行动文件”表新增“修复附件”列。
+- 更新 `scripts/generate-optimization-handoff.js`:
+  - `normalizeOwnerArtifacts` 保留每个 owner 的 `repairArtifacts`。
+  - “负责人行动文件”表新增“修复附件”列。
+- 更新 smoke:
+  - `latest-form:index:smoke` 断言商务和商务/投放 owner repair artifacts 不丢失。
+  - `handoff:summary:smoke` 断言 handoff 中商务和商务/投放 owner repair artifacts 不丢失。
+
+### 已验证
+
+- `node --check scripts/latest-form-index.js` 通过。
+- `node --check scripts/generate-optimization-handoff.js` 通过。
+- `npm.cmd run latest-form:index:smoke` 通过。
+- `npm.cmd run handoff:summary:smoke` 通过。
+- `npm.cmd run round:refresh:smoke` 通过。
+- `npm.cmd run hygiene:smoke` 通过:
+  - `hitCount=0`
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 已刷新真实 latest:
+  - 10 个 refresh 步骤全部 `pass`
+  - `round-deposition.passed=true`
+  - `round-deposition.passCount=52`
+  - `round-deposition.failCount=0`
+- 抽查确认:
+  - `outputs/latest-form-index-latest/latest-form-index-summary.json` 中 `nextActions.ownerArtifacts` 已保留 owner 级 `repairArtifacts`。
+  - `outputs/optimization-handoff-latest/optimization-handoff-summary.json` 中 `businessProofNextActions.ownerArtifacts` 已保留 owner 级 `repairArtifacts`。
+  - `outputs/latest-form-index-latest/latest-form-index.md` 和 `outputs/optimization-handoff-latest/optimization-handoff-report.md` 都包含“修复附件”、`intake-readiness-repair-actions.csv` 和 `video-resource-repair-actions.csv`。
+- `npm.cmd run acceptance` 通过,npm pack / workspace install smoke / MCP smoke 均正常。
+
+### 当前边界
+
+- 这轮修复的是信息透传一致性:负责人文件、最新索引和交接摘要现在都能看到 owner 级修复附件。
+- 当前真实业务证明缺口仍为 5 个,`optimization-status.complete=false`,不能宣称命中率或客户效果已达标。
+
+## 2026-06-07 owner 级修复附件纳入每轮沉淀审计
+
+### 问题
+
+- latest index 和 handoff 已经能透传 owner 级 `repairArtifacts`,但 `round-deposition` 之前只检查全局 `repairActions`。
+- 如果后续刷新时 owner 级修复附件丢失,负责人文件、最新索引和交接摘要会再次信息不一致,而沉淀审计不会拦截。
+
+### 本轮修正
+
+- 更新 `scripts/round-deposition-audit.js`:
+  - 新增检查项 `latest-form-index-owner-repair-actions`。
+  - 新增检查项 `handoff-owner-repair-actions`。
+  - `observed` 新增 `latestFormIndexOwnerRepairSummary` 和 `handoffOwnerRepairSummary`。
+  - 报告“当前观测”区块展示 owner 级修复附件摘要。
+- 更新 `scripts/round-deposition-smoke.js`:
+  - fixture 中补齐 latestFormIndex owner repair artifacts。
+  - fixture 中补齐 handoff.businessProofNextActions owner repair artifacts。
+  - 新增断言:latest index 和 handoff 必须记录商务 data repair、商务/投放 video repair。
+
+### 已验证
+
+- `node --check scripts/round-deposition-audit.js` 通过。
+- `node --check scripts/round-deposition-smoke.js` 通过。
+- `npm.cmd run round:deposition:smoke` 通过:
+  - `passCount=54`
+  - `failCount=0`
+- `npm.cmd run round:refresh:smoke` 通过:
+  - `roundPassCount=54`
+  - `failCount=0`
+- `npm.cmd run hygiene:smoke` 通过:
+  - `hitCount=0`
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 已刷新真实 latest:
+  - 10 个 refresh 步骤全部 `pass`
+  - `round-deposition.passed=true`
+  - `round-deposition.passCount=54`
+  - `round-deposition.failCount=0`
+  - `latest-form-index-owner-repair-actions=pass`
+  - `handoff-owner-repair-actions=pass`
+  - `latestFormIndexOwnerRepairSummary` 和 `handoffOwnerRepairSummary` 均记录商务、商务/投放 owner repair 附件。
+- `npm.cmd run acceptance` 通过,npm pack / workspace install smoke / MCP smoke 均正常。
+
+### 当前边界
+
+- 本轮完成的是 owner 级修复附件的沉淀门禁,不证明业务效果。
+- 当前真实业务证明缺口仍为 5 个,`optimization-status.complete=false`。
+
+## 2026-06-07 修复清单纳入证据台账
+
+### 问题
+
+- 最新提号表单索引、负责人行动文件和 handoff 已经能指向 `intake-readiness-repair-actions.csv` 与 `video-resource-repair-actions.csv`。
+- 但 `evidence-index` 之前只把这些修复清单折叠在 latest-form-index 或 readiness 产物里,没有独立证据条目。
+- 这会让下一轮 AI 或商务复盘时看得到“有入口”,但在证据台账汇总里看不清“具体有多少条修复动作、属于哪类补证执行材料”。
+
+### 本轮修正
+
+- 更新 `scripts/evidence-index.js`:
+  - 新增 `intake-repair-actions` 分类。
+  - 新增 `video-resource-repair-actions` 分类。
+  - 读取对应 CSV 并统计动作数。
+  - 证明等级固定为 `smoke_or_local`,只表示补证执行清单存在,不升级为真实业务证明。
+- 更新 `scripts/evidence-index-smoke.js`:
+  - fixture 中新增两份 repair action CSV。
+  - 断言 evidence index 能识别两类 repair actions。
+  - 断言历史/复核修复动作数为 2,视频资源修复动作数为 1。
+
+### 已验证
+
+- `node --check scripts/evidence-index.js` 通过。
+- `node --check scripts/evidence-index-smoke.js` 通过。
+- `npm.cmd run evidence:index:smoke` 通过:
+  - `total=30`
+  - `smoke_or_local=16`
+  - `not_business_proof=9`
+  - `real_evidence=5`
+- `npm.cmd run round:refresh:smoke` 通过。
+- `npm.cmd run hygiene:smoke` 通过:
+  - `hitCount=0`
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 已刷新真实 latest:
+  - 10 个 refresh 步骤全部 `pass`
+  - `evidence-index.total=154`
+  - `evidence-index.smoke_or_local=28`
+  - `evidence-index.not_business_proof=121`
+  - `evidence-index.real_evidence=5`
+  - `intake-repair-actions=1`
+  - `video-resource-repair-actions=1`
+  - `round-deposition.passed=true`
+  - `round-deposition.passCount=54`
+  - `round-deposition.failCount=0`
+- `npm.cmd run acceptance` 通过,npm pack / workspace install smoke / MCP smoke / provider mock 均正常。
+
+### 当前边界
+
+- 这轮提升的是证据台账对商务补证执行材料的可见性,不证明客户效果或命中率提升。
+- 当前 `proof-gap-closure.openCount=5`,真实历史 Brief、真实视频资源、人工复核标注、真实视频 A/B、真实 live/provider 长跑证明仍未关闭。
+- 当前 `optimization-status.complete=false`。
+
+## 2026-06-07 商务执行总表
+
+### 问题
+
+- 最新补证表单、负责人行动文件、修复清单、proof-gap closure 和证据台账已经存在,但商务执行时仍需要在多个入口之间跳转。
+- 下一轮 AI 也需要同时读取 latest form、business-proof next actions、repair CSV 和 handoff,才能确认当前应补什么、谁负责、验收看什么。
+
+### 本轮修正
+
+- 新增 `scripts/business-execution-index.js`:
+  - 汇总负责人行动、负责人 CSV/Markdown、修复清单、最新补证表单、最新候选名单和交接摘要。
+  - 输出 `business-execution-index-summary.json`、`business-execution-index.md`、`business-execution-index.csv`。
+  - 每行保留负责人、类型、标题、优先级、状态、主文件、修复清单、动作数、命令、预期产物、验收标准和边界。
+  - `directCustomerProof=false`,证明等级固定为 `smoke_or_local`。
+- 新增 `scripts/business-execution-index-smoke.js`,覆盖负责人行动、修复清单和关键文件行。
+- 更新 `package.json`:新增 `business-execution:index` 和 `business-execution:index:smoke`。
+- 更新 `scripts/refresh-round-artifacts.js`:`round:refresh` 增加 `business-execution-index` 步骤。
+- 更新 `scripts/evidence-index.js` 和 smoke:新增 `business-execution-index` 证据分类。
+- 更新 `scripts/generate-optimization-handoff.js` 和 smoke:handoff 新增“商务执行总表”入口。
+- 更新 `scripts/acceptance.js`:发布前验收纳入 `business-execution:index:smoke`。
+
+### 已验证
+
+- `node --check scripts/business-execution-index.js` 通过。
+- `node --check scripts/business-execution-index-smoke.js` 通过。
+- `node --check scripts/refresh-round-artifacts.js` 通过。
+- `node --check scripts/generate-optimization-handoff.js` 通过。
+- `node --check scripts/evidence-index.js` 通过。
+- `npm.cmd run business-execution:index:smoke` 通过:`rowCount=8`,`repairRowCount=2`。
+- `npm.cmd run evidence:index:smoke` 通过:`total=31`,`smoke_or_local=17`。
+- `npm.cmd run round:refresh:smoke` 通过,新增 `business-execution-index:pass`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 已刷新真实 latest:
+  - `business-execution-index.passed=true`
+  - `business-execution-index.rowCount=11`
+  - `business-execution-index.actionRowCount=5`
+  - `business-execution-index.repairRowCount=3`
+  - `business-execution-index.proofOpenCount=5`
+  - `business-execution-index.directCustomerProof=false`
+  - `evidence-index.total=155`
+  - `evidence-index.smoke_or_local=29`
+  - `evidence-index.not_business_proof=121`
+  - `evidence-index.real_evidence=5`
+- `npm.cmd run handoff:summary:smoke` 通过。
+- `npm.cmd run hygiene:smoke` 通过:`hitCount=0`。
+- `npm.cmd run acceptance` 通过,npm pack / workspace install smoke / MCP smoke / provider mock 均正常。
+
+### 当前边界
+
+- 这轮提升的是商务补证执行入口聚合,不证明客户效果或命中率提升。
+- 当前 `proof-gap-closure.openCount=5`,真实历史 Brief、真实视频资源、人工复核标注、真实视频 A/B、真实 live/provider 长跑证明仍未关闭。
+- 当前 `optimization-status.complete=false`。
+## 2026-06-07 商务执行总表纳入每轮沉淀审计
+
+### 问题
+
+- `business-execution-index` 已经能把负责人行动、修复清单、关键文件和证明边界汇总成商务可执行总表,但上一版 `round-deposition` 还没有把它作为每轮沉淀的硬门槛。
+- 如果后续刷新时 handoff 漏掉商务执行总表入口,或者总表没有保留 `directCustomerProof=false`、`proofOpenCount`、负责人行动/修复清单/关键文件三类行,旧审计不会拦截。
+- `generate-optimization-handoff` 在临时 `outputsDir` 场景下仍可能读取根目录 `outputs` 的旧产物,导致 smoke 刷新和真实刷新看到的执行总表不一致。
+
+### 本轮修正
+
+- 更新 `scripts/round-deposition-audit.js`:新增 `businessExecutionIndexPath` 输入,并新增 `business-execution-index-summary`、`business-execution-index-passed`、`handoff-business-execution-index` 三个检查项。
+- 更新 `scripts/round-deposition-audit.js`:`observed` 增加 `businessExecutionIndexPassed`、`businessExecutionIndexRowCount`、`businessExecutionIndexActionRowCount`、`businessExecutionIndexRepairRowCount`、`businessExecutionIndexProofOpenCount`、`businessExecutionIndexDirectCustomerProof`。
+- 更新 `scripts/round-deposition-smoke.js`:新增 `business-execution-index-latest` 夹具,覆盖负责人行动、修复清单、关键文件三类行,并断言报告展示 `businessExecutionIndex.rowCount` 和 `businessExecutionIndex.directCustomerProof`。
+- 更新 `scripts/generate-optimization-handoff.js`:`latestOutputDir` 改为尊重传入的 `outputsDir`,并新增 `artifactPath`、`artifactRef`,保证根目录相对产物和临时绝对路径产物读取一致。
+
+### 已验证
+
+- `node --check scripts/round-deposition-audit.js` 通过。
+- `node --check scripts/round-deposition-smoke.js` 通过。
+- `node --check scripts/generate-optimization-handoff.js` 通过。
+- `npm.cmd run round:deposition:smoke` 通过:`passCount=57`,`failCount=0`,`proofGapUnresolvedCount=5`。
+- `npm.cmd run round:refresh:smoke` 通过:11 个刷新步骤全部 `pass`,`roundPassCount=57`。
+- `npm.cmd run hygiene:smoke` 通过:`hitCount=0`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 已刷新真实 latest:`business-execution-index.passed=true`,`rowCount=11`,`actionRowCount=5`,`repairRowCount=3`,`proofOpenCount=5`,`directCustomerProof=false`,`round-deposition.passCount=57`,`round-deposition.failCount=0`。
+- `npm.cmd run acceptance` 通过,包含 package smoke、workspace install smoke、MCP smoke、provider mock、round refresh smoke 和 round deposition smoke。
+
+### 当前边界
+
+- 这轮提升的是商务补证执行路径和每轮沉淀审计闭环,不证明命中率提升或客户效果达标。
+- 当前真实业务证明缺口仍为 5 个:真实历史 Brief、真实视频资源、人工复核标注、真实视频 A/B、真实 live/provider 长跑证明。
+- 当前 `optimization-status.complete=false`,`proof-gap-closure.openCount=5`,不能宣布长期优化目标完成。
+## 2026-06-07 真实材料字段级补齐核对表
+
+### 问题
+
+- 当前最高优先级缺口仍是外部真实数据不足:真实历史 Brief 只有 1/5,历史数据、人工复核和视频资源表里仍有 example.com、示例、待补、待客户反馈等模板占位。
+- 之前 `intake-readiness-repair-actions.csv` 能给出“问题类型级”的修复动作,但商务实际补数时还需要逐行、逐字段知道该填哪里、当前值是什么、通过标准是什么。
+- 如果只保留抽象缺口,下一轮 AI 或商务容易在“历史数据不足”“视频资源不足”之间反复确认,补证效率不够高。
+
+### 本轮修正
+
+- 新增 `scripts/intake-field-checklist.js`:
+  - 读取 `data-intake-pack-latest/history-data-template.csv`、`data-intake-pack-latest/manual-review-template.csv` 和 `video-intake-pack-latest/video-resource-template.csv`。
+  - 输出 `intake-field-checklist-summary.json`、`intake-field-checklist.md`、`intake-field-checklist.csv`。
+  - 按负责人、区域、行号、字段、当前值、修复动作、验收标准生成逐格补齐清单。
+  - 保留 `proofLevel=smoke_or_local`、`directCustomerProof=false`,明确不证明客户效果。
+- 新增 `scripts/intake-field-checklist-smoke.js`:
+  - 覆盖最少 5 个真实历史 Brief 缺口、占位内容、缺客户选择、缺真实候选视频等场景。
+  - 验证输出不含 sessionToken 或模型 token。
+- 更新 `package.json`:
+  - 新增 `intake:field-checklist` 和 `intake:field-checklist:smoke`。
+- 更新 `scripts/refresh-round-artifacts.js`:
+  - `round:refresh` 新增 `intake-field-checklist` 步骤,且显式使用本轮 `outputRoot` 下的 data/video pack。
+- 更新 `scripts/refresh-round-artifacts-smoke.js`:
+  - 刷新步骤从 11 个增加为 12 个。
+  - 验证 `intake-field-checklist` 产物存在,并进入证据台账。
+- 更新 `scripts/evidence-index.js`:
+  - 新增 `intake-field-checklist` 证据分类,等级为 `smoke_or_local`。
+- 更新 `scripts/business-execution-index.js`:
+  - 商务执行总表新增“真实材料字段级补齐核对表”关键文件行,商务可以从总表直接跳到逐格补数 CSV。
+- 更新 `scripts/acceptance.js`:
+  - 发布验收纳入 `intake:field-checklist:smoke`。
+
+### 已验证
+
+- `node --check scripts/intake-field-checklist.js` 通过。
+- `node --check scripts/intake-field-checklist-smoke.js` 通过。
+- `node --check scripts/refresh-round-artifacts.js` 通过。
+- `node --check scripts/evidence-index.js` 通过。
+- `node --check scripts/business-execution-index.js` 通过。
+- `npm.cmd run intake:field-checklist:smoke` 通过:`itemCount=21`,`missingRequiredCount=8`,`placeholderCount=13`。
+- `npm.cmd run round:refresh:smoke` 通过:12 个刷新步骤全部 `pass`,字段级核对表被证据台账识别。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 已刷新真实 latest:
+  - `intake-field-checklist.passed=true`
+  - `intake-field-checklist.itemCount=25`
+  - `intake-field-checklist.missingRequiredCount=8`
+  - `intake-field-checklist.placeholderCount=17`
+  - `business-execution-index.rowCount=12`
+  - `evidence-index.total=156`
+  - `evidence-index.smoke_or_local=30`
+  - `round-deposition.passCount=57`
+  - `round-deposition.failCount=0`
+- `npm.cmd run acceptance` 通过,包含新增字段级核对表 smoke、round refresh smoke、workspace install smoke、MCP smoke 和 provider mock。
+
+### 当前边界
+
+- 这轮把“外部真实材料缺失”细化成商务可逐格补齐的执行表,但没有替客户填写真实 Brief、客户选择、真实视频 URL 或人工复核结论。
+- 当前 `optimization-status.complete=false`,`proof-gap-closure.openCount=5`。
+- 真实业务证明缺口仍为 5 个:真实历史 Brief、真实视频资源、人工复核标注、真实视频 A/B、真实 live/provider 长跑证明。
+
+## 2026-06-08 字段级补齐核对表纳入 handoff 和每轮沉淀审计
+
+### 问题
+
+- 上一轮已经生成 `intake-field-checklist`,但 handoff 和 round deposition 还需要把它作为硬性交接与沉淀项。
+- 如果交接摘要没有显式展示逐格补数 CSV,商务仍可能只看到抽象缺口,无法直接知道哪张表、哪一行、哪一列要补。
+- 如果每轮沉淀审计不检查该核对表,后续刷新时可能丢失字段级补齐入口而不报警。
+
+### 本轮修正
+
+- 更新 `scripts/generate-optimization-handoff.js`:
+  - 读取最新 `intake-field-checklist-summary.json`。
+  - 在 handoff summary 中新增 `intakeFieldChecklist`。
+  - 在 Markdown 报告中新增“真实材料字段级补齐核对表”区块。
+  - 下一轮命令新增 `npm run intake:field-checklist -- --data-pack outputs\data-intake-pack-latest --video-pack outputs\video-intake-pack-latest --output outputs\intake-field-checklist-latest`。
+- 更新 `scripts/generate-optimization-handoff-smoke.js`:
+  - 断言 handoff 必须记录 summary/report/csv 三个入口。
+  - 断言 `directCustomerProof=false`。
+  - 断言报告展示 `intake-field-checklist.csv`。
+- 更新 `scripts/round-deposition-audit.js`:
+  - 新增 `intakeFieldChecklistPath` 输入。
+  - 新增 `intake-field-checklist-summary`、`intake-field-checklist-passed`、`handoff-intake-field-checklist` 三个检查项。
+  - `observed` 新增 `intakeFieldChecklistPassed`、`intakeFieldChecklistItemCount`、`intakeFieldChecklistMissingRequiredCount`、`intakeFieldChecklistPlaceholderCount`、`intakeFieldChecklistDirectCustomerProof`。
+- 更新 `scripts/round-deposition-smoke.js`:
+  - 新增字段级核对表 fixture。
+  - 断言沉淀审计报告展示字段级核对表数量和 `directCustomerProof=false`。
+
+### 已验证
+
+- `node --check scripts/generate-optimization-handoff.js` 通过。
+- `node --check scripts/round-deposition-audit.js` 通过。
+- `node --check scripts/round-deposition-smoke.js` 通过。
+- `npm.cmd run handoff:summary:smoke` 通过。
+- `npm.cmd run round:deposition:smoke` 通过:`passCount=60`,`failCount=0`。
+- `npm.cmd run round:refresh:smoke` 通过:12 个刷新步骤全部 `pass`,`roundPassCount=60`。
+- `npm.cmd run hygiene:smoke` 通过:`hitCount=0`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 已刷新真实 latest:
+  - `intake-field-checklist.passed=true`
+  - `intake-field-checklist.itemCount=25`
+  - `intake-field-checklist.missingRequiredCount=8`
+  - `intake-field-checklist.placeholderCount=17`
+  - `business-execution-index.rowCount=12`
+  - `evidence-index.total=156`
+  - `evidence-index.smoke_or_local=30`
+  - `round-deposition.passCount=60`
+  - `round-deposition.failCount=0`
+  - `optimization-status.complete=false`
+  - `proof-gap-closure.openCount=5`
+- `npm.cmd run acceptance` 通过,包含字段级核对表 smoke、handoff、round refresh、round deposition、MCP、workspace install、npm pack 和 provider mock。
+
+### 当前边界
+
+- 这轮提升的是交接完整度和沉淀审计强度,不证明命中率提升、客户选中率提升或人工补号减少。
+- 当前 `optimization-status.complete=false`,长期优化目标还不能标记完成。
+- 当前 `proof-gap-closure.openCount=5`,仍需外部真实历史 Brief、真实视频资源、人工复核标注、真实视频 A/B 和真实 live/provider 长跑证明。
+
+## 2026-06-08 视频 A/B 真实证明缺失项结构化
+
+### 问题
+
+- `proof-gap-closure` 已经能阻止 sample、smoke 或缺少 `proofContext` 的视频 A/B 产物关闭真实证明缺口。
+- 但旧报告只在“当前证据”里展示 `mode=unknown`、`generatedBy=unknown` 等信息,商务或下一轮 AI 需要自己推断到底缺了哪些证明字段。
+- `scripts/proof-gap-closure-audit.js` 的用户可见文案存在历史编码显示问题,虽然产物可读,但维护时容易误改。
+
+### 本轮修正
+
+- 重写 `scripts/proof-gap-closure-audit.js` 的用户可见文案为可读中文。
+- 新增 `getVideoAbMissingRequirements(videoAb)`:
+  - 明确检查 `acceptance.passed=true`。
+  - 明确检查 `proofContext.mode=live`。
+  - 明确检查 `proofContext.collectionMode=live`。
+  - 明确检查 `proofContext.generatedBy=acceptance:video-ab`。
+  - 明确检查 `proofContext.requiresRuntimeCredential=true`。
+  - 明确检查 `proofContext.requiresVocSocialProvider=true`。
+  - 明确检查 `proofContext.requiresVideoAnalysisProvider=true`。
+  - 明确检查 `enhanced.provider.evidence.providerStatus=ok`。
+- `proof-gap-closure-summary.json` 的 `video-ab-live-proof` 行新增 `missingProofRequirements`。
+- `proof-gap-closure-report.md` 新增“缺失证明”列,让视频 A/B 为什么仍为 open 可以直接读出来。
+- 重写 `scripts/proof-gap-closure-smoke.js`:
+  - 保留 open、closed、fake video 三类场景。
+  - 新增断言:缺 `proofContext` 的视频 A/B 即使 `acceptance.passed=true`,也必须保持 open。
+  - 新增断言:报告必须包含“缺失证明”列。
+
+### 已验证
+
+- `node --check scripts\proof-gap-closure-audit.js` 通过。
+- `node --check scripts\proof-gap-closure-smoke.js` 通过。
+- `npm.cmd run proof-gap:closure:smoke` 通过:`openCount=5`,`closedCount=5`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过:
+  - `proof-gap-closure.complete=false`
+  - `proof-gap-closure.openCount=5`
+  - `video-ab-live-proof.status=open`
+  - `video-ab-live-proof.missingProofRequirements` 包含 `proofContext.mode=live`、`proofContext.collectionMode=live`、`proofContext.generatedBy=acceptance:video-ab`、`proofContext.requiresRuntimeCredential=true`、`proofContext.requiresVocSocialProvider=true`、`proofContext.requiresVideoAnalysisProvider=true`
+  - `round-deposition.passCount=60`
+  - `round-deposition.failCount=0`
+- `npm.cmd run hygiene:smoke` 通过:`hitCount=0`。
+- `npm.cmd run acceptance` 通过,包含 proof-gap closure smoke、round refresh smoke、round deposition smoke、MCP、workspace install、npm pack 和 provider mock。
+
+### 当前边界
+
+- 这轮只是把“视频 A/B 为什么还不能算真实证明”讲清楚并纳入结构化产物,不证明视频分析已经提升提号命中率。
+- 当前已有视频 A/B 产物仍缺真实 live proofContext,不能关闭 `video-ab-live-proof`。
+- 当前 `optimization-status.complete=false`,`proof-gap-closure.openCount=5`。
+
+## 2026-06-08 视频 A/B 缺失证明透传到行动队列
+
+### 问题
+
+- `proof-gap-closure` 已经结构化输出 `video-ab-live-proof.missingProofRequirements`。
+- 但商务补证下一步行动队列仍只展示 `mode=unknown`、`generatedBy=unknown` 等当前证据,技术/AI 打开负责人文件时还需要回到 closure 报告推断缺什么。
+- 这会让“真实视频 A/B 验收”动作不够可执行,容易再次把接口通过或 smoke 结果误会成真实 live proof。
+
+### 本轮修正
+
+- 更新 `scripts/business-proof-next-actions.js`:
+  - `actionFromClosureRow` 保留 `missingProofRequirements`。
+  - closure action 的 `next` 字段自动追加“缺失证明:...”。
+  - action 合并时通过 `mergeList` 保留缺失证明列表,避免技术动作合并后丢失。
+- 新增 `scripts/business-proof-next-actions-missing-proof-smoke.js`:
+  - 构造只包含 `video-ab-live-proof` 的 proof-gap closure fixture。
+  - 验证 summary action 保留 `missingProofRequirements`。
+  - 验证总报告、技术/AI 负责人文件和 CSV 都能看到 `proofContext.mode=live`、`proofContext.collectionMode=live`、`proofContext.generatedBy=acceptance:video-ab`。
+  - 验证输出不含 sessionToken 或模型 token。
+- 更新 `package.json`:
+  - 新增 `business-proof:next-actions:missing-proof:smoke`。
+- 更新 `scripts/acceptance.js`:
+  - 默认发布验收纳入该 smoke。
+
+### 已验证
+
+- `node --check scripts\business-proof-next-actions.js` 通过。
+- `node --check scripts\business-proof-next-actions-missing-proof-smoke.js` 通过。
+- `npm.cmd run business-proof:next-actions:missing-proof:smoke` 通过:`missingCount=3`。
+- `npm.cmd run business-proof:next-actions:smoke` 通过。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过:
+  - `business-proof-next-actions.actionCount=5`
+  - `gap-video-ab-live-proof.missingProofRequirements` 包含 6 个缺失证明项。
+  - `business-proof-next-actions-report.md`、`by-owner/tech-ai.md`、`business-proof-next-actions.csv` 均展示 `proofContext.mode=live`、`proofContext.generatedBy=acceptance:video-ab` 等缺失证明。
+  - `round-deposition.passCount=60`
+  - `round-deposition.failCount=0`
+- `npm.cmd run hygiene:smoke` 通过:`hitCount=0`。
+- `npm.cmd run acceptance` 通过,包含新增 missing-proof smoke、round refresh smoke、round deposition smoke、MCP、workspace install、npm pack 和 provider mock。
+
+### 当前边界
+
+- 这轮提升的是补证动作可执行性:技术/AI 可以直接从行动队列看到视频 A/B 还缺哪些真实 live proof 字段。
+- 它仍不证明视频分析提升了命中率;只有真实 `acceptance:video-ab` 产物带完整 live `proofContext` 并通过 closure 审计,才能关闭 `video-ab-live-proof`。
+- 当前 `optimization-status.complete=false`,`proof-gap-closure.openCount=5`。
+
+## 2026-06-08 视频 A/B 缺失证明透传到商务执行总表
+
+### 问题
+
+- 上一轮已经把 `video-ab-live-proof.missingProofRequirements` 透传到下一步行动队列、技术/AI 负责人文件和 CSV。
+- 但商务执行总表仍只展示“真实视频 A/B 验收”的验收标准,没有展开 `proofContext.mode=live`、`proofContext.generatedBy=acceptance:video-ab` 等缺失证明。
+- 下一轮 AI 或商务如果只打开商务执行总表,仍需要再跳到负责人文件或 closure 报告才能知道视频 A/B 缺口的具体原因。
+
+### 本轮修正
+
+- 更新 `scripts/business-execution-index.js`:
+  - 负责人行动行读取 action 的 `missingProofRequirements`。
+  - 通过 `appendMissingProofRequirements` 把缺失证明追加进总表验收标准。
+  - Markdown 和 CSV 都沿用现有“验收标准”字段展示,不额外改变总表列结构。
+- 更新 `scripts/business-execution-index-smoke.js`:
+  - fixture 中新增视频 A/B action 的 `missingProofRequirements`。
+  - 断言商务执行总表中视频 A/B 行必须展示 `proofContext.mode=live`。
+
+### 已验证
+
+- `node --check scripts\business-execution-index.js` 通过。
+- `node --check scripts\business-execution-index-smoke.js` 通过。
+- `npm.cmd run business-execution:index:smoke` 通过:`rowCount=8`,`repairRowCount=2`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过:
+  - `business-execution-index.passed=true`
+  - `business-execution-index.rowCount=12`
+  - `proofOpenCount=5`
+  - `business-execution-index.md` 和 `business-execution-index.csv` 的“真实视频 A/B 验收”行均展示 `proofContext.mode=live`、`proofContext.collectionMode=live`、`proofContext.generatedBy=acceptance:video-ab`、`proofContext.requiresRuntimeCredential=true`、`proofContext.requiresVocSocialProvider=true`、`proofContext.requiresVideoAnalysisProvider=true`。
+  - `round-deposition.passCount=60`
+  - `round-deposition.failCount=0`
+- `npm.cmd run hygiene:smoke` 通过:`hitCount=0`。
+- `npm.cmd run acceptance` 通过,包含 business execution smoke、missing-proof smoke、round refresh smoke、round deposition smoke、MCP、workspace install、npm pack 和 provider mock。
+
+### 当前边界
+
+- 这轮只提升商务执行总表的信息完整度,不证明视频 A/B 已经真实提升提号效果。
+- 当前视频 A/B 缺口仍为 open;必须跑出真实 live `acceptance:video-ab` 产物并通过 closure 审计后才能关闭。
+- 当前 `optimization-status.complete=false`,`proof-gap-closure.openCount=5`。
+
+## 2026-06-08 proof-gap closure 纳入 handoff 正文
+
+### 问题
+
+- `proof-gap-closure`、下一步行动队列、负责人文件和商务执行总表已经能展示视频 A/B 缺失证明。
+- 但 `optimization-handoff` 之前没有直接记录 closure 明细,只提供了相关入口链接。
+- 下一轮 AI 如果只读 handoff 正文,仍可能不知道当前 `video-ab-live-proof` 为什么是 open,以及还缺哪些 live proof 字段。
+
+### 本轮修正
+
+- 更新 `scripts/generate-optimization-handoff.js`:
+  - 新增读取 `proof-gap-closure-summary.json`。
+  - handoff summary 新增 `proofGapClosure` 字段,包含 `summary`、`report`、`complete`、`openCount`、`closedCount`、`intakeReady` 和标准化 `rows`。
+  - handoff report 新增“真实证明缺口关闭状态”区块。
+  - open 缺口表直接展示当前证据、缺失证明、验收标准和下一步命令。
+- 更新 `scripts/generate-optimization-handoff-smoke.js`:
+  - 断言 handoff 必须包含 proof-gap closure summary/report。
+  - 断言 handoff 必须暴露 `openCount`。
+  - 断言 `video-ab-live-proof` 必须展示 `proofContext.mode=live`。
+
+### 已验证
+
+- `node --check scripts\generate-optimization-handoff.js` 通过。
+- `node --check scripts\generate-optimization-handoff-smoke.js` 通过。
+- `npm.cmd run handoff:summary:smoke` 通过。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过:
+  - `optimization-handoff.proofGapClosure.openCount=5`
+  - `optimization-handoff.proofGapClosure.rows=6`
+  - `video-ab-live-proof.missingProofRequirements` 包含 6 个 live proof 缺失项。
+  - `optimization-handoff-report.md` 开头新增“真实证明缺口关闭状态”,直接展示 `proofContext.mode=live`、`proofContext.collectionMode=live`、`proofContext.generatedBy=acceptance:video-ab` 等缺失证明。
+  - `round-deposition.passCount=60`
+  - `round-deposition.failCount=0`
+- `npm.cmd run hygiene:smoke` 通过:`hitCount=0`。
+- `npm.cmd run acceptance` 通过,包含 handoff smoke、business execution smoke、missing-proof smoke、round refresh smoke、round deposition smoke、MCP、workspace install、npm pack 和 provider mock。
+
+### 当前边界
+
+- 这轮提升的是交接摘要的信息完整度,不证明任何真实业务效果。
+- 当前所有入口都更清楚地指向同一事实:视频 A/B 仍缺真实 live proofContext,不能作为提号率提升证明。
+- 当前 `optimization-status.complete=false`,`proof-gap-closure.openCount=5`。
+
+## 2026-06-08 handoff proof-gap closure 纳入每轮沉淀审计
+
+### 问题
+
+- 上一轮已经把 `proofGapClosure` 写入 `optimization-handoff-summary.json` 和 handoff 正文。
+- 但 `round-deposition` 只审 standalone 的 `proof-gap-closure-summary.json`,没有独立校验 handoff 内部的 `proofGapClosure` 是否完整。
+- 这会留下一个薄弱点:下一轮 AI 或商务如果只打开 handoff,可能看不到 `video-ab-live-proof` 仍缺哪些 live proof 字段,进而误把接口 200、sample 或 smoke 当作业务证明。
+
+### 本轮修正
+
+- 更新 `scripts/round-deposition-audit.js`:
+  - 新增 `handoff-proof-gap-closure` 检查项。
+  - 新增 `hasHandoffProofGapClosure(handoff, closure)`,校验 handoff 中的 `proofGapClosure.complete=false`、`openCount`、`closedCount`、`intakeReady`、summary/report 路径和 rows 与 closure summary 对齐。
+  - 强制校验 handoff 中 `video-ab-live-proof.missingProofRequirements` 必须包含 6 个 live proof 缺失项:`proofContext.mode=live`、`proofContext.collectionMode=live`、`proofContext.generatedBy=acceptance:video-ab`、`proofContext.requiresRuntimeCredential=true`、`proofContext.requiresVocSocialProvider=true`、`proofContext.requiresVideoAnalysisProvider=true`。
+  - round-deposition observed 新增 `handoffProofGapClosureOpenCount`、`handoffProofGapClosureRows`、`handoffVideoAbMissingProofCount`。
+  - round-deposition report 新增 handoff proof-gap closure 的观测行。
+- 更新 `scripts/round-deposition-smoke.js`:
+  - handoff fixture 新增 `proofGapClosure`。
+  - proof-gap closure fixture 补齐 `video-ab-live-proof` 和 `missingProofRequirements`。
+  - 新增断言:必须存在 `handoff-proof-gap-closure` 检查项,且 report/summary 必须展示 handoff closure openCount、rows 和视频 A/B 缺失证明数。
+
+### 已验证
+
+- `node --check scripts\round-deposition-audit.js` 通过。
+- `node --check scripts\round-deposition-smoke.js` 通过。
+- `npm.cmd run round:deposition:smoke` 通过:
+  - `passCount=61`
+  - `failCount=0`
+  - `proofGapUnresolvedCount=5`
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过:
+  - `round-deposition.passCount=61`
+  - `round-deposition.failCount=0`
+  - `round-deposition.warnCount=0`
+  - `proof-gap-closure.openCount=5`
+  - `optimization-handoff.complete=false`
+  - `optimization-status.complete=false`
+- `npm.cmd run acceptance` 通过,包含 round refresh smoke、round deposition smoke、MCP、workspace install、npm pack 和 provider mock。
+- `npm.cmd run hygiene:smoke` 通过:
+  - `hitCount=0`
+
+### 当前边界
+
+- 这一轮提升的是每轮沉淀审计的防漏能力,不证明命中率提升、客户选中率提升或人工补号减少。
+- 当前 `handoff.proofGapClosure.openCount=5`,`video-ab-live-proof` 仍缺 6 个真实 live proof 字段。
+- 当前 `optimization-status.complete=false`,长期优化目标仍不能标记完成。
+
+## 2026-06-08 反馈二轮闭环审计纳入每轮沉淀
+
+### 问题
+
+- 长期优化计划里的 P0 缺口要求:商务标注“拉黑、跑偏、调性不符、主页质感不符、参考账号不像”等负反馈后,第二轮必须自动剔除或降权。
+- 原有 `feedback:roundtrip` 能证明 sample 工作流里记忆会影响第二轮名单,但没有独立沉淀“负反馈账号是否泄漏进第二轮可发客户名单”的审计产物。
+- 如果没有独立产物,下一轮 AI 或商务只能从 smoke 推断闭环能力,无法在 evidence、handoff、round-deposition 中看到当前真实反馈闭环是否已经具备客户证明。
+
+### 本轮修正
+
+- 新增 `scripts/feedback-loop-closure-audit.js`:
+  - 输入 `--feedback <反馈CSV>`、`--memory <记忆JSON>`、`--result <第二轮tihao-sourcing-result.json>`。
+  - 输出 `feedback-loop-closure-summary.json`、`feedback-loop-closure-report.md`、`feedback-loop-closure.csv`。
+  - 统计负反馈记录数、拉黑/降权账号数、写入记忆账号数、第二轮候选数、泄漏进第二轮可发名单数、团队规则建议数。
+  - 默认没有真实输入时输出 `passed=false`、`complete=false`、`directCustomerProof=false` 和缺失输入清单。
+  - 即使 sample 二轮闭环通过,也只标记 `proofLevel=smoke_or_local`,不作为客户效果证明。
+- 新增 `scripts/feedback-loop-closure-smoke.js`:
+  - 构造 4 条负反馈,其中 3 条同类主页质感问题触发团队规则建议。
+  - 导入 feedback memory 后重跑第二轮 sample 提号。
+  - 审计确认负反馈账号写入记忆,且 `leakedCount=0`。
+  - 明确断言 `directCustomerProof=false`。
+- 更新 `package.json`:
+  - 新增 `feedback:closure`。
+  - 新增 `feedback:closure:smoke`。
+- 更新 `scripts/acceptance.js`:
+  - 默认验收纳入 `feedback:closure:smoke`。
+- 更新 `scripts/refresh-round-artifacts.js` 和 `scripts/refresh-round-artifacts-smoke.js`:
+  - 每轮刷新新增 `feedback-loop-closure` 步骤。
+  - refresh 步骤数从 12 增加到 13。
+- 更新 `scripts/evidence-index.js`:
+  - 新增 `feedback-loop-closure` 分类。
+  - 根据 `directCustomerProof`、`passed` 区分 `real_evidence`、`smoke_or_local`、`not_business_proof`。
+- 更新 `scripts/generate-optimization-handoff.js` 和 smoke:
+  - handoff summary 新增 `feedbackLoopClosure`。
+  - handoff report 新增“反馈二轮闭环审计”区块。
+  - nextCommands 新增 `feedback:closure` 命令。
+  - 找不到产物时也明确提示当前 `directCustomerProof=false`。
+- 更新 `scripts/round-deposition-audit.js` 和 smoke:
+  - 新增 `feedback-loop-closure-summary`、`feedback-loop-closure-boundary`、`handoff-feedback-loop-closure` 三个检查项。
+  - round-deposition observed 新增反馈闭环通过状态、客户证明边界、负反馈数和泄漏数。
+- 更新 `scripts/optimization-status-audit.js`:
+  - `feedback-roundtrip` 能力判断纳入 `feedback:closure`、`feedback:closure:smoke`、`directCustomerProof` 和 `leakedCount` 口径。
+
+### 已验证
+
+- `node --check scripts\feedback-loop-closure-audit.js` 通过。
+- `node --check scripts\feedback-loop-closure-smoke.js` 通过。
+- `node --check scripts\refresh-round-artifacts.js` 通过。
+- `node --check scripts\generate-optimization-handoff.js` 通过。
+- `node --check scripts\round-deposition-audit.js` 通过。
+- `npm.cmd run feedback:closure:smoke` 通过:
+  - `negativeFeedbackCount=4`
+  - `leakedCount=0`
+  - `directCustomerProof=false`
+- `npm.cmd run optimization:status:smoke` 通过。
+- `npm.cmd run handoff:summary:smoke` 通过。
+- `npm.cmd run round:refresh:smoke` 通过:
+  - `steps=13`
+  - `feedback-loop-closure.directCustomerProof=false`
+  - `roundPassCount=64`
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过:
+  - `feedback-loop-closure-latest.passed=false`
+  - `feedback-loop-closure-latest.complete=false`
+  - `feedback-loop-closure-latest.directCustomerProof=false`
+  - `feedback-loop-closure-latest.negativeFeedbackCount=0`
+  - `feedback-loop-closure-latest.leakedCount=0`
+  - `evidence-index.total=158`
+  - `evidence-index.smoke_or_local=31`
+  - `evidence-index.not_business_proof=122`
+  - `round-deposition.passCount=64`
+  - `round-deposition.failCount=0`
+- `npm.cmd run acceptance` 通过,包含 feedback closure smoke、round refresh smoke、round deposition smoke、MCP、workspace install、npm pack 和 provider mock。
+- `npm.cmd run hygiene:smoke` 通过:
+  - `hitCount=0`
+
+### 当前边界
+
+- 这一轮证明的是“反馈二轮剔除/降权闭环”具备可审计入口,不证明真实客户选中率提升。
+- 当前还没有传入真实反馈 CSV、真实 preference memory 和真实第二轮 `tihao-sourcing-result.json`,所以 `feedback-loop-closure-latest.passed=false` 且 `directCustomerProof=false` 是正确状态。
+- 当前 `optimization-status.complete=false`,`proof-gap-closure.openCount=5`,长期优化目标仍不能标记完成。
+## 2026-06-08 主页近期内容证据就绪审计纳入每轮刷新
+
+### 问题
+
+- 长期优化计划 P0 要求主页近期内容 provider 稳定覆盖最近 10/20 篇内容、封面、标题、互动、发布时间和风险信号。
+- 之前已有 `homepage-evidence-provider.js` 和 `homepage:smoke`,可以验证 provider 请求契约、成功挂载和鉴权失败 fallback。
+- 但缺少一个可沉淀的 readiness 审计产物,用来判断当前候选名单里的主页证据是否真的足以支撑强推荐复核。
+- 没有该审计时,容易把“有主页链接”“接口 200”或“fallback 轻量判断”误当成真实主页近期内容证据。
+
+### 本轮修正
+
+- 新增 `scripts/homepage-evidence-readiness-audit.js`:
+  - 支持输入 `tihao-sourcing-result.json`、主页证据 JSON 或 CSV。
+  - 输出 `homepage-evidence-readiness-summary.json`、`homepage-evidence-readiness-report.md`、`homepage-evidence-repair-actions.csv`。
+  - 审计 provider/path 证据、最近内容窗口、标题/正文、封面、发布时间、互动字段、风险复核和敏感信息泄露。
+  - 默认非 strict 模式会写出修复清单,不阻断每轮刷新;`--strict` 下证据不足会返回非零。
+  - 始终保留 `directCustomerProof=false`,避免把主页证据 ready 误报为客户效果证明。
+- 新增 `scripts/homepage-evidence-readiness-smoke.js`,覆盖 ready、weak 和 strict weak 三种场景。
+- `package.json` 新增 `homepage:readiness` 和 `homepage:readiness:smoke`。
+- `acceptance`、`round:refresh`、`evidence:index`、`handoff:summary`、`optimization:status`、`round:deposition` 均已纳入该审计。
+
+### 已验证
+
+- `node --check scripts\homepage-evidence-readiness-audit.js` 通过。
+- `node --check scripts\homepage-evidence-readiness-smoke.js` 通过。
+- `node --check scripts\refresh-round-artifacts.js` 通过。
+- `node --check scripts\round-deposition-audit.js` 通过。
+- `npm.cmd run homepage:readiness:smoke` 通过。
+- `npm.cmd run evidence:index:smoke` 通过:`total=34`,`smoke_or_local=19`,`not_business_proof=10`,`real_evidence=5`。
+- `npm.cmd run handoff:summary:smoke` 通过:`readyCapabilities=29`,`readyNotProven=2`,`externalBlockers=2`。
+- `npm.cmd run optimization:status:smoke` 通过。
+- `npm.cmd run round:refresh:smoke` 通过:`steps=14`,`roundPassCount=69`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过:
+  - `homepage-evidence-readiness-latest.ready=false`
+  - `homepage-evidence-readiness-latest.proofLevel=not_business_proof`
+  - `homepage-evidence-readiness-latest.failureCount=9`
+  - `homepage-evidence-readiness-latest.repairActionCount=9`
+  - `optimization-status.complete=false`
+  - `proof-gap-closure.openCount=5`
+  - `evidence-index.total=161`
+  - `round-deposition.passCount=69`
+  - `round-deposition.failCount=0`
+- `npm.cmd run acceptance` 通过,包含 homepage readiness smoke、round refresh smoke、round deposition smoke、MCP、workspace install、npm pack 和 provider mock。
+- `npm.cmd run hygiene:smoke` 通过:`hitCount=0`。
+
+### 当前边界
+
+- 本轮提升的是主页近期内容证据的审计和交接能力,不证明客户命中率提升。
+- 当前真实 outputs 中没有传入 provider/path 级主页近期内容证据,所以 `homepage-evidence-readiness-latest.ready=false` 是正确状态。
+- 当前仍不能把主页链接、fallback、sample、smoke 或接口 200 当作强推荐证据。
+- 当前 `optimization-status.complete=false`,`proof-gap-closure.openCount=5`,长期优化目标仍不能标记完成。
+- 追加日志:`docs/tihao-experience-implementation-log-20260608-business-execution-field-recheck-rows.md`。
+
+## 2026-06-08 商务执行总表展开字段负责人和复验命令
+
+### 本轮修正
+
+- `business-execution-index` 不再只展示字段核对表入口,而是把 `intake-field-checklist` 中的 `ownerGroups` 展开为 `字段补齐负责人` 行。
+- `business-execution-index` 已把 `recheckCommands` 展开为 `补齐后复验命令` 行,商务补完材料后可以直接看到复验命令。
+- 执行总表统一补齐“不证明客户效果”边界,避免“不得宣称效果”等弱表达被误读为已完成业务证明。
+- `round-deposition` 守卫新增对 `fieldOwnerRowCount` 和 `recheckCommandRowCount` 的观察与校验,防止后续刷新丢失这两类行。
+
+### 已验证
+
+- `npm.cmd run business-execution:index:smoke` 通过。
+- `npm.cmd run round:deposition:smoke` 通过,`passCount=74`,`failCount=0`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过。
+
+### 当前边界
+
+- 当前只是让商务执行总表更可分派、更可复验,不证明命中率或客户选中率提升。
+- 当前 `proof-gap-closure.openCount=5`,`optimization-status.complete=false`。
+- 追加日志:`docs/tihao-experience-implementation-log-20260608-latest-form-business-execution-entry.md`。
+
+## 2026-06-08 最新提号表单索引加入商务执行总表入口
+
+### 本轮修正
+
+- `latest-form:index` 新增 `businessExecutionIndex`,指向 `business-execution-index.md/csv/summary`。
+- 最新提号表单索引第一屏新增“商务补证执行总表”,展示总行数、字段负责人行数和复验命令行数。
+- `round-deposition` 新增检查项 `latest-form-index-business-execution-index`,确保最新索引不会丢失商务执行总表入口。
+
+### 已验证
+
+- `npm.cmd run latest-form:index:smoke` 通过。
+- `npm.cmd run round:deposition:smoke` 通过,`passCount=75`,`failCount=0`。
+
+### 当前边界
+
+- 本轮只提升最新表单入口的可执行性,不证明客户效果。
+- 当前仍需真实历史 Brief、真实视频资源、真实视频 A/B、live/provider 长跑和客户效果审计关闭证明缺口。
+
+## 2026-06-08 最新提号表单索引加入商务填表执行视图
+
+### 本轮修正
+
+- `latest-form:index` 新增 `businessFillGuide`,把字段补齐负责人分组转换为商务可直接使用的填表执行视图。
+- 最新表单索引 Markdown 新增“商务填表执行视图”,按 history、video、review 展示填表文件、优先 FIELD、问题数、填表动作、补齐后复验命令、验收标准和证明边界。
+- `latest-form-index:smoke` 新增对 `businessFillGuide` 的结构、复验命令和“不证明客户效果”边界校验。
+- `round-deposition` 新增检查项 `latest-form-index-business-fill-guide`,防止最新提号表单索引退化为只给文件路径、不告诉商务如何填和如何复验。
+
+### 已验证
+
+- `npm.cmd run latest-form:index:smoke` 通过。
+- `npm.cmd run round:deposition:smoke` 通过,`passCount=76`,`failCount=0`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,真实 outputs 的 `round-deposition.passCount=76`,`failCount=0`。
+- `npm.cmd run hygiene:smoke` 通过,`hitCount=0`。
+- `npm.cmd run acceptance` 通过。
+
+### 当前边界
+
+- 本轮提升的是商务填表和复验的可执行性,不证明客户命中率、客户选中率或视频分析提升提号率。
+- 当前 `proof-gap-closure.openCount=5`,`optimization-status.complete=false`。
+- 追加日志:`docs/tihao-experience-implementation-log-20260608-business-fill-guide.md`。
+
+## 2026-06-08 交接摘要承接商务填表执行视图
+
+### 本轮修正
+
+- `handoff:summary` 已从最新提号表单索引读取 `businessFillGuide`,并写入 `optimization-handoff-summary.json` 的 `latestFormIndex.businessFillGuide`。
+- `optimization-handoff-report.md` 的“最新提号表单索引”段落新增“商务填表执行视图”,展示 history、video、review 三类填表任务的文件、优先 FIELD、复验命令、验收标准和证明边界。
+- `handoff:summary:smoke` 新增对 `businessFillGuide` 的结构、真实模板文件、视频资源复验命令和证明边界断言。
+- `round-deposition` 新增检查项 `handoff-latest-form-business-fill-guide`,确保下一轮 AI 从交接摘要即可看到商务填表顺序。
+
+### 已验证
+
+- `npm.cmd run handoff:summary:smoke` 通过。
+- `npm.cmd run round:deposition:smoke` 通过,`passCount=77`,`failCount=0`。
+- `npm.cmd run hygiene:smoke` 通过,`hitCount=0`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,真实 outputs 的 `round-deposition.passCount=77`,`failCount=0`。
+- `npm.cmd run acceptance` 通过。
+
+### 当前边界
+
+- 本轮提升的是交接摘要对商务填表流程的承接能力,不证明客户命中率、客户选中率或视频分析提升提号率。
+- 当前 `proof-gap-closure.openCount=5`,`optimization-status.complete=false`。
+- 追加日志:`docs/tihao-experience-implementation-log-20260608-handoff-business-fill-guide.md`。
+
+## 2026-06-08 人工复核标签与归因指南
+
+### 本轮修正
+
+- 新增 `manual-review:label-guide`,生成 `outputs/manual-review-label-guide-latest/manual-review-label-guide.md/csv/summary.json`。
+- 指南沉淀 10 个复核/客户结果标签、8 类负样本归因类型,并明确负样本和客户拒绝样本必须补归因与反馈原因。
+- `latest-form:index` 新增 `manualReviewLabelGuide`,商务打开最新提号表单索引即可看到复核标签指南入口。
+- `handoff:summary` 承接 `latestFormIndex.manualReviewLabelGuide`,下一轮 AI 能从交接摘要拿到复核标签和归因口径。
+- `round:refresh` 新增 `manual-review-label-guide` 步骤,`round-deposition` 新增指南本体、最新表单索引和交接摘要三类守卫。
+
+### 已验证
+
+- `npm.cmd run manual-review:label-guide:smoke` 通过。
+- `npm.cmd run latest-form:index:smoke` 通过。
+- `npm.cmd run handoff:summary:smoke` 通过。
+- `npm.cmd run round:deposition:smoke` 通过,`passCount=81`,`failCount=0`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,真实 outputs 的 `round-deposition.passCount=81`,`failCount=0`。
+
+### 当前边界
+
+- 本轮提升的是商务复核标注一致性和负样本可归因性,不证明客户效果完成。
+- 当前 `proof-gap-closure.openCount=5`,`optimization-status.complete=false`。
+
+## 2026-06-08 技能参考与人工复核交接文档中文化
+
+### 本轮修正
+
+- 重写 `skills/tihao-creator-sourcing/references/user-workflow.md`,把提号 SOP、参考视频补证、豆包视频分析、人工复核标签、归因类型和验收命令整理为中文可读流程。
+- 重写 `skills/tihao-creator-sourcing/references/output-format.md`,把软件端交付表固定表头、人工复核字段、去重规则、JSON/MCP 保留字段和效果证明边界沉淀为中文。
+- 重写 `docs/manual-review-handoff.md`,让商务能直接看到复核表位置、标签口径、归因类型、复核顺序、质量指标和客户效果审计命令。
+- 新增 `skill:refs:smoke` 与 `scripts/skill-reference-readability-smoke.js`,检查三份关键文档必须包含人工复核标签、归因类型、客户效果边界,并阻止常见乱码回退。
+- `acceptance` 已纳入 `skill:refs:smoke`,发布前会自动校验技能参考文档和人工复核交接文档。
+
+### 已验证
+
+- `npm.cmd run skill:refs:smoke` 通过,3 份关键文档 `hasCommonMojibake=false`。
+- `npm.cmd run hygiene:smoke` 通过,`hitCount=0`。
+- `npm.cmd run acceptance` 通过,包含 workspace install smoke 与 `npm pack --dry-run`。
+
+### 当前边界
+
+- 本轮解决的是“文档可读、技能能按中文 SOP 读取、发布前防回退”,不证明客户命中率或视频分析提升效果。
+- 真实效果仍需真实客户复核 CSV、历史 Brief 数据集、真实视频资源、live provider 审计和 customer-effect 审计关闭证明缺口。
+
+## 2026-06-08 长期优化计划主文档中文化
+
+### 本轮修正
+
+- 将目标文件 `docs/tihao-experience-optimization-plan.md` 从乱码版替换为中文可读执行版。
+- 保留并明确 8 个核心段落:核心判断、当前能力、关键差距、长跑优化任务、软件端表格验收、质量验收标准、给 AI 的优化任务、每轮沉淀。
+- 主计划继续明确:当前目标是把真实证据转成规则和审计闭环,不得用 sample/smoke/provider fallback 或接口 200 宣称命中率提升。
+- 主计划保留真实效果证明口径:必须有真实历史 Brief、客户复核 CSV、客户最终选择、历史人工补号基线、本轮人工补号量和 `customer-effect:audit` 通过。
+
+### 已验证
+
+- `npm.cmd run plan:consistency:smoke` 通过。
+- `npm.cmd run hygiene:smoke` 通过,`hitCount=0`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,15 个刷新步骤全部 pass。
+- 最新 `round-deposition.passCount=81`,`failCount=0`,`warnCount=0`。
+
+### 当前边界
+
+- 本轮解决的是计划本体可读性和每轮执行口径同步,不证明真实业务效果完成。
+- 当前 `proof-gap-closure.openCount=5`,`optimization-status.complete=false`。
+- 下一步仍优先关闭真实数据缺口:历史 Brief、真实候选视频、真实视频 A/B live proof、真实 live/provider 长跑、客户效果审计。
+
+## 2026-06-08 计划执行状态面板
+
+### 本轮修正
+
+- 新增 `plan:execution-status`,生成 `outputs/plan-execution-status-latest/plan-execution-status.md/csv/summary.json`。
+- 状态面板按长期优化计划的 8 个章节输出执行总表:核心判断、当前已具备能力、关键差距、长跑优化任务、软件端表格验收、质量验收标准、给 AI 的优化任务、每轮沉淀。
+- 每一行都包含状态、当前证据、当前产物、下一步、验收标准和边界,方便商务/项目负责人实时看见还差什么。
+- 新增 `plan:execution-status:smoke`,校验 8 个章节、中文报告、proofOpenCount、防误报边界、无 token 泄露和无常见乱码。
+- `round:refresh` 从 15 步扩展为 16 步,最后一步自动刷新计划执行状态面板。
+
+### 已验证
+
+- `npm.cmd run plan:execution-status:smoke` 通过,`rowCount=8`,`proofOpenCount=5`。
+- `npm.cmd run round:refresh:smoke` 通过,16 个步骤顺序稳定。
+- `npm.cmd run hygiene:smoke` 通过,`hitCount=0`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,真实 outputs 已生成 `outputs/plan-execution-status-latest/*`。
+
+### 当前边界
+
+- 状态面板只同步计划执行进度,不证明客户效果。
+- 当前 `outputs/plan-execution-status-latest/plan-execution-status-summary.json` 显示 `complete=false`、`proofOpenCount=5`。
+- 仍需真实历史 Brief、真实候选视频、真实视频 A/B live proof、真实 live/provider 长跑、客户效果审计来关闭长期优化目标。
+
+## 2026-06-08 计划执行状态入口与每轮审计闭环
+
+### 本轮修正
+
+- 将 `plan-execution-status` 纳入 `latest-form:index`,最新提号表单索引现在直接展示 `plan-execution-status.md/csv/summary.json`、`rowCount=8`、`complete=false` 和 `proofOpenCount`。
+- 将 `plan-execution-status` 纳入 `handoff:summary`,交接摘要同时在顶层和 latest-form-index 子节点展示计划执行状态面板,方便下一轮 AI 与商务从一个入口看到当前完成度。
+- 将 `plan-execution-status` 纳入 `round:deposition` 审计,新增 4 个守卫:面板存在、8 个章节与未完成边界、最新表单索引可见、交接摘要可见。
+- 调整 `round:refresh` 顺序:先生成 `plan-execution-status`,再生成 `latest-form-index`、`optimization-handoff` 和 `round-deposition`,确保三类入口引用同一轮状态面板。
+- 调整计划状态面板对后置入口的语义:当 latest/handoff/round 尚未刷新时标为 `open`,不再误报为 `missing`。
+
+### 已验证
+
+- `npm.cmd run plan:execution-status:smoke` 通过,`rowCount=8`,`proofOpenCount=5`。
+- `npm.cmd run latest-form:index:smoke` 通过,最新表单索引已校验 `plan-execution-status.md/csv/summary.json`。
+- `npm.cmd run handoff:summary:smoke` 通过,交接摘要已校验顶层和 latest-form-index 内的计划状态入口。
+- `npm.cmd run round:deposition:smoke` 通过,`passCount=85`,`failCount=0`。
+- `npm.cmd run round:refresh:smoke` 通过,16 步顺序为 `... evidence-index, plan-execution-status, latest-form-index, optimization-handoff, round-deposition`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,真实 outputs 已刷新。
+- `npm.cmd run hygiene:smoke` 通过,`hitCount=0`。
+- `npm.cmd run acceptance` 通过,包含 workspace install smoke 与 `npm pack --dry-run`。
+
+### 当前边界
+
+- 本轮解决的是计划执行状态的入口可见性和每轮审计闭环,不证明客户效果完成。
+- 当前 `proof-gap-closure.openCount=5`,`plan-execution-status.complete=false`,`optimization-status.complete=false`。
+- 仍需真实历史 Brief、真实候选视频、真实视频 A/B live proof、真实 live/provider 长跑、人工复核/客户效果证明来关闭长期优化目标。
+
+## 2026-06-08 补证操作包与 17 步刷新闭环
+
+### 本轮修正
+
+- 新增 `proof-gap:operator-pack`,生成 `outputs/proof-gap-operator-pack-latest/proof-gap-operator-pack.md/csv/summary.json`。
+- 操作包把 5 个未关闭证明缺口压成软件端可执行表:负责人、优先级、填写文件、待补字段/证明、复验命令、通过标准和证明边界。
+- 操作包全部使用中文可读文案,输出 Markdown/CSV 带 BOM,便于商务直接打开。
+- 操作包保留严格边界:`directCustomerProof=false`、`proofLevel=not_business_proof`;sample、接口 200、长跑稳定或补齐材料都不能单独证明客户效果。
+- 将操作包纳入 `round:refresh`,刷新链路从 16 步扩展为 17 步:先刷新计划执行状态,再生成补证操作包,再刷新 evidence index、latest form、handoff 和 round deposition。
+- 将操作包纳入 `evidence:index`、`latest-form:index` 和 `handoff:summary`,商务和下一轮 AI 可以从最新表单索引或交接摘要直接找到补证操作表。
+
+### 已验证
+
+- `npm.cmd run proof-gap:operator-pack:smoke` 通过,`rowCount=5`,`openCount=5`。
+- `npm.cmd run latest-form:index:smoke` 通过,最新表单索引可读取新增操作包入口。
+- `npm.cmd run handoff:summary:smoke` 通过,交接摘要可读取顶层和 latest-form-index 内的操作包入口。
+- `npm.cmd run evidence:index:smoke` 通过,证据索引支持 `proof-gap-operator-pack` 类型。
+- `npm.cmd run round:refresh:smoke` 通过,17 步顺序稳定,且 evidence index 已收录操作包。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,真实 outputs 已刷新。
+- `npm.cmd run hygiene:smoke` 通过,`hitCount=0`。
+- `npm.cmd run acceptance` 通过,包含 workspace install smoke 与 `npm pack --dry-run`。
+
+### 当前边界
+
+- 本轮解决的是“未关闭证明缺口如何派工、填表、复验”的操作入口,不证明提号率提升或客户效果达标。
+- 当前 `outputs/proof-gap-operator-pack-latest/proof-gap-operator-pack-summary.json` 显示 `passed=true`、`complete=false`、`openCount=5`、`rowCount=5`。
+- 长期优化目标仍未完成:真实历史 Brief 数据集、真实候选视频资源、真实视频 A/B live proof、真实 live/provider 长跑、商务复核和客户效果证明仍需真实材料关闭。
+
+## 2026-06-08 主优化计划中文可读修复
+
+### 本轮修正
+
+- 将目标主文件 `docs/tihao-experience-optimization-plan.md` 替换为中文可读执行版,不再依赖 `.zh-CN.md` 作为备用入口。
+- 主计划保留 8 个核心段落:核心判断、当前能力、关键差距、长跑优化任务、软件端表格验收、质量验收标准、给下一轮 AI 的任务、每轮沉淀。
+- 主计划继续明确证明边界:sample、smoke、provider fallback、接口 200、补齐操作包和候选名单都不能证明客户效果完成。
+- 调整 `plan:consistency:smoke`:对主计划改为检查中文关键短语,包括“全局博主唯一”“平台 + 规范化主页链接”“平台 + 规范化博主名称”“不得宣称客户效果完成”。
+
+### 已验证
+
+- `npm.cmd run plan:consistency:smoke` 通过,`hits=[]`。
+- `npm.cmd run plan:execution-status:smoke` 通过,`rowCount=8`,`proofOpenCount=5`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,17 步全部 pass。
+- `npm.cmd run hygiene:smoke` 通过,`hitCount=0`。
+- `npm.cmd run acceptance` 通过,包含 workspace install smoke 与 `npm pack --dry-run`。
+
+### 当前边界
+
+- 本轮解决的是目标计划文档本体可读性和一致性校验口径,不证明长期优化目标完成。
+- 当前 `proof-gap-closure.openCount=5`,`plan-execution-status.complete=false`,`optimization-status.complete=false`。
+- 下一步仍需要真实历史 Brief、真实候选视频、真实视频 A/B live proof、真实 live/provider 长跑、商务复核和客户效果审计关闭证明缺口。
+
+## 2026-06-10 长跑前置门禁入口补强
+
+### 本轮修正
+
+- 将 `outputs/long-run-readiness-latest/long-run-readiness-report.md` 和 `long-run-readiness-summary.json` 作为一等入口纳入 `latest-form:index`。
+- `latest-form-index-summary.json` 新增 `longRunReadiness`,暴露 `mode`、`ready`、`passCount`、`failCount`、`warnCount`、`directCustomerProof=false` 和 `proofLevel=not_business_proof`。
+- `handoff:summary` 顶层新增“长跑前置门禁”段落,同时在 `latestFormIndex.longRunReadiness` 中透传同一入口,避免下一轮 AI 只能从 `round-refresh-summary.json` 间接定位长跑门禁。
+- `handoff:summary` 的下一轮命令新增 `longrun:readiness`,明确启动 full-matrix/live 长跑或客户效果审计前必须先跑前置门禁。
+- 更新 `latest-form:index:smoke` 和 `handoff:summary:smoke`,要求报告包含 `long-run-readiness-report.md`、`ready=false` 和“不证明客户效果”的边界。
+
+### 已验证
+
+- `node --check scripts\latest-form-index.js` 通过。
+- `node --check scripts\generate-optimization-handoff.js` 通过。
+- `node --check scripts\latest-form-index-smoke.js` 通过。
+- `node --check scripts\generate-optimization-handoff-smoke.js` 通过。
+- `npm.cmd run latest-form:index:smoke` 通过,`longRunReadiness.ready=false`、`failCount>=1`、`proofLevel=not_business_proof`。
+- `npm.cmd run handoff:summary:smoke` 通过,handoff 顶层和 latestFormIndex 嵌套节点都能看到长跑前置门禁。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,22 步全部 `pass`,真实输出已刷新;当前 `long-run-readiness.ready=false`、`failCount=6`。
+- `npm.cmd run round:refresh:smoke` 通过,临时 22 步刷新链路同样保留长跑前置门禁入口。
+- `npm.cmd run task:handoff-index:smoke` 通过,任务交接入口继续保留 `openCount=5`、`readyForClaim=false` 边界。
+- `npm.cmd run hygiene:smoke` 通过,扫描 166 个文件,`hitCount=0`。
+- `npm.cmd run acceptance` 通过,总验收保持通过,且 `latest-form:index:smoke`、`handoff:summary:smoke`、`round:refresh:smoke` 均在 acceptance 中通过。
+
+### 当前边界
+
+- 本轮只补强长跑前置门禁的可见性和交接入口,不补真实业务材料,也不关闭真实证明缺口。
+- 当前 `proof-gap-closure.openCount=5`,`optimization-completion.readyForClaim=false`,不得宣称提号率提升、客户效果达标或人工补号量下降。
+- `longrun:readiness.ready=false` 是真实卡点,不是交付失败;它说明 full-matrix/live 长跑和客户效果审计还不能启动或宣称完成。
+
+## 2026-06-10 前置门禁纳入 22 步刷新链路
+
+### 本轮修正
+
+- 将 `intake:readiness`、`video:resource-readiness` 和 `longrun:readiness` 纳入 `round:refresh`,刷新链路从 19 步扩展为 22 步。
+- 三个前置门禁在 `round:refresh --strict` 中不使用 strict 失败退出,而是如实刷新当前状态:`overallReady=false`、`readyForVideoAbPreflight=false`、`longrun.ready=false`。
+- `round-refresh-summary.json` 的 `artifacts` 新增 `intakeReadiness`、`videoResourceReadiness`、`longRunReadiness`,让完成度审计不再依赖旧的前置门禁 summary。
+- 更新 `round:refresh:smoke`,要求 22 步顺序稳定,并断言 intake/video/longrun 三个前置门禁产物被刷新。
+- 更新 `docs/tihao-handoff-index.md` 和 `task:handoff-index:smoke`,当前交接入口明确显示 `round-refresh-summary.json` 为 22 步全部 `pass`。
+
+### 已验证
+
+- `node --check scripts\refresh-round-artifacts.js` 通过。
+- `node --check scripts\refresh-round-artifacts-smoke.js` 通过。
+- `npm.cmd run round:refresh:smoke` 通过,22 步顺序稳定。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,真实 outputs 已刷新为 22 步;当前 `intake-readiness.failureCount=9`、`video-resource-readiness.failureCount=1`、`long-run-readiness.failCount=6`。
+- `npm.cmd run task:handoff-index:smoke` 通过,交接入口已同步 22 步口径。
+
+### 当前边界
+
+- 本轮只是让每轮刷新覆盖真实材料、视频资源和长跑前置门禁的最新失败状态,不补真实业务材料。
+- 当前 `proof-gap-closure.openCount=5`,`optimization-completion.readyForClaim=false`,不得宣称提号率提升、客户效果达标或人工补号量下降。
+- `round:refresh` 22 步全部 `pass` 只代表审计/索引/交接产物成功刷新,不代表 22 个业务门槛都通过。
+
+## 2026-06-10 优化完成度审计与 19 步刷新闭环
+
+### 本轮修正
+
+- 新增 `scripts/optimization-completion-audit.js` 和 `scripts/optimization-completion-smoke.js`。
+- 新增 npm script:`optimization:completion`、`optimization:completion:smoke`。
+- 完成度审计会聚合 intake readiness、video resource readiness、proof-gap closure、真实视频 A/B live proof、真实 live/provider 长跑、客户效果审计、optimization pipeline、交接摘要和 15 步补证进度。
+- 当前审计必须输出 `complete=false`、`readyForClaim=false`、`proofGapOpenCount=5`,并把 `canClaim.tihaoRateImproved`、`canClaim.customerEffectAchieved`、`canClaim.manualSupplementReduced` 全部保持为 `false`。
+- 将完成度审计 smoke 纳入 `acceptance`,将完成度审计产物纳入 `round:refresh`,刷新链路从 18 步扩展为 19 步。
+- `evidence:index` 新增 `optimization-completion` 类型:只有 `complete=true`、`readyForClaim=true` 且 `proofGapOpenCount=0` 时才可归为 `real_evidence`;当前 incomplete 时归为 `not_business_proof`。
+- 更新 `docs/tihao-handoff-index.md`,把 `outputs/optimization-completion-latest/optimization-completion-report.md` 作为长期优化完成判断入口,并明确它本身不是业务证明。
+- 更新 `docs/business-proof-action-order.zh-CN.md` 的长期完成判定,要求 `optimization:completion` 同时满足 `complete=true`、`readyForClaim=true`、`proofGapOpenCount=0`。
+- `round-deposition` 的补证操作顺序守卫现在检查 `optimization:completion` 和 `readyForClaim=true`,避免后续只看 `proof-gap:closure` 就误判长期目标完成。
+- `latest-form:index` 现在把 `optimization-completion-report.md/summary.json` 作为一等入口,summary 暴露 `complete`、`readyForClaim`、`proofGapOpenCount`、`blockingReasonCount` 和三个 `canClaim=false` 开关。
+- `real-proof-intake-bundle` 现在在“打开顺序”中加入优化完成度审计报告,在 `sourceState` 暴露 `optimizationCompletionComplete=false`、`optimizationCompletionReadyForClaim=false`,并新增 `optimization:completion` 复验命令。
+- `real-proof-intake-bundle.complete` 现在同时要求 `proof-gap:closure.complete=true`、`plan-execution-status.complete=true`、`optimization-completion.complete=true`、`readyForClaim=true` 和 `proofOpenCount=0`,避免真实证明缺口未关时误判长期目标完成。
+
+### 已验证
+
+- `npm.cmd run optimization:completion:smoke` 通过。
+- `npm.cmd run optimization:completion -- --output outputs\optimization-completion-latest` 通过,输出 `complete=false`、`readyForClaim=false`、`proofGapOpenCount=5`、`blockingReasonCount=10`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,19 步全部 `pass`;`optimization-completion` 已纳入刷新链路,`round-deposition` 通过项为 85、`failCount=0`。
+- `npm.cmd run evidence:index:smoke` 通过,已识别 `optimization-completion` 类型。
+- `npm.cmd run optimization:status:smoke` 通过,状态面板已识别 `optimization-completion` 稳定项。
+- `npm.cmd run round:refresh:smoke` 通过,19 步顺序稳定。
+- `npm.cmd run round:deposition:smoke` 通过,补证操作顺序守卫已覆盖 `optimization:completion`。
+- `npm.cmd run round:deposition -- --outputs outputs --output outputs\round-deposition-latest --strict` 通过,`passCount=85`、`failCount=0`。
+- `npm.cmd run task:handoff-index:smoke` 通过,交接入口已记录 `optimization-completion.readyForClaim=false`。
+- `npm.cmd run handoff:summary:smoke` 通过,交接摘要仍保持 `complete=false`。
+- `npm.cmd run latest-form:index:smoke` 通过,最新表单入口仍保持补证/收集材料边界。
+- `node --check scripts\latest-form-index.js`、`node --check scripts\real-proof-intake-bundle.js`、`node --check scripts\latest-form-index-smoke.js`、`node --check scripts\real-proof-intake-bundle-smoke.js` 均通过。
+- `npm.cmd run proof-gap:intake-bundle:smoke` 通过,新增完成度审计入口后仍为 `complete=false`、`proofOpenCount=5`、`exportRowCount=32`。
+- `npm.cmd run latest-form:index:smoke` 通过,最新表单索引已校验 `optimization-completion-report.md`、`readyForClaim=false`、`proofGapOpenCount>=1` 和三个禁用宣称开关。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 重新通过,19 步全部 `pass`;latest-form index、real-proof intake bundle 和 optimization-completion latest 产物已刷新。
+- `npm.cmd run round:refresh:smoke`、`npm.cmd run task:handoff-index:smoke`、`npm.cmd run hygiene:smoke`、`npm.cmd run round:deposition:smoke` 均通过。
+- `npm.cmd run acceptance` 在完成度审计和补证顺序守卫更新后通过;包内容门禁显示 `entryCount=167`,workspace install smoke 通过。
+
+### 当前边界
+
+- 本轮新增的是“能不能宣称完成”的机器审计,不补真实业务材料,也不关闭真实证明缺口。
+- 当前 `proof-gap-closure.openCount=5`,`optimization-completion.readyForClaim=false`,不得宣称提号率提升、客户效果达标或人工补号量下降。
+- `real-proof-intake-bundle`、`proof-gap-request`、`proof-gap-operator-pack`、最新索引、执行总表和完成度审计都不是客户效果证明。
+
+## 2026-06-09 真实验收材料收集总包与 18 步刷新闭环
+
+### 本轮修正
+
+- 新增 `proof-gap:intake-bundle`,生成 `outputs/real-proof-intake-bundle-latest/real-proof-intake-bundle.md/csv/summary.json`。
+- 总包把真实材料模板、字段级缺口、修复清单、5 个证明缺口和补齐后复验命令集中到一个商务可打开入口。
+- 总包收录历史 Brief 数据模板、人工复核模板、视频资源模板、字段级补齐核对表、补证操作包、人工复核标签指南、商务执行总表和最新提号表单索引。
+- 总包继续保留证明边界:`directCustomerProof=false`、`proofLevel=not_business_proof`;总包通过只代表补证入口完整,不证明客户效果。
+- 将总包纳入 `round:refresh`,刷新链路从 17 步扩展为 18 步,并在 `evidence:index`、`latest-form:index` 和 `handoff:summary` 中暴露入口。
+- 修正 latest form 索引报告里两处“缺失”乱码占位,避免中文交付入口继续夹杂乱码。
+
+### 已验证
+
+- `npm.cmd run proof-gap:intake-bundle:smoke` 通过,`proofOpenCount=5`,`exportRowCount=30`。
+- `npm.cmd run latest-form:index:smoke` 通过,最新表单索引可读取 `real-proof-intake-bundle.md/csv/summary.json`。
+- `npm.cmd run handoff:summary:smoke` 通过,交接摘要可读取顶层真实验收材料收集总包入口。
+- `npm.cmd run round:refresh:smoke` 通过,18 步顺序稳定,且 evidence index 已收录 `real-proof-intake-bundle` 类型。
+
+### 当前边界
+
+- 本轮解决的是“商务如何从一个入口补齐真实验收材料”的操作体验,不证明提号率提升、客户效果达标或人工补号量下降。
+- 当前 `proofOpenCount=5`,长期优化目标仍未完成。
+- 仍需真实历史 Brief 数据集、真实候选视频资源、真实视频 A/B live proof、真实 live/provider 长跑、商务复核和客户效果证明来关闭真实证明缺口。
+
+## 2026-06-09 真实证明缺口请求包入口补强
+
+### 本轮修正
+
+- 将 `proof-gap-request-latest` 作为一等入口纳入 `latest-form:index`,最新表单索引现在展示 `proof-gap-request-report.md/csv/summary.json`、`unresolvedCount`、`rowCount` 和证明边界。
+- 将真实证明缺口请求包纳入 `handoff:summary` 顶层,并在 `latestFormIndex` 嵌套节点中同步展示;即使 latest-form 索引尚未刷新,handoff 也会回退到独立的 `proof-gap-request-latest` 产物路径。
+- 更新 `round:refresh:smoke`,确认 18 步刷新后 latest-form 和 handoff 都能看到请求包入口。
+- 保持边界:请求包只列出仍需补齐的真实证明材料,不证明客户效果、命中率提升或人工补号量下降。
+
+### 已验证
+
+- `npm.cmd run latest-form:index:smoke` 通过,请求包入口已进入最新表单索引。
+- `npm.cmd run handoff:summary:smoke` 通过,请求包入口已进入交接摘要顶层和 latest-form 嵌套节点。
+- `npm.cmd run round:refresh:smoke` 通过,18 步顺序稳定,且整轮交接仍为 `passed=true`。
+
+### 当前边界
+
+- 当前仍是材料请求和补证入口补强,不关闭任何真实业务证明缺口。
+- `proofOpenCount/openCount` 仍为 5 时,不得宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-09 发布前泄密/乱码审计范围补强
+
+### 本轮修正
+
+- 扩大 `hygiene:smoke` 默认扫描范围,除 `docs`、`scripts`、`mcp/src`、`skills`、`fixtures` 外,新增覆盖 `package.json`、`install.js`、`bin`、`skill-package-manifest.json`、`.mcp.json`、`.claude-plugin` 和 `memory-templates`。
+- 这样 acceptance 和发布前 dry-run 会自动扫描包入口、安装脚本、CLI、manifest、MCP 配置和记忆模板,避免关键交付文件绕过 token/乱码审计。
+- 保持秘密保护边界:不得在报告、日志、模板、安装产物或 npm 包入口中写入 sessionToken、Authorization、模型 token 或 npm token。
+
+### 已验证
+
+- `npm.cmd run hygiene:smoke` 通过,默认扫描文件数从 154 扩展到 161,`hitCount=0`。
+- `npm.cmd run skill:refs:smoke` 通过,技能引用文档仍无常见乱码。
+
+### 当前边界
+
+- 本轮只增强发布前安全审计覆盖面,不关闭真实客户效果证明缺口。
+- 当前 `proofOpenCount/openCount=5`,仍需真实历史 Brief、真实候选视频、真实 live/provider 和客户效果材料后才能进入完成判断。
+
+## 2026-06-09 npm 包内容门禁
+
+### 本轮修正
+
+- 新增 `package:contents:smoke`,基于 `npm pack --dry-run --json` 解析真实 npm 包文件清单。
+- 门禁要求 npm 包必须包含插件入口、MCP 配置、CLI、安装脚本、manifest、memory 模板、MCP server、skill 主文件、关键 reference 和安装/付费/补证 SOP 文档。
+- 门禁禁止把 `outputs/`、`output/`、`node_modules/`、`.git/`、`.codex-run/`、`.npm-cache`、`.claude/`、`.vscode/`、`dist/`、`tmp/`、`coverage/`、`.env`、`.npmrc`、`package-lock.json`、日志、tgz 或 zip 打入 npm 包。
+- 将 `package:contents:smoke` 接入 `acceptance`,放在原有 `npm pack --dry-run` 前面,先机器断言,再保留 dry-run 清单给人工查看。
+
+### 已验证
+
+- `npm.cmd run package:contents:smoke` 通过,`entryCount=163`,必需文件 17 项全部存在。
+- `npm.cmd run hygiene:smoke` 通过,默认扫描文件数扩展到 162,`hitCount=0`。
+
+### 当前边界
+
+- 本轮只保证 npm 包内容边界和发布清单安全,不证明真实客户效果。
+- 当前仍有 5 个真实证明缺口未关闭,不能宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-09 任务交接入口文档补强
+
+### 本轮修正
+
+- 新增 `docs/tihao-handoff-index.md`,作为下一轮 AI、商务或投放接手长期优化任务时的第一入口。
+- 入口文档按阅读顺序指向主计划、15 步补证顺序、最新表单索引、真实验收材料总包、proof-gap 请求包、商务执行总表和交接摘要。
+- 将任务交接入口纳入 `latest-form:index` 和 `handoff:summary` 的 `supportDocs`,避免入口只存在于 `docs/` 目录而没有出现在最新交接产物中。
+- 新增 `task:handoff-index:smoke`,校验入口文档、README、最新表单索引、交接摘要和 acceptance 门禁都能看到该入口。
+- 将 `docs/tihao-handoff-index.md` 加入 npm 包内容门禁必需文件。
+
+### 当前边界
+
+- 本轮只解决任务入口和交接可发现性,不关闭任何真实业务证明缺口。
+- 当前 `proofOpenCount/openCount=5`,仍不能宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-09 按任务文档执行优化复验
+
+### 本轮执行
+
+- 按 `docs/tihao-handoff-index.md` 的复验顺序执行 `intake:readiness`、`video:resource-readiness --strict`、`proof-gap:closure` 和 `round:refresh --strict`。
+- 将复验结果写回 `docs/tihao-handoff-index.md` 的“最近一次复验结果”章节,方便下一轮 AI、商务和投放直接看到当前卡点。
+- 扩展 `task:handoff-index:smoke`,要求任务入口保留 `overallReady=false`、`failureCount=9`、`readyForVideoAbPreflight=false`、`realCandidateRows=0`、`openCount=5` 和“不能用 0 或示例值代替”的边界。
+
+### 已验证
+
+- `npm.cmd run intake:readiness -- --data-pack outputs\data-intake-pack-latest --video-pack outputs\video-intake-pack-latest --output outputs\intake-readiness-latest` 通过执行,结果为 `overallReady=false`、`failureCount=9`。
+- `npm.cmd run video:resource-readiness -- --input outputs\video-intake-pack-latest\video-resource-template.csv --output outputs\video-resource-readiness-latest --strict` 按预期失败,结果为 `readyForVideoAbPreflight=false`、`failureCount=1`、`realCandidateRows=0`。
+- `npm.cmd run proof-gap:closure -- --output outputs\proof-gap-closure-latest` 通过执行,结果为 `complete=false`、`openCount=5`、`closedCount=0`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,18 步全部 `pass`。
+
+### 当前边界
+
+- 当前已完成的是“按任务文档复验并同步真实卡点”,不是长期优化目标完成。
+- `optimization:pipeline` 未运行,因为缺真实 `<本轮人工补号量>` 和可通过 intake readiness 的真实材料;不能用 `0`、示例值或模板值替代。
+- 当前仍有 5 个真实证明缺口未关闭,不能宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-10 提号补证操作包字段审计闭环
+
+### 本轮修正
+
+- 将 `proof-gap-operator-pack` 强化为可机器审计的执行件,summary 稳定输出 `gapIds`、`fillFileCount`、`recheckCommandCount` 和 `ownerGroupCount`。
+- 操作包当前记录 5 个真实证明缺口:`historical-dataset`、`video-real-candidate`、`video-ab-live-proof`、`live-provider-overnight-proof`、`manual-review-and-customer-effect`。
+- 当前操作包字段为 `fillFileCount=3`、`recheckCommandCount=9`、`ownerGroupCount=3`,每行同时暴露主填写文件和首个复验命令,方便商务、投放和技术按缺口派工。
+- `latest-form:index` 和 `handoff:summary` 已透传操作包的 markdown、csv、summary、5 个 gapId、填写文件数、复验命令数和负责人组数。
+- `round-deposition` 新增 4 个检查:`proof-gap-operator-pack-summary`、`proof-gap-operator-pack-actionable`、`latest-form-index-proof-gap-operator-pack`、`handoff-proof-gap-operator-pack`,本轮真实 `passCount=101`、`failCount=0`。
+- `docs/tihao-handoff-index.md` 已补充操作包阅读入口、最近复验结果和可说/不可说边界,并由 `task:handoff-index:smoke` 守住关键字段。
+
+### 已验证
+
+- `npm.cmd run proof-gap:operator-pack:smoke` 通过,`openCount=5`、`rowCount=5`。
+- `npm.cmd run proof-gap:operator-pack -- --outputs outputs --output outputs\proof-gap-operator-pack-latest --strict` 通过,真实输出已刷新。
+- `npm.cmd run latest-form:index:smoke` 通过,最新表单索引可读取 `proof-gap-operator-pack.md/csv/summary.json`。
+- `npm.cmd run handoff:summary:smoke` 通过,交接摘要可读取顶层和 latest-form 嵌套的操作包入口。
+- `npm.cmd run round:deposition:smoke` 通过,`passCount=101`、`failCount=0`。
+- `npm.cmd run round:refresh:smoke` 通过,22 步顺序稳定。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,真实 outputs 已刷新,`round-deposition.passCount=101`。
+
+### 当前边界
+
+- 操作包只回答“5 个缺口谁补、补哪个文件、补完跑什么命令”,不证明提号率提升、客户效果达标或人工补号量下降。
+- 当前 `proof-gap-closure.openCount=5`,`optimization-completion.readyForClaim=false`,`longrun:readiness.ready=false`。
+- 仍需真实历史 Brief 数据集、真实候选视频资源、真实视频 A/B live proof、真实 live/provider 长跑证明、商务复核和客户效果证明后,才能关闭真实证明缺口。
+
+## 2026-06-10 提号补证操作包当前证据与下一步动作列
+
+### 本轮修正
+
+- 在 `proof-gap-operator-pack` summary 中新增 `evidenceRowCount`、`nextActionCount` 和 `missingProofRequirementCount`,当前真实输出为 `evidenceRowCount=5`、`nextActionCount=5`。
+- 在操作包 markdown 和 CSV 中新增“当前证据”和“下一步”列,让商务/投放/技术打开表格后能直接看到每个缺口当前卡点和补完后该跑什么。
+- 去重“待补字段/证明”展示,避免 `video-ab-live-proof` 这类缺口把 `proofContext.*` 重复列出。
+- `latest-form:index`、`handoff:summary` 和 `round-deposition` 已透传并审计 `evidenceRowCount`、`nextActionCount`,防止操作包退化成只有文件链接和命令清单。
+- `docs/tihao-handoff-index.md` 已同步记录 5 条当前证据和 5 条下一步动作,`task:handoff-index:smoke` 会守住这些字段。
+
+### 已验证
+
+- `node --check scripts\proof-gap-operator-pack.js`、`node --check scripts\latest-form-index.js`、`node --check scripts\generate-optimization-handoff.js`、`node --check scripts\round-deposition-audit.js` 均通过。
+- `npm.cmd run proof-gap:operator-pack:smoke` 通过,操作包报告/CSV 已包含“当前证据”和“下一步”列。
+- `npm.cmd run latest-form:index:smoke` 通过,最新表单索引已透传 `evidenceRowCount=5`、`nextActionCount=5`。
+- `npm.cmd run handoff:summary:smoke` 通过,交接摘要已透传操作包证据行和下一步动作数。
+- `npm.cmd run round:deposition:smoke` 通过,每轮沉淀审计继续 `passCount=101`、`failCount=0`。
+
+### 当前边界
+
+- 本轮只让补证操作包更可执行、更可审计,不补真实历史 Brief、真实候选视频或客户效果材料。
+- 当前 5 个真实证明缺口仍未关闭;`optimization-completion.readyForClaim=false` 时仍不得宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-10 真实验收材料总包透传操作包证据与下一步
+
+### 本轮修正
+
+- 将 `proof-gap-operator-pack` 的当前证据和下一步动作透传进 `real-proof-intake-bundle`,summary 新增 `operatorPackEvidenceRowCount=5`、`operatorPackNextActionCount=5`、`operatorPackMissingProofRequirementCount=6`。
+- 在 `real-proof-intake-bundle.md` 的“证明缺口闭环”表新增“当前证据”和“下一步”列,CSV 同步新增 `当前证据`、`下一步` 两列,商务打开总包时可直接看到每个真实证明缺口当前卡点和补齐动作。
+- `latest-form:index` 和 `handoff:summary` 已透传真实验收材料总包的操作包证据/下一步计数,`round-deposition` 已把这些字段作为观测项输出。
+- `task:handoff-index:smoke` 已补充对 `realProofIntakeBundle` 顶层、latest-form 和 handoff 嵌套入口的字段校验。
+- `docs/tihao-handoff-index.md` 已同步当前结论、阅读顺序、最近复验结果和可说/不可说边界。
+
+### 已验证
+
+- `node --check scripts\real-proof-intake-bundle.js`、`node --check scripts\latest-form-index.js`、`node --check scripts\generate-optimization-handoff.js`、`node --check scripts\round-deposition-audit.js`、`node --check scripts\task-handoff-index-smoke.js` 均通过。
+- `npm.cmd run proof-gap:intake-bundle:smoke` 通过,真实验收材料总包输出 `operatorPackEvidenceRowCount=5`、`operatorPackNextActionCount=5`。
+- `npm.cmd run latest-form:index:smoke` 通过,最新表单索引已展示真实验收材料总包的操作包证据/下一步计数。
+- `npm.cmd run handoff:summary:smoke` 通过,交接摘要顶层和 latest-form 嵌套入口均可读取真实验收材料总包字段。
+- `npm.cmd run round:deposition:smoke` 通过,round deposition 继续 `passCount=101`、`failCount=0`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,22 步全部 `pass`,真实 outputs 已刷新。
+- `npm.cmd run task:handoff-index:smoke` 通过,任务交接入口已守住 `operatorPackEvidenceRowCount=5`、`operatorPackNextActionCount=5`。
+- `npm.cmd run acceptance` 通过,发布前总门禁、包内容门禁、安全审计和任务交接入口均保持通过。
+
+### 当前边界
+
+- 本轮只把补证操作包的当前证据和下一步动作带入真实验收材料总包,减少商务/投放在多个入口间跳转,不补真实业务材料。
+- 当前 `proof-gap-closure.openCount=5`、`optimization-completion.readyForClaim=false`、`longrun:readiness.ready=false`,仍不得宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-10 提号补证操作包负责人附件
+
+### 本轮修正
+
+- `proof-gap-operator-pack` 新增按负责人拆分的 `by-owner` 附件,当前输出 `ownerArtifactCount=3`。
+- 当前负责人附件为 `outputs/proof-gap-operator-pack-latest/by-owner/business.md/csv`、`outputs/proof-gap-operator-pack-latest/by-owner/business-media.md/csv`、`outputs/proof-gap-operator-pack-latest/by-owner/tech-ai.md/csv`。
+- 每份负责人附件保留对应 `gapIds`、`topPriority`、`actionCount`、`evidenceRowCount`、`nextActionCount`,让商务、商务/投放、技术/AI 能直接打开自己的补证清单,不必在总表里手动筛选。
+- `latest-form:index`、`handoff:summary` 和 `round-deposition` 已透传并审计 `ownerArtifactCount=3` 与三份 by-owner 文件路径,防止后续刷新丢失角色附件。
+- `docs/tihao-handoff-index.md` 已同步当前结论、阅读顺序、接手角色分工、最近复验结果和可说/不可说边界,明确这些负责人附件仍只是补证派工材料。
+
+### 已验证
+
+- `node --check scripts\latest-form-index.js` 通过。
+- `node --check scripts\latest-form-index-smoke.js` 通过。
+- `node --check scripts\generate-optimization-handoff.js` 通过。
+- `node --check scripts\generate-optimization-handoff-smoke.js` 通过。
+- `node --check scripts\round-deposition-audit.js` 通过。
+- `node --check scripts\round-deposition-smoke.js` 通过。
+- `npm.cmd run proof-gap:operator-pack:smoke` 通过,操作包 smoke 已检查 by-owner 附件。
+- `npm.cmd run latest-form:index:smoke` 通过,最新表单索引已透传 `ownerArtifactCount=3`。
+- `npm.cmd run handoff:summary:smoke` 通过,交接摘要已透传顶层和 latest-form 嵌套的负责人附件。
+- `npm.cmd run round:deposition:smoke` 通过,`passCount=101`、`failCount=0`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,22 步全部 `pass`,真实 outputs 已刷新。
+- `npm.cmd run task:handoff-index:smoke` 通过,任务交接入口已守住负责人附件字段。
+- `npm.cmd run acceptance` 通过,包内容门禁、任务交接入口 smoke、安全审计和 workspace install smoke 均保持通过。
+- 抽读真实 outputs 确认 `proofGapOperatorPack.ownerArtifactCount=3`、`latestFormIndex.proofGapOperatorPackOwnerArtifactCount=3`、`handoff.proofGapOperatorPackOwnerArtifactCount=3`。
+
+### 当前边界
+
+- 本轮只增强补证操作包的角色分发和防回退审计,不补真实历史 Brief、真实候选视频、live/provider 长跑或客户效果材料。
+- 当前 `proof-gap-closure.openCount=5`、`optimization-completion.readyForClaim=false`、`longrun:readiness.ready=false`。
+- `real-proof-intake-bundle`、`proof-gap-request`、`proof-gap-operator-pack`、by-owner 附件、最新索引和执行总表仍都是协调/执行材料,不是业务证明;不得宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-10 真实验收材料总包透传操作包负责人附件
+
+### 本轮修正
+
+- `real-proof-intake-bundle` 新增 `operatorPackOwnerArtifactCount=3` 和 `operatorPackOwnerArtifacts`,直接透传 `proof-gap-operator-pack` 的三份 by-owner 派工文件。
+- 总包 Markdown 新增“操作包负责人附件”表,展示商务、商务/投放、技术/AI 的 Markdown、CSV、gapIds、`evidenceRowCount` 和 `nextActionCount`。
+- 总包 CSV 新增 3 行 `负责人附件`,因此当前真实输出 `exportRowCount=37`;商务只打开总包 CSV 时,也能看到 `by-owner/business.csv`、`by-owner/business-media.csv`、`by-owner/tech-ai.csv`。
+- `latest-form:index` 和 `handoff:summary` 已透传真实总包内的 `operatorPackOwnerArtifactCount=3` 和三份附件路径。
+- `round-deposition` 新增 `real-proof-intake-bundle-operator-owner-artifacts` 防回退检查,当前真实 `passCount=102`、`failCount=0`。
+- `docs/tihao-handoff-index.md` 已同步当前结论、阅读顺序、最近复验结果和可说/不可说边界,明确这些负责人附件仍只是补证派工材料。
+
+### 已验证
+
+- `node --check scripts\real-proof-intake-bundle.js` 通过。
+- `node --check scripts\real-proof-intake-bundle-smoke.js` 通过。
+- `node --check scripts\latest-form-index.js` 通过。
+- `node --check scripts\latest-form-index-smoke.js` 通过。
+- `node --check scripts\generate-optimization-handoff.js` 通过。
+- `node --check scripts\generate-optimization-handoff-smoke.js` 通过。
+- `node --check scripts\round-deposition-audit.js` 通过。
+- `node --check scripts\round-deposition-smoke.js` 通过。
+- `npm.cmd run proof-gap:intake-bundle:smoke` 通过,真实验收材料总包输出 `exportRowCount=37`、`operatorPackOwnerArtifactCount=3`。
+- `npm.cmd run latest-form:index:smoke` 通过,最新表单索引已展示真实总包的操作包负责人附件字段。
+- `npm.cmd run handoff:summary:smoke` 通过,交接摘要顶层和 latest-form 嵌套入口均可读取真实总包负责人附件字段。
+- `npm.cmd run round:deposition:smoke` 通过,`passCount=102`、`failCount=0`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,22 步全部 `pass`,真实 outputs 已刷新。
+- `npm.cmd run task:handoff-index:smoke` 通过,任务交接入口已守住 `operatorPackOwnerArtifactCount=3`。
+- `npm.cmd run hygiene:smoke` 通过,`scannedFiles=166`、`hitCount=0`。
+- `npm.cmd run acceptance` 通过,包内容门禁、任务交接入口 smoke、安全审计和 workspace install smoke 均保持通过。
+
+### 当前边界
+
+- 本轮只减少商务/投放在总包和操作包之间跳转,不补真实历史 Brief、真实候选视频、live/provider 长跑或客户效果材料。
+- 当前 `proof-gap-closure.openCount=5`、`optimization-completion.readyForClaim=false`、`longrun:readiness.ready=false`。
+- `real-proof-intake-bundle`、by-owner 附件、最新索引、交接摘要和 22 步 refresh 仍都不是业务证明;不得宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-10 商务执行总表透传操作包负责人附件
+
+### 本轮修正
+
+- `business-execution-index` 新增读取 `proof-gap-operator-pack-summary.json`,并把三份 by-owner 派工文件写成 3 行 `操作包负责人附件`。
+- 商务执行总表 summary 新增 `operatorPackOwnerArtifactCount=3`、`operatorPackOwnerArtifactRowCount=3` 和 `operatorPackOwnerArtifacts`,当前真实输出 `rowCount=27`、`proofOpenCount=5`。
+- 新增三行分别指向 `outputs/proof-gap-operator-pack-latest/by-owner/business.md/csv`、`business-media.md/csv`、`tech-ai.md/csv`,并保留 `gapIds`、`evidenceRowCount`、`nextActionCount` 和“不证明客户效果”边界。
+- `latest-form:index`、`handoff:summary` 和 `round-deposition` 已透传并审计商务执行总表里的操作包负责人附件;`round:refresh` 顺序已调整为先刷新 `proof-gap-operator-pack`,再刷新 `business-execution-index`。
+- `docs/tihao-handoff-index.md` 已同步当前结论、阅读顺序、最近复验结果、接手角色分工和可说/不可说边界,明确商务执行总表里的负责人附件仍只是补证派工材料。
+
+### 已验证
+
+- `node --check scripts\business-execution-index.js`、`node --check scripts\business-execution-index-smoke.js` 通过。
+- `node --check scripts\latest-form-index.js`、`node --check scripts\generate-optimization-handoff.js`、`node --check scripts\round-deposition-audit.js`、`node --check scripts\refresh-round-artifacts.js` 通过。
+- `node --check scripts\generate-optimization-handoff-smoke.js`、`node --check scripts\round-deposition-smoke.js`、`node --check scripts\task-handoff-index-smoke.js`、`node --check scripts\refresh-round-artifacts-smoke.js` 通过。
+- `npm.cmd run business-execution:index:smoke` 通过,fixture 输出包含 3 行操作包负责人附件。
+- `npm.cmd run latest-form:index:smoke` 通过,最新表单索引已展示商务执行总表的 `operatorPackOwnerArtifactCount=3`。
+- `npm.cmd run handoff:summary:smoke` 通过,交接摘要已透传商务执行总表里的操作包负责人附件。
+- `npm.cmd run round:deposition:smoke` 通过,`passCount=104`、`failCount=0`。
+- `npm.cmd run round:refresh:smoke` 通过,22 步顺序已改为 `proof-gap-operator-pack` 先于 `business-execution-index`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,22 步全部 `pass`,真实 outputs 已刷新。
+- `npm.cmd run task:handoff-index:smoke` 通过,任务交接入口已守住商务执行总表的 `operatorPackOwnerArtifactCount=3`。
+- `npm.cmd run hygiene:smoke` 通过,`scannedFiles=166`、`hitCount=0`。
+- `npm.cmd run acceptance` 通过,包内容门禁、任务交接入口 smoke、安全审计和 workspace install smoke 均保持通过。
+
+### 当前边界
+
+- 本轮只让商务执行总表直接承接 proof-gap 操作包 by-owner 附件,减少商务/投放/技术在多个入口间跳转,不补真实业务材料。
+- 当前 `proof-gap-closure.openCount=5`、`optimization-completion.readyForClaim=false`、`longrun:readiness.ready=false`。
+- 商务执行总表、`操作包负责人附件`、真实验收材料总包、最新索引、交接摘要和 22 步 refresh 仍都不是业务证明;不得宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-10 业务进度与计划执行面板透传操作包负责人附件
+
+### 本轮修正
+
+- `business-proof-progress` 新增读取 `proof-gap-operator-pack-summary.json` 和 `business-execution-index-summary.json`,summary 透传 `operatorPackOwnerArtifactCount=3` 与三份 `operatorPackOwnerArtifacts`。
+- `business-proof-progress-report.md` 新增 `proof-gap 操作包负责人附件` 区块,让商务查看进度表时也能直接定位 `by-owner/business.md/csv`、`business-media.md/csv`、`tech-ai.md/csv`。
+- `plan-execution-status` 新增透传 `operatorPackOwnerArtifactCount=3`、`operatorPackOwnerArtifacts`、`businessExecutionOperatorPackOwnerArtifactCount=3` 和 `businessExecutionOperatorPackOwnerArtifactRowCount=3`。
+- `plan-execution-status.md` 新增 `proof-gap 操作包负责人附件` 表,并在计划第 3、4 行证据中显示操作包负责人附件计数和商务执行总表附件行数。
+- `latest-form:index` 和 `handoff:summary` 已把计划执行状态面板里的负责人附件字段继续透传到最新索引和交接摘要。
+- `round-deposition` 新增 `business-proof-progress-operator-owner-artifacts`、`plan-execution-status-operator-owner-artifacts` 两个防回退检查,真实 outputs 当前 `passCount=106`、`failCount=0`。
+- `latest-form:index:smoke` 和 `handoff:summary:smoke` 的 fixture 准备顺序已和 `round:refresh` 对齐,先刷新 `plan-execution-status-latest` 再生成 latest/handoff,避免单脚本 smoke 读到旧面板。
+- `docs/tihao-handoff-index.md` 已同步当前结论、阅读顺序、最近复验结果和可说/不可说边界,明确这些字段仍只是执行/审计入口,不是业务证明。
+
+### 已验证
+
+- `node --check scripts\business-proof-progress.js`、`node --check scripts\business-proof-progress-smoke.js` 通过。
+- `node --check scripts\plan-execution-status.js`、`node --check scripts\plan-execution-status-smoke.js` 通过。
+- `node --check scripts\latest-form-index.js`、`node --check scripts\latest-form-index-smoke.js` 通过。
+- `node --check scripts\generate-optimization-handoff.js`、`node --check scripts\generate-optimization-handoff-smoke.js` 通过。
+- `node --check scripts\refresh-round-artifacts.js`、`node --check scripts\refresh-round-artifacts-smoke.js` 通过。
+- `node --check scripts\round-deposition-audit.js`、`node --check scripts\round-deposition-smoke.js` 通过。
+- `npm.cmd run business-proof:progress:smoke` 通过,fixture 输出 `operatorPackOwnerArtifactCount=3` 且 `complete=false`。
+- `npm.cmd run plan:execution-status:smoke` 通过,fixture 输出 `rowCount=8`、`proofOpenCount=5`、`operatorPackOwnerArtifactCount=3`。
+- `npm.cmd run latest-form:index:smoke` 通过,最新表单索引已透传计划执行状态面板的 `operatorPackOwnerArtifactCount=3` 和 `businessExecutionOperatorPackOwnerArtifactRowCount=3`。
+- `npm.cmd run handoff:summary:smoke` 通过,交接摘要顶层和 latest-form 嵌套入口均可读取计划执行状态面板负责人附件字段。
+- `npm.cmd run round:refresh:smoke` 和 `npm.cmd run round:deposition:smoke` 通过,沉淀审计 `passCount=106`、`failCount=0`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,22 步全部 `pass`,真实 outputs 已刷新。
+- `npm.cmd run task:handoff-index:smoke` 通过,任务交接入口仍记录 `openCount=5`、`businessExecutionOperatorPackOwnerArtifactCount=3`、`readyForClaim=false`。
+- `npm.cmd run hygiene:smoke` 通过,`scannedFiles=166`、`hitCount=0`。
+- `npm.cmd run acceptance` 通过,包内容门禁、任务交接入口 smoke、安全审计和 workspace install smoke 均保持通过。
+- 抽读真实 outputs 确认 `businessProofProgress.operatorPackOwnerArtifactCount=3`、`planExecutionStatus.operatorPackOwnerArtifactCount=3`、`latestFormIndex.planExecutionStatus.operatorPackOwnerArtifactCount=3`、`handoff.planExecutionStatus.operatorPackOwnerArtifactCount=3`。
+
+### 当前边界
+
+- 本轮只把 proof-gap 操作包 by-owner 附件继续传播到业务证明进度表和长期计划执行状态面板,减少商务/投放/技术在多个入口间跳转,不补真实业务材料。
+- 当前 `proof-gap-closure.openCount=5`、`optimization-completion.readyForClaim=false`、`longrun:readiness.ready=false`。
+- `business-proof-progress`、`plan-execution-status`、商务执行总表、真实验收材料总包、最新索引、交接摘要和 22 步 refresh 仍都不是业务证明;不得宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-10 交接摘要透传补证进度表负责人附件
+
+### 本轮修正
+
+- `generate-optimization-handoff` 现在读取 `business-proof-progress-summary.json`,不再只在交接摘要里放补证进度表路径。
+- 交接摘要 `businessProofProgress` 新增 `complete`、`counts`、`operatorPackOwnerArtifactCount=3`、`operatorPackOwnerArtifacts`、`businessExecutionOperatorPackOwnerArtifactRowCount=3` 等字段。
+- `optimization-handoff-report.md` 的“补证进度表”区块新增 pass/pending/fail/blocked 统计、负责人附件计数和三份 by-owner 文件表,便于下一轮 AI 只读交接摘要也能定位补证进度派工文件。
+- `handoff:summary:smoke` 已先刷新 `business-proof-progress-latest`,再生成交接摘要,避免读取旧的进度表。
+- `task-handoff-index-smoke` 新增读取真实 `business-proof-progress-summary.json` 和 `plan-execution-status-summary.json`,并校验 handoff/latest 中的负责人附件传播字段。
+- `round-deposition` 新增 `handoff-business-proof-progress-operator-owner-artifacts` 防回退检查,真实 outputs 当前 `passCount=107`、`failCount=0`。
+- `docs/tihao-handoff-index.md` 已同步当前结论、阅读顺序、最近复验结果和可说/不可说边界,明确交接摘要里的进度表负责人附件仍只是补证派工入口。
+
+### 已验证
+
+- `node --check scripts\generate-optimization-handoff.js`、`node --check scripts\generate-optimization-handoff-smoke.js` 通过。
+- `node --check scripts\task-handoff-index-smoke.js` 通过。
+- `node --check scripts\round-deposition-audit.js`、`node --check scripts\round-deposition-smoke.js` 通过。
+- `npm.cmd run handoff:summary:smoke` 通过,交接摘要已透传 `businessProofProgress.operatorPackOwnerArtifactCount=3`。
+- `npm.cmd run round:deposition:smoke` 通过,`passCount=107`、`failCount=0`。
+- `npm.cmd run task:handoff-index:smoke` 通过,输出已记录 `businessProofProgressOperatorPackOwnerArtifactCount=3`、`planExecutionStatusOperatorPackOwnerArtifactCount=3`、`planExecutionStatusBusinessExecutionOperatorPackOwnerArtifactRowCount=3`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,22 步全部 `pass`,真实 outputs 已刷新。
+
+### 当前边界
+
+- 本轮只把补证进度表里的操作包负责人附件继续带入交接摘要和任务交接 smoke,不补真实业务材料。
+- 当前 `proof-gap-closure.openCount=5`、`optimization-completion.readyForClaim=false`、`longrun:readiness.ready=false`。
+- `businessProofProgress`、`optimization-handoff`、`round-deposition` 和 `task-handoff-index` 仍是协调/审计入口,不是业务证明;不得宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-10 交接摘要透传下一步行动队列摘要
+
+### 本轮修正
+
+- `generate-optimization-handoff` 现在把 `business-proof-next-actions-summary.json` 的 `complete`、`actionCount=5`、`ownerGroupCount=3`、`ownerArtifactCount=3`、`sourceState.proofGapOpenCount=5` 和前 5 个高优先级 `topActions` 写入交接摘要。
+- `optimization-handoff-report.md` 的“下一步行动队列”区块现在展示 actionCount、proofGapOpenCount、intake/video/pipeline 状态和 top action 表,下一轮 AI 只读交接摘要也能看到首批补证动作。
+- `handoff:summary:smoke` 会先刷新 `business-proof-next-actions-latest`,再校验 handoff 中的 `step-03` 历史数据补齐动作和 `gap-video-ab-live-proof` 视频 A/B live proof 动作。
+- `round-deposition` 新增 `handoff-business-proof-next-actions-top-actions` 防回退检查,并把 `handoff.businessProofNextActions.count/topActionCount/proofGapOpenCount` 写入 observed 和报告;真实 outputs 当前 `passCount=108`、`failCount=0`。
+- `task-handoff-index-smoke` 新增读取真实 `business-proof-next-actions-summary.json`,校验当前 handoff 和临时生成 handoff 都透传 `actionCount=5`、`proofGapOpenCount=5` 与 topActions。
+- `docs/tihao-handoff-index.md` 已同步当前结论、最近复验结果和可说/不可说边界,明确下一步行动队列仍只是补证派工入口。
+
+### 已验证
+
+- `node --check scripts\generate-optimization-handoff.js`、`node --check scripts\generate-optimization-handoff-smoke.js` 通过。
+- `node --check scripts\round-deposition-audit.js`、`node --check scripts\round-deposition-smoke.js` 通过。
+- `node --check scripts\task-handoff-index-smoke.js` 通过。
+- `npm.cmd run handoff:summary:smoke` 通过,交接摘要已透传 `businessProofNextActions.actionCount=5` 和 `topActions`。
+- `npm.cmd run round:deposition:smoke` 通过,`passCount=108`、`failCount=0`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,22 步全部 `pass`,真实 outputs 已刷新。
+- `npm.cmd run task:handoff-index:smoke` 通过,输出已记录 `businessProofNextActionCount=5`、`handoffBusinessProofNextActionCount=5`。
+- `npm.cmd run round:refresh:smoke` 通过,临时刷新链路的 `roundPassCount=108`。
+- `npm.cmd run hygiene:smoke` 通过,`scannedFiles=166`、`hitCount=0`。
+- `npm.cmd run acceptance` 通过,包内容门禁、任务交接入口 smoke、安全审计和 workspace install smoke 均保持通过。
+
+### 当前边界
+
+- 本轮只让下一步行动队列在交接摘要里更可读、更可审计,不补真实历史 Brief、真实候选视频、live/provider 长跑或客户效果材料。
+- 当前 `proof-gap-closure.openCount=5`、`optimization-completion.readyForClaim=false`、`longrun:readiness.ready=false`。
+- `businessProofNextActions`、`optimization-handoff`、`round-deposition`、`task-handoff-index` 和 22 步 refresh 仍是协调/审计入口,不是业务证明;不得宣称提号率提升、客户效果达标或人工补号量下降。
+## 2026-06-12 推进优化:evidence-index 与 business-proof-progress 拒收薄历史审计
+### 本轮推进
+
+- `evidence:index` 的 `historical-dataset` 分类已复用完整历史证明门槛;`historical-dataset-audit.json` 只写 `acceptance.readyForCustomerEffectProof=true`、`readyForLongRun=true` 或 `briefCount>=5` 不再能进入 `real_evidence`。
+- `business-proof-progress` 的第 7 步“历史数据审计”已改为输出 `missing=<count>`,薄历史审计只能保持 `blocked_by_external_data`,不能因为有审计文件或顶层 ready 字段而误判为 pass。
+- 历史证据现在必须同时具备完整 acceptance 子门槛、5 条以上可审计 `items`、客户选中/拒绝记录、拒绝原因覆盖、人工最终名单覆盖和历史人工补号量基线覆盖,才能支撑客户效果证明。
+- `business-proof:progress:smoke` 新增薄历史审计 fixture:历史导入因文件存在为 pass,但历史审计继续 blocked,锁住“有文件不等于有业务证明”的边界。
+
+### 已验证
+
+- `node --check scripts\evidence-index.js`
+- `node --check scripts\business-proof-progress.js`
+- `node --check scripts\evidence-index-smoke.js`
+- `node --check scripts\business-proof-progress-smoke.js`
+- `npm.cmd run evidence:index:smoke`
+- `npm.cmd run business-proof:progress:smoke`
+- `npm.cmd run round:refresh -- --output-root outputs --strict`
+- `npm.cmd run hygiene:smoke`
+- `npm.cmd run task:handoff-index:smoke`
+- `npm.cmd run acceptance`
+
+### 当前边界
+
+- 最新真实输出仍是 `proof-gap-closure.complete=false`、`proof-gap-closure.openCount=3`、`proof-gap-closure.closedCount=2`。
+- 最新 `optimization-completion` 仍是 `complete=false`、`readyForClaim=false`、`proofGapOpenCount=3`,且 `canClaim.*=false`。
+- 本轮只让证据台账和业务进度表更不容易误收薄证明;仍不能宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-12 推进优化:压制 stale optimization-pipeline 下一步动作
+
+### 本轮推进
+
+- `business-proof:next-actions` 现在读取最新 `optimization-completion-summary.json`,根据 completion 对 `optimization-pipeline` 的阻塞原因判断旧 pipeline 是否 stale。
+- 当 `optimizationPipeline.stale=true` 或 completion 显示 pipeline 不能作为当前证明时,旧 `optimization-pipeline-summary.json.nextActions` 不再进入当前业务派工队列,只在 `sourceState` 中保留原始数量、压制数量和 stale 原因。
+- 最新真实输出中 `businessProofNextActions.actionCount=3`,只保留 `step-03` 真实历史 Brief、`step-12` 人工复核标注和 `gap-video-ab-live-proof` 真实视频 A/B 验收;旧 pipeline 的 3 条 nextActions 已全部被压制。
+- `latest-form:index` 与 `handoff:summary` 已透传 `pipelineStale=true`、`pipelineRawNextActionCount=3`、`pipelineSuppressedNextActionCount=3` 和 `pipelineStaleReason`,下一轮接手时能看到旧 pipeline 为什么不能继续派工。
+- `round-deposition` 新增 `business-proof-next-actions-stale-pipeline-suppression` 防回退检查,避免 stale pipeline 动作再次混入当前行动队列。
+- `latest-form:index:smoke` 与 `handoff:summary:smoke` 已新增直接断言,校验 `pipelineStale=true`、`pipelineNextActionCount=0`、`pipelineRawNextActionCount>=1`、`pipelineSuppressedNextActionCount=raw`、stale reason 和缺失证明要求,并确认 topActions 不含 `optimization-pipeline` 来源。
+- `optimization-handoff-summary.json` 新增顶层 `pipelineStaleSummary`,把 `pipelineStale`、`pipelineReadyForClaim`、raw/suppressed/current pipeline 动作数量、缺失证明要求数量和不可派工边界直接放到交接摘要顶层,避免只扫顶层 JSON 的接手方漏读 `businessProofNextActions.sourceState`。
+
+### 已验证
+
+- `node --check scripts\business-proof-next-actions.js`
+- `node --check scripts\business-proof-next-actions-missing-proof-smoke.js`
+- `node --check scripts\latest-form-index.js`
+- `node --check scripts\generate-optimization-handoff.js`
+- `node --check scripts\round-deposition-audit.js`
+- `node --check scripts\task-handoff-index-smoke.js`
+- `npm.cmd run business-proof:next-actions:smoke`
+- `npm.cmd run business-proof:next-actions:missing-proof:smoke`
+- `npm.cmd run round:deposition:smoke`,当前 `passCount=128`、`failCount=0`。
+- `npm.cmd run task:handoff-index:smoke`
+- `npm.cmd run round:refresh -- --output-root outputs --strict`
+- `npm.cmd run latest-form:index:smoke`
+- `npm.cmd run handoff:summary:smoke`
+- `node --check scripts\generate-optimization-handoff.js`
+- `node --check scripts\generate-optimization-handoff-smoke.js`
+
+### 当前边界
+
+- 最新真实输出仍是 `proof-gap-closure.complete=false`、`proof-gap-closure.openCount=3`、`proof-gap-closure.closedCount=2`。
+- 最新 `optimization-completion` 仍是 `complete=false`、`readyForClaim=false`、`proofGapOpenCount=3`,且 `canClaim.*=false`。
+- 本轮只让下一步行动队列不再被旧 pipeline 误导;不能宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-12 推进优化:proof-gap closure 业务/预审计数显式化
+
+### 本轮推进
+
+- `proof-gap:closure` summary 新增 `businessOpenCount`、`businessClosedCount`、`precheckRowCount`、`precheckOpenCount`、`precheckClosedCount` 和 `countSemantics`,明确 `openCount/closedCount` 只是业务证明缺口兼容别名,不包含 `intake-readiness` 预审行。
+- `proof-gap-closure-report.md` 现在同时展示业务证明缺口计数和预审行计数,避免看到 `rows.length=6` 与 `openCount=3` 时误以为计数不一致。
+- `handoff:summary` 已透传 proof-gap closure 的 business/precheck 计数字段,`optimization-handoff-report.md` 也会显示 `businessOpenCount`、`businessClosedCount`、`precheckOpenCount` 和 `countSemantics.openCount`。
+- `latest-form:index` 新增 `proofGapClosure` 区块,索引页可直接定位 `proof-gap-closure-summary.json/report.md`,并显示 `openCount/businessOpenCount/precheckOpenCount/totalRows`。
+- `proof-gap:closure:smoke`、`handoff:summary:smoke`、`latest-form:index:smoke` 均新增断言,防止后续把 intake 预审误算进业务 openCount,或在交接层丢失计数语义。
+
+### 已验证
+
+- `node --check scripts\proof-gap-closure-audit.js`
+- `node --check scripts\proof-gap-closure-smoke.js`
+- `node --check scripts\generate-optimization-handoff.js`
+- `node --check scripts\generate-optimization-handoff-smoke.js`
+- `node --check scripts\latest-form-index.js`
+- `node --check scripts\latest-form-index-smoke.js`
+- `npm.cmd run proof-gap:closure:smoke`
+- `npm.cmd run handoff:summary:smoke`
+- `npm.cmd run latest-form:index:smoke`
+- `npm.cmd run round:refresh -- --output-root outputs --strict`,28 步全部 `pass`,`round-deposition.passCount=129`、`failCount=0`。
+- `npm.cmd run hygiene:smoke`,`hitCount=0`。
+- `npm.cmd run task:handoff-index:smoke`
+- `npm.cmd run acceptance`
+
+### 当前边界
+
+- 本轮是计数语义和交接可读性优化,不关闭任何业务证明缺口。
+- 当前仍应按 `businessOpenCount/openCount=3` 判断剩余真实业务证明缺口;`precheckOpenCount` 只表示 intake 预审是否仍有未就绪行。
+- 仍不能宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-12 推进优化:round-deposition 锁定 proof-gap 计数语义
+
+### 本轮推进
+
+- 在 `round-deposition` 中新增 `proof-gap-closure-count-semantics` 防回退守卫,要求 `proof-gap-closure-summary.json` 的 `openCount/businessOpenCount` 只统计业务证明缺口,`precheckOpenCount/precheckRowCount` 单独统计 intake 预审行。
+- `round-deposition` observed/report 已新增 standalone 与 handoff 两套计数字段:`businessOpenCount`、`businessClosedCount`、`businessGapCount`、`precheckOpenCount` 和 `countSemantics.openCount`,避免只看沉淀报告或交接摘要时误读计数口径。
+- `handoff-proof-gap-closure` 校验也同步检查 business/precheck 计数和 `countSemantics` 是否与 standalone closure 一致,防止 handoff 丢字段或旧口径回流。
+- `round-deposition:smoke` fixture 与断言已补齐,覆盖新守卫、observed 字段和报告展示字段。
+
+### 已验证
+
+- `node --check scripts\round-deposition-audit.js`
+- `node --check scripts\round-deposition-smoke.js`
+- `npm.cmd run round:deposition:smoke`,当前 `passCount=129`、`failCount=0`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict`,28 步全部 `pass`,最新真实 `round-deposition.passCount=129`、`failCount=0`。
+
+### 当前边界
+
+- 当前仍是 `proof-gap-closure.complete=false`、`businessOpenCount/openCount=3`、`precheckOpenCount=1`、`optimization-completion.readyForClaim=false`。
+- 本轮只是把 proof-gap 计数语义纳入每轮沉淀审计,防止后续把预审行误算进业务缺口;不关闭任何真实业务证明缺口。
+- 仍不能宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-12 推进优化:business-proof next-actions 顶层摘要可审计化
+
+### 本轮推进
+
+- `business-proof:next-actions` summary 顶层新增 `actionCount`、`ownerGroupCount`、`ownerRepairArtifactCount` 和 `topActions`;负责人附件写入后同步补 `ownerArtifactCount`,避免下游只能从 `actions.length`、`ownerGroups.length` 或文件存在性反推行动队列状态。
+- `latest-form:index` 和 `handoff:summary` 已优先读取 next-actions 顶层 `actionCount/topActions/ownerGroupCount/ownerArtifactCount`,没有顶层字段时才兼容回退到旧 `actions` 数组。
+- `round-deposition` 的 `business-proof-next-actions-open` 守卫已收紧:要求顶层 `actionCount` 与 `actions.length` 一致,`topActions` 至少覆盖前 5 个高优先级动作,并且每个 top action 带 owner、title、command 和 acceptance。
+- `business-proof:next-actions:smoke`、`business-proof:next-actions:missing-proof:smoke` 和 `round:deposition:smoke` 已补断言,防止后续 summary 退化为只剩 actions 数组。
+
+### 已验证
+
+- `node --check scripts\business-proof-next-actions.js`
+- `node --check scripts\latest-form-index.js`
+- `node --check scripts\generate-optimization-handoff.js`
+- `node --check scripts\round-deposition-audit.js`
+- `node --check scripts\round-deposition-smoke.js`
+- `npm.cmd run business-proof:next-actions:smoke`
+- `npm.cmd run business-proof:next-actions:missing-proof:smoke`
+- `npm.cmd run latest-form:index:smoke`
+- `npm.cmd run handoff:summary:smoke`
+- `npm.cmd run round:deposition:smoke`,当前 `passCount=129`、`failCount=0`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,28 步全部 `pass`,最新真实 `round-deposition.counts.pass=129`、`counts.fail=0`。
+- `npm.cmd run hygiene:smoke` 通过,`scannedFiles=175`、`hitCount=0`。
+- `npm.cmd run task:handoff-index:smoke` 通过,交接入口仍记录 `openCount=3`、`businessProofNextActionCount=3`、`readyForClaim=false`。
+- `npm.cmd run acceptance` 通过,包级 smoke、任务交接入口 smoke、安全审计、包内容门禁和 workspace install smoke 均保持通过。
+
+### 当前边界
+
+- 最新真实 next-actions summary 已记录 `actionCount=3`、`topActions.length=3`、`ownerGroupCount=2`、`ownerArtifactCount=2`,对应真实历史 Brief、人工复核标注和真实视频 A/B 验收。
+- 本轮只增强行动队列的机器可读性和交接稳定性,不补真实历史 Brief、不生成真实视频 A/B live proof、不关闭客户效果证明。
+- 当前仍是 `proof-gap-closure.complete=false`、`businessOpenCount/openCount=3`、`optimization-completion.readyForClaim=false`,不能宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-12 推进优化:acceptance:video-ab 前置失败可审计化
+
+### 本轮推进
+
+- `acceptance:video-ab` 现在会在缺运行凭证、company、VOC social provider 或视频分析 provider 时先输出 `video-hit-rate-preflight-summary.json` 和 `video-hit-rate-preflight-report.md`,退出码仍为失败,但不再把缺凭证场景表现为一段 stack trace。
+- preflight 失败摘要固定记录 `materialType=video_ab_runtime_preflight`、`proofLevel=not_business_proof`、`canCloseProofGap=false`、`proofContext.generatedBy=acceptance:video-ab-preflight`,并只记录 token 是否存在,不写入 token 明文。
+- `evidence:index` 已新增 `video-ab-runtime-preflight` 分类;即使 `readyForVideoAb=true`,该类型也固定为 `not_business_proof`,必须生成 live `video-hit-rate-summary.json` 才能进入 proof-gap closure。
+- `round-deposition` 新增 `evidence-index-video-ab-runtime-preflight-boundary` 守卫,要求证据台账中的 video A/B runtime preflight 不能替代 `acceptance:video-ab` live proof。
+
+### 已验证
+
+- `node --check scripts\video-hit-rate-audit.js`
+- `node --check scripts\video-hit-rate-audit-smoke.js`
+- `npm.cmd run acceptance:video-ab:smoke`
+- 当前环境执行 `npm.cmd run acceptance:video-ab` 会生成 `outputs\video-hit-rate-preflight-latest\video-hit-rate-preflight-summary.json`,并以失败退出;本轮用退出码 2 作为预期失败验证通过。
+- `node --check scripts\evidence-index.js`
+- `node --check scripts\evidence-index-smoke.js`
+- `npm.cmd run evidence:index:smoke`,当前 fixture `total=43`、`not_business_proof=18`。
+- `node --check scripts\round-deposition-audit.js`
+- `node --check scripts\round-deposition-smoke.js`
+- `npm.cmd run round:deposition:smoke`,当前 `passCount=130`、`failCount=0`。
+- `npm.cmd run round:refresh -- --output-root outputs --strict` 通过,28 步全部 `pass`,最新真实 `round-deposition.passCount=130`、`failCount=0`。
+
+### 当前边界
+
+- 本轮只让真实视频 A/B 的“跑不起来”状态也可审计、可交接,不生成真实 live proof。
+- `outputs/video-hit-rate-preflight-latest/video-hit-rate-preflight-summary.json` 不能关闭 `video-ab-live-proof`,也不能证明视频分析提升提号结果。
+- 当前仍是 `proof-gap-closure.complete=false`、`businessOpenCount/openCount=3`、`optimization-completion.readyForClaim=false`,不能宣称提号率提升、客户效果达标或人工补号量下降。
+## 2026-06-13 推进优化:三个真实缺口材料审计与 1.0.1 patch
+
+### 本轮推进
+
+- 新增 `real-gap:materials` 三缺口材料审计脚本,统一读取 DHA PoC 原始工作簿、`local-seed-to-intake-worklist`、真实视频资源 readiness、`acceptance:video-ab` preflight、经验逐字稿索引和最新 `proof-gap:closure`。
+- 新增 `real-gap:materials:smoke` 并纳入 `npm run acceptance`,固定断言三个缺口都能找到项目材料,同时 `canCloseNow=false`,防止把候选名单、逐字稿经验、preflight 或 readiness 误升格为业务证明。
+- 审计产物写入 `outputs/real-gap-material-audit-latest/`,当前 `gapCount=3`、`materialFoundCount=3`、`openGapCount=3`、`canCloseProofGap=false`。
+- 审计已明确三块材料状态:历史缺口有 DHA 工作簿和 12 行历史草稿但只有 `briefCount=1`;video A/B 有真实候选视频资源和 15 条候选视频 note 线索但缺 live proofContext;客户效果有 12 行人工复核草稿和经验规则但缺真实客户选择、反馈原因、本轮人工补号量和通过的 history audit。
+- `package.json`、`package-lock.json`、`.claude-plugin/plugin.json`、`skill-package-manifest.json` 和 MCP server 版本同步到 `1.0.1`,准备作为 1.0 后的补丁发布。
+- `docs/release-notes.md` 与 `docs/tihao-handoff-index.md` 已补 1.0.1 交接说明,要求后续先读三缺口材料审计再继续补证。
+
+### 已验证
+
+- `npm.cmd run real-gap:materials:smoke`
+- `npm.cmd run real-gap:materials`
+- `npm.cmd run acceptance`
+- `npm.cmd publish --dry-run --access public --registry https://registry.npmjs.org/`
+- `npm.cmd publish --access public --registry https://registry.npmjs.org/`,返回 `+ @vocmarket/tihao-sop@1.0.1`
+- `npm.cmd view @vocmarket/tihao-sop@1.0.1 version --registry https://registry.npmjs.org/`,返回 `1.0.1`
+- `npm.cmd view @vocmarket/tihao-sop dist-tags.latest version --registry https://registry.npmjs.org/`,返回 `latest=1.0.1`
+- `npx.cmd --yes @vocmarket/tihao-sop@latest workspace --smoke`
+
+### 当前边界
+
+- 这轮关闭的是“资料没找到/没接入”的事实问题,不关闭业务证明缺口。
+- 当前仍是 `proof-gap-closure.complete=false`、`businessOpenCount/openCount=3`、`optimization-completion.readyForClaim=false`。
+- 仍不能宣称提号率提升、客户效果达标或人工补号量下降。

+ 253 - 0
claude-code-tihao-sourcing/docs/tihao-experience-optimization-plan.md

@@ -0,0 +1,253 @@
+# 提号经验长期优化计划(中文执行版)
+
+本文用于把商务、投放同事的真实提号经验沉淀到 `@vocmarket/tihao` 技能包中,形成可长期复用的工作流、测试方法和验收标准。当前目标不是宣称“已经提升命中率”,而是持续把每一轮真实证据转成可执行规则,并用自动化审计区分真实业务证明、smoke/local 证明和未证明项。
+
+## 核心判断
+
+提号最难的不是粉丝数、预算、地域、互动量这些硬指标,而是:
+
+- 产品理解:识别 Brief 没写明但商务默认知道的隐性规则。
+- 参考账号拆解:客户给参考博主时,真正有价值的是账号类型、调性、内容结构、场景和表达方式。
+- 主页近期内容判断:人工通常先看主页最近 10-20 篇封面、标题、内容类型和互动,再决定是否深看视频。
+- 视频和图文证据:只有拿到视频 URL、封面、字幕、帧图、正文或 ASR,才能声明完成真实分析。
+- 商务可解释性:输出必须能让商务向客户解释“为什么推荐”和“哪里需要复核”。
+
+## 当前已具备能力
+
+- Brief 解析和 sample/live 两种模式。
+- VOC 电商数据中台 live 调用入口。
+- 参考账号、参考链接识别。
+- 参考风格指纹和多模态证据卡结构。
+- 主页近期内容轻量证据分。
+- 美护、母婴 DHA、敏感肌、食品饮料、家清家居、生活方式等品类隐性规则。
+- Markdown、JSON、CSV 输出。
+- 偏好记忆和反馈导入。
+- no-token、额度不足等友好提示。
+- 软件端 CSV 固定表头、严格去重、连续排名和 Markdown 表格导出。
+- 证据台账:区分 `real_evidence`、`smoke_or_local`、`not_business_proof`。
+
+## 关键差距
+
+| 优先级 | 差距 | 目标 |
+| --- | --- | --- |
+| P0 | 真实主页近期内容 provider 仍需稳定 | 接入最近 10/20 篇内容、封面、标题、互动、发布时间和风险信号。 |
+| P0 | 反馈导入后缺少二轮闭环证明 | 商务标注的拉黑、跑偏、调性不符账号,第二轮必须自动剔除或降权。 |
+| P0 | 视频分析对命中率的提升需要真实 A/B 验证 | 对比无视频证据与有视频证据名单质量,不能只看接口 200。 |
+| P1 | 参考账号相似链路需要更强 | 小红书优先走相似账号链路;抖音单独走星图、关键词和招募策略。 |
+| P1 | 团队规则升级机制不足 | 同类反馈连续出现 3 次以上,才建议升级为团队规则。 |
+| P1 | 软件端表格需要持续稳定 | 表头固定、中文不乱码、重复键为 0、排名连续。 |
+
+## 长跑优化任务
+
+### 数据准备
+
+准备 5-10 个真实历史 Brief,覆盖:
+
+- 美容仪、脱毛仪。
+- 母婴 DHA。
+- 敏感肌、护肤修护。
+- 食品饮料、健康零食。
+- 家清家居。
+- 生活方式、高质感女性场景。
+
+每个 Brief 需要包含:
+
+- 客户原始 Brief。
+- 参考账号或参考视频。
+- 人工最终提报名单。
+- 客户最终选中、拒绝或待反馈记录。
+- 拒绝原因或商务复核标签。
+- 历史人工补号量基线;如果缺失,只能做流程验证,不能证明人工补号减少。
+
+历史数据可先按 CSV 整理,再转成 JSON 数据集:
+
+```powershell
+npm run history:from-csv -- --input <历史数据CSV> --output <history-dataset目录>
+npm run history:audit -- --input <history-dataset目录> --output <审计输出目录> --strict
+```
+
+### 策略矩阵
+
+每个 Brief 跑以下策略:
+
+| 策略 | 说明 |
+| --- | --- |
+| baseline-live | 只用基础 live 召回和硬性规则。 |
+| reference-account | 增加参考账号拆解和相似度判断。 |
+| homepage-evidence | 增加主页最近内容证据。 |
+| video-enhanced | 增加参考视频和候选视频证据。 |
+| result-first | 先生成结果,再用证据和规则重排。 |
+| result-first-broad | 扩大召回池后再重排。 |
+
+### 每轮输出
+
+每轮必须输出:
+
+- `tihao-sourcing-report.md`
+- `tihao-sourcing-result.json`
+- `tihao-sourcing-client-list.csv`
+- 软件端去重表。
+- 最新提号表单索引:集中指向补证表单、软件端候选名单和负责人行动文件。
+- 重复键统计。
+- 人工复核标签统计。
+- 失败样本归因。
+- 如果汇总多个策略或多个 Brief,必须输出软件端去重表;同一 Brief 内排名从 1 连续。
+
+## 软件端表格验收
+
+固定表头见 `docs/software-client-table-format.md`。
+
+硬性要求:
+
+- CSV 使用 UTF-8 BOM,Excel 打开中文不乱码。
+- 表头固定为软件端格式。
+- 软件端交付表按“全局博主唯一”去重,同一 `平台 + 规范化主页链接` 不重复。
+- 没有主页链接时,同一 `平台 + 规范化博主名称` 不重复。
+- 同一博主如被多个 Brief 或多个策略召回,只保留证据更完整、综合分更高的一条进入交付表。
+- 已剔除账号不进入软件端交付表。
+- 去重和剔除后,排名从 1 开始连续。
+- 推荐理由能解释 Brief 匹配、参考风格或主页证据。
+- 风险提示能说明具体待复核事项。
+- Markdown 表格和 CSV 字段一致,适合软件端或商务文档引用。
+
+## 质量验收标准
+
+### 基础链路
+
+- `npm run acceptance` 通过。
+- `npm run acceptance:providers:mock` 通过。
+- `npm run smoke:package` 通过。
+- 输出不泄露 Parse sessionToken、模型 token、Authorization、npm token。
+- live/no-token/403/401 返回友好状态,不暴露原始错误、堆栈或密钥。
+
+### 硬性规则
+
+- 硬性约束违规率 = 0。
+- 明确剔除账号不得进入软件端交付表。
+- 粉丝、地域、预算、平台不符的账号不得标为强推荐。
+- 美容仪、脱毛仪默认偏女性使用场景,男性泛账号不得标为强推荐,除非 Brief 明确允许。
+- 多参考账号风格冲突时,不得强行合成一个统一参考标准。
+
+### 风格调性
+
+- 每个强推荐账号至少有 2 条 Brief 命中点。
+- 每个强推荐账号至少有 1 条参考风格或主页证据命中点,并在 JSON 中结构化保留 `referenceStyleHitPoints` 或 `homepageEvidenceHitPoints`。
+- `需补相似账号证据`、`待补参考风格证据` 等占位提示只能进入 `referenceFallbackHitPoints`,不得写入 `referenceStyleHitPoints`,也不得单独支撑强推荐。
+- 候选 JSON 必须保留 `referenceEvidenceConcrete`;强推荐必须有真实参考信号或主页证据,不能只依赖 fallback reference hint。
+- 有参考账号时,强推荐必须说明“类型相似”或“调性相似”的依据。
+- Top 10 负样本风险率低于 10%。
+- 明显下沉、封面混乱、质感不符账号不得标为强推荐;候选 JSON 应保留 `homepageQualityRisks`,风险提示应说明主页质感问题。
+
+### 视频和多模态
+
+- 有参考视频时,必须尝试 VOC social/TikHub 视频详情和豆包视频分析。
+- 拿到视频 URL、封面、字幕、ASR、帧图或正文后,才能声明视频分析完成。
+- 没有真实资源时,只能写“待补视频证据”或“待补帧图证据”。
+- A/B 测试中,`video-enhanced` 不得低于 baseline:
+  - 强推荐数量不下降。
+  - Top 10 平均综合分不下降。
+  - Top 10 平均参考风格分不下降。
+  - 证据命中候选数量增加。
+  - 输出 token 泄露为 0。
+
+### 业务效果
+
+短期目标:
+
+- “可直接发客户 + 商务复核”占比达到 60% 以上。
+- 负样本率低于 10%。
+- 负样本归因覆盖率达到 100%。
+- 每个 Brief 至少产出目标数量 1.5 倍的可复核候选。
+
+中期目标:
+
+- 客户选中率达到 30% 以上。
+- 有参考账号的 Brief,客户选中率达到 40% 以上。
+- 参考账号链路召回的候选,通过率高于纯关键词召回。
+- `review:metrics` 必须输出分策略指标和参考链路对照;缺少对照组时,只能标记为待补对照组。
+
+长期目标:
+
+- 客户选中率稳定达到 50% 以上。
+- 同等客户需求下,人工补号量减少 50%。
+- 每轮客户反馈都能沉淀为规则或偏好,下一轮负样本率下降。
+
+客户效果证明必须使用独立审计:
+
+```powershell
+npm run customer-effect:audit -- --review-csv <已标注CSV> --history-audit <historical-dataset-audit.json> --current-manual-supplement-count <本轮人工补号量> --output <输出目录>
+```
+
+只有 `customer-effect-report.md` 中客户选中率、参考链路客户选中率、历史人工补号基线、本轮人工补号量和人工补号减少率全部通过,才可以声明业务效果达标。
+
+## 给下一轮 AI 的任务
+
+目标:优化 `@vocmarket/tihao` 的提号命中率和商务可用率,让上传 Brief 后能稳定输出商务可复核、可解释、可沉淀的博主名单。
+
+输入:
+
+1. 5-10 个真实历史 Brief。
+2. 每个 Brief 的参考账号或参考视频。
+3. 人工最终名单和客户最终选中/拒绝记录。
+4. 拒绝原因或商务复核标签。
+5. 历史人工补号量基线。
+
+任务:
+
+1. 跑 `baseline-live`、`reference-account`、`homepage-evidence`、`video-enhanced`、`result-first`、`result-first-broad` 策略矩阵。
+2. 每轮输出 Markdown、JSON、软件端 CSV。
+3. 检查重复键、乱码、token 泄露和排名连续性。
+4. 对比每个策略的强推荐数量、Top 10 平均分、参考风格分、主页证据覆盖率、人工复核通过率、负样本率。
+5. 把失败样本归因为:需求解析错、隐性规则漏、召回关键词错、主页证据不足、视频证据误判、排序权重错、输出解释错、软件端表重复或排名不连续。
+6. 每轮只优化一个主要失败原因,重跑矩阵并记录提升。
+
+通过标准:
+
+1. `failureCount=0`。
+2. 每个 Brief 至少一个策略通过。
+3. leak scan=0。
+4. 软件端表重复键为 0。
+5. 每个 Brief 排名连续。
+6. Top 10 负样本率低于 10%。
+7. 负样本归因覆盖率=100%。
+8. 人工复核通过率相对 baseline 提升 20% 以上。
+9. 有参考账号的 Brief,`reference-account` 或 `homepage-evidence` 策略优于 brief-only。
+10. 优化结论能沉淀为明确规则、权重或工作流变更。
+11. `customer-effect:audit` 通过;缺少客户选择、历史人工补号基线或本轮人工补号量时,不得宣称客户效果完成。
+
+## 每轮沉淀
+
+每轮结束优先运行:
+
+```powershell
+npm run round:refresh -- --output-root outputs --strict
+```
+
+刷新后检查 `outputs/latest-form-index-latest/latest-form-index.md`。它只用于定位最新表单和行动文件,不证明客户效果完成。
+
+真实历史数据和客户复核表都到位后,可用 pipeline 串起本地审计闭环:
+
+```powershell
+npm run optimization:pipeline -- --history-csv <历史数据CSV> --review-csv <已标注CSV> --current-manual-supplement-count <本轮人工补号量> --output <输出目录> --strict
+```
+
+如果商务/投放直接填写的是统一收集包,优先使用 data-pack/video-pack 入口:
+
+```powershell
+npm run optimization:pipeline -- --data-pack outputs\data-intake-pack-latest --video-pack outputs\video-intake-pack-latest --current-manual-supplement-count <本轮人工补号量> --output <输出目录> --strict
+```
+
+必须记录:
+
+- 运行命令。
+- 输出目录。
+- 是否 live。
+- 是否打开严格 provider 门槛。
+- 是否真实视频资源。
+- `failureCount`、`failedGateCount`、`overallPass`。
+- 软件端重复键和排名连续性。
+- 人工复核指标或客户选中率。
+- 仍未证明的边界。
+
+如果状态里仍有 `ready_not_proven` 或 `blocked_by_external_data`,不得宣布长期优化目标完成。
+

+ 253 - 0
claude-code-tihao-sourcing/docs/tihao-experience-optimization-plan.zh-CN.md

@@ -0,0 +1,253 @@
+# 提号经验长期优化计划(中文执行版)
+
+本文用于把商务、投放同事的真实提号经验沉淀到 `@vocmarket/tihao` 技能包中,形成可长期复用的工作流、测试方法和验收标准。当前目标不是宣称“已经提升命中率”,而是持续把每一轮真实证据转成可执行规则,并用自动化审计区分真实业务证明、smoke/local 证明和未证明项。
+
+## 核心判断
+
+提号最难的不是粉丝数、预算、地域、互动量这些硬指标,而是:
+
+- 产品理解:识别 Brief 没写明但商务默认知道的隐性规则。
+- 参考账号拆解:客户给参考博主时,真正有价值的是账号类型、调性、内容结构、场景和表达方式。
+- 主页近期内容判断:人工通常先看主页最近 10-20 篇封面、标题、内容类型和互动,再决定是否深看视频。
+- 视频和图文证据:只有拿到视频 URL、封面、字幕、帧图、正文或 ASR,才能声明完成真实分析。
+- 商务可解释性:输出必须能让商务向客户解释“为什么推荐”和“哪里需要复核”。
+
+## 当前已具备能力
+
+- Brief 解析和 sample/live 两种模式。
+- VOC 电商数据中台 live 调用入口。
+- 参考账号、参考链接识别。
+- 参考风格指纹和多模态证据卡结构。
+- 主页近期内容轻量证据分。
+- 美护、母婴 DHA、敏感肌、食品饮料、家清家居、生活方式等品类隐性规则。
+- Markdown、JSON、CSV 输出。
+- 偏好记忆和反馈导入。
+- no-token、额度不足等友好提示。
+- 软件端 CSV 固定表头、严格去重、连续排名和 Markdown 表格导出。
+- 证据台账:区分 `real_evidence`、`smoke_or_local`、`not_business_proof`。
+
+## 关键差距
+
+| 优先级 | 差距 | 目标 |
+| --- | --- | --- |
+| P0 | 真实主页近期内容 provider 仍需稳定 | 接入最近 10/20 篇内容、封面、标题、互动、发布时间和风险信号。 |
+| P0 | 反馈导入后缺少二轮闭环证明 | 商务标注的拉黑、跑偏、调性不符账号,第二轮必须自动剔除或降权。 |
+| P0 | 视频分析对命中率的提升需要真实 A/B 验证 | 对比无视频证据与有视频证据名单质量,不能只看接口 200。 |
+| P1 | 参考账号相似链路需要更强 | 小红书优先走相似账号链路;抖音单独走星图、关键词和招募策略。 |
+| P1 | 团队规则升级机制不足 | 同类反馈连续出现 3 次以上,才建议升级为团队规则。 |
+| P1 | 软件端表格需要持续稳定 | 表头固定、中文不乱码、重复键为 0、排名连续。 |
+
+## 长跑优化任务
+
+### 数据准备
+
+准备 5-10 个真实历史 Brief,覆盖:
+
+- 美容仪、脱毛仪。
+- 母婴 DHA。
+- 敏感肌、护肤修护。
+- 食品饮料、健康零食。
+- 家清家居。
+- 生活方式、高质感女性场景。
+
+每个 Brief 需要包含:
+
+- 客户原始 Brief。
+- 参考账号或参考视频。
+- 人工最终提报名单。
+- 客户最终选中、拒绝或待反馈记录。
+- 拒绝原因或商务复核标签。
+- 历史人工补号量基线;如果缺失,只能做流程验证,不能证明人工补号减少。
+
+历史数据可先按 CSV 整理,再转成 JSON 数据集:
+
+```powershell
+npm run history:from-csv -- --input <历史数据CSV> --output <history-dataset目录>
+npm run history:audit -- --input <history-dataset目录> --output <审计输出目录> --strict
+```
+
+### 策略矩阵
+
+每个 Brief 跑以下策略:
+
+| 策略 | 说明 |
+| --- | --- |
+| baseline-live | 只用基础 live 召回和硬性规则。 |
+| reference-account | 增加参考账号拆解和相似度判断。 |
+| homepage-evidence | 增加主页最近内容证据。 |
+| video-enhanced | 增加参考视频和候选视频证据。 |
+| result-first | 先生成结果,再用证据和规则重排。 |
+| result-first-broad | 扩大召回池后再重排。 |
+
+### 每轮输出
+
+每轮必须输出:
+
+- `tihao-sourcing-report.md`
+- `tihao-sourcing-result.json`
+- `tihao-sourcing-client-list.csv`
+- 软件端去重表。
+- 最新提号表单索引:集中指向补证表单、软件端候选名单和负责人行动文件。
+- 重复键统计。
+- 人工复核标签统计。
+- 失败样本归因。
+- 如果汇总多个策略或多个 Brief,必须输出软件端去重表;同一 Brief 内排名从 1 连续。
+
+## 软件端表格验收
+
+固定表头见 `docs/software-client-table-format.md`。
+
+硬性要求:
+
+- CSV 使用 UTF-8 BOM,Excel 打开中文不乱码。
+- 表头固定为软件端格式。
+- 软件端交付表按“全局博主唯一”去重,同一 `平台 + 规范化主页链接` 不重复。
+- 没有主页链接时,同一 `平台 + 规范化博主名称` 不重复。
+- 同一博主如被多个 Brief 或多个策略召回,只保留证据更完整、综合分更高的一条进入交付表。
+- 已剔除账号不进入软件端交付表。
+- 去重和剔除后,排名从 1 开始连续。
+- 推荐理由能解释 Brief 匹配、参考风格或主页证据。
+- 风险提示能说明具体待复核事项。
+- Markdown 表格和 CSV 字段一致,适合软件端或商务文档引用。
+
+## 质量验收标准
+
+### 基础链路
+
+- `npm run acceptance` 通过。
+- `npm run acceptance:providers:mock` 通过。
+- `npm run smoke:package` 通过。
+- 输出不泄露 Parse sessionToken、模型 token、Authorization、npm token。
+- live/no-token/403/401 返回友好状态,不暴露原始错误、堆栈或密钥。
+
+### 硬性规则
+
+- 硬性约束违规率 = 0。
+- 明确剔除账号不得进入软件端交付表。
+- 粉丝、地域、预算、平台不符的账号不得标为强推荐。
+- 美容仪、脱毛仪默认偏女性使用场景,男性泛账号不得标为强推荐,除非 Brief 明确允许。
+- 多参考账号风格冲突时,不得强行合成一个统一参考标准。
+
+### 风格调性
+
+- 每个强推荐账号至少有 2 条 Brief 命中点。
+- 每个强推荐账号至少有 1 条参考风格或主页证据命中点,并在 JSON 中结构化保留 `referenceStyleHitPoints` 或 `homepageEvidenceHitPoints`。
+- `需补相似账号证据`、`待补参考风格证据` 等占位提示只能进入 `referenceFallbackHitPoints`,不得写入 `referenceStyleHitPoints`,也不得单独支撑强推荐。
+- 候选 JSON 必须保留 `referenceEvidenceConcrete`;强推荐必须有真实参考信号或主页证据,不能只依赖 fallback reference hint。
+- 有参考账号时,强推荐必须说明“类型相似”或“调性相似”的依据。
+- Top 10 负样本风险率低于 10%。
+- 明显下沉、封面混乱、质感不符账号不得标为强推荐;候选 JSON 应保留 `homepageQualityRisks`,风险提示应说明主页质感问题。
+
+### 视频和多模态
+
+- 有参考视频时,必须尝试 VOC social/TikHub 视频详情和豆包视频分析。
+- 拿到视频 URL、封面、字幕、ASR、帧图或正文后,才能声明视频分析完成。
+- 没有真实资源时,只能写“待补视频证据”或“待补帧图证据”。
+- A/B 测试中,`video-enhanced` 不得低于 baseline:
+  - 强推荐数量不下降。
+  - Top 10 平均综合分不下降。
+  - Top 10 平均参考风格分不下降。
+  - 证据命中候选数量增加。
+  - 输出 token 泄露为 0。
+
+### 业务效果
+
+短期目标:
+
+- “可直接发客户 + 商务复核”占比达到 60% 以上。
+- 负样本率低于 10%。
+- 负样本归因覆盖率达到 100%。
+- 每个 Brief 至少产出目标数量 1.5 倍的可复核候选。
+
+中期目标:
+
+- 客户选中率达到 30% 以上。
+- 有参考账号的 Brief,客户选中率达到 40% 以上。
+- 参考账号链路召回的候选,通过率高于纯关键词召回。
+- `review:metrics` 必须输出分策略指标和参考链路对照;缺少对照组时,只能标记为待补对照组。
+
+长期目标:
+
+- 客户选中率稳定达到 50% 以上。
+- 同等客户需求下,人工补号量减少 50%。
+- 每轮客户反馈都能沉淀为规则或偏好,下一轮负样本率下降。
+
+客户效果证明必须使用独立审计:
+
+```powershell
+npm run customer-effect:audit -- --review-csv <已标注CSV> --history-audit <historical-dataset-audit.json> --current-manual-supplement-count <本轮人工补号量> --output <输出目录>
+```
+
+只有 `customer-effect-report.md` 中客户选中率、参考链路客户选中率、历史人工补号基线、本轮人工补号量和人工补号减少率全部通过,才可以声明业务效果达标。
+
+## 给下一轮 AI 的任务
+
+目标:优化 `@vocmarket/tihao` 的提号命中率和商务可用率,让上传 Brief 后能稳定输出商务可复核、可解释、可沉淀的博主名单。
+
+输入:
+
+1. 5-10 个真实历史 Brief。
+2. 每个 Brief 的参考账号或参考视频。
+3. 人工最终名单和客户最终选中/拒绝记录。
+4. 拒绝原因或商务复核标签。
+5. 历史人工补号量基线。
+
+任务:
+
+1. 跑 `baseline-live`、`reference-account`、`homepage-evidence`、`video-enhanced`、`result-first`、`result-first-broad` 策略矩阵。
+2. 每轮输出 Markdown、JSON、软件端 CSV。
+3. 检查重复键、乱码、token 泄露和排名连续性。
+4. 对比每个策略的强推荐数量、Top 10 平均分、参考风格分、主页证据覆盖率、人工复核通过率、负样本率。
+5. 把失败样本归因为:需求解析错、隐性规则漏、召回关键词错、主页证据不足、视频证据误判、排序权重错、输出解释错、软件端表重复或排名不连续。
+6. 每轮只优化一个主要失败原因,重跑矩阵并记录提升。
+
+通过标准:
+
+1. `failureCount=0`。
+2. 每个 Brief 至少一个策略通过。
+3. leak scan=0。
+4. 软件端表重复键为 0。
+5. 每个 Brief 排名连续。
+6. Top 10 负样本率低于 10%。
+7. 负样本归因覆盖率=100%。
+8. 人工复核通过率相对 baseline 提升 20% 以上。
+9. 有参考账号的 Brief,`reference-account` 或 `homepage-evidence` 策略优于 brief-only。
+10. 优化结论能沉淀为明确规则、权重或工作流变更。
+11. `customer-effect:audit` 通过;缺少客户选择、历史人工补号基线或本轮人工补号量时,不得宣称客户效果完成。
+
+## 每轮沉淀
+
+每轮结束优先运行:
+
+```powershell
+npm run round:refresh -- --output-root outputs --strict
+```
+
+刷新后检查 `outputs/latest-form-index-latest/latest-form-index.md`。它只用于定位最新表单和行动文件,不证明客户效果完成。
+
+真实历史数据和客户复核表都到位后,可用 pipeline 串起本地审计闭环:
+
+```powershell
+npm run optimization:pipeline -- --history-csv <历史数据CSV> --review-csv <已标注CSV> --current-manual-supplement-count <本轮人工补号量> --output <输出目录> --strict
+```
+
+如果商务/投放直接填写的是统一收集包,优先使用 data-pack/video-pack 入口:
+
+```powershell
+npm run optimization:pipeline -- --data-pack outputs\data-intake-pack-latest --video-pack outputs\video-intake-pack-latest --current-manual-supplement-count <本轮人工补号量> --output <输出目录> --strict
+```
+
+必须记录:
+
+- 运行命令。
+- 输出目录。
+- 是否 live。
+- 是否打开严格 provider 门槛。
+- 是否真实视频资源。
+- `failureCount`、`failedGateCount`、`overallPass`。
+- 软件端重复键和排名连续性。
+- 人工复核指标或客户选中率。
+- 仍未证明的边界。
+
+如果状态里仍有 `ready_not_proven` 或 `blocked_by_external_data`,不得宣布长期优化目标完成。
+

+ 235 - 0
claude-code-tihao-sourcing/docs/tihao-handoff-index.md

@@ -0,0 +1,235 @@
+# 提号任务交接入口
+
+本文是下一轮 AI、商务或技术同事接手 `@vocmarket/tihao-sop` 长期优化任务时的第一入口。先看这里,再按链接进入具体表单和报告。
+
+## 当前结论
+
+- 2026-06-13 1.0.2 收尾发布完成:`@vocmarket/tihao-sop@1.0.2` 已发布到 npm,registry 已验证 `dist-tags.latest=1.0.2`,fresh `npx --yes @vocmarket/tihao-sop@latest workspace --smoke` 已通过。客户现在下载到的是主链路已接入 `real-gap-material-audit` 的 latest 版本。
+- 2026-06-13 1.0.2 主链路加固:`round:refresh` 现在包含 29 步,并在 `local-seed-to-intake-worklist` 后执行 `real-gap-material-audit`;latest form、handoff、evidence index 和 round-deposition 都会传播或守卫该审计结果,其中机器字段为 `realGapMaterialAudit`。
+- 2026-06-13 1.0.2 边界确认:三缺口材料审计当前 `gapCount=3`、`materialFoundCount=3`、`openGapCount=3`、`canCloseProofGap=false`。这只说明项目材料已找到并接入审计链路,不关闭业务证明缺口。
+- 2026-06-13 1.0.1 补丁发布完成:`@vocmarket/tihao-sop@1.0.1` 已发布到 npm,发布时 registry 已验证 `dist-tags.latest=1.0.1`,fresh `npx --yes @vocmarket/tihao-sop@latest workspace --smoke` 已通过;当前 latest 已由 1.0.2 接管。
+- 2026-06-13 1.0.1 补丁推进:新增 `real-gap:materials` 三缺口材料审计,当前输出 `gapCount=3`、`materialFoundCount=3`、`openGapCount=3`、`canCloseProofGap=false`。这证明项目资料已找到并接入审计底稿,但三个真实业务证明缺口仍不能关闭。
+- 2026-06-13 三缺口真实资料核对:`historical-dataset` 已连接 DHA Brief/PoC/TikHub 工作簿、`historyDraftRows=12` 和逐字稿中的 DHA 提报/未反馈事实;`video-ab-live-proof` 已连接 `videoResourceReady=true`、`realCandidateRows=1`、`realReferenceRows=1` 和 15 条候选视频 note 线索;`manual-review-and-customer-effect` 已连接 `manualReviewDraftRows=12`、客户选中率目标和人工复核经验规则。
+- 2026-06-13 仍未关闭的真实字段:历史缺口仍缺 `briefCount>=5`、客户选中/拒绝、拒绝原因、历史人工补号量;video A/B 仍缺 live `proofContext.generatedBy=acceptance:video-ab`、runtime credential、company、VOC social provider、video analysis provider;客户效果仍缺真实人工复核标签、客户选择、反馈原因、本轮人工补号量和通过的 history audit。
+- 2026-06-13 1.0 正式发布完成:`@vocmarket/tihao-sop@1.0.0` 已发布到 npm,发布时 registry 已验证 `dist-tags.latest=1.0.0`,`npx --yes @vocmarket/tihao-sop@latest workspace --smoke` 已通过;当前 latest 已由 1.0.2 接管。
+- 2026-06-13 资料桥接补强:项目资料已被找到并接入 `local-seed-to-intake-worklist`,最新 `proof-gap:closure` open 行带有 `materialBridge`。`historical-dataset` 连接 `history-intake-draft.csv` 12 行,`video-ab-live-proof` 连接 `video-resource-worklist.csv` 且保留 4 条参考种子与 3 条候选视频种子,`manual-review-and-customer-effect` 连接 `manual-review-draft.csv` 12 行。
+- 2026-06-13 边界复核:资料桥接不等于业务证明。历史草稿仍缺 5 个以上真实历史 Brief、客户选中/拒绝、拒绝原因和历史人工补号基线;视频 A/B 仍缺真实运行凭证/company/VOC social provider/video analysis provider 生成 `proofContext.generatedBy=acceptance:video-ab`;人工复核草稿仍缺真实标注、客户选择、归因、反馈原因和本轮人工补号量。
+- 2026-06-12 补强记录:`round-deposition` 已新增 3 个派工链路 video A/B preflight 守卫:`proof-gap-operator-pack-video-ab-preflight-context`、`real-proof-intake-bundle-video-ab-preflight-context`、`real-proof-closure-work-order-video-ab-preflight-context`,并新增 closure/completion 递归证明产物发现守卫。该轮记录的沉淀计数为 `round-deposition.passCount=137`、`failCount=0`;当前最新计数见 1.0.2 记录。
+- 2026-06-12 最新补强:history/proof-gap/pipeline 防薄摘要门槛已加硬。`historical-dataset-audit.json` 不能只写 `readyForCustomerEffectProof=true`;还必须带完整 acceptance 子门槛、5 条以上可审计 item 明细、客户选中/拒绝、拒绝原因、历史人工补号基线等覆盖字段。`optimization:completion` 也不再接受薄 `proof-gap-closure-summary.json` 或薄 `business-proof-progress-summary.json`。
+- 2026-06-12 最新补强:`proof-gap:closure` 和 `optimization:completion` 已收紧 customer-effect 证明门槛;薄的 `customer-effect-summary.json` 只写 `acceptance.overallPass=true` 不再能关闭 `manual-review-and-customer-effect`,customer-effect 还必须绑定当前通过的 `historical-dataset-audit.json`,并带客户选择、参考链路、历史人工补号基线、本轮人工补号量和人工补号减少率字段。
+- 2026-06-12 最新补强:`round-deposition` 已新增 `proof-gap-closure-count-semantics` 守卫,沉淀 `proofGapClosure.businessOpenCount/precheckOpenCount/countSemantics` 及 handoff 同步字段,防止后续把 intake 预审行误算进业务证明缺口。
+- 2026-06-12 最新补强:`business-proof:next-actions` summary 已顶层输出 `actionCount/topActions/ownerGroupCount/ownerArtifactCount`,`latest-form:index`、`handoff:summary` 和 `round-deposition` 均优先读取并校验这些机器可读字段,避免只靠 `actions.length` 间接推断行动队列。
+- 2026-06-12 最新补强:`acceptance:video-ab` 缺运行凭证或 provider 时会输出 `video-hit-rate-preflight-summary.json/report.md`,`evidence:index` 将其归类为 `video-ab-runtime-preflight` 且固定 `not_business_proof`;`round-deposition` 已守住该 preflight 不能替代 live `video-hit-rate-summary.json`。
+- 2026-06-12 最新补强:`proof-gap:closure`、`optimization:completion`、`latest-form:index`、`business-execution:index`、`handoff:summary` 和 `round-deposition` 已同步消费 video A/B runtime preflight 失败摘要作为非证明上下文;当前 `videoAbPreflightReadyForVideoAb=false`、`videoAbPreflightFailureCount=4`,只用于解释 live proof 缺口,不能关闭 `video-ab-live-proof`。
+- 2026-06-12 最新补强:`proof-gap-operator-pack:smoke`、`real-proof-intake-bundle:smoke` 和 `real-proof:work-order:smoke` 已锁住 `video-ab-live-proof` 在派工包、真实材料总包、闭环工单、CSV 和 `by-owner/tech-ai.md` 中的 preflight 失败上下文;闭环工单 Markdown 新增“缺失证明要求”列,避免技术/AI 接手时只看到当前证据却漏掉 live proof 条件。
+- 当前优化方向符合提号需求:工作重心仍是把真实 Brief、真实视频资源、真实视频 A/B、商务复核和客户效果证明纳入可审计闭环,没有把候选名单、worklist、闭环工单、本地种子材料或 smoke 结果误升格为业务效果证明。
+- 当前长期优化目标仍未完成:`proof-gap-closure.openCount=3`,`proof-gap-closure.closedCount=2`,`optimization-handoff.complete=false`,`optimization-completion.readyForClaim=false`,`longrun:readiness.ready=false`。
+- 当前 `proof-gap-closure.openCount` 是业务证明缺口计数,等同于 `businessOpenCount=3`;`intake-readiness` 预审行单独用 `precheckOpenCount=1` 记录,不额外增加业务 open 缺口。
+- 已关闭的真实证明缺口有 2 个:`video-real-candidate` 和 `live-provider-overnight-proof`。其中真实候选视频资源已满足 `readyForVideoAbPreflight=true`、`failureCount=0`、`realCandidateRows=1`;真实 live/provider 长跑证明已由 `outputs/overnight-quality-1780682025651/aggregate-summary.json` 关闭。
+- 最新 `optimization-status` 已与 proof-gap 口径对齐:`optimization-status.counts.passed=31`、`ready_not_proven=2`、`blocked_by_external_data=1`;`optimization-handoff.externalBlockers=1`,仅剩 `customer-effect-proof`;`live-provider-overnight-proof` 不再出现在 `externalBlockers`,但仍不能替代客户效果证明。
+- 最新 `evidence:index` 已加 pipeline-scoped customer-effect 防误判:位于 `optimization-pipeline` 目录内的 `customer-effect-summary.json` 只有在 pipeline `readyForClaim=true` 且 `proofGapClosure.complete=true/openCount=0` 时才可作为 `real_evidence`;否则保持 `not_business_proof` 并暴露 `pipelineBlocked`,不能绕过当前 completion 审计。
+- 剩余 3 个真实业务证明缺口是:真实历史 Brief 数据集、真实视频 A/B live proof、商务复核和客户效果证明。
+- 旧 `video-hit-rate-summary.json` 虽然出现过 `acceptance.passed=true`,但当前证明缺口仍显示 `proofContext.mode=unknown`、`proofContext.collectionMode=unknown`、`proofContext.generatedBy=unknown`;必须重跑 `npm run acceptance:video-ab` 并生成 `proofContext.generatedBy=acceptance:video-ab` 的 live 产物,不能用旧 A/B 结果关闭真实视频 A/B 缺口。
+- 仍然不能宣称提号率提升,不能宣称客户效果达标,不能宣称人工补号量下降。当前所有协调表、审计表、操作包和交接摘要都不是业务证明。
+
+## 已到位与未到位
+
+- 已到位:视频资源证明从待补变为可进入视频 A/B 前置,`video-resource-readiness` 当前 `readyForVideoAbPreflight=true`、`failureCount=0`、`realCandidateRows=1`。
+- 已到位:`proof-gap-operator-pack` 已稳定输出 5 个 `gapIds=historical-dataset,video-real-candidate,video-ab-live-proof,live-provider-overnight-proof,manual-review-and-customer-effect`,当前 `openCount=3`,并记录 `fillFileCount=3`、`recheckCommandCount=9`、`ownerGroupCount=2`、`ownerArtifactCount=2`、`evidenceRowCount=5`、`nextActionCount=5`、`missingProofRequirementCount=38`。
+- 已到位:`proof-gap-request` 已按 `proof-gap-closure` 当前状态重算,`unresolvedCount=3`;已关闭的 `video-real-candidate` 和 `live-provider-overnight-proof` 保留为 `ready` 证据链,不再回到待补队列。
+- 已到位:`optimization-status` 不再只扫描最近 80 个输出目录,能识别较旧但可关闭的 live/provider aggregate;当前 `optimization-handoff.readyCapabilities=31`、`readyNotProven=video-ab-proof,historical-dataset`、`externalBlockers=customer-effect-proof`。
+- 已到位:`evidence:index` 区分 standalone customer-effect 与未完成 pipeline 内嵌 customer-effect;旧 `optimization-pipeline-latest` 或 open proof-gap 下的客户效果摘要不能被误升格为当前业务证明。
+- 已到位:`real-proof-intake-bundle` 已透传 `proofOpenCount=3`、`operatorPackEvidenceRowCount=5`、`operatorPackNextActionCount=5`、`operatorPackOwnerArtifactCount=2`,继续保留 `proofLevel=not_business_proof`。
+- 已到位:`real-proof-closure-work-order` 已更新为 `workOrderCount=13`、`realProofClosureWorkOrder.ownerArtifactCount=2`、`proofGapOpenCount=3`、`intakeFailureCount=6`、`realCandidateRows=1`、`realProofClosureWorkOrder.canCloseProofGap=false`;总报告和负责人附件已展示“缺失证明要求”,其中 `by-owner/tech-ai.md` 保留 video A/B preflight 失败上下文和 live proof 条件。
+- 商务执行总表已把 `outputs/long-run-readiness-latest/long-run-readiness-report.md` 作为 `长跑前置门禁报告` 关键文件行,当前 `status=blocked`、`actionCount=9`。`businessExecutionIndex.longRunReadiness` 已在交接摘要中透传;`longrun:readiness` 当前读取 `outputs/live-preflight-latest/live-preflight-summary.json` 并以 `proof-gap-closure.openCount=3` 为准,不再被旧 request 数量误导。
+- 未到位:当前本地运行环境没有可用于 `acceptance:video-ab` 的 `TIHAO_SESSION_TOKEN`、`TIHAO_COMPANY`、`VOC_SOCIAL_TOKEN`、`VIDEO_ANALYSIS_TOKEN` 等运行凭证;本地直接运行 `npm.cmd run acceptance:video-ab` 只会产出 `outputs/video-hit-rate-preflight-latest/video-hit-rate-preflight-summary.json` 这类前置失败摘要,不能产出真实 live proof。
+- 商务执行总表已透传当前 2 份 `操作包负责人附件`,`operatorPackOwnerArtifactCount=2`、`operatorPackOwnerArtifactRowCount=2`、`rowCount=21`、`proofOpenCount=3`。
+- 业务证明进度表已透传当前负责人附件:`businessProofProgress.operatorPackOwnerArtifactCount=2`,并继续显示 `complete=false`。
+- 下一步行动队列已去掉“补真实候选视频资源”的过期动作:`businessProofNextActions.actionCount=3`,当前动作是填真实历史 Brief、人工复核标注、真实视频 A/B 验收。
+- 长期计划执行状态面板已透传当前负责人附件:`planExecutionStatus.operatorPackOwnerArtifactCount=2`、`businessExecutionOperatorPackOwnerArtifactRowCount=2`、`proofOpenCount=3`、`complete=false`。
+- 本地种子材料索引 `local-seed-material-index`、本地种子转 intake 补表 worklist `local-seed-to-intake-worklist` 和经验逐字稿索引 `experience-transcript-index` 仍只作为补真实材料和规则校准入口,均 `canCloseProofGap=false`。当前 `historyDraftRows=12`、`manualReviewDraftRows=12`、`candidateSeedRows=12`、`videoCandidateNeedsResourceRows=3`、`transcriptCount=2`、`coveredRuleCount=8`。
+- 未到位:`optimization-completion` 仍为 `complete=false`、`readyForClaim=false`、`proofGapOpenCount=3`、`blockingReasonCount=8`、`missingProofRequirementCount=113`、`videoAbPreflightReadyForVideoAb=false`、`videoAbPreflightFailureCount=4`。旧 `optimization-pipeline-latest` 已被标记为 `stale=true`,但其中 nested `history-audit/historical-dataset-audit.json` 和 `customer-effect-audit/customer-effect-summary.json` 会被 closure/completion 递归发现并作为失败证据读取,不能继续当作当前完成证据。
+
+## 阅读顺序
+
+| 顺序 | 先看什么 | 路径 | 用途 |
+| --- | --- | --- | --- |
+| 1 | 主计划 | `docs/tihao-experience-optimization-plan.md` | 确认长期目标、关键差距、验收边界和每轮沉淀要求。 |
+| 2 | 15 步补证顺序 | `docs/business-proof-action-order.zh-CN.md` | 按顺序补齐真实历史 Brief、真实视频 A/B live proof、商务复核和客户效果证明。 |
+| 3 | 最新表单索引 | `outputs/latest-form-index-latest/latest-form-index.md` | 从一个入口定位补证表单、候选名单、字段级核对表、负责人行动文件、下一步行动队列和复验命令。 |
+| 4 | 真实验收材料总包 | `outputs/real-proof-intake-bundle-latest/real-proof-intake-bundle.md` | 给商务和技术集中补齐真实材料,包含模板、证明缺口、当前证据、下一步动作、负责人附件和复验命令。 |
+| 5 | 真实证明闭环工单 | `outputs/real-proof-closure-work-order-latest/real-proof-closure-work-order.md` | 把 intake、proof-gap、completion 未关闭项拆成 13 条字段级补证工单和 2 份负责人附件;展示当前证据和缺失证明要求,不写入正式模板,不关闭 proof-gap。 |
+| 6 | 本地种子材料索引 | `outputs/local-seed-material-index-latest/local-seed-material-index.md` | 查看 `dha_brief.xlsx`、DHA PoC 推荐表、TikHub 参考分析版和旧 pipeline/live 自动化候选;只作为补真实模板和候选池种子。 |
+| 7 | 本地种子转 intake 补表 worklist | `outputs/local-seed-to-intake-worklist-latest/local-seed-to-intake-worklist.md` | 把 DHA Brief、PoC 候选和参考账号解析转成商务可补正式 intake 的草稿行。 |
+| 8 | 经验逐字稿索引 | `outputs/experience-transcript-index-latest/experience-transcript-index.md` | 查看提号经验逐字稿覆盖的 8 条经验规则;只作为经验来源追踪和规则校准种子。 |
+| 9 | 三缺口材料审计 | `outputs/real-gap-material-audit-latest/real-gap-material-audit.md` | 查看三个真实缺口对应的项目资料、已接产物、仍缺字段和不能关闭原因。 |
+| 10 | proof-gap 请求包 | `outputs/proof-gap-request-latest/proof-gap-request-report.md` | 查看 5 个原始真实证明缺口各自需要谁补、补什么、验收产物是什么。 |
+| 11 | proof-gap 操作包 | `outputs/proof-gap-operator-pack-latest/proof-gap-operator-pack.md` | 查看 5 个 gapId、其中 3 个仍 open 的负责人、主填写文件、复验命令和证明边界。 |
+| 12 | 商务执行总表 | `outputs/business-execution-index-latest/business-execution-index.md` | 按负责人执行补证、查看修复清单、2 份 `操作包负责人附件` 和补齐后复验命令。 |
+| 13 | 业务证明进度表 | `outputs/business-proof-progress-latest/business-proof-progress-report.md` | 查看 15 步补证进度、外部材料阻塞和负责人附件。 |
+| 14 | 长期计划执行状态面板 | `outputs/plan-execution-status-latest/plan-execution-status.md` | 查看长期优化计划 8 个章节的执行状态、证明缺口和负责人附件。 |
+| 15 | 优化完成度审计 | `outputs/optimization-completion-latest/optimization-completion-report.md` | 确认 `complete=false`、`readyForClaim=false`、`proofGapOpenCount=3` 和禁用宣称。 |
+| 16 | 长跑前置门禁 | `outputs/long-run-readiness-latest/long-run-readiness-report.md` | 确认 full-matrix/live 长跑是否可启动;当前 `ready=false`。 |
+| 17 | 交接摘要 | `outputs/optimization-handoff-latest/optimization-handoff-report.md` | 查看本轮状态、最新索引、补证进度、证据台账、下一步命令和证明边界。 |
+
+## 3 个仍需关闭的真实证明缺口
+
+1. 真实历史 Brief 数据集:需要 `history:audit` 显示 `readyForCustomerEffectProof=true`。
+2. 真实视频 A/B live proof:需要 `acceptance:video-ab` 在真实 provider、真实视频资源和 live `proofContext` 下通过。
+3. 商务复核和客户效果证明:需要 `customer-effect:audit` 显示 `overallPass=true`,并带真实客户选中、拒绝归因和人工补号量。
+
+已关闭但不代表整体完成的单项:
+
+1. 真实候选视频资源:`readyForVideoAbPreflight=true`、`failureCount=0`、`realCandidateRows=1`,只说明可以进入视频 A/B 前置。
+2. 真实 live/provider 长跑证明:`liveEnabled=true`、`overallPass=true`、`failureCount=0`、`failedGateCount=0`,不能替代客户效果证明。
+
+## 最近一次复验结果
+
+复验时间:2026-06-13。
+
+| 命令 | 结果 | 结论 |
+| --- | --- | --- |
+| `npm run intake:readiness -- --data-pack outputs\data-intake-pack-latest --video-pack outputs\video-intake-pack-latest --output outputs\intake-readiness-latest` | `overallReady=false`、`failureCount=6` | 历史数据、商务复核指标和客户效果仍未就绪。 |
+| `npm run video:resource-readiness -- --input outputs\video-intake-pack-latest\video-resource-template.csv --output outputs\video-resource-readiness-latest --strict` | `readyForVideoAbPreflight=true`、`failureCount=0`、`realCandidateRows=1` | 真实候选视频资源缺口已关闭,可进入真实视频 A/B 前置。 |
+| `npm run proof-gap:closure -- --output outputs\proof-gap-closure-latest` | `complete=false`、`openCount=3`、`businessOpenCount=3`、`closedCount=2`、`precheckOpenCount=1` | 视频资源和 live/provider overnight 已 closed;仍剩 3 个真实业务证明缺口;intake 预审未就绪行单独统计,不计入业务 openCount。 |
+| `npm run proof-gap:request -- --output outputs\proof-gap-request-latest` | `complete=false`、`unresolvedCount=3`、closed 行为 `ready` | 请求包已跟随 closure 当前状态;closed 缺口保留证据链,不再误报为待补。 |
+| `npm run proof-gap:operator-pack -- --outputs outputs --output outputs\proof-gap-operator-pack-latest --strict` | `passed=true`、`openCount=3`、`rowCount=5`、`fillFileCount=3`、`recheckCommandCount=9`、`ownerGroupCount=2`、`ownerArtifactCount=2`、`evidenceRowCount=5`、`nextActionCount=5`、`missingProofRequirementCount=38` | 操作包已按当前 open 缺口派工;closed 行只保留证据链,history/customer-effect 行展示具体失败条件,video A/B preflight 失败只是 live proof 缺口上下文。 |
+| `npm run proof-gap:intake-bundle -- --output outputs\real-proof-intake-bundle-latest --strict` | `passed=true`、`complete=false`、`proofOpenCount=3`、`operatorPackEvidenceRowCount=5`、`operatorPackNextActionCount=5`、`operatorPackOwnerArtifactCount=2` | 真实验收材料总包已同步 3 个 open 缺口和 2 份负责人附件。 |
+| `npm run real-proof:work-order -- --outputs outputs --output outputs\real-proof-closure-work-order-latest --strict` | `passed=true`、`complete=false`、`workOrderCount=13`、`ownerArtifactCount=2`、`proofGapOpenCount=3`、`intakeFailureCount=6`、`realCandidateRows=1`、`canCloseProofGap=false` | 闭环工单已去掉过期视频资源补证动作,并在 Markdown/负责人附件展示缺失证明要求;仍只是派工入口。 |
+| `npm run business-execution:index -- --outputs outputs --output outputs\business-execution-index-latest --strict` | `passed=true`、`rowCount=21`、`proofOpenCount=3`、`operatorPackOwnerArtifactCount=2`、`operatorPackOwnerArtifactRowCount=2` | 商务执行总表已透传当前 2 份操作包负责人附件。 |
+| `npm run business-proof:progress -- --outputs outputs --output outputs\business-proof-progress-latest` | `complete=false`、`pass=5`、`blocked_by_external_data=5`、`pending=3`、`fail=2`、`businessProofProgress.operatorPackOwnerArtifactCount=2` | 进度表仍只说明补证进度,不是业务证明。 |
+| `npm run business-proof:next-actions -- --output outputs\business-proof-next-actions-latest` | `businessProofNextActions.actionCount=3`、`proofGapOpenCount=3` | 下一步只剩历史 Brief、人工复核标注、真实视频 A/B 验收。 |
+| `npm run plan:execution-status -- --outputs outputs --output outputs\plan-execution-status-latest --strict` | `complete=false`、`rowCount=8`、`proofOpenCount=3`、`planExecutionStatus.operatorPackOwnerArtifactCount=2`、`businessExecutionOperatorPackOwnerArtifactRowCount=2` | 长期计划执行状态面板已同步当前 open 缺口和负责人附件。 |
+| `npm run optimization:completion -- --output outputs\optimization-completion-latest` | `complete=false`、`readyForClaim=false`、`proofGapOpenCount=3`、`blockingReasonCount=8`、`missingProofRequirementCount=113`、`videoAbPreflightReadyForVideoAb=false`、`videoAbPreflightFailureCount=4`、`optimizationPipeline.stale=true` | 完成度审计仍禁止任何提号效果宣称;video A/B preflight 失败只是 live proof 缺口上下文,旧 pipeline 只能贡献 nested 失败证据,不能作为当前完成证据。 |
+| `npm run optimization:status -- --output outputs\optimization-status-latest` | `complete=false`、`passed=31`、`ready_not_proven=2`、`blocked_by_external_data=1` | 状态审计已识别可关闭 live/provider aggregate;`live-provider-overnight-proof` 不再误列为外部阻塞。 |
+| `npm run handoff:summary -- --output outputs\optimization-handoff-latest` | `complete=false`、`readyCapabilities=31`、`readyNotProven=2`、`externalBlockers=1` | 交接摘要当前只把 `customer-effect-proof` 作为外部阻塞;`video-ab-proof` 和 `historical-dataset` 仍是 ready-not-proven。 |
+| `npm run live:preflight -- --strict` | `readyForLiveAcceptance=false`、`readyForVideoAb=false`、`failures=2`、`warnings=3` | 当前缺 `sessionToken`、`company` 和真实视频/证据 provider,不能启动真实 live 或真实视频 A/B。 |
+| `npm run longrun:readiness -- --mode full-matrix --intake-readiness outputs\intake-readiness-latest\intake-readiness-summary.json --proof-gap outputs\proof-gap-request-latest\proof-gap-request-summary.json --proof-gap-closure outputs\proof-gap-closure-latest\proof-gap-closure-summary.json --output outputs\long-run-readiness-latest` | `ready=false`、`failCount=9`、`proof-gap-closure.openCount=3` | 仍不能启动或宣称 full-matrix/live 长跑完成;当前已读取 latest preflight,并以 closure openCount 为准。 |
+| `npm run local-seed-material:index -- --outputs outputs --output outputs\local-seed-material-index-latest` | `existingMaterialCount=3`、`workbookCount=3`、`canCloseProofGap=false` | 本地种子材料索引只用于补真实模板和候选池。 |
+| `npm run local-seed-to-intake:worklist -- --outputs outputs --output outputs\local-seed-to-intake-worklist-latest` | `historyDraftRows=1`、`candidateSeedRows=12`、`videoCandidateNeedsResourceRows=3`、`canCloseProofGap=false` | worklist 只是补正式 intake 的草稿和线索。 |
+| `npm run experience-transcript:index -- --output outputs\experience-transcript-index-latest` | `transcriptCount=2`、`coveredRuleCount=8`、`canCloseProofGap=false` | 经验逐字稿索引只用于规则校准。 |
+| `npm run round:refresh -- --output-root outputs --strict` | `passed=true`、29 步全部 `pass`、`round-deposition.passCount=142`、`failCount=0` | 工程交付链路通过,并已包含 `real-gap-material-audit` 主链路审计、proof-gap 计数语义、video A/B preflight 非证明守卫、closure/completion preflight 上下文传播以及递归证明产物发现守卫;但这不等于业务证明完成。 |
+| `npm run plan:consistency:smoke` | `ok=true`、`hits=[]` | 静态 5 类缺口表与当前 3 条 open action 队列保持一致,closed 视频资源不会回到待办。 |
+| `npm run acceptance` | `acceptance ok` | 包级 smoke、计划一致性、每轮刷新 smoke、安全审计、包内容门禁和 workspace install smoke 通过。 |
+| `npm publish --dry-run --access public` | `@vocmarket/tihao-sop@1.0.0`、176 个文件、package size 约 598.5 kB | 发布前 tarball 和包内容确认通过。 |
+| `npm publish --access public --registry https://registry.npmjs.org/` | `+ @vocmarket/tihao-sop@1.0.0` | 1.0.0 已正式发布到 npm。 |
+| `npm view @vocmarket/tihao-sop@1.0.0 version --registry https://registry.npmjs.org/` | `1.0.0` | 指定版本 registry 可见。 |
+| `npm view @vocmarket/tihao-sop dist-tags.latest version --registry https://registry.npmjs.org/` | `dist-tags.latest = '1.0.0'`、`version = '1.0.0'` | `latest` 已指向 1.0.0。 |
+| `npx --yes @vocmarket/tihao-sop@latest workspace --smoke` | 安装 `.claude/plugins/tihao-sourcing` 与 `.claude/skills/tihao-creator-sourcing`,`tihao smoke ok` | 用户侧 fresh workspace 下载、安装和 smoke 通过。 |
+| `npm run optimization:pipeline -- --data-pack outputs\data-intake-pack-latest --video-pack outputs\video-intake-pack-latest --current-manual-supplement-count <本轮人工补号量> --output <输出目录> --strict` | 未运行 | 缺真实 `<本轮人工补号量>` 和可通过 intake readiness 的真实材料;不能用 `0` 或示例值代替。 |
+
+## 接手角色分工
+
+| 角色 | 先做什么 | 填什么或跑什么 | 复验看什么 |
+| --- | --- | --- | --- |
+| 商务 | 打开 `outputs/real-proof-intake-bundle-latest/real-proof-intake-bundle.md`、`outputs/real-proof-closure-work-order-latest/by-owner/business.md`、`outputs/proof-gap-operator-pack-latest/by-owner/business.md`、`outputs/local-seed-to-intake-worklist-latest/history-intake-draft.csv` 和 `outputs/business-execution-index-latest/business-execution-index.md`。 | 真实历史 Brief、客户选中/拒绝、拒绝原因、历史人工补号量基线、本轮人工补号量。 | `intake-readiness-summary.json` 的 `overallReady=true`、`failureCount=0`;`customer-effect-summary.json` 的 `overallPass=true`。 |
+| 技术/AI | 打开 `outputs/proof-gap-operator-pack-latest/by-owner/tech-ai.md`、`outputs/real-proof-closure-work-order-latest/by-owner/tech-ai.md`、`outputs/business-execution-index-latest/business-execution-index.md`。 | 跑真实视频 A/B live proof、刷新审计链路、沉淀交接摘要;不替业务填写客户材料。 | `acceptance:video-ab` 带完整 live `proofContext`;`proof-gap-closure-summary.json` 的 `openCount=0` 后才允许进入完成判断。 |
+| 下一轮 AI | 先读本文,再读主计划、完成度审计、真实证明闭环工单、本地种子材料索引、本地种子转 intake 补表 worklist、三缺口材料审计、经验逐字稿索引和交接摘要。 | 不新增未经证明的业务结论;只把真实材料审计结果写回文档和 outputs。 | `round-refresh-summary.json` 的 29 步全部 `pass`,且 `optimization-completion-summary.json` 仍如实记录 `readyForClaim=false`。 |
+
+## 完成判定矩阵
+
+| 判定项 | 当前状态 | 完成证据 | 不能用什么替代 |
+| --- | --- | --- | --- |
+| 历史数据可用于客户效果证明 | 未完成 | `history:audit` 显示 `readyForCustomerEffectProof=true`。 | 示例 CSV、空模板、只有 Brief 没有客户选择。 |
+| 视频资源可进入 A/B 前置 | 单项已关闭 | `video:resource-readiness` 显示 `readyForVideoAbPreflight=true`、`failureCount=0`、`realCandidateRows=1`。 | 只有参考视频、无真实候选视频、模板 URL。 |
+| 视频 A/B live proof 成立 | 未完成 | `acceptance:video-ab` 在真实 provider、真实视频资源和 live `proofContext` 下通过。 | sample 视频、接口 200、离线 mock、缺 `proofContext` 的通过结果。 |
+| live/provider 长跑成立 | 单项已关闭 | `outputs/overnight-quality-1780682025651/aggregate-summary.json` 显示 `liveEnabled=true`、`overallPass=true`、`failureCount=0`、`failedGateCount=0`。 | provider mock、fallback、dry-run;也不能用该单项替代客户效果证明。 |
+| 长跑前置门禁可启动 | 未完成 | `longrun:readiness` 显示 `ready=true`,且真实 live preflight、历史数据、intake readiness 和 proof-gap closure 均通过。 | 29 步 refresh 通过、smoke 通过、只存在 long-run-readiness 报告。 |
+| 客户效果证明成立 | 未完成 | `customer-effect:audit` 显示 `overallPass=true`,并带真实客户选中、拒绝归因和人工补号量。 | 人工主观判断、商务觉得名单更好、只有候选名单。 |
+| 长期优化目标可声明 | 未完成 | `optimization:completion` 显示 `complete=true`、`readyForClaim=true`、`proofGapOpenCount=0`。 | 任一单项 smoke 通过、表单入口齐全、npm 包验收通过。 |
+| 长期优化目标完成 | 未完成 | `proof-gap:closure` 显示 `complete=true`、`openCount=0`,`optimization:completion` 显示 `readyForClaim=true`,再运行 `round:refresh --strict` 全部通过。 | 完成度审计存在但 `readyForClaim=false`、只有交接摘要或表单入口。 |
+
+## 可说与不可说
+
+- 可以说:当前方向没有偏离提号需求,优化工作正在围绕真实证据闭环收敛。
+- 可以说:真实候选视频资源已关闭,当前 `readyForVideoAbPreflight=true`、`failureCount=0`、`realCandidateRows=1`,可进入真实视频 A/B 前置。
+- 可以说:真实 live/provider overnight 证明已关闭,`liveEnabled=true`、`overallPass=true`,但它不是客户效果证明。
+- 可以说:当前 proof-gap 操作包已列出 5 个 `gapIds`,其中 3 个仍 open,`ownerGroupCount=2`、`ownerArtifactCount=2`、`operatorPackOwnerArtifactCount=2`。
+- 可以说:当前真实证明闭环工单已输出 `workOrderCount=13`、`ownerArtifactCount=2`、`proofGapOpenCount=3`、`realCandidateRows=1`、`canCloseProofGap=false`。
+- 可以说:当前最新表单索引和交接摘要已透传下一步行动队列,`businessProofNextActions.actionCount=3`、`proofGapOpenCount=3`。
+- 可以说:当前本地 smoke、包内容门禁、安全审计、`round:refresh --strict` 和 `acceptance` 已通过。
+- 不可以说:提号率已经提升。
+- 不可以说:客户效果已经达标。
+- 不可以说:人工补号量已经下降。
+- 不可以说:`real-proof-intake-bundle`、`proof-gap-request`、`proof-gap-operator-pack`、`real-proof-closure-work-order`、候选名单、本地种子材料或经验逐字稿索引本身就是业务证明。
+
+## 补齐材料后的复验命令
+
+```powershell
+npm run intake:readiness -- --data-pack outputs\data-intake-pack-latest --video-pack outputs\video-intake-pack-latest --output outputs\intake-readiness-latest
+npm run video:resource-readiness -- --input outputs\video-intake-pack-latest\video-resource-template.csv --output outputs\video-resource-readiness-latest --strict
+npm run local-seed-to-intake:worklist -- --outputs outputs --output outputs\local-seed-to-intake-worklist-latest
+npm run real-proof:work-order -- --outputs outputs --output outputs\real-proof-closure-work-order-latest --strict
+npm run proof-gap:request -- --output outputs\proof-gap-request-latest
+npm run live:preflight -- --strict
+npm run longrun:readiness -- --mode full-matrix --intake-readiness outputs\intake-readiness-latest\intake-readiness-summary.json --proof-gap outputs\proof-gap-request-latest\proof-gap-request-summary.json --proof-gap-closure outputs\proof-gap-closure-latest\proof-gap-closure-summary.json --output outputs\long-run-readiness-latest
+npm run optimization:pipeline -- --data-pack outputs\data-intake-pack-latest --video-pack outputs\video-intake-pack-latest --current-manual-supplement-count <本轮人工补号量> --output <输出目录> --strict
+npm run proof-gap:closure -- --output outputs\proof-gap-closure-latest
+npm run optimization:completion -- --output outputs\optimization-completion-latest
+npm run round:refresh -- --output-root outputs --strict
+```
+
+## 安全边界
+
+- 不要把 `sessionToken`、`Authorization`、模型 token/API key 或 npm token 写进文档、报告、模板、日志或 npm 包入口。
+- 不要把 sample、smoke、dry-run、模板、provider fallback 或接口 200 当成客户效果证明。
+- 只有真实材料审计通过、`proof-gap-closure.openCount=0` 且 `optimization-completion.readyForClaim=true` 后,才能进入长期目标完成判断。
+## 2026-06-12 最新补记:历史审计薄 JSON 防误收
+
+- `evidence:index` 与 `business-proof-progress` 已统一复用完整历史证明门槛;薄 `historical-dataset-audit.json` 只写顶层 `readyForCustomerEffectProof=true` 不再能成为 `real_evidence` 或历史审计 pass。
+- 历史证据必须包含完整 acceptance 子门槛、5 条以上可审计 items、客户选中/拒绝记录、拒绝原因覆盖、人工最终名单覆盖和历史人工补号量基线覆盖,才能支撑客户效果证明链。
+- 当前仍是 `proof-gap-closure.complete=false`、`openCount=3`、`optimization-completion.readyForClaim=false`;只能说证明链更严格、方向仍对齐提号需求,不能宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-12 最新补记:stale pipeline 行动压制
+
+- `business-proof:next-actions` 已基于最新 `optimization-completion` freshness/blocker 证据压制旧 `optimization-pipeline` 的 nextActions;stale pipeline 动作不能作为当前派工或完成证据。
+- 最新行动队列为 `businessProofNextActions.actionCount=3`,只剩真实历史 Brief、人工复核标注、真实视频 A/B 验收;`sourceState.pipelineStale=true`、`pipelineRawNextActionCount=3`、`pipelineSuppressedNextActionCount=3`、`pipelineNextActionCount=0`。
+- `latest-form:index` 和 `handoff:summary` 已透传 `pipelineStale=true`、`pipelineSuppressedNextActionCount=3` 与 stale 原因;`round-deposition` 已用 `business-proof-next-actions-stale-pipeline-suppression` 守住该回归点。
+- `latest-form:index:smoke` 与 `handoff:summary:smoke` 也已直接断言 stale pipeline 字段和 topActions 不含 `optimization-pipeline` 来源;单脚本回归会更早暴露。
+- `optimization-handoff-summary.json` 顶层已新增 `pipelineStaleSummary`;只扫顶层 JSON 时也能看到旧 pipeline 当前不可派工、不可作为完成证据。
+- 当前仍是 `proof-gap-closure.complete=false`、`openCount=3`、`optimization-completion.readyForClaim=false`;只能说当前证明链更严格、行动队列更干净,不能宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-12 最新补记:proof-gap closure 业务/预审计数拆分
+
+- `proof-gap:closure` 已把 `openCount/closedCount` 明确为业务证明缺口计数,并新增 `businessOpenCount/businessClosedCount` 作为同义字段;`intake-readiness` 预审行通过 `precheckOpenCount/precheckRowCount` 单独记录。
+- 当前业务证明缺口仍是 `businessOpenCount=3`,对应真实历史 Brief、真实视频 A/B live proof、商务复核和客户效果证明;`precheckOpenCount=1` 只说明 intake 预审仍未就绪,不额外增加业务 open 缺口。
+- `latest-form:index` 已新增 `proofGapClosure` 区块,`handoff:summary` 已透传 `businessOpenCount`、`businessClosedCount`、`precheckOpenCount` 和 `countSemantics.openCount`;只看索引或交接摘要时也能读懂计数口径。
+- `proof-gap:closure:smoke`、`latest-form:index:smoke`、`handoff:summary:smoke` 已直接断言该语义,避免后续把预审行误算进业务缺口。
+- 当前仍是 `proof-gap-closure.complete=false`、`businessOpenCount/openCount=3`、`optimization-completion.readyForClaim=false`;这轮只是让计数边界更清楚,不能宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-12 最新补记:派工包锁定 video A/B preflight 上下文
+
+- `proof-gap-operator-pack:smoke` 现在直接检查 `video-ab-live-proof` 行、总 Markdown、CSV、`by-owner/tech-ai.md` 和 `by-owner/tech-ai.csv`,要求保留 `preflight.readyForVideoAb=false`、`preflight.failureCount=4`、`preflight.generatedBy=acceptance:video-ab-preflight`、runtime/company/provider token 布尔失败状态,以及 `videoAbPreflight.readyForVideoAb=true` 等 live proof 缺失要求。
+- `real-proof-intake-bundle:smoke` 已检查真实材料总包的 proof gap 行、CSV 导出行、Markdown/CSV 都继续携带该 preflight 上下文。
+- `real-proof-closure-work-order` 总报告和负责人附件新增“缺失证明要求”列;`real-proof:work-order:smoke` 已检查 summary、CSV、总 Markdown 和 `by-owner/tech-ai.md` 都保留 video A/B preflight 失败原因及 live proof 条件。
+- 最新 `round:refresh -- --output-root outputs --strict`、`handoff:summary:smoke`、`task:handoff-index:smoke`、`hygiene:smoke` 和 `acceptance` 均通过;当前仍是 `businessOpenCount/openCount=3`、`optimization-completion.readyForClaim=false`,不能宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-12 最新补记:round-deposition 锁定 proof-gap 计数语义
+
+- `round-deposition` 已新增 `proof-gap-closure-count-semantics` 守卫,要求 standalone `proof-gap-closure-summary.json` 的 `openCount/businessOpenCount` 只表示业务证明缺口,并要求 `precheckOpenCount`、`precheckRowCount` 和 `countSemantics` 与 rows 中的预审行一致。
+- `round-deposition` observed/report 现在同时记录 standalone 与 handoff 中的 `businessOpenCount`、`businessClosedCount`、`businessGapCount`、`precheckOpenCount` 和 `countSemantics.openCount`,防止交接摘要丢失计数口径。
+- 最新 `round:refresh --strict` 为 29 步全部 `pass`,`round-deposition.counts.pass=142`、`counts.fail=0`;但当前仍是 `businessOpenCount/openCount=3`、`precheckOpenCount=1`、`optimization-completion.readyForClaim=false`。
+- 这轮只是把计数语义纳入每轮沉淀审计,不关闭任何真实业务证明缺口,不能宣称提号率提升、客户效果达标或人工补号量下降。
+
+## 2026-06-12 最新补记:next-actions summary 顶层可读化
+
+- `business-proof:next-actions` summary 现在顶层记录 `actionCount`、`topActions`、`ownerGroupCount`、`ownerRepairArtifactCount`,写完负责人附件后还记录 `ownerArtifactCount`,不再只能从 `actions.length` 和 owner 文件间接推断。
+- `latest-form:index` 和 `handoff:summary` 已优先读取这些顶层字段;`round-deposition` 的 `business-proof-next-actions-open` 守卫也要求顶层 `actionCount/topActions` 与 `actions` 队列一致。
+- 当前真实行动队列仍是 `actionCount=3`、`topActions.length=3`:真实历史 Brief、人工复核标注、真实视频 A/B 验收。
+- 这轮只增强行动队列交接可读性和机器可审计性,不关闭任何真实业务证明缺口;当前仍是 `businessOpenCount/openCount=3`、`optimization-completion.readyForClaim=false`。
+
+## 2026-06-12 最新补记:video A/B preflight 失败上下文进入 closure/completion
+
+- `proof-gap:closure` 和 `optimization:completion` 现在会读取最新 `video-hit-rate-preflight-summary.json`,在真实 live `video-hit-rate-summary.json` 缺失或不可关闭时,把 preflight 失败原因写入 `video-ab-live-proof` 的 evidence 和 missing requirements。
+- 该 preflight 只记录运行环境是否具备 `session/company/VOC social provider/video analysis provider` 等布尔状态,不记录 token 明文;当前 `videoAbPreflightReadyForVideoAb=false`、`videoAbPreflightFailureCount=4`。
+- `latest-form:index`、`business-execution:index`、`handoff:summary` 顶层和 `handoff.latestFormIndex.optimizationCompletion` 已展示 `optimizationCompletion.videoAbPreflightReadyForVideoAb` 与 `optimizationCompletion.videoAbPreflightFailureCount`;`round-deposition` 已新增 closure/completion 两个 preflight context 守卫,`task:handoff-index:smoke` 也会校验该上下文不丢失。
+- 最新 `round:refresh -- --output-root outputs --strict` 通过,`round-deposition.counts.pass=142`、`counts.fail=0`;`optimization-completion.missingProofRequirementCount=113`。
+- 这轮只是让真实视频 A/B live proof 缺口更可解释、更可交接;preflight 失败不是业务证明,不能关闭 `video-ab-live-proof`,当前仍是 `businessOpenCount/openCount=3`、`optimization-completion.readyForClaim=false`。
+
+## 2026-06-12 最新补记:递归证明产物发现与具体失败条件沉淀
+
+- `proof-gap:closure` 和 `optimization:completion` 已递归发现旧 `optimization-pipeline-latest` 内嵌的 `history-audit/historical-dataset-audit.json` 与 `customer-effect-audit/customer-effect-summary.json`,并在 `sourceFiles.history`、`sourceFiles.customerEffect` 中记录真实来源。
+- 这些 nested 产物只能作为失败证据读取,不能作为当前完成证据:历史 Brief 当前仍不满足 `briefCount>=5`、`items.length>=5`、客户选中/拒绝、拒绝原因和历史人工补号基线覆盖;customer-effect 当前仍不满足 `overallPass=true`、客户选中率实测、参考链路证明和人工补号量证明。
+- `round-deposition` 已新增 `proof-gap-closure-recursive-proof-artifact-discovery` 与 `optimization-completion-recursive-proof-artifact-discovery` 守卫,并在 observed/report 中沉淀 `proofGapClosure.sourceFiles.history`、`proofGapClosure.sourceFiles.customerEffect`、`optimizationCompletion.sourceFiles.history`、`optimizationCompletion.sourceFiles.customerEffect` 等字段,防止后续回退成“找不到历史/客户效果文件”的粗粒度失败。
+- 最新验证已通过:`npm.cmd run round:deposition:smoke`、`npm.cmd run round:refresh -- --output-root outputs --strict`、`npm.cmd run task:handoff-index:smoke`、`npm.cmd run hygiene:smoke`、`npm.cmd run proof-gap:intake-bundle:smoke`、`npm.cmd run round:refresh:smoke` 和 `npm.cmd run acceptance`。
+- 当前边界不变:`proof-gap-closure.complete=false`、`businessOpenCount/openCount=3`、`optimization-completion.readyForClaim=false`、`missingProofRequirementCount=113`;仍不能宣称提号率提升、客户效果达标或人工补号量下降。

+ 23 - 0
claude-code-tihao-sourcing/fixtures/quality-briefs/618-list-seeding.json

@@ -0,0 +1,23 @@
+{
+  "id": "618-list-seeding",
+  "name": "618 List Seeding",
+  "briefText": "618 multi-category list seeding. Target Xiaohongshu list creators: mom-baby stocking, skincare empties, food stocking, home good-things lists.",
+  "input": {
+    "brand": "618\u597d\u7269\u79cd\u8349",
+    "category": "\u751f\u6d3b\u65b9\u5f0f",
+    "platforms": ["xiaohongshu"],
+    "keywords": ["618", "\u5408\u96c6", "\u6e05\u5355", "\u56e4\u8d27", "\u597d\u7269", "\u6d4b\u8bc4"],
+    "stylePreferences": ["\u5408\u96c6", "\u6e05\u5355", "\u4ef7\u683c\u654f\u611f", "\u771f\u5b9e\u63a8\u8350"],
+    "exclusions": ["\u533b\u7f8e", "\u5a5a\u793c", "\u5a31\u4e50", "\u642c\u8fd0"],
+    "fanRange": { "min": 10000, "max": 1000000 },
+    "budgetRange": { "min": 3000, "max": 25000 }
+  },
+  "referenceLinks": [],
+  "expected": {
+    "targetPlatforms": ["xiaohongshu"],
+    "targetCreatorTypes": ["618\u6e05\u5355", "\u597d\u7269\u5408\u96c6", "\u56e4\u8d27", "\u6d4b\u8bc4"],
+    "offTopicTerms": ["\u533b\u7f8e", "\u5a5a\u793c", "\u5a31\u4e50", "\u642c\u8fd0"],
+    "mustHaveSignals": ["618", "\u5408\u96c6"],
+    "targetCount": 15
+  }
+}

+ 29 - 0
claude-code-tihao-sourcing/fixtures/quality-briefs/dha-mom-baby.json

@@ -0,0 +1,29 @@
+{
+  "id": "dha-mom-baby",
+  "name": "Mom Baby DHA Seeding",
+  "briefText": "DHA mom-baby nutrition seeding. Target Xiaohongshu creators: moms, parenting experts, nutritionists, baby-product reviewers, 618 list creators. Exclude wedding, party, medical beauty, pet-heavy accounts, and hard-sell accounts.",
+  "input": {
+    "brand": "DHA\u6bcd\u5a74\u8425\u517b\u54c1",
+    "category": "\u6bcd\u5a74",
+    "platforms": ["xiaohongshu"],
+    "keywords": ["DHA", "\u6bcd\u5a74", "\u5988\u5988", "\u80b2\u5a74\u5e08", "\u8425\u517b\u5e08", "618\u5408\u96c6"],
+    "stylePreferences": ["\u771f\u5b9e\u4f53\u9a8c", "\u4e13\u4e1a\u79d1\u666e", "\u6e05\u5355\u63a8\u8350"],
+    "exclusions": ["\u5a5a\u793c", "\u6d3e\u5bf9", "\u533b\u7f8e", "\u5ba0\u7269", "\u786c\u5e7f"],
+    "fanRange": { "min": 10000, "max": 500000 },
+    "budgetRange": { "min": 3000, "max": 18000 }
+  },
+  "referenceLinks": [
+    {
+      "url": "http://xhslink.com/o/2fg3MzG2Iau",
+      "platform": "xiaohongshu",
+      "contentType": "video"
+    }
+  ],
+  "expected": {
+    "targetPlatforms": ["xiaohongshu"],
+    "targetCreatorTypes": ["\u5988\u5988", "\u80b2\u5a74\u5e08", "\u8425\u517b\u5e08", "\u6bcd\u5a74\u6d4b\u8bc4"],
+    "offTopicTerms": ["\u5a5a\u793c", "\u6d3e\u5bf9", "\u5ba0\u7269", "\u533b\u7f8e", "\u88c5\u4fee"],
+    "mustHaveSignals": ["\u6bcd\u5a74", "\u5988\u5988"],
+    "targetCount": 10
+  }
+}

+ 23 - 0
claude-code-tihao-sourcing/fixtures/quality-briefs/healthy-snack.json

@@ -0,0 +1,23 @@
+{
+  "id": "healthy-snack",
+  "name": "Healthy Snack Seeding",
+  "briefText": "Low-sugar healthy snack seeding. Target Xiaohongshu and Douyin creators: food reviewers, fitness/control-sugar lifestyle, office snacks, mom pantry stocking, unboxing lists.",
+  "input": {
+    "brand": "\u4f4e\u7cd6\u5065\u5eb7\u96f6\u98df",
+    "category": "\u98df\u54c1\u996e\u6599",
+    "platforms": ["xiaohongshu", "douyin"],
+    "keywords": ["\u98df\u54c1", "\u96f6\u98df", "\u4f4e\u7cd6", "\u5065\u5eb7", "\u63a7\u7cd6", "\u6d4b\u8bc4", "\u5f00\u7bb1"],
+    "stylePreferences": ["\u771f\u5b9e\u8bd5\u5403", "\u914d\u6599\u8868\u89e3\u91ca", "\u573a\u666f\u5316\u79cd\u8349"],
+    "exclusions": ["\u533b\u7f8e", "\u5ba0\u7269", "\u88c5\u4fee", "\u5a5a\u793c"],
+    "fanRange": { "min": 10000, "max": 800000 },
+    "budgetRange": { "min": 2000, "max": 20000 }
+  },
+  "referenceLinks": [],
+  "expected": {
+    "targetPlatforms": ["xiaohongshu", "douyin"],
+    "targetCreatorTypes": ["\u98df\u54c1\u6d4b\u8bc4", "\u5065\u8eab\u63a7\u7cd6", "\u529e\u516c\u5ba4\u96f6\u98df", "\u5065\u5eb7\u751f\u6d3b"],
+    "offTopicTerms": ["\u533b\u7f8e", "\u5ba0\u7269", "\u88c5\u4fee", "\u5a5a\u793c"],
+    "mustHaveSignals": ["\u98df\u54c1", "\u96f6\u98df"],
+    "targetCount": 12
+  }
+}

+ 23 - 0
claude-code-tihao-sourcing/fixtures/quality-briefs/home-cleaning.json

@@ -0,0 +1,23 @@
+{
+  "id": "home-cleaning",
+  "name": "Home Cleaning Seeding",
+  "briefText": "Home cleaning product seeding. Target Xiaohongshu creators: home cleaning, storage, rental life, mom chores, real comparison reviews.",
+  "input": {
+    "brand": "\u5bb6\u5c45\u6e05\u6d01\u7528\u54c1",
+    "category": "\u5bb6\u5c45\u751f\u6d3b",
+    "platforms": ["xiaohongshu"],
+    "keywords": ["\u5bb6\u5c45", "\u6e05\u6d01", "\u6536\u7eb3", "\u79df\u623f", "\u5bb6\u52a1", "\u6d4b\u8bc4"],
+    "stylePreferences": ["\u771f\u5b9e\u4f7f\u7528", "\u524d\u540e\u5bf9\u6bd4", "\u6b65\u9aa4\u6559\u7a0b", "\u751f\u6d3b\u5316"],
+    "exclusions": ["\u5a5a\u793c", "\u533b\u7f8e", "\u7a7f\u642d", "\u5ba0\u7269"],
+    "fanRange": { "min": 10000, "max": 400000 },
+    "budgetRange": { "min": 1500, "max": 12000 }
+  },
+  "referenceLinks": [],
+  "expected": {
+    "targetPlatforms": ["xiaohongshu"],
+    "targetCreatorTypes": ["\u5bb6\u5c45\u6e05\u6d01", "\u6536\u7eb3\u6574\u7406", "\u79df\u623f\u751f\u6d3b", "\u771f\u5b9e\u6d4b\u8bc4"],
+    "offTopicTerms": ["\u5a5a\u793c", "\u533b\u7f8e", "\u7a7f\u642d", "\u5ba0\u7269"],
+    "mustHaveSignals": ["\u5bb6\u5c45", "\u6e05\u6d01"],
+    "targetCount": 10
+  }
+}

+ 23 - 0
claude-code-tihao-sourcing/fixtures/quality-briefs/sensitive-skin-repair.json

@@ -0,0 +1,23 @@
+{
+  "id": "sensitive-skin-repair",
+  "name": "Sensitive Skin Repair Seeding",
+  "briefText": "Sensitive-skin repair skincare seeding. Target Xiaohongshu creators: sensitive skin, ingredient explainers, real empties, commuting skincare, barrier repair. Exclude medical beauty, exaggerated claims, entertainment, and unrelated mom-baby accounts.",
+  "input": {
+    "brand": "\u654f\u611f\u808c\u4fee\u62a4\u62a4\u80a4\u54c1\u724c",
+    "category": "\u62a4\u80a4\u7f8e\u5986",
+    "platforms": ["xiaohongshu"],
+    "keywords": ["\u654f\u611f\u808c", "\u4fee\u62a4", "\u62a4\u80a4", "\u6210\u5206", "\u7a7a\u74f6", "\u5c4f\u969c\u4fee\u62a4"],
+    "stylePreferences": ["\u771f\u5b9e\u4f53\u9a8c", "\u6210\u5206\u89e3\u91ca", "\u7d20\u4eba\u611f", "\u4f4e\u786c\u5e7f"],
+    "exclusions": ["\u533b\u7f8e", "\u6bcd\u5a74", "\u5ba0\u7269", "\u5a5a\u793c", "\u88c5\u4fee"],
+    "fanRange": { "min": 10000, "max": 300000 },
+    "budgetRange": { "min": 2000, "max": 15000 }
+  },
+  "referenceLinks": [],
+  "expected": {
+    "targetPlatforms": ["xiaohongshu"],
+    "targetCreatorTypes": ["\u654f\u611f\u808c", "\u6210\u5206\u515a", "\u62a4\u80a4\u6d4b\u8bc4", "\u771f\u5b9e\u7a7a\u74f6"],
+    "offTopicTerms": ["\u533b\u7f8e", "\u6bcd\u5a74", "\u5ba0\u7269", "\u5a5a\u793c", "\u88c5\u4fee"],
+    "mustHaveSignals": ["\u62a4\u80a4", "\u654f\u611f\u808c"],
+    "targetCount": 10
+  }
+}

+ 7 - 0
claude-code-tihao-sourcing/fixtures/sample-briefs/skincare-sample.md

@@ -0,0 +1,7 @@
+客户名称:花漾肌研
+产品:敏感肌屏障修护精华
+关键词:敏感肌、修护、护肤、成分、测评、真实体验
+平台与人数:小红书 6 人,抖音 2 人
+粉丝量范围:2万-50万
+预算范围:3000-18000
+偏好真实体验、素人感、成分拆解、敏感肌修护,不要硬广。

+ 177 - 0
claude-code-tihao-sourcing/install.js

@@ -0,0 +1,177 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const { spawnSync } = require('child_process');
+
+const ROOT = __dirname;
+const PLUGIN_NAME = 'tihao';
+const SKILL_NAME = 'tihao';
+const SKILL_NAMES = ['tihao', 'fmode-image-analysis'];
+const LEGACY_PLUGIN_NAMES = ['tihao-sourcing'];
+const LEGACY_SKILL_NAMES = ['tihao-creator-sourcing'];
+
+function main() {
+  const args = new Set(process.argv.slice(2));
+  if (args.has('--check')) {
+    console.log('tihao package is readable.');
+    return;
+  }
+
+  const workspaceMode = args.has('--workspace') || process.argv.includes('workspace');
+  const baseDir = workspaceMode
+    ? path.join(process.cwd(), '.claude')
+    : path.join(os.homedir(), '.claude');
+  const pluginTarget = path.join(baseDir, 'plugins', PLUGIN_NAME);
+  const primarySkillTarget = path.join(baseDir, 'skills', SKILL_NAME);
+
+  copyDir(ROOT, pluginTarget, shouldCopyPackageFile);
+  for (const skillName of SKILL_NAMES) {
+    copyDir(path.join(ROOT, 'skills', skillName), path.join(baseDir, 'skills', skillName), () => true);
+  }
+  removeLegacyPlugins(baseDir, pluginTarget);
+  removeLegacySkills(baseDir, primarySkillTarget);
+  ensureRuntimeDeps(pluginTarget);
+  writeWorkspaceMcp(process.cwd(), pluginTarget, workspaceMode);
+
+  console.log(`Installed ${PLUGIN_NAME} plugin to ${pluginTarget}`);
+  for (const skillName of SKILL_NAMES) {
+    console.log(`Installed ${skillName} skill to ${path.join(baseDir, 'skills', skillName)}`);
+  }
+
+  if (args.has('--smoke')) {
+    const result = spawnSync(process.execPath, [path.join(pluginTarget, 'scripts/smoke-package.js')], {
+      cwd: pluginTarget,
+      stdio: 'inherit'
+    });
+    process.exit(result.status || 0);
+  }
+}
+
+function removeLegacyPlugins(baseDir, currentPluginTarget) {
+  const pluginsRoot = path.resolve(baseDir, 'plugins');
+  const resolvedCurrent = path.resolve(currentPluginTarget);
+  for (const legacyName of LEGACY_PLUGIN_NAMES) {
+    const legacyTarget = path.resolve(pluginsRoot, legacyName);
+    const relativeLegacy = path.relative(pluginsRoot, legacyTarget);
+    if (legacyTarget === resolvedCurrent) continue;
+    if (relativeLegacy.startsWith('..') || path.isAbsolute(relativeLegacy)) continue;
+    removeDirRecursive(legacyTarget);
+  }
+}
+
+function removeLegacySkills(baseDir, currentSkillTarget) {
+  const skillsRoot = path.resolve(baseDir, 'skills');
+  const resolvedCurrent = path.resolve(currentSkillTarget);
+  for (const legacyName of LEGACY_SKILL_NAMES) {
+    const legacyTarget = path.resolve(skillsRoot, legacyName);
+    const relativeLegacy = path.relative(skillsRoot, legacyTarget);
+    if (legacyTarget === resolvedCurrent) continue;
+    if (relativeLegacy.startsWith('..') || path.isAbsolute(relativeLegacy)) continue;
+    removeDirRecursive(legacyTarget);
+  }
+}
+
+function removeDirRecursive(target) {
+  if (!fs.existsSync(target)) return;
+  try {
+    fs.rmSync(target, { recursive: true, force: true });
+  } catch (error) {
+    if (!fs.existsSync(target)) return;
+  }
+  if (!fs.existsSync(target)) return;
+
+  for (const entry of fs.readdirSync(target, { withFileTypes: true })) {
+    const child = path.join(target, entry.name);
+    if (entry.isDirectory() && !entry.isSymbolicLink()) {
+      removeDirRecursive(child);
+    } else {
+      fs.unlinkSync(child);
+    }
+  }
+  fs.rmdirSync(target);
+}
+
+function shouldCopyPackageFile(source) {
+  const rel = path.relative(ROOT, source).replace(/\\/g, '/');
+  if (!rel) return true;
+  if (rel.startsWith('node_modules/')) return false;
+  if (rel.startsWith('outputs/')) return false;
+  if (rel.startsWith('.claude/')) return false;
+  if (rel === '.env' || rel === '.env.local' || rel === '.npmrc') return false;
+  return true;
+}
+
+function copyDir(source, target, predicate) {
+  if (!fs.existsSync(source)) return;
+  fs.mkdirSync(target, { recursive: true });
+  for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
+    const sourcePath = path.join(source, entry.name);
+    if (!predicate(sourcePath)) continue;
+    const targetPath = path.join(target, entry.name);
+    if (entry.isDirectory()) {
+      copyDir(sourcePath, targetPath, predicate);
+    } else if (entry.isFile()) {
+      fs.mkdirSync(path.dirname(targetPath), { recursive: true });
+      fs.copyFileSync(sourcePath, targetPath);
+    }
+  }
+}
+
+function writeWorkspaceMcp(cwd, pluginTarget, workspaceMode) {
+  if (!workspaceMode) return;
+  const mcpFile = path.join(cwd, '.mcp.json');
+  const existing = fs.existsSync(mcpFile) ? safeJson(fs.readFileSync(mcpFile, 'utf8')) : {};
+  const mcpServers = { ...(existing.mcpServers || {}) };
+  for (const legacyName of LEGACY_PLUGIN_NAMES) {
+    delete mcpServers[legacyName];
+  }
+  mcpServers[PLUGIN_NAME] = {
+    command: 'node',
+    args: [path.join(pluginTarget, 'mcp/src/server.js')]
+  };
+  const next = {
+    ...existing,
+    mcpServers
+  };
+  fs.writeFileSync(mcpFile, JSON.stringify(next, null, 2), 'utf8');
+}
+
+function ensureRuntimeDeps(pluginTarget) {
+  const sdkPath = path.join(pluginTarget, 'node_modules', '@modelcontextprotocol', 'sdk');
+  if (fs.existsSync(sdkPath)) return;
+  const sourceNodeModules = path.join(ROOT, 'node_modules');
+  if (fs.existsSync(sourceNodeModules)) {
+    copyDir(sourceNodeModules, path.join(pluginTarget, 'node_modules'), () => true);
+    if (fs.existsSync(sdkPath)) return;
+  }
+  const npmCommand = resolveNpmCommand(['install', '--omit=dev']);
+  const result = spawnSync(npmCommand.command, npmCommand.args, {
+    cwd: pluginTarget,
+    stdio: 'inherit',
+    shell: npmCommand.shell
+  });
+  if (result.status !== 0) {
+    throw new Error('Failed to install runtime dependencies for copied plugin.');
+  }
+}
+
+function resolveNpmCommand(args) {
+  if (process.env.npm_execpath) {
+    return { command: process.execPath, args: [process.env.npm_execpath, ...args], shell: false };
+  }
+  if (process.platform === 'win32') {
+    return { command: 'npm.cmd', args, shell: true };
+  }
+  return { command: 'npm', args, shell: false };
+}
+
+function safeJson(raw) {
+  try {
+    return JSON.parse(raw);
+  } catch {
+    return {};
+  }
+}
+
+main();

+ 108 - 0
claude-code-tihao-sourcing/mcp/src/core/credentials.js

@@ -0,0 +1,108 @@
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+
+function readEnvFile(cwd = process.cwd()) {
+  const candidates = [
+    path.join(cwd, '.env.local'),
+    path.join(cwd, '.env'),
+    path.join(os.homedir(), '.claude', 'tihao-credentials.json'),
+    path.join(os.homedir(), '.claude', 'voc-credentials.json')
+  ];
+  const values = {};
+  for (const file of candidates) {
+    if (!fs.existsSync(file)) continue;
+    try {
+      const raw = fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, '');
+      if (file.endsWith('.json')) {
+        Object.assign(values, JSON.parse(raw));
+      } else {
+        for (const line of raw.split(/\r?\n/)) {
+          const match = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.+?)\s*$/i);
+          if (match) values[match[1]] = match[2].replace(/^['"]|['"]$/g, '');
+        }
+      }
+    } catch {
+      // Ignore malformed local credential files; live tools will return friendly states.
+    }
+  }
+  return values;
+}
+
+function readTihaoToken(input = {}) {
+  const fileEnv = readEnvFile(input.cwd || process.cwd());
+  const env = { ...process.env, ...fileEnv };
+  return (
+    input.tihaoToken ||
+    input.sessionToken ||
+    input.vocToken ||
+    env.TIHAO_SESSION_TOKEN ||
+    env.VOC_ECOMMERCE_TOKEN ||
+    env.VOC_TOKEN ||
+    env.sessionToken ||
+    env.tihaoToken ||
+    ''
+  );
+}
+
+// 社媒分析服务 token(对外只叫「社媒分析服务」)。
+// 优先用平台 sessionToken / vocSocialToken;tikhub* 只作为历史兼容项保留,不对外暴露。
+function readSocialAnalysisToken(input = {}) {
+  const fileEnv = readEnvFile(input.cwd || process.cwd());
+  const env = { ...process.env, ...fileEnv };
+  return (
+    input.socialAnalysisToken ||
+    input.vocSocialToken ||
+    input.sessionToken ||
+    input.tihaoToken ||
+    input.vocToken ||
+    input.tikhubToken ||
+    input.tikHubToken ||
+    env.VOC_SOCIAL_TOKEN ||
+    env.TIHAO_SESSION_TOKEN ||
+    env.VOC_TOKEN ||
+    env.TIKHUB_TOKEN ||
+    env.TIKHUB_API_KEY ||
+    env.TIKHUB_KEY ||
+    ''
+  );
+}
+
+// Backward-compatible alias; prefer readSocialAnalysisToken in new code.
+function readTikhubToken(input = {}) {
+  return readSocialAnalysisToken(input);
+}
+
+function readEvidenceProviderToken(input = {}) {
+  const fileEnv = readEnvFile(input.cwd || process.cwd());
+  const env = { ...process.env, ...fileEnv };
+  return (
+    input.evidenceProviderToken ||
+    input.multimodalEvidenceToken ||
+    input.doubaoVisionToken ||
+    input.videoAnalysisToken ||
+    env.TIHAO_EVIDENCE_TOKEN ||
+    env.MULTIMODAL_EVIDENCE_TOKEN ||
+    env.EVIDENCE_PROVIDER_TOKEN ||
+    env.DOUBAO_VISION_TOKEN ||
+    env.VIDEO_ANALYSIS_TOKEN ||
+    env.ANTHROPIC_AUTH_TOKEN ||
+    // 默认视频/视觉分析走公司 fmode 豆包网关,复用平台 sessionToken。
+    input.fmodeToken ||
+    input.sessionToken ||
+    input.tihaoToken ||
+    input.vocToken ||
+    env.FMODE_TOKEN ||
+    env.TIHAO_SESSION_TOKEN ||
+    env.VOC_TOKEN ||
+    ''
+  );
+}
+
+module.exports = {
+  readTihaoToken,
+  readSocialAnalysisToken,
+  readTikhubToken,
+  readEvidenceProviderToken,
+  readEnvFile
+};

+ 183 - 0
claude-code-tihao-sourcing/mcp/src/core/files.js

@@ -0,0 +1,183 @@
+const fs = require('fs');
+const path = require('path');
+const zlib = require('zlib');
+
+function ensureDir(dir) {
+  fs.mkdirSync(dir, { recursive: true });
+}
+
+function writeJson(file, data) {
+  ensureDir(path.dirname(file));
+  fs.writeFileSync(file, JSON.stringify(data, null, 2), 'utf8');
+}
+
+function writeText(file, text) {
+  ensureDir(path.dirname(file));
+  fs.writeFileSync(file, withExcelBom(file, text), 'utf8');
+}
+
+function withExcelBom(file, text) {
+  const value = String(text ?? '');
+  if (!String(file || '').toLowerCase().endsWith('.csv')) return value;
+  return value.startsWith('\uFEFF') ? value : `\uFEFF${value}`;
+}
+
+function readTextMaybe(file) {
+  if (!file || !fs.existsSync(file)) return '';
+  const buffer = fs.readFileSync(file);
+  const lower = file.toLowerCase();
+  if (lower.endsWith('.xlsx')) return extractXlsxText(buffer);
+  if (lower.endsWith('.xls')) return extractSpreadsheetText(buffer);
+  if (lower.endsWith('.docx')) return extractDocxText(buffer);
+  return buffer.toString('utf8');
+}
+
+function listZipEntries(buffer) {
+  const entries = [];
+  let offset = 0;
+  while (offset < buffer.length - 46) {
+    if (buffer.readUInt32LE(offset) !== 0x02014b50) {
+      offset += 1;
+      continue;
+    }
+    const method = buffer.readUInt16LE(offset + 10);
+    const compressedSize = buffer.readUInt32LE(offset + 20);
+    const fileNameLength = buffer.readUInt16LE(offset + 28);
+    const extraLength = buffer.readUInt16LE(offset + 30);
+    const commentLength = buffer.readUInt16LE(offset + 32);
+    const localHeaderOffset = buffer.readUInt32LE(offset + 42);
+    const name = buffer.toString('utf8', offset + 46, offset + 46 + fileNameLength);
+    entries.push({ name, method, compressedSize, localHeaderOffset });
+    offset += 46 + fileNameLength + extraLength + commentLength;
+  }
+  return entries;
+}
+
+function readZipEntry(buffer, entryName) {
+  const entry = listZipEntries(buffer).find(item => item.name === entryName);
+  if (!entry) return null;
+  const localOffset = entry.localHeaderOffset;
+  if (buffer.readUInt32LE(localOffset) !== 0x04034b50) return null;
+  const fileNameLength = buffer.readUInt16LE(localOffset + 26);
+  const extraLength = buffer.readUInt16LE(localOffset + 28);
+  const dataStart = localOffset + 30 + fileNameLength + extraLength;
+  const compressed = buffer.subarray(dataStart, dataStart + entry.compressedSize);
+  if (entry.method === 0) return compressed;
+  if (entry.method === 8) return zlib.inflateRawSync(compressed);
+  return null;
+}
+
+function decodeXml(value) {
+  return String(value || '')
+    .replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => String.fromCodePoint(Number.parseInt(hex, 16)))
+    .replace(/&#(\d+);/g, (_, decimal) => String.fromCodePoint(Number.parseInt(decimal, 10)))
+    .replace(/&lt;/g, '<')
+    .replace(/&gt;/g, '>')
+    .replace(/&quot;/g, '"')
+    .replace(/&apos;/g, "'")
+    .replace(/&amp;/g, '&');
+}
+
+function parseSharedStrings(xmlBuffer) {
+  if (!xmlBuffer) return [];
+  const xml = xmlBuffer.toString('utf8');
+  const strings = [];
+  const itemRegex = /<si\b[^>]*>([\s\S]*?)<\/si>/g;
+  let match;
+  while ((match = itemRegex.exec(xml)) !== null) {
+    const parts = [];
+    const textRegex = /<t\b[^>]*>([\s\S]*?)<\/t>/g;
+    let textMatch;
+    while ((textMatch = textRegex.exec(match[1])) !== null) parts.push(decodeXml(textMatch[1]));
+    strings.push(parts.join(''));
+  }
+  return strings;
+}
+
+function extractXlsxText(buffer) {
+  const sharedStrings = parseSharedStrings(readZipEntry(buffer, 'xl/sharedStrings.xml'));
+  const entries = listZipEntries(buffer)
+    .filter(entry => /^xl\/worksheets\/sheet\d+\.xml$/.test(entry.name))
+    .sort((a, b) => a.name.localeCompare(b.name));
+  const rows = [];
+  for (const entry of entries) {
+    const xmlBuffer = readZipEntry(buffer, entry.name);
+    if (!xmlBuffer) continue;
+    const xml = xmlBuffer.toString('utf8');
+    const rowRegex = /<row\b[^>]*>([\s\S]*?)<\/row>/g;
+    let rowMatch;
+    while ((rowMatch = rowRegex.exec(xml)) !== null) {
+      const cells = [];
+      const cellRegex = /<c\b([^>]*)>([\s\S]*?)<\/c>/g;
+      let cellMatch;
+      while ((cellMatch = cellRegex.exec(rowMatch[1])) !== null) {
+        const attrs = cellMatch[1];
+        const body = cellMatch[2];
+        const type = attrs.match(/\bt="([^"]+)"/)?.[1];
+        const rawValue = body.match(/<v[^>]*>([\s\S]*?)<\/v>/)?.[1] || '';
+        const value = type === 's' ? sharedStrings[Number(rawValue)] || '' : decodeXml(rawValue);
+        if (value) cells.push(value);
+      }
+      if (cells.length) rows.push(cells.join(' | '));
+    }
+  }
+  return rows.join('\n');
+}
+
+function extractSpreadsheetText(buffer) {
+  if (buffer.length >= 4 && buffer.readUInt32LE(0) === 0x04034b50) {
+    return extractXlsxText(buffer);
+  }
+  const text = buffer.toString('utf8').replace(/^\uFEFF/, '');
+  if (/<html[\s>]/i.test(text) || /<table[\s>]/i.test(text)) {
+    return extractHtmlTableText(text);
+  }
+  return text;
+}
+
+function extractHtmlTableText(html) {
+  const rows = [];
+  const rowRegex = /<tr\b[^>]*>([\s\S]*?)<\/tr>/gi;
+  let rowMatch;
+  while ((rowMatch = rowRegex.exec(html)) !== null) {
+    const cells = [];
+    const cellRegex = /<t[dh]\b[^>]*>([\s\S]*?)<\/t[dh]>/gi;
+    let cellMatch;
+    while ((cellMatch = cellRegex.exec(rowMatch[1])) !== null) {
+      const value = stripHtml(cellMatch[1]);
+      if (value) cells.push(value);
+    }
+    if (cells.length) rows.push(cells.join(' | '));
+  }
+  if (rows.length) return rows.join('\n');
+  return stripHtml(html);
+}
+
+function stripHtml(value) {
+  return decodeXml(String(value || '')
+    .replace(/<script\b[\s\S]*?<\/script>/gi, ' ')
+    .replace(/<style\b[\s\S]*?<\/style>/gi, ' ')
+    .replace(/<br\s*\/?>/gi, '\n')
+    .replace(/<\/p>|<\/div>|<\/li>/gi, '\n')
+    .replace(/<[^>]+>/g, ' ')
+    .replace(/\s+/g, ' ')
+    .trim());
+}
+
+function extractDocxText(buffer) {
+  const xmlBuffer = readZipEntry(buffer, 'word/document.xml');
+  if (!xmlBuffer) return buffer.toString('utf8');
+  const xml = xmlBuffer.toString('utf8');
+  const parts = [];
+  const regex = /<w:t[^>]*>([^<]*)<\/w:t>/g;
+  let match;
+  while ((match = regex.exec(xml)) !== null) parts.push(decodeXml(match[1]));
+  return parts.join(' ');
+}
+
+module.exports = {
+  ensureDir,
+  readTextMaybe,
+  writeJson,
+  writeText
+};

+ 146 - 0
claude-code-tihao-sourcing/mcp/src/core/payment-links.js

@@ -0,0 +1,146 @@
+const TIHAO_ECOMMERCE_APIG_ID = 'G0vmsUI44d';
+const TIHAO_RECHARGE_FUN_ID = 'voc-e-commerce';
+const TIHAO_ECOMMERCE_APIG_PATH = '/apig/voc-e-commerce';
+const RECHARGE_BASE_URL = 'https://app.fmode.cn/dev/apig-pay/';
+const PARSE_USERS_ME_URL = 'https://server.fmode.cn/parse/users/me';
+
+// 社媒分析服务(参考博主补证 / 视频·视觉分析)对应的 APIG 充值服务。
+// 对客户只暴露「社媒分析服务」这一名称,不暴露任何底层供应商名。
+const VOC_SOCIAL_APIG_ID = 'Vo3ROWEvDy';
+const VOC_SOCIAL_RECHARGE_FUN_ID = 'HOkkX72PMF';
+const VOC_SOCIAL_API_ROOT = 'https://server.fmode.cn/api/voc-social';
+
+function buildTihaoRechargeUrl(options = {}) {
+  const url = new URL(RECHARGE_BASE_URL);
+  if (options.userId) url.searchParams.set('user', String(options.userId));
+  url.searchParams.set('apigid', TIHAO_ECOMMERCE_APIG_ID);
+  url.searchParams.set('fun_id', TIHAO_RECHARGE_FUN_ID);
+  return url.toString();
+}
+
+async function resolveUserId(sessionToken) {
+  const info = await resolveTihaoUserInfo(sessionToken);
+  return info.userId;
+}
+
+async function resolveTihaoUserInfo(sessionToken) {
+  const empty = { userId: '', companyId: '' };
+  if (!sessionToken) return empty;
+  try {
+    const response = await fetch(PARSE_USERS_ME_URL, {
+      headers: {
+        'X-Parse-Application-Id': 'ncloudmaster',
+        'X-Parse-Session-Token': sessionToken,
+        Accept: 'application/json'
+      }
+    });
+    if (!response.ok) return empty;
+    const json = await response.json();
+    return {
+      userId: json && json.objectId ? String(json.objectId) : '',
+      companyId: resolveCompanyId(json)
+    };
+  } catch {
+    return empty;
+  }
+}
+
+function resolveCompanyId(json) {
+  const company = json && json.company;
+  if (!company) return '';
+  if (typeof company === 'string') return company;
+  if (typeof company === 'object') return String(company.objectId || company.id || '');
+  return '';
+}
+
+async function buildTihaoRechargeInfo(options = {}) {
+  const userId = await resolveUserId(options.sessionToken || options.tihaoToken);
+  return {
+    paymentUrl: buildTihaoRechargeUrl({ userId }),
+    fallbackPaymentUrl: buildTihaoRechargeUrl(),
+    apigId: TIHAO_ECOMMERCE_APIG_ID,
+    apigPath: TIHAO_ECOMMERCE_APIG_PATH,
+    funId: TIHAO_RECHARGE_FUN_ID,
+    personalized: Boolean(userId)
+  };
+}
+
+function buildSocialRechargeUrl(options = {}) {
+  const url = new URL(RECHARGE_BASE_URL);
+  if (options.userId) url.searchParams.set('user', String(options.userId));
+  url.searchParams.set('apigid', VOC_SOCIAL_APIG_ID);
+  url.searchParams.set('fun_id', VOC_SOCIAL_RECHARGE_FUN_ID);
+  return url.toString();
+}
+
+async function buildSocialRechargeInfo(options = {}) {
+  const userId = await resolveUserId(options.sessionToken || options.tihaoToken || options.vocToken);
+  return {
+    paymentUrl: buildSocialRechargeUrl({ userId }),
+    fallbackPaymentUrl: buildSocialRechargeUrl(),
+    apigId: VOC_SOCIAL_APIG_ID,
+    apiRoot: VOC_SOCIAL_API_ROOT,
+    funId: VOC_SOCIAL_RECHARGE_FUN_ID,
+    personalized: Boolean(userId)
+  };
+}
+
+function buildSocialMissingTokenMessage(paymentUrl) {
+  return [
+    '还没有检测到社媒分析服务的开通/余额信息,暂时不能补出参考账号的作者主页、近作和互动中位数。',
+    '',
+    `请先打开社媒分析服务开通/充值链接:${paymentUrl}`,
+    '',
+    '开通后,把平台 sessionToken 配置到当前工作区 `.env.local`,或传入 MCP 工具的 `sessionToken` / `tihaoToken` 字段。',
+    '',
+    '系统只会用 token 发起接口请求,不会在报告、日志或聊天输出里回显 token。'
+  ].join('\n');
+}
+
+function buildSocialRechargeRequiredMessage(paymentUrl) {
+  return [
+    '社媒分析服务未开通或余额不足,暂时不能补出参考账号基线,已保留原参考链接继续生成候选名单。',
+    '',
+    `请先打开社媒分析服务充值链接补充余额:${paymentUrl}`,
+    '',
+    '底层接口权限或余额错误已被隐藏,不会把原始 403、鉴权信息或上游响应展示给商务用户。'
+  ].join('\n');
+}
+
+function buildMissingTokenMessage(paymentUrl) {
+  return [
+    '还没有检测到提号电商数据服务 sessionToken,暂时不能进行真实 JustOne 代理检索。',
+    '',
+    `请先打开开通/充值链接:${paymentUrl}`,
+    '',
+    '开通后,把平台 sessionToken 配置到当前工作区 `.env.local`,或传入 MCP 工具的 `tihaoToken` / `sessionToken` 字段。',
+    '',
+    '系统只会用 token 发起接口请求,不会在报告、日志或聊天输出里回显 token。'
+  ].join('\n');
+}
+
+function buildRechargeRequiredMessage(paymentUrl) {
+  return [
+    '提号电商数据服务未开通或额度不足,暂时不能继续真实博主检索。',
+    '',
+    `请先打开开通/充值链接补充额度:${paymentUrl}`,
+    '',
+    '底层接口权限或余额错误已被隐藏,不会把原始 403、鉴权信息或上游响应展示给商务用户。'
+  ].join('\n');
+}
+
+module.exports = {
+  TIHAO_ECOMMERCE_APIG_ID,
+  TIHAO_ECOMMERCE_APIG_PATH,
+  VOC_SOCIAL_APIG_ID,
+  VOC_SOCIAL_RECHARGE_FUN_ID,
+  buildTihaoRechargeInfo,
+  buildTihaoRechargeUrl,
+  resolveTihaoUserInfo,
+  buildMissingTokenMessage,
+  buildRechargeRequiredMessage,
+  buildSocialRechargeInfo,
+  buildSocialRechargeUrl,
+  buildSocialMissingTokenMessage,
+  buildSocialRechargeRequiredMessage
+};

+ 54 - 0
claude-code-tihao-sourcing/mcp/src/core/result-envelope.js

@@ -0,0 +1,54 @@
+function okResult(payload = {}) {
+  return {
+    status: payload.status || 'ok',
+    assistantMessage: payload.assistantMessage || '',
+    summary: payload.summary || {},
+    data: payload.data || {},
+    files: payload.files || [],
+    nextActions: payload.nextActions || [],
+    warnings: payload.warnings || [],
+    errors: payload.errors || []
+  };
+}
+
+function friendlyState(status, assistantMessage, payload = {}) {
+  return {
+    status,
+    assistantMessage,
+    summary: payload.summary || {},
+    data: payload.data || {},
+    files: payload.files || [],
+    nextActions: payload.nextActions || [],
+    warnings: payload.warnings || [],
+    errors: []
+  };
+}
+
+function errorResult(message, payload = {}) {
+  return {
+    status: 'error',
+    assistantMessage: message,
+    summary: payload.summary || {},
+    data: payload.data || {},
+    files: payload.files || [],
+    nextActions: payload.nextActions || [],
+    warnings: payload.warnings || [],
+    errors: payload.errors || [{ message }]
+  };
+}
+
+function redactSecrets(value) {
+  if (typeof value !== 'string') return value;
+  return value
+    .replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/g, 'Bearer ***')
+    .replace(/sessionToken["':=\s]+[A-Za-z0-9._~+/=-]+/gi, 'sessionToken=***')
+    .replace(/token["':=\s]+[A-Za-z0-9._~+/=-]+/gi, 'token=***')
+    .replace(/sk-[A-Za-z0-9_-]+/g, 'sk-***');
+}
+
+module.exports = {
+  okResult,
+  friendlyState,
+  errorResult,
+  redactSecrets
+};

+ 376 - 0
claude-code-tihao-sourcing/mcp/src/features/fmode-image-analysis/image-analysis.js

@@ -0,0 +1,376 @@
+const fs = require('fs');
+const path = require('path');
+const { readEnvFile } = require('../../core/credentials');
+const { buildTihaoRechargeInfo } = require('../../core/payment-links');
+const { okResult, friendlyState, errorResult, redactSecrets } = require('../../core/result-envelope');
+
+const DEFAULT_BASE_URL = 'https://api.fmode.cn';
+const DEFAULT_PATH = 'v1/chat/completions';
+const DEFAULT_MODEL = 'doubao-seed-2-0-pro-260215';
+const DEFAULT_PROMPT = '请分析图片内容,提取可见文字、主体对象、场景、关键信息、潜在风险,并给出简洁结论。';
+const MAX_IMAGE_BYTES = 20 * 1024 * 1024;
+
+async function analyzeFmodeImage(input = {}) {
+  const images = resolveImageInputs(input);
+  if (!images.length) {
+    return errorResult('请提供要分析的图片路径或图片 URL,例如 imagePath、imageUrl 或 images。', {
+      errors: [],
+      nextActions: ['传入本地图片路径、HTTP 图片 URL,或 data:image/... base64。']
+    });
+  }
+
+  const token = resolveFmodeToken(input);
+  if (!token) {
+    const recharge = await buildTihaoRechargeInfo();
+    return friendlyState('needs_token', buildMissingTokenMessage(recharge.paymentUrl), {
+      summary: { imageCount: images.length, model: resolveModel(input) },
+      data: { recharge, model: resolveModel(input), imageCount: images.length },
+      nextActions: [
+        '在当前工作区 .env.local 配置 TIHAO_SESSION_TOKEN / VOC_TOKEN / FMODE_TOKEN。',
+        '或在 MCP 工具参数中传入 sessionToken / vocToken / fmodeToken。'
+      ]
+    });
+  }
+
+  const provider = resolveProvider(input);
+  let payload;
+  try {
+    payload = buildChatPayload(input, provider, images);
+  } catch (error) {
+    return errorResult(redactSecrets(error.message || String(error)), {
+      errors: [],
+      summary: { imageCount: images.length, model: provider.model }
+    });
+  }
+
+  let response;
+  try {
+    response = await fetch(provider.url, {
+      method: 'POST',
+      headers: {
+        Accept: 'application/json',
+        'Content-Type': 'application/json',
+        Authorization: `Bearer ${token}`
+      },
+      body: JSON.stringify(payload)
+    });
+  } catch {
+    return errorResult('图片分析服务暂时无法连接,请稍后重试或检查 Fmode 网关地址。', {
+      errors: [],
+      summary: { imageCount: images.length, model: provider.model },
+      warnings: ['network_error']
+    });
+  }
+
+  if (response.status === 401) {
+    return friendlyState('needs_valid_token', '当前平台 token 无效或已过期,请换成有效的 sessionToken / vocToken / fmodeToken 后重试。', {
+      summary: { imageCount: images.length, model: provider.model },
+      data: { model: provider.model, imageCount: images.length }
+    });
+  }
+
+  if (response.status === 402 || response.status === 403) {
+    const recharge = await buildTihaoRechargeInfo({ sessionToken: token });
+    return friendlyState('needs_recharge', buildRechargeMessage(recharge.paymentUrl), {
+      summary: { imageCount: images.length, model: provider.model },
+      data: { recharge, model: provider.model, imageCount: images.length }
+    });
+  }
+
+  if (!response.ok) {
+    return errorResult('图片分析服务返回异常;上游错误详情已隐藏,避免泄露鉴权或计费信息。', {
+      errors: [],
+      summary: { imageCount: images.length, model: provider.model },
+      data: { providerStatus: 'http_error' }
+    });
+  }
+
+  let json;
+  try {
+    json = await response.json();
+  } catch {
+    return errorResult('图片分析服务返回内容无法解析,请稍后重试。', {
+      errors: [],
+      summary: { imageCount: images.length, model: provider.model },
+      warnings: ['parse_error']
+    });
+  }
+
+  const analysis = extractAssistantText(json).trim();
+  if (!analysis) {
+    return errorResult('图片分析服务没有返回可用文本结果。', {
+      errors: [],
+      summary: { imageCount: images.length, model: provider.model },
+      warnings: ['empty_result']
+    });
+  }
+
+  const files = writeOutputs(input, {
+    analysis,
+    model: provider.model,
+    imageCount: images.length,
+    usage: json.usage || {},
+    sources: images.map(item => item.label)
+  });
+
+  return okResult({
+    assistantMessage: buildAssistantMessage({ analysis, provider, images, files }),
+    summary: {
+      imageCount: images.length,
+      model: provider.model,
+      hasUsage: Boolean(json.usage)
+    },
+    data: {
+      analysis,
+      model: provider.model,
+      imageCount: images.length,
+      imageSources: images.map(item => item.label),
+      usage: json.usage || {},
+      finishReason: json.choices?.[0]?.finish_reason || ''
+    },
+    files,
+    nextActions: [
+      '如需结构化 JSON,可补充字段要求后再次调用 fmode_image_analysis。',
+      '如需把识图结果用于提号 brief,可将 analysis 作为 productContext 或 chatRecords 传给 tihao_brief_sourcing_run。'
+    ]
+  });
+}
+
+function buildChatPayload(input, provider, images) {
+  const prompt = [
+    input.prompt || input.question || input.text || DEFAULT_PROMPT,
+    input.context ? `\n补充背景:${input.context}` : '',
+    input.outputFormat === 'json' || input.responseFormat === 'json'
+      ? '\n请返回 JSON 字符串,不要使用 markdown 代码块。'
+      : ''
+  ].filter(Boolean).join('\n');
+
+  return {
+    model: provider.model,
+    temperature: Number(input.temperature ?? 0.2),
+    max_tokens: Number(input.maxTokens || input.max_tokens || 1800),
+    messages: [
+      {
+        role: 'system',
+        content: 'You are an image analysis assistant. Answer in Chinese unless the user asks for another language. Do not expose tokens, headers, or upstream error details.'
+      },
+      {
+        role: 'user',
+        content: [
+          { type: 'text', text: prompt },
+          ...images.map(image => ({
+            type: 'image_url',
+            image_url: {
+              url: image.url,
+              detail: input.detail || 'auto'
+            }
+          }))
+        ]
+      }
+    ]
+  };
+}
+
+function resolveProvider(input = {}) {
+  const baseUrl = String(input.baseUrl || input.fmodeBaseUrl || input.doubaoVisionBaseUrl || input.videoAnalysisBaseUrl || process.env.FMODE_BASE_URL || process.env.DOUBAO_VISION_BASE_URL || DEFAULT_BASE_URL);
+  const providerPath = String(input.path || input.fmodePath || input.doubaoVisionPath || input.videoAnalysisPath || process.env.FMODE_PATH || process.env.DOUBAO_VISION_PATH || DEFAULT_PATH);
+  return {
+    baseUrl,
+    path: providerPath,
+    model: resolveModel(input),
+    url: buildProviderUrl(baseUrl, providerPath)
+  };
+}
+
+function resolveModel(input = {}) {
+  return String(input.model || input.fmodeModel || input.doubaoVisionModel || input.videoAnalysisModel || process.env.FMODE_MODEL || process.env.DOUBAO_VISION_MODEL || DEFAULT_MODEL);
+}
+
+function resolveFmodeToken(input = {}) {
+  const explicit = (
+    input.fmodeToken ||
+    input.imageAnalysisToken ||
+    input.doubaoVisionToken ||
+    input.videoAnalysisToken ||
+    input.sessionToken ||
+    input.tihaoToken ||
+    input.vocToken ||
+    ''
+  );
+  if (explicit) return explicit;
+  if (input.allowEnvToken === false) return '';
+
+  const fileEnv = readEnvFile(input.cwd || process.cwd());
+  const env = { ...process.env, ...fileEnv };
+  return (
+    env.FMODE_TOKEN ||
+    env.FMODE_SESSION_TOKEN ||
+    env.IMAGE_ANALYSIS_TOKEN ||
+    env.DOUBAO_VISION_TOKEN ||
+    env.VIDEO_ANALYSIS_TOKEN ||
+    env.TIHAO_SESSION_TOKEN ||
+    env.VOC_TOKEN ||
+    env.VOC_ECOMMERCE_TOKEN ||
+    env.sessionToken ||
+    env.tihaoToken ||
+    ''
+  );
+}
+
+function resolveImageInputs(input = {}) {
+  const raw = [
+    input.imagePath,
+    input.image,
+    input.imageUrl,
+    input.url,
+    ...(Array.isArray(input.imagePaths) ? input.imagePaths : []),
+    ...(Array.isArray(input.imageUrls) ? input.imageUrls : []),
+    ...(Array.isArray(input.images) ? input.images : [])
+  ].filter(Boolean);
+
+  const seen = new Set();
+  const images = [];
+  for (const item of raw) {
+    const normalized = normalizeImageInput(item, input);
+    if (!normalized) continue;
+    if (seen.has(normalized.url)) continue;
+    seen.add(normalized.url);
+    images.push(normalized);
+  }
+  return images;
+}
+
+function normalizeImageInput(item, input = {}) {
+  if (typeof item === 'object' && item) {
+    return normalizeImageInput(item.url || item.imageUrl || item.path || item.imagePath || item.dataUrl, input);
+  }
+  const value = String(item || '').trim();
+  if (!value) return null;
+  if (/^data:image\//i.test(value)) return { url: value, label: 'data-url-image' };
+  if (/^https?:\/\//i.test(value)) return { url: value, label: safeUrlLabel(value) };
+  if (/^file:\/\//i.test(value)) return fileToImageUrl(new URL(value), input);
+  return fileToImageUrl(value, input);
+}
+
+function fileToImageUrl(file, input = {}) {
+  const filePath = file instanceof URL
+    ? file
+    : path.resolve(input.cwd || process.cwd(), String(file));
+  const resolved = file instanceof URL ? file : filePath;
+  const fsPath = file instanceof URL ? file : resolved;
+  if (!fs.existsSync(fsPath)) throw new Error(`图片文件不存在:${String(fsPath)}`);
+  const stats = fs.statSync(fsPath);
+  if (!stats.isFile()) throw new Error(`不是可读取的图片文件:${String(fsPath)}`);
+  if (stats.size > Number(input.maxImageBytes || MAX_IMAGE_BYTES)) {
+    throw new Error(`图片文件过大:${path.basename(String(fsPath))},请压缩到 20MB 以内后重试。`);
+  }
+  const mime = mimeFromPath(String(fsPath));
+  const base64 = fs.readFileSync(fsPath).toString('base64');
+  return {
+    url: `data:${mime};base64,${base64}`,
+    label: path.basename(String(fsPath))
+  };
+}
+
+function mimeFromPath(file) {
+  const ext = path.extname(file).toLowerCase();
+  if (ext === '.jpg' || ext === '.jpeg') return 'image/jpeg';
+  if (ext === '.png') return 'image/png';
+  if (ext === '.webp') return 'image/webp';
+  if (ext === '.gif') return 'image/gif';
+  if (ext === '.bmp') return 'image/bmp';
+  return 'image/png';
+}
+
+function safeUrlLabel(value) {
+  try {
+    const url = new URL(value);
+    return `${url.hostname}${url.pathname}`.slice(0, 120);
+  } catch {
+    return 'remote-image';
+  }
+}
+
+function buildProviderUrl(baseUrl, providerPath) {
+  const cleaned = String(baseUrl || '').replace(/\/+$/, '');
+  const pathPart = String(providerPath || '').replace(/^\/+/, '');
+  return new URL(pathPart ? `${cleaned}/${pathPart}` : cleaned);
+}
+
+function extractAssistantText(json) {
+  const content = json?.choices?.[0]?.message?.content || json?.choices?.[0]?.text || json?.output_text || json?.output || '';
+  if (typeof content === 'string') return content;
+  if (Array.isArray(content)) {
+    return content.map(part => {
+      if (typeof part === 'string') return part;
+      return part.text || part.content || '';
+    }).filter(Boolean).join('\n');
+  }
+  return '';
+}
+
+function writeOutputs(input, result) {
+  const target = input.output || input.outputPath || input.outputDir || '';
+  if (!target) return [];
+  const resolved = path.resolve(input.cwd || process.cwd(), String(target));
+  const ext = path.extname(resolved).toLowerCase();
+  const dir = ext ? path.dirname(resolved) : resolved;
+  fs.mkdirSync(dir, { recursive: true });
+
+  if (ext === '.json') {
+    fs.writeFileSync(resolved, JSON.stringify(result, null, 2), 'utf8');
+    return [resolved];
+  }
+  if (ext === '.md') {
+    fs.writeFileSync(resolved, `# Fmode Image Analysis\n\n${result.analysis}\n`, 'utf8');
+    return [resolved];
+  }
+
+  const jsonPath = path.join(dir, 'fmode-image-analysis.json');
+  const markdownPath = path.join(dir, 'fmode-image-analysis.md');
+  fs.writeFileSync(jsonPath, JSON.stringify(result, null, 2), 'utf8');
+  fs.writeFileSync(markdownPath, `# Fmode Image Analysis\n\n${result.analysis}\n`, 'utf8');
+  return [markdownPath, jsonPath];
+}
+
+function buildAssistantMessage({ analysis, provider, images, files }) {
+  return [
+    '## Fmode 图片分析结果',
+    '',
+    analysis,
+    '',
+    `模型:${provider.model}`,
+    `图片数量:${images.length}`,
+    files && files.length ? `输出文件:${files.join(';')}` : ''
+  ].filter(Boolean).join('\n');
+}
+
+function buildMissingTokenMessage(paymentUrl) {
+  return [
+    '还没有检测到可用于 Fmode 图片分析的平台 token,暂时不能调用 Doubao vision 模型。',
+    '',
+    `如需开通或充值,请打开:${paymentUrl}`,
+    '',
+    '开通后,可把平台 token 配置到当前工作区 `.env.local`,字段可用 `TIHAO_SESSION_TOKEN`、`VOC_TOKEN` 或 `FMODE_TOKEN`;也可以在工具参数中传入 `sessionToken` / `vocToken` / `fmodeToken`。',
+    '',
+    '系统只会把 token 用作接口鉴权,不会在聊天、报告、日志或输出文件里回显。'
+  ].join('\n');
+}
+
+function buildRechargeMessage(paymentUrl) {
+  return [
+    'Fmode 图片分析服务未开通或额度不足,暂时不能继续调用 Doubao vision 模型。',
+    '',
+    `请打开开通或充值链接补充额度:${paymentUrl}`,
+    '',
+    '底层鉴权或额度错误详情已隐藏,不会把上游响应、Authorization header 或 token 展示给用户。'
+  ].join('\n');
+}
+
+module.exports = {
+  DEFAULT_MODEL,
+  analyzeFmodeImage,
+  resolveImageInputs,
+  buildChatPayload,
+  resolveFmodeToken
+};

+ 653 - 0
claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/brief-parser.js

@@ -0,0 +1,653 @@
+const path = require('path');
+const { readTextMaybe } = require('../../core/files');
+
+function parseBrief(input = {}) {
+  const productContextText = normalizeTextInput(input.productIntro || input.productDescription || input.productContext || input.product);
+  const chatContextText = normalizeTextInput(input.chatText || input.chatRecords || input.customerChat || input.conversation);
+  const briefText = [
+    input.briefText || input.text || '',
+    input.brief || input.briefPath ? readTextMaybe(path.resolve(input.brief || input.briefPath)) : '',
+    productContextText,
+    chatContextText
+  ].filter(Boolean).join('\n\n');
+
+  const text = [
+    briefText,
+    input.brand,
+    input.category,
+    Array.isArray(input.keywords) ? input.keywords.join(' ') : input.keyword
+  ].filter(Boolean).join('\n\n');
+
+  const platforms = resolvePlatforms(input, text);
+  const platformTargets = resolvePlatformTargets(text);
+  const keywords = resolveKeywords(input, text);
+  const targetCount = Number(input.targetCount || sumTargets(platformTargets) || resolveTargetCount(text) || 10);
+  const budgetRange = resolveBudget(input, text);
+  const fanRange = resolveFanRange(input, text);
+  const exclusions = resolveList(input.excludeTags || input.exclusions, text, [
+    '硬广', '泛美妆', '医美', '整形', '虚假宣传', '品牌纠纷', '夸大减肥', '低质搬运', '争议账号',
+    '纯娱乐', '泛娱乐', '宠物', '装修', '婚礼', '纯自拍', '纯娱乐账号'
+  ]);
+  const blockedCreators = resolveList(input.blockedCreators, text, []);
+  const referenceLinks = resolveReferenceLinks(input, text);
+  const referenceAccounts = resolveReferenceAccounts(input, text);
+  const referenceStyleAnchors = resolveReferenceStyleAnchors(text, referenceLinks, referenceAccounts);
+  const referenceSignals = resolveReferenceSignals(text, referenceStyleAnchors);
+
+  return {
+    rawText: text,
+    source: input.brief || input.briefPath || (briefText ? 'inline' : 'input'),
+    contextSources: buildContextSources({ input, briefText, productContextText, chatContextText, referenceLinks, referenceAccounts }),
+    brand: input.brand || resolveBrand(text) || resolveProduct(text) || '未命名品牌',
+    category: input.category || inferCategory(text),
+    platforms,
+    platformTargets,
+    keywords,
+    targetCount,
+    budgetRange,
+    fanRange,
+    region: input.region || inferRegion(text),
+    gender: input.gender || (text.includes('女性') ? 'female' : ''),
+    stylePreferences: resolveList(input.stylePreferences || input.preferredStyles, text, [
+      '真实体验', '素人感', '成分拆解', '敏感肌', '通勤生活', '空瓶记录', '真实测评',
+      '真实试吃', '配料表解释', '场景化种草', '生活化开箱', '真实吃法', '成分党',
+      '真实使用', '前后对比', '步骤教程', '家居清洁', '收纳', '租房生活',
+      '教程型', '案例型', '职场效率', '效率工具', '创业', '企业管理',
+      '本地生活', '探店', '餐饮创业', '同城转化', '门店拍摄',
+      'Vlog', '生活分享', '生活记录', '风景', '社交场景', '颜值', '自拍',
+      '美食', '店铺', '门面', '美妆', '妆教', '妆容教程', '产品合集', '日常妆容',
+      '时尚穿搭', '港风', '成熟风', '通勤穿搭', '女装', '男装', '精致生活'
+    ]),
+    referenceLinks,
+    referenceAccounts,
+    referenceStyleAnchors,
+    referenceSignals,
+    blockedCreators,
+    exclusions,
+    performanceRequirements: resolvePerformanceRequirements(text),
+    deliveryRequirements: ['账号名称', '平台', '粉丝数', '报价', '推荐理由', '风险提示', '分级']
+  };
+}
+
+function resolvePerformanceRequirements(text) {
+  const requirements = [];
+  const cpe = text.match(/CPE\s*[≤<=]\s*(\d+(?:\.\d+)?)/i);
+  if (cpe) requirements.push(`CPE≤${cpe[1]}`);
+  const cp1 = text.match(/CP1\s*[≤<=]\s*(\d+(?:\.\d+)?)/i);
+  if (cp1) requirements.push(`CP1≤${cp1[1]}`);
+  const cpm = text.match(/CPM\s*[≤<=]\s*(\d+(?:\.\d+)?)/i);
+  if (cpm) requirements.push(`CPM≤${cpm[1]}`);
+  const readMedian = text.match(/阅读中位数\s*[≧≥>=]\s*(\d+(?:,\d{3})*)/);
+  if (readMedian) requirements.push(`阅读中位数≥${readMedian[1]}`);
+  const averageLikes = text.match(/(?:平均点赞|月均点赞|近\s*30\s*天平均点赞|近一个月平均点赞)[^\d]{0,12}(\d+(?:,\d{3})*)/);
+  if (averageLikes) requirements.push(`近30天平均点赞≥${averageLikes[1]}`);
+  if (/百赞以下|100赞以下|点赞低于\s*100|平均点赞低于\s*100/.test(text)) requirements.push('近30天平均点赞≥100');
+  if (/近一个月(?:没有|没|未)发|停更一个月|一个月(?:没有|没|未)发布|近\s*30\s*天(?:没有|没|未)更新/.test(text)) requirements.push('近30天需有更新');
+  if (/重复评论|同一个人评论|刷评|数据造假|数据真实性|数据波动/.test(text)) requirements.push('复核数据真实性/重复评论风险');
+  const femaleRatio = text.match(/(?:女粉|女性粉丝|女性占比|女粉占比)[^\d]{0,8}(\d+)%/);
+  if (femaleRatio) requirements.push(`女粉占比≥${femaleRatio[1]}%`);
+  const maleRatio = text.match(/(?:男粉|男性粉丝|男性占比|男粉占比)[^\d]{0,8}(\d+)%/);
+  if (maleRatio) requirements.push(`男粉占比≥${maleRatio[1]}%`);
+  if (/报价.*(?:300|三百).*以上|300\s*元以上|高于\s*300|超过\s*300/.test(text)) requirements.push('报价300元以上需复核封面清晰度/背景统一/内容精致度');
+  const readCost = text.match(/预估阅读单价\s*[≤<=]\s*(\d+(?:\.\d+)?)/);
+  if (readCost) requirements.push(`预估阅读单价≤${readCost[1]}`);
+  const homepageInteraction = text.match(/(?:主页互动数据|主页互动|互动数据|互动数)[^\d]{0,12}(\d+(?:,\d{3})*)\s*\+?/);
+  if (homepageInteraction) requirements.push(`主页互动数据≥${homepageInteraction[1]}`);
+  const audience = text.match(/(\d{2}-\d{2}岁占比\d+%以上)/);
+  if (audience) requirements.push(audience[1]);
+  return unique(requirements);
+}
+
+function normalizeTextInput(value) {
+  if (!value) return '';
+  if (Array.isArray(value)) return value.map(normalizeTextInput).filter(Boolean).join('\n');
+  if (typeof value === 'object') return Object.values(value).map(normalizeTextInput).filter(Boolean).join('\n');
+  return String(value || '').trim();
+}
+
+function buildContextSources({ input, briefText, productContextText, chatContextText, referenceLinks = [], referenceAccounts = [] }) {
+  return {
+    brief: Boolean(input.brief || input.briefPath || input.briefText || input.text || briefText),
+    product: Boolean(productContextText),
+    chat: Boolean(chatContextText),
+    reference: Boolean((input.referenceLinks || []).length || (input.referenceAccounts || []).length || referenceLinks.length || referenceAccounts.length)
+  };
+}
+
+function resolvePlatformTargets(text) {
+  const targets = {};
+  const tableRows = text.split(/\r?\n/)
+    .filter(line => /^\|\s*(小红书|抖音|B站|bilibili|xiaohongshu|douyin)\s*\|/.test(line));
+  for (const line of tableRows) {
+    const cells = line.split('|').map(cell => cell.trim()).filter(Boolean);
+    const platform = normalizePlatforms([cells[0]])[0];
+    const count = Number((cells[1] || '').match(/(\d+)/)?.[1] || 0);
+    if (platform && count) targets[platform] = count;
+  }
+  for (const match of text.matchAll(/(小红书|抖音|B站|bilibili|xiaohongshu|douyin)\s*[::]?\s*(\d+)\s*(?:人|位|个|账号|达人)/g)) {
+    const platform = normalizePlatforms([match[1]])[0];
+    const count = Number(match[2] || 0);
+    if (platform && count) targets[platform] = Math.max(Number(targets[platform] || 0), count);
+  }
+  return targets;
+}
+
+function sumTargets(platformTargets) {
+  return Object.values(platformTargets || {}).reduce((sum, count) => sum + Number(count || 0), 0);
+}
+
+function resolveBrand(text) {
+  const lines = text.split(/\r?\n/).map(cleanMarkdownLine).filter(Boolean);
+  const explicit = lines.find(line => /^(客户名称|客户|品牌名称|品牌)[::]/.test(line));
+  if (explicit) return explicit.replace(/^(客户名称|客户|品牌名称|品牌)[::]\s*/, '').trim();
+  const productLine = lines.find(line => /本次推广产品|推广产品/.test(line));
+  if (productLine) {
+    const product = productLine.replace(/^(本次推广产品|推广产品)[::]\s*/, '').trim();
+    const match = product.match(/^(.{2,12}?)(?:修护|精华|面膜|口红|产品)/);
+    if (match) return match[1];
+  }
+  return '';
+}
+
+function resolveProduct(text) {
+  const lines = text.split(/\r?\n/).map(cleanMarkdownLine).filter(Boolean);
+  const explicit = lines.find(line => /^(产品|推广产品|本次推广产品)[::]/.test(line));
+  return explicit ? explicit.replace(/^(产品|推广产品|本次推广产品)[::]\s*/, '').trim() : '';
+}
+
+function cleanMarkdownLine(line) {
+  return String(line || '')
+    .replace(/^\s*[-*#]+\s*/, '')
+    .replace(/\*\*/g, '')
+    .replace(/\s+/g, ' ')
+    .trim();
+}
+
+function resolveTargetCount(text) {
+  const tableRows = text.split(/\r?\n/)
+    .filter(line => /^\|\s*(小红书|抖音|B站|bilibili|xiaohongshu|douyin)\s*\|/.test(line));
+  const counts = tableRows
+    .map(line => {
+      const cells = line.split('|').map(cell => cell.trim()).filter(Boolean);
+      const countCell = cells[1] || '';
+      const match = countCell.match(/(\d+)\s*(?:人|位|个|账号|达人)?/);
+      return match ? Number(match[1]) : 0;
+    })
+    .filter(Boolean);
+  if (counts.length) return counts.reduce((sum, count) => sum + count, 0);
+
+  const direct = text.match(/(?:需求人数|推荐数量|目标数量)[^\d]{0,12}(\d+)\s*(?:人|位|个|账号|达人)?/);
+  return direct ? Number(direct[1]) : 0;
+}
+
+function resolvePlatforms(input, text) {
+  if (Array.isArray(input.platforms) && input.platforms.length) return normalizePlatforms(input.platforms);
+  const platforms = [];
+  if (/小红书|xiaohongshu|xhs/i.test(text)) platforms.push('xiaohongshu');
+  if (/抖音|douyin/i.test(text)) platforms.push('douyin');
+  if (/B站|bilibili|哔哩/i.test(text)) platforms.push('bilibili');
+  return platforms.length ? platforms : ['xiaohongshu', 'douyin'];
+}
+
+function normalizePlatforms(platforms) {
+  const map = {
+    xhs: 'xiaohongshu',
+    '小红书': 'xiaohongshu',
+    xiaohongshu: 'xiaohongshu',
+    '抖音': 'douyin',
+    douyin: 'douyin',
+    'B站': 'bilibili',
+    bilibili: 'bilibili'
+  };
+  return [...new Set(platforms.map(item => map[String(item)] || String(item)))];
+}
+
+function resolveKeywords(input, text) {
+  const provided = Array.isArray(input.keywords) ? input.keywords : String(input.keyword || '').split(/[,\s,、]+/);
+  const categoryTermMap = {
+    护肤美妆: ['敏感肌', '屏障', '修护', '护肤', '成分党', '成分拆解', '美妆', '测评', '空瓶', '胶原蛋白', '面膜', '精华', '防晒', '洁面', '妆教', '妆容教程', '步骤教程', '产品合集', '日常妆容', '口红', '彩妆'],
+    母婴: ['DHA', '营养师', '育婴师', '妈妈', '母婴', '萌娃', '记录生活', '618合集', '干货科普', '儿童营养', '宝宝', '备孕'],
+    食品餐饮: ['食品', '零食', '低糖', '健康', '控糖', '健身', '办公室零食', '试吃', '开箱', '真实吃法', '配料表', '成分党', '燕麦', '蛋白', '餐饮', '美食', '探店', '本地生活', '同城转化', '门店拍摄', '餐饮创业', '小面', '加盟', '店铺', '门面', '店面', '店招', '门头'],
+    家居生活: ['家居', '家清', '清洁', '收纳', '租房', '家务', '厨房', '油污', '日化', '卫生间', '真实使用', '前后对比', '步骤教程', '妈妈'],
+    B2B软件: ['SaaS', 'B2B', '项目管理', '效率工具', '创业', '企业管理', '职场效率', '教程型', '案例型', '协同办公', '软件', '工具'],
+    服饰穿搭: ['时尚穿搭', '穿搭', '港风', '成熟风', '通勤穿搭', '女装', '男装', '服饰', '搭配', '精致生活', '职场穿搭', '轻熟', '高级感'],
+    生活方式: ['种草', '国货', '通勤', '生活方式', '测评', '真实体验', '素人感', '开箱', '教程', '案例', 'Vlog', '生活分享', '生活记录', '风景', '社交场景', '颜值', '自拍', '精致生活']
+  };
+  const category = inferCategory(text);
+  const commonTerms = [
+    '种草', '测评', '开箱', '教程', '真实体验', '素人感', '生活方式',
+    '女性', '妈妈', '职场', '同城', '本地生活', 'Vlog', '生活分享', '生活记录', '颜值'
+  ];
+  const domainTerms = [
+    ...(categoryTermMap[category] || []),
+    ...commonTerms,
+    ...Object.values(categoryTermMap).flat()
+  ];
+  const explicitTerms = extractLabeledTerms(text);
+  const hits = domainTerms.filter(term => text.includes(term));
+  return unique([...provided, ...hits, ...explicitTerms]
+    .map(cleanKeywordTerm)
+    .filter(Boolean)).slice(0, 12);
+}
+
+function extractLabeledTerms(text) {
+  const terms = [];
+  for (const line of String(text || '').split(/\r?\n/).map(cleanMarkdownLine).filter(Boolean)) {
+    const kv = parseKeyValueLine(line);
+    if (!kv || !/关键词|关键字|风格|偏好|要求|重点|目标|任务|人群|内容/.test(kv.key)) continue;
+    terms.push(...splitKeywordTerms(kv.value));
+  }
+  return terms;
+}
+
+function splitKeywordTerms(value) {
+  return String(value || '')
+    .replace(/找(小红书|抖音|B站|bilibili|xiaohongshu|douyin)?\s*\d+\s*(?:个|位|人|账号|达人)?/gi, ' ')
+    .replace(/(小红书|抖音|B站|bilibili|xiaohongshu|douyin)\s*\d+\s*(?:个|位|人|账号|达人)?/gi, ' ')
+    .replace(/\d+(?:\.\d+)?\s*(?:万|k|K)?\s*(?:-|到|至|~)\s*\d+(?:\.\d+)?\s*(?:万|k|K)?/g, ' ')
+    .split(/[,,、;;\/|和与&\s]+/)
+    .map(cleanKeywordTerm)
+    .filter(Boolean);
+}
+
+function cleanKeywordTerm(value) {
+  const cleaned = String(value || '')
+    .replace(/^(找|寻找|需要|招募|重点看|要求|偏好|风格偏|不要|排除)/, '')
+    .replace(/(?:博主|达人|账号|内容创作者|类内容创作者|类达人|类博主|流量号|经验|优先)$/g, '')
+    .replace(/类$/g, '')
+    .trim();
+  if (cleaned.length < 2 || cleaned.length > 12) return '';
+  if (/^(预算|粉丝|平台|人数|需求|目标|任务|产品|品牌|小红书|抖音|B站|bilibili|xiaohongshu|douyin)$/i.test(cleaned)) return '';
+  return cleaned;
+}
+
+function resolveBudget(input, text) {
+  if (input.budgetRange) return normalizeRange(input.budgetRange, { min: 0, max: 20000 });
+  const upperBound = text.match(/(?:单个预算|预算|报价|单人预算|单篇预算)[^\d]{0,12}([\d,.]+)\s*(?:元|块|rmb|RMB)?\s*(?:内|以内|以下|封顶|上限)/i);
+  if (upperBound) return { min: 0, max: toNumber(upperBound[1]) };
+  const money = [...text.matchAll(/(?:¥|¥)?\s*([\d,.]+)\s*(?:-|到|至|~)\s*(?:¥|¥)?\s*([\d,.]+)/g)]
+    .map(match => [toNumber(match[1]), toNumber(match[2])])
+    .filter(([min, max]) => max > 0);
+  const picked = money.find(([min, max]) => max >= 1000) || money[0];
+  return picked ? { min: picked[0], max: picked[1] } : { min: 0, max: 20000 };
+}
+
+function resolveFanRange(input, text) {
+  if (input.fanRange) return normalizeRange(input.fanRange, { min: 0, max: 500000 });
+  const ranges = [];
+  const lines = text.split(/\r?\n/);
+  let inFanSection = false;
+  for (const line of lines) {
+    if (/粉丝量范围|粉丝范围|粉丝数范围/.test(line)) {
+      inFanSection = true;
+      continue;
+    }
+    if (inFanSection && /^#{1,4}\s+/.test(line) && !/粉丝/.test(line)) break;
+    if (!inFanSection && !/粉丝|小红书|抖音|B站/.test(line)) continue;
+    const match = line.match(/(\d+(?:\.\d+)?)\s*万?\s*(?:-|到|至|~)\s*(\d+(?:\.\d+)?)\s*万?\s*(?:粉|粉丝)?/);
+    if (!match) continue;
+    const hasWan = match[0].includes('万') || /粉丝/.test(line);
+    const min = Number(match[1]) * (hasWan ? 10000 : 1);
+    const max = Number(match[2]) * (hasWan ? 10000 : 1);
+    if (max >= 10000) ranges.push({ min, max });
+  }
+  if (!ranges.length) return { min: 0, max: 500000 };
+  return {
+    min: Math.min(...ranges.map(range => range.min)),
+    max: Math.max(...ranges.map(range => range.max))
+  };
+}
+
+function normalizeRange(value, fallback) {
+  return {
+    min: Number(value.min ?? fallback.min),
+    max: Number(value.max ?? fallback.max)
+  };
+}
+
+function inferCategory(text) {
+  if (/SaaS|B2B|项目管理|效率工具|企业管理|职场效率|协同办公|软件|CRM|企业级|数字化/.test(text)) return 'B2B软件';
+  if (/家清|清洁|家居|收纳|洗衣|厨房|油污|卫生间|日化|租房|家务/.test(text)) return '家居生活';
+  if (/食品|饮料|餐饮|美食|零食|低糖|控糖|燕麦|蛋白|试吃|配料表|探店|本地生活|同城|门店|小面|加盟|餐饮创业/.test(text)) return '食品餐饮';
+  if (/母婴|宝宝|孕|育婴|备孕|DHA|儿童营养/.test(text)) return '母婴';
+  if (/护肤|面膜|敏感肌|屏障|修护|精华|美妆|口红|胶原蛋白|洁面|防晒|泛红|刺痛/.test(text)) return '护肤美妆';
+  if (/时尚穿搭|穿搭|港风|成熟风|通勤穿搭|女装|男装|服饰|搭配|轻熟|高级感/.test(text)) return '服饰穿搭';
+  return '生活方式';
+}
+
+function inferRegion(text) {
+  const cities = ['北京', '上海', '广州', '深圳', '杭州', '成都', '南京', '武汉', '重庆', '西安'];
+  return cities.filter(city => text.includes(city));
+}
+
+function resolveList(inputList, text, vocabulary) {
+  const provided = normalizeListInput(inputList);
+  const hits = vocabulary.filter(item => text.includes(item));
+  return [...new Set([...provided, ...hits].map(String).filter(Boolean))];
+}
+
+function normalizeListInput(inputList) {
+  if (Array.isArray(inputList)) return inputList.flatMap(normalizeListInput);
+  if (inputList == null || inputList === false) return [];
+  if (typeof inputList === 'string') {
+    return inputList.split(/[,,;;|]/).map(item => item.trim()).filter(Boolean);
+  }
+  return [inputList];
+}
+
+function toNumber(value) {
+  return Number(String(value || '').replace(/,/g, '')) || 0;
+}
+
+function resolvePlatformTargets(text) {
+  const targets = {};
+  for (const line of text.split(/\r?\n/).map(cleanMarkdownLine).filter(Boolean)) {
+    const cells = splitCells(line);
+    if (cells.length >= 2) {
+      const platform = normalizePlatforms([cells[0]])[0];
+      const count = Number((cells[1] || '').match(/(\d+)/)?.[1] || 0);
+      if (isKnownPlatform(platform) && count) targets[platform] = Math.max(Number(targets[platform] || 0), count);
+    }
+    for (const match of line.matchAll(/(小红书|抖音|B站|bilibili|xiaohongshu|douyin)\s*[::]?\s*(\d+)\s*(?:人|位|个|账号|达人)/gi)) {
+      const platform = normalizePlatforms([match[1]])[0];
+      const count = Number(match[2] || 0);
+      if (isKnownPlatform(platform) && count) targets[platform] = Math.max(Number(targets[platform] || 0), count);
+    }
+  }
+  return targets;
+}
+
+function resolveBrand(text) {
+  const lines = text.split(/\r?\n/).map(cleanMarkdownLine).filter(Boolean);
+  for (const line of lines) {
+    const kv = parseKeyValueLine(line);
+    if (!kv) continue;
+    if (/^(客户名称|客户|品牌名称|品牌)$/i.test(kv.key)) return kv.value;
+  }
+  for (const line of lines) {
+    const kv = parseKeyValueLine(line);
+    if (!kv || !/^(产品|推广产品|本次推广产品)$/i.test(kv.key)) continue;
+    const match = kv.value.match(/^(.{2,18}?)(?:修护|精华|面膜|口红|产品|(|\()/);
+    if (match) return match[1].trim();
+    return kv.value;
+  }
+  return '';
+}
+
+function resolveProduct(text) {
+  const lines = text.split(/\r?\n/).map(cleanMarkdownLine).filter(Boolean);
+  for (const line of lines) {
+    const kv = parseKeyValueLine(line);
+    if (kv && /^(产品|推广产品|本次推广产品)$/i.test(kv.key)) return kv.value;
+  }
+  return '';
+}
+
+function resolveTargetCount(text) {
+  const counts = Object.values(resolvePlatformTargets(text)).map(Number).filter(Boolean);
+  if (counts.length) return counts.reduce((sum, count) => sum + count, 0);
+  const direct = text.match(/(?:需求人数|推荐数量|目标数量)[^\d]{0,12}(\d+)\s*(?:人|位|个|账号|达人)?/);
+  return direct ? Number(direct[1]) : 0;
+}
+
+function resolvePlatforms(input, text) {
+  if (Array.isArray(input.platforms) && input.platforms.length) return normalizePlatforms(input.platforms);
+  const platforms = [];
+  if (/小红书|xiaohongshu|xhs/i.test(text)) platforms.push('xiaohongshu');
+  if (/抖音|douyin/i.test(text)) platforms.push('douyin');
+  if (/B站|bilibili|哔哩/i.test(text)) platforms.push('bilibili');
+  return platforms.length ? [...new Set(platforms)] : ['xiaohongshu', 'douyin'];
+}
+
+function normalizePlatforms(platforms) {
+  const map = {
+    xhs: 'xiaohongshu',
+    小红书: 'xiaohongshu',
+    xiaohongshu: 'xiaohongshu',
+    抖音: 'douyin',
+    douyin: 'douyin',
+    B站: 'bilibili',
+    b站: 'bilibili',
+    bilibili: 'bilibili',
+    哔哩哔哩: 'bilibili'
+  };
+  return [...new Set(platforms.map(item => map[String(item).trim()] || String(item).trim()))];
+}
+
+function resolveFanRange(input, text) {
+  if (input.fanRange) return normalizeRange(input.fanRange, { min: 0, max: 500000 });
+  const ranges = [];
+  for (const line of text.split(/\r?\n/).map(cleanMarkdownLine).filter(Boolean)) {
+    if (!/粉丝|小红书|抖音|B站|bilibili|xiaohongshu|douyin/i.test(line)) continue;
+    const cells = splitCells(line);
+    const scan = isolateFanRangeText(cells.length >= 2 && /粉丝/.test(cells[0]) ? cells.slice(1).join(' ') : line);
+    for (const match of scan.matchAll(/(\d+(?:\.\d+)?)\s*(万|w|W|k|K)?\s*(?:-|到|至|~)\s*(\d+(?:\.\d+)?)\s*(万|w|W|k|K)?/g)) {
+      const min = normalizeFanCount(match[1], match[2], match[4]);
+      const max = normalizeFanCount(match[3], match[4], match[2]);
+      if (max >= 1000 && min > 0 && min <= max) ranges.push({ min, max });
+    }
+  }
+  if (!ranges.length) return { min: 0, max: 500000 };
+  return {
+    min: Math.min(...ranges.map(range => range.min)),
+    max: Math.max(...ranges.map(range => range.max))
+  };
+}
+
+function isolateFanRangeText(line) {
+  const text = String(line || '');
+  const fanIndex = text.search(/粉丝|fans?/i);
+  const platformOnly = fanIndex < 0;
+  const start = platformOnly ? 0 : fanIndex;
+  let scan = text.slice(start);
+  const budgetIndex = scan.search(/预算|报价|单篇|费用|价格|投放|CPE|CPM|阅读单价|合作费|坑位费/i);
+  if (budgetIndex >= 0) scan = scan.slice(0, budgetIndex);
+  return scan;
+}
+
+function normalizeFanCount(value, unit, oppositeUnit) {
+  const numeric = Number(value || 0);
+  if (!numeric) return 0;
+  const normalizedUnit = String(unit || '').toLowerCase();
+  const normalizedOppositeUnit = String(oppositeUnit || '').toLowerCase();
+  if (normalizedUnit === '万' || normalizedUnit === 'w') return Math.round(numeric * 10000);
+  if (normalizedUnit === 'k') return Math.round(numeric * 1000);
+  if (normalizedOppositeUnit === '万' || normalizedOppositeUnit === 'w') {
+    return numeric >= 1000 ? Math.round(numeric) : Math.round(numeric * 10000);
+  }
+  if (normalizedOppositeUnit === 'k') return numeric >= 1000 ? Math.round(numeric) : Math.round(numeric * 1000);
+  return Math.round(numeric);
+}
+
+function parseKeyValueLine(line) {
+  const cells = splitCells(line);
+  if (cells.length >= 2) return { key: cells[0], value: cells.slice(1).join(' | ').trim() };
+  const match = line.match(/^(.{2,14}?)[::]\s*(.+)$/);
+  return match ? { key: match[1].trim(), value: match[2].trim() } : null;
+}
+
+function splitCells(line) {
+  return String(line || '')
+    .split('|')
+    .map(cell => cell.trim())
+    .filter(Boolean)
+    .filter(cell => !/^[-:]{2,}$/.test(cell));
+}
+
+function isKnownPlatform(platform) {
+  return platform === 'xiaohongshu' || platform === 'douyin' || platform === 'bilibili';
+}
+
+function resolveReferenceLinks(input, text) {
+  const provided = Array.isArray(input.referenceLinks) ? input.referenceLinks.map(normalizeReferenceLink).filter(Boolean) : [];
+  const urls = [...String(text || '').matchAll(/https?:\/\/[^\s|,,;;))]+/gi)]
+    .map(match => match[0].replace(/[。.!?]+$/, ''));
+  const parsed = unique(urls).map(url => normalizeReferenceLink(url));
+  return dedupeObjects([...provided, ...parsed], item => item.url || `${item.platform}:${item.noteId}`);
+}
+
+function normalizeReferenceLink(item) {
+  if (!item) return null;
+  if (typeof item === 'string') {
+    return {
+      url: item,
+      platform: inferPlatformFromUrl(item),
+      noteId: extractXhsNoteId(item),
+      contentType: inferContentTypeFromText(item),
+      needsEnrichment: true
+    };
+  }
+  const url = String(item.url || item.link || '').trim();
+  if (!url) return null;
+  return {
+    url,
+    platform: item.platform || inferPlatformFromUrl(url),
+    noteId: item.noteId || item.note_id || extractXhsNoteId(url),
+    contentType: item.contentType || item.type || inferContentTypeFromText(`${item.sourceText || ''} ${url}`),
+    sourceText: item.sourceText || '',
+    needsEnrichment: item.needsEnrichment !== false
+  };
+}
+
+function resolveReferenceAccounts(input, text) {
+  const provided = Array.isArray(input.referenceAccounts) ? input.referenceAccounts : [];
+  const accounts = provided.map(item => normalizeReferenceAccount(item)).filter(Boolean);
+  const lines = String(text || '').split(/\r?\n/).map(cleanMarkdownLine).filter(Boolean);
+  for (const line of lines) {
+    if (!/参考|对标|标杆|账号|博主|达人|redId|小红书号|抖音号/i.test(line)) continue;
+    if (/^(账号类型|.*参考账号|.*参考博主)[::]?$/.test(line)) continue;
+    const withoutUrls = line.replace(/https?:\/\/\S+/gi, '').trim();
+    const kv = parseKeyValueLine(withoutUrls);
+    if (kv && /^(账号类型|参考账号|参考博主|风格参考)$/i.test(kv.key)) continue;
+    const value = kv ? kv.value : withoutUrls;
+    for (const part of value.split(/[、,,;;\/]+/).map(item => item.trim()).filter(Boolean)) {
+      if (/^(图文|视频|名单字段|关键词参考|风格参考|参考账号|参考博主|账号类型)$/.test(part)) continue;
+      if (/参考账号|参考博主|账号类型/.test(part)) continue;
+      if (part.length < 2 || part.length > 40) continue;
+      accounts.push({
+        name: part,
+        platform: inferPlatformFromText(line),
+        sourceText: line,
+        needsEnrichment: true
+      });
+    }
+  }
+  return dedupeObjects(accounts, item => `${item.platform || ''}:${item.name || item.redId || item.userId || item.url || ''}`);
+}
+
+function normalizeReferenceAccount(item) {
+  if (!item) return null;
+  if (typeof item === 'string') {
+    return { name: item, platform: inferPlatformFromText(item), sourceText: item, needsEnrichment: true };
+  }
+  return {
+    name: item.name || item.nickname || item.displayName || '',
+    redId: item.redId || item.red_id || '',
+    userId: item.userId || item.user_id || '',
+    platform: item.platform || inferPlatformFromText(`${item.url || ''} ${item.sourceText || ''}`),
+    url: item.url || '',
+    sourceText: item.sourceText || '',
+    needsEnrichment: item.needsEnrichment !== false
+  };
+}
+
+function resolveReferenceStyleAnchors(text, referenceLinks, referenceAccounts) {
+  const anchors = [];
+  let currentBucket = '';
+  for (const rawLine of String(text || '').split(/\r?\n/)) {
+    const line = cleanMarkdownLine(rawLine);
+    if (!line) continue;
+    if (/参考账号|参考博主|对标账号|标杆账号|账号类型/.test(line)) {
+      currentBucket = line.replace(/[::]\s*$/, '');
+    }
+    if (/https?:\/\//i.test(line)) {
+      const links = (referenceLinks || []).filter(item => line.includes(item.url));
+      anchors.push({
+        bucket: currentBucket || inferReferenceBucket(line),
+        contentType: /视频|video/i.test(line) ? 'video' : /图文|image|note/i.test(line) ? 'image_text' : (links[0]?.contentType || ''),
+        platform: inferPlatformFromText(line) || links[0]?.platform || '',
+        sourceText: line,
+        links: links.map(item => item.url),
+        needsSocialAnalysisEnrichment: true
+      });
+    }
+  }
+  for (const account of referenceAccounts || []) {
+    anchors.push({
+      bucket: inferReferenceBucket(account.sourceText || account.name || ''),
+      platform: account.platform || '',
+      sourceText: account.sourceText || account.name || '',
+      accountHints: [account.name || account.redId || account.userId].filter(Boolean),
+      needsSocialAnalysisEnrichment: true
+    });
+  }
+  return dedupeObjects(anchors, item => `${item.bucket}:${item.sourceText}:${(item.links || []).join('|')}:${(item.accountHints || []).join('|')}`);
+}
+
+function resolveReferenceSignals(text, anchors) {
+  const vocabulary = [
+    '干货科普', '营养师', '育婴师', '妈妈', '母婴', '618合集', '合集', '图文', '视频',
+    '素人感', '真实体验', '真实测评', '通勤生活', '成分党', '敏感肌', '开箱体验',
+    '不硬广', '好物分享', '生活化', '专业背书', '选品建议'
+  ];
+  const source = [
+    text,
+    ...(anchors || []).map(item => `${item.bucket || ''} ${item.sourceText || ''}`)
+  ].join('\n');
+  return vocabulary.filter(item => source.includes(item));
+}
+
+function inferReferenceBucket(text) {
+  if (/618|合集/.test(text)) return '618合集参考';
+  if (/干货|科普|营养师|育婴师/.test(text)) return '干货科普参考';
+  if (/风格/.test(text)) return '风格参考';
+  return '参考账号';
+}
+
+function inferPlatformFromUrl(url) {
+  if (/xhslink|xiaohongshu/i.test(url)) return 'xiaohongshu';
+  if (/douyin/i.test(url)) return 'douyin';
+  if (/bilibili|b23\.tv/i.test(url)) return 'bilibili';
+  return '';
+}
+
+function inferPlatformFromText(text) {
+  const value = String(text || '');
+  if (/小红书|xhs|xiaohongshu|xhslink/i.test(value)) return 'xiaohongshu';
+  if (/抖音|douyin/i.test(value)) return 'douyin';
+  if (/B站|bilibili|b23\.tv/i.test(value)) return 'bilibili';
+  return inferPlatformFromUrl(value);
+}
+
+function inferContentTypeFromText(text) {
+  const value = String(text || '');
+  if (/视频|video|douyin|bilibili|b23\.tv/i.test(value)) return 'video';
+  if (/图文|image|note|xhslink|xiaohongshu/i.test(value)) return 'image_text';
+  if (/主页|profile|account|home/i.test(value)) return 'account_home';
+  return '';
+}
+
+function extractXhsNoteId(url) {
+  const match = String(url || '').match(/\/(?:discovery\/item|explore)\/([0-9a-f]{16,32})/i);
+  return match ? match[1] : '';
+}
+
+function unique(items) {
+  return [...new Set(items.filter(Boolean).map(String))];
+}
+
+function dedupeObjects(items, keyFn) {
+  const seen = new Set();
+  const result = [];
+  for (const item of items.filter(Boolean)) {
+    const key = keyFn(item);
+    if (seen.has(key)) continue;
+    seen.add(key);
+    result.push(item);
+  }
+  return result;
+}
+
+module.exports = {
+  parseBrief
+};

+ 311 - 0
claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/experience-rules.js

@@ -0,0 +1,311 @@
+function enrichCriteriaWithExperience(criteria = {}) {
+  const categoryRules = buildCategoryRules(criteria);
+  const requirementLayers = buildRequirementLayers(criteria, categoryRules);
+  const referenceUsability = buildReferenceUsability(criteria);
+  const sourcingStrategy = buildPlatformSourcingStrategy(criteria);
+  return {
+    ...criteria,
+    categoryRules,
+    requirementLayers,
+    referenceUsability,
+    sourcingStrategy
+  };
+}
+
+function buildCategoryRules(criteria = {}) {
+  const text = [
+    criteria.rawText,
+    criteria.brand,
+    criteria.category,
+    ...(criteria.keywords || [])
+  ].filter(Boolean).join(' ');
+  const rules = [];
+
+  if (hasAny(text, ['脱毛仪', '美容仪', 'Ulike', 'ulike', '颜值', '美体'])) {
+    rules.push({
+      id: 'beauty-device-female-context',
+      category: '美容仪/脱毛仪',
+      hard: true,
+      description: '美容仪/脱毛仪默认偏女性使用场景,强推荐应优先女性颜值、美妆、生活方式或精致护理账号。',
+      positiveSignals: ['女性', '美妆', '护肤', '颜值', '精致', '生活方式', '护理', '真人出镜'],
+      negativeSignals: ['男性泛账号', '男性博主', '男生', '游戏', '体育', '汽车', '音乐达人'],
+      manualReview: ['如果 Brief 未显式写女性,也要按产品使用场景复核账号性别/人群。']
+    });
+  }
+
+  if (hasAny(text, ['DHA', '母婴', '宝宝', '儿童营养', '育婴', '备孕'])) {
+    rules.push({
+      id: 'dha-mom-baby-context',
+      category: '母婴/DHA',
+      hard: false,
+      description: 'DHA/母婴强推荐应具备妈妈、育婴师、营养师、宝宝成长记录或儿童营养语境。',
+      positiveSignals: ['DHA', '母婴', '妈妈', '育婴师', '营养师', '宝宝', '儿童营养', '备孕', '成长记录'],
+      negativeSignals: ['泛生活方式', '纯穿搭', '纯美妆', '无育儿语境'],
+      manualReview: ['宝宝营养、功效表达需要人工合规复核。']
+    });
+  }
+
+  if (hasAny(text, ['敏感肌', '屏障', '修护', '护肤', '成分', '泛红', '刺痛'])) {
+    rules.push({
+      id: 'sensitive-skin-evidence',
+      category: '敏感肌/护肤',
+      hard: false,
+      description: '敏感肌/修护应偏真实体验、成分党、屏障护理和护肤测评,避免泛美妆硬广。',
+      positiveSignals: ['敏感肌', '屏障', '修护', '成分党', '真实体验', '护肤测评', '泛红', '刺痛'],
+      negativeSignals: ['硬广', '医美', '整形', '泛美妆'],
+      manualReview: ['功效宣称、前后对比和医美关联需要人工复核。']
+    });
+  }
+
+  if (hasAny(text, ['美妆', '妆教', '妆容教程', '日常妆容', '产品合集', '彩妆', '口红'])) {
+    rules.push({
+      id: 'beauty-makeup-subtype',
+      category: '美妆/彩妆子类',
+      hard: false,
+      description: '美妆需求不能只按泛美妆标签筛选,需要区分妆教步骤、产品合集测评、日常妆容分享等子类型,并复核真人出镜和近期内容结构。',
+      positiveSignals: ['美妆', '妆教', '妆容教程', '步骤教程', '产品合集', '日常妆容', '彩妆', '口红', '真人出镜'],
+      negativeSignals: ['纯护肤无彩妆', '泛颜值自拍', '硬广', '低质搬运'],
+      manualReview: ['打开主页最近 10 篇,看是妆教步骤、测评合集还是日常妆容,不能只看平台美妆标签。']
+    });
+  }
+
+  if (hasAny(text, ['食品', '饮料', '零食', '健康饮食', '低脂', '奶制品', '食材'])) {
+    rules.push({
+      id: 'food-beverage-context',
+      category: '食品饮料',
+      hard: false,
+      description: '食品饮料应关注健康饮食、零食测评、家庭/办公室消费场景,避免强不相关类目。',
+      positiveSignals: ['食品', '饮料', '零食', '健康饮食', '测评', '家庭', '办公室', '食材'],
+      negativeSignals: ['纯美妆', '纯数码', '强不相关'],
+      manualReview: ['食品安全、功效表达和竞品露出需要人工复核。']
+    });
+  }
+
+  if (hasAny(text, ['Vlog', 'vlog', '生活分享', '生活记录', '风景', '社交场景', '颜值', '自拍', '精致生活'])) {
+    rules.push({
+      id: 'vlog-life-visual-fit',
+      category: 'Vlog/生活方式/颜值',
+      hard: false,
+      description: 'Vlog/生活方式账号要看封面是否呈现生活分享、风景、社交场景或真实记录;颜值账号可接受自拍,但非颜值需求不能把纯自拍当作生活方式适配。',
+      positiveSignals: ['Vlog', 'vlog', '生活分享', '生活记录', '风景', '社交场景', '真实记录', '颜值', '精致生活'],
+      negativeSignals: ['纯自拍', '泛颜值自拍', '无生活场景', '封面混乱', '画面模糊'],
+      manualReview: ['最近 10 篇封面若只有自拍,需确认 Brief 是否真的要颜值型;否则降级为需复核。']
+    });
+  }
+
+  if (hasAny(text, ['时尚穿搭', '穿搭', '港风', '成熟风', '通勤穿搭', '女装', '男装', '服饰', '轻熟', '高级感'])) {
+    rules.push({
+      id: 'fashion-subtype-refinement',
+      category: '服饰穿搭子类',
+      hard: false,
+      description: '时尚穿搭是宽类目,强推荐前必须结合产品、客户聊天和参考账号拆到港风、成熟风、通勤、女装/男装等子类型。',
+      positiveSignals: ['时尚穿搭', '穿搭', '港风', '成熟风', '通勤穿搭', '女装', '男装', '服饰', '轻熟', '高级感'],
+      negativeSignals: ['泛生活方式', '无穿搭展示', '纯自拍', '无场景'],
+      manualReview: ['如果 Brief 只写“精致生活/时尚穿搭”,先从产品介绍或客户聊天里确认更偏港风、成熟风、通勤还是其他子类。']
+    });
+  }
+
+  if (hasAny(text, ['家清', '清洁', '家居', '收纳', '洗衣', '厨房', '卫生间'])) {
+    rules.push({
+      id: 'home-cleaning-context',
+      category: '家清家居',
+      hard: false,
+      description: '家清家居应偏真实家居场景、清洁测评、收纳整理、家庭生活内容。',
+      positiveSignals: ['家居', '家清', '清洁', '收纳', '厨房', '卫生间', '真实家居', '测评'],
+      negativeSignals: ['纯颜值', '纯娱乐', '无家庭场景'],
+      manualReview: ['需复核真实使用场景和是否有清洁前后对比证据。']
+    });
+  }
+
+  return rules.length ? rules : [{
+    id: 'general-tone-fit',
+    category: criteria.category || '通用品类',
+    hard: false,
+    description: '未命中特定品类规则,按 Brief 关键词、参考账号、主页最近内容和风格调性综合判断。',
+    positiveSignals: [...(criteria.keywords || []), ...(criteria.stylePreferences || [])].slice(0, 12),
+    negativeSignals: criteria.exclusions || [],
+    manualReview: ['需要商务复核主页最近内容、报价有效期和合作状态。']
+  }];
+}
+
+function buildRequirementLayers(criteria, categoryRules) {
+  return {
+    hardConstraints: [
+      `平台:${(criteria.platforms || []).join('、') || '未指定'}`,
+      `粉丝:${criteria.fanRange?.min ?? 0}-${criteria.fanRange?.max ?? 0}`,
+      `预算:${criteria.budgetRange?.min ?? 0}-${criteria.budgetRange?.max ?? 0}`,
+      `目标数量:${criteria.targetCount || 0}`,
+      ...((criteria.performanceRequirements || []).map(item => `数据/成本:${item}`)),
+      ...(criteria.region?.length ? [`地域:${Array.isArray(criteria.region) ? criteria.region.join('、') : criteria.region}`] : []),
+      ...(criteria.exclusions?.length ? [`排除项:${criteria.exclusions.join('、')}`] : [])
+    ],
+    contextChecklist: buildContextChecklist(criteria),
+    implicitRules: categoryRules.map(rule => rule.description),
+    toneEvidence: [
+      ...(criteria.stylePreferences || []).map(item => `风格偏好:${item}`),
+      ...(criteria.referenceSignals || []).map(item => `参考信号:${item}`),
+      ...((criteria.referenceLinks || []).length ? [`参考链接:${criteria.referenceLinks.length} 条`] : []),
+      ...((criteria.referenceAccounts || []).length ? [`参考账号:${criteria.referenceAccounts.length} 个`] : [])
+    ],
+    dataReviewFocus: buildDataReviewFocus(criteria),
+    reviewFocus: buildReviewFocus(criteria)
+  };
+}
+
+function buildContextChecklist(criteria = {}) {
+  const sources = criteria.contextSources || {};
+  return [
+    sources.brief ? '已读取 Brief 主体。' : '未读取到 Brief 主体,需要补 brief 文件或 briefText。',
+    sources.product ? '已并入产品介绍/产品上下文,用于拆隐性人群和子类。' : '未单独提供产品介绍,宽类目需求需人工确认产品场景。',
+    sources.chat ? '已并入客户聊天记录,可用于识别隐藏偏好、禁区和子类型。' : '未提供客户聊天记录,需留意 Brief 外的隐藏要求缺口。',
+    sources.reference ? '已识别参考账号/链接,可作为类型或调性锚点。' : '未提供参考账号/链接,首轮只按品类和 Brief 规则提号。'
+  ];
+}
+
+function buildDataReviewFocus(criteria = {}) {
+  const budgetMax = Number(criteria.budgetRange?.max || 0);
+  const budgetMin = Number(criteria.budgetRange?.min || 0);
+  const items = [
+    '先用关键词、预算、CPE/CP1/CPM、粉丝量、男女粉占比和城市做第一轮硬筛。',
+    '复核近 30 天是否有更新;近一个月无发布通常不能强推。',
+    '复核近作平均点赞和互动波动,百赞以下或波动异常需降级。',
+    '检查多篇笔记是否反复出现同一批评论者,命中重复评论/刷评风险时不要强推。'
+  ];
+  if (budgetMax >= 300 || budgetMin >= 300) {
+    items.push('报价 300 元以上的账号必须额外复核封面清晰度、背景统一度和内容精致度。');
+  } else {
+    items.push('报价 300 元以下可相对放宽视觉精致度,但仍需排除明显低质、混乱和搬运。');
+  }
+  return items;
+}
+
+function buildReviewFocus(criteria = {}) {
+  const category = criteria.category || '';
+  const focus = [
+    '先看主页最近 10 篇封面和标题判断垂类、质感、场景和调性。',
+    '封面判断不清时再补图文正文、视频帧或 ASR。',
+    '强推荐必须同时满足 Brief 命中点和风格/调性证据。',
+    '宽类目 Brief 必须结合产品介绍、客户聊天和参考账号拆成可执行子类。'
+  ];
+  if (/服饰|穿搭/.test(category)) focus.push('穿搭类要复核港风、成熟风、通勤、女装/男装等子类型,不只看“时尚”标签。');
+  if (/护肤|美妆/.test(category)) focus.push('美妆护肤要拆护肤、妆教、测评合集、日常妆容等子方向。');
+  return focus;
+}
+
+function buildReferenceUsability(criteria = {}) {
+  const anchors = criteria.referenceStyleAnchors || [];
+  const links = criteria.referenceLinks || [];
+  const accounts = criteria.referenceAccounts || [];
+  const signals = criteria.referenceSignals || [];
+  const fingerprints = criteria.referenceStyleFingerprints || [];
+  const hasReferences = anchors.length || links.length || accounts.length || fingerprints.length;
+  if (!hasReferences) {
+    return {
+      status: 'no_reference',
+      typeAnchor: false,
+      toneAnchor: false,
+      conflict: false,
+      note: 'Brief 未提供明确参考账号或链接,本轮按品类和 Brief 规则提号。',
+      anchors: []
+    };
+  }
+
+  const contentTypes = new Set([
+    ...anchors.map(item => item.contentType).filter(Boolean),
+    ...fingerprints.map(item => item.contentType).filter(Boolean)
+  ]);
+  const toneTerms = new Set([
+    ...signals,
+    ...fingerprints.flatMap(item => [item.tone, item.personaType, item.contentStructure].filter(Boolean))
+  ]);
+  const conflict = fingerprints.length >= 2 && toneTerms.size >= Math.max(4, fingerprints.length * 2);
+  return {
+    status: conflict ? 'reference_conflict' : 'usable',
+    typeAnchor: Boolean(anchors.length || accounts.length || fingerprints.length),
+    toneAnchor: Boolean(signals.length || fingerprints.length),
+    conflict,
+    note: conflict
+      ? '参考账号/链接风格可能不一致,不能强行合并为单一调性标准;建议先让客户确认更看重哪一类参考。'
+      : '参考账号可作为类型或调性锚点;仍需用主页最近内容和商务复核确认。',
+    anchors: [
+      ...anchors.map((item, index) => ({
+        id: `anchor-${index + 1}`,
+        typeAnchor: true,
+        toneAnchor: Boolean(signals.length),
+        contentType: item.contentType || 'unknown',
+        platform: item.platform || '',
+        links: item.links || []
+      })),
+      ...fingerprints.map((item, index) => ({
+        id: item.referenceId || `fingerprint-${index + 1}`,
+        typeAnchor: Boolean(item.contentType),
+        toneAnchor: Boolean(item.tone || item.personaType || item.contentStructure),
+        contentType: item.contentType || 'unknown',
+        tone: item.tone || '',
+        personaType: item.personaType || '',
+        missingEvidenceNotes: item.missingEvidenceNotes || []
+      }))
+    ]
+  };
+}
+
+function buildPlatformSourcingStrategy(criteria = {}) {
+  return (criteria.platforms || []).map(platform => {
+    if (platform === 'xiaohongshu') {
+      return {
+        platform,
+        preferredPath: 'reference_similarity',
+        note: '小红书/蒲公英优先使用参考账号相似推荐或等价 provider,再按类型和调性二次筛选。'
+      };
+    }
+    if (platform === 'douyin') {
+      return {
+        platform,
+        preferredPath: 'keyword_or_recruitment',
+        note: '抖音星图标签只作为候选入口,不作为适配证明;召回弱时需 APP 关键词搜索或招募补量。'
+      };
+    }
+    return {
+      platform,
+      preferredPath: 'manual_or_resource_pool',
+      note: '该平台 live 覆盖有限时,需要人工或资源库补充。'
+    };
+  });
+}
+
+function evaluateImplicitRuleFit(creator = {}, categoryRules = []) {
+  const text = [
+    creator.displayName,
+    ...(creator.contentTags || []),
+    ...(creator.personaTags || []),
+    ...(creator.recentEvidence || [])
+  ].join(' ');
+  const hits = [];
+  const violations = [];
+  const reviewNotes = [];
+  for (const rule of categoryRules || []) {
+    const positiveHits = (rule.positiveSignals || []).filter(signal => text.includes(signal));
+    const negativeHits = (rule.negativeSignals || []).filter(signal => text.includes(signal));
+    if (positiveHits.length) hits.push(`${rule.category}:${positiveHits.slice(0, 3).join('/')}`);
+    if (negativeHits.length) {
+      const label = `${rule.category}:${negativeHits.slice(0, 3).join('/')}`;
+      if (rule.hard) violations.push(label);
+      else reviewNotes.push(label);
+    }
+  }
+  return {
+    hitPoints: [...new Set(hits)].slice(0, 8),
+    violations: [...new Set(violations)].slice(0, 8),
+    reviewNotes: [...new Set(reviewNotes)].slice(0, 8),
+    score: Math.max(0, Math.min(100, 70 + hits.length * 8 - violations.length * 35 - reviewNotes.length * 10))
+  };
+}
+
+function hasAny(text, terms) {
+  return terms.some(term => String(text || '').includes(term));
+}
+
+module.exports = {
+  enrichCriteriaWithExperience,
+  evaluateImplicitRuleFit
+};

+ 233 - 0
claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/homepage-evidence-provider.js

@@ -0,0 +1,233 @@
+const fs = require('fs');
+const path = require('path');
+const { buildHomepageEvidence } = require('./homepage-evidence');
+
+async function enrichHomepageEvidence(criteria = {}, creators = [], input = {}) {
+  const pathRecords = readHomepageEvidencePath(input);
+  if (pathRecords.length) {
+    return attachRecords(criteria, creators, pathRecords, {
+      requested: true,
+      status: 'ok',
+      source: 'path',
+      evidenceCount: pathRecords.length,
+      note: `已从本地文件加载 ${pathRecords.length} 条主页最近内容证据。`
+    });
+  }
+
+  const baseUrl = input.homepageEvidenceBaseUrl || input.homepageProviderBaseUrl || process.env.TIHAO_HOMEPAGE_EVIDENCE_BASE_URL;
+  if (!baseUrl) {
+    return attachFallback(criteria, creators, {
+      requested: false,
+      status: 'not_requested',
+      source: 'fallback',
+      evidenceCount: 0,
+      note: '本轮未配置主页最近内容 provider,使用候选标签和已有 recentEvidence 做轻量判断。'
+    });
+  }
+
+  const token = input.homepageEvidenceToken || input.homepageProviderToken || process.env.TIHAO_HOMEPAGE_EVIDENCE_TOKEN;
+  const providerPath = input.homepageEvidencePath || 'homepage-evidence';
+  const url = joinUrl(baseUrl, providerPath);
+  const limit = Number(input.homepageEvidenceLimit || input.recentContentsLimit || 10);
+  const payload = {
+    contractVersion: 'tihao-homepage-evidence-v1',
+    requestedCapabilities: ['recent_posts', 'cover_quality', 'category_consistency', 'tone_consistency', 'risk_review'],
+    criteria: {
+      brand: criteria.brand,
+      category: criteria.category,
+      keywords: criteria.keywords || [],
+      stylePreferences: criteria.stylePreferences || [],
+      referenceSignals: criteria.referenceSignals || [],
+      categoryRules: criteria.categoryRules || []
+    },
+    limit,
+    creators: creators.slice(0, Number(input.homepageEvidenceCreatorsLimit || 20)).map(creator => ({
+      platform: creator.platform,
+      platformUserId: creator.platformUserId,
+      displayName: creator.displayName,
+      profileUrl: creator.profileUrl,
+      contentTags: creator.contentTags || [],
+      personaTags: creator.personaTags || [],
+      recentEvidence: creator.recentEvidence || []
+    }))
+  };
+
+  try {
+    const response = await fetch(url, {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json',
+        ...(token ? { Authorization: `Bearer ${token}` } : {})
+      },
+      body: JSON.stringify(payload)
+    });
+    if (response.status === 401 || response.status === 403) {
+      return attachFallback(criteria, creators, {
+        requested: true,
+        status: 'unauthorized_or_quota',
+        source: 'provider',
+        httpStatus: response.status,
+        evidenceCount: 0,
+        warning: '主页最近内容 provider 鉴权或额度不可用,已回退到本地轻量判断。'
+      });
+    }
+    if (!response.ok) {
+      return attachFallback(criteria, creators, {
+        requested: true,
+        status: 'http_error',
+        source: 'provider',
+        httpStatus: response.status,
+        evidenceCount: 0,
+        warning: '主页最近内容 provider 返回异常,已回退到本地轻量判断。'
+      });
+    }
+    const json = await response.json();
+    const records = normalizeProviderRecords(json);
+    if (!records.length) {
+      return attachFallback(criteria, creators, {
+        requested: true,
+        status: 'empty_result',
+        source: 'provider',
+        httpStatus: response.status,
+        evidenceCount: 0,
+        warning: '主页最近内容 provider 未返回可用证据,已回退到本地轻量判断。'
+      });
+    }
+    return attachRecords(criteria, creators, records, {
+      requested: true,
+      status: 'ok',
+      source: 'provider',
+      httpStatus: response.status,
+      evidenceCount: records.length,
+      note: `已通过 provider 加载 ${records.length} 条主页最近内容证据。`
+    });
+  } catch {
+    return attachFallback(criteria, creators, {
+      requested: true,
+      status: 'network_error',
+      source: 'provider',
+      evidenceCount: 0,
+      warning: '主页最近内容 provider 网络不可用,已回退到本地轻量判断。'
+    });
+  }
+}
+
+function readHomepageEvidencePath(input = {}) {
+  const file = input.homepageEvidenceFile || input.homepageEvidenceJsonPath || (isLocalFile(input.homepageEvidencePath) ? input.homepageEvidencePath : '');
+  if (!file) return [];
+  const resolved = path.resolve(file);
+  if (!fs.existsSync(resolved)) return [];
+  try {
+    return normalizeProviderRecords(JSON.parse(fs.readFileSync(resolved, 'utf8')));
+  } catch {
+    return [];
+  }
+}
+
+function isLocalFile(value) {
+  if (!value || /^https?:\/\//i.test(String(value))) return false;
+  return /[\\/.]\w+$/.test(String(value));
+}
+
+function attachFallback(criteria, creators, status) {
+  return {
+    creators: creators.map(creator => ({
+      ...creator,
+      homepageEvidence: creator.homepageEvidence || buildHomepageEvidence(creator, criteria)
+    })),
+    status,
+    warnings: status.warning ? [status.warning] : []
+  };
+}
+
+function attachRecords(criteria, creators, records, status) {
+  const byId = new Map();
+  for (const record of records) {
+    for (const key of recordKeys(record)) {
+      if (key) byId.set(key, record);
+    }
+  }
+  const attached = creators.map(creator => {
+    const record = recordKeys(creator).map(key => byId.get(key)).find(Boolean);
+    if (!record) {
+      return {
+        ...creator,
+        homepageEvidence: creator.homepageEvidence || buildHomepageEvidence(creator, criteria)
+      };
+    }
+    const evidence = normalizeHomepageEvidenceRecord(record, creator, criteria);
+    return {
+      ...creator,
+      recentPosts: record.recentPosts || record.posts || [],
+      recentEvidence: mergeRecentEvidence(creator.recentEvidence || [], evidence.reviewNotes || []),
+      homepageEvidence: evidence
+    };
+  });
+  return { creators: attached, status, warnings: [] };
+}
+
+function normalizeHomepageEvidenceRecord(record = {}, creator = {}, criteria = {}) {
+  const fallback = buildHomepageEvidence({
+    ...creator,
+    recentEvidence: mergeRecentEvidence(creator.recentEvidence || [], record.recentEvidence || [])
+  }, criteria);
+  const evidence = record.homepageEvidence || record.evidence || record;
+  const posts = record.recentPosts || record.posts || [];
+  const reviewNotes = [
+    ...(evidence.reviewNotes || []),
+    ...(record.recentEvidence || []),
+    ...posts.slice(0, 5).map(post => post.title || post.desc || '').filter(Boolean)
+  ];
+  return {
+    ...fallback,
+    ...pickDefined({
+      source: evidence.source || 'provider',
+      recentContentWindow: evidence.recentContentWindow || evidence.recentPostCount || posts.length || fallback.recentContentWindow,
+      matchedTerms: evidence.matchedTerms,
+      riskHits: evidence.riskHits,
+      visualRiskSignals: evidence.visualRiskSignals,
+      categoryConsistencyScore: Number(evidence.categoryConsistencyScore ?? evidence.recentContentFitScore ?? fallback.categoryConsistencyScore),
+      visualQualityScore: Number(evidence.visualQualityScore ?? fallback.visualQualityScore),
+      toneConsistencyScore: Number(evidence.toneConsistencyScore ?? fallback.toneConsistencyScore),
+      confidence: evidence.confidence || 'provider',
+      deepDiveNeeded: Boolean(evidence.deepDiveNeeded ?? fallback.deepDiveNeeded),
+      reviewNotes: mergeRecentEvidence(reviewNotes, fallback.reviewNotes || [])
+    })
+  };
+}
+
+function normalizeProviderRecords(json) {
+  if (Array.isArray(json)) return json;
+  if (Array.isArray(json.homepageEvidence)) return json.homepageEvidence;
+  if (Array.isArray(json.evidence)) return json.evidence;
+  if (Array.isArray(json.records)) return json.records;
+  if (Array.isArray(json.data?.homepageEvidence)) return json.data.homepageEvidence;
+  if (Array.isArray(json.data?.records)) return json.data.records;
+  return [];
+}
+
+function recordKeys(record = {}) {
+  return [
+    record.platformUserId && `${record.platform || ''}:${record.platformUserId}`,
+    record.profileUrl && `url:${record.profileUrl}`,
+    record.displayName && `${record.platform || ''}:name:${record.displayName}`,
+    record.creatorId && `${record.platform || ''}:${record.creatorId}`
+  ].filter(Boolean);
+}
+
+function mergeRecentEvidence(...groups) {
+  return [...new Set(groups.flat().filter(Boolean).map(String))].slice(0, 20);
+}
+
+function pickDefined(value) {
+  return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined && item !== null));
+}
+
+function joinUrl(baseUrl, pathName) {
+  if (/^https?:\/\//i.test(pathName || '')) return pathName;
+  return `${String(baseUrl).replace(/\/+$/, '')}/${String(pathName || '').replace(/^\/+/, '')}`;
+}
+
+module.exports = {
+  enrichHomepageEvidence
+};

+ 183 - 0
claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/homepage-evidence.js

@@ -0,0 +1,183 @@
+function buildHomepageEvidence(creator = {}, criteria = {}) {
+  const recentEvidence = creator.recentEvidence || [];
+  const contentTags = creator.contentTags || [];
+  const personaTags = creator.personaTags || [];
+  const text = [
+    creator.displayName,
+    ...contentTags,
+    ...personaTags,
+    ...recentEvidence
+  ].join(' ');
+  const terms = [
+    ...(criteria.keywords || []),
+    ...(criteria.stylePreferences || []),
+    ...(criteria.referenceSignals || []),
+    ...((criteria.categoryRules || []).flatMap(rule => rule.positiveSignals || []))
+  ].filter(Boolean);
+  const matchedTerms = [...new Set(terms.filter(term => includesTerm(text, term)))].slice(0, 10);
+  const riskTerms = [
+    ...(criteria.exclusions || []),
+    ...((criteria.categoryRules || []).flatMap(rule => rule.negativeSignals || []))
+  ].filter(Boolean);
+  const riskHits = [...new Set(riskTerms.filter(term => includesTerm(text, term)))].slice(0, 8);
+
+  const recentCount = inferRecentCount(recentEvidence);
+  const categoryConsistencyScore = Math.min(95, 52 + matchedTerms.length * 7 + (recentEvidence.length ? 10 : 0));
+  const visualQualitySignals = inferVisualQualitySignals(text);
+  const visualRiskSignals = inferVisualRiskSignals(text, criteria);
+  const activitySignals = inferActivitySignals(creator, text);
+  const dataRiskSignals = inferDataRiskSignals(creator, text);
+  const visualQualityScore = Math.max(20, Math.min(95, 55 + visualQualitySignals.length * 10 + (recentEvidence.length ? 5 : 0) - visualRiskSignals.length * 25));
+  const toneConsistencyScore = Math.min(95, 55 + personaTags.length * 6 + matchedTerms.length * 4);
+  const confidence = recentEvidence.length >= 2 || matchedTerms.length >= 3
+    ? 'medium'
+    : recentEvidence.length || matchedTerms.length ? 'low' : 'missing';
+  const deepDiveNeeded = confidence === 'missing' || confidence === 'low' || riskHits.length > 0 || dataRiskSignals.length > 0;
+
+  return {
+    source: recentEvidence.length ? 'recentEvidence' : 'profile_tags',
+    recentContentWindow: recentCount || 10,
+    matchedTerms,
+    riskHits,
+    visualQualitySignals,
+    visualRiskSignals,
+    activitySignals,
+    dataRiskSignals,
+    categoryConsistencyScore,
+    visualQualityScore,
+    toneConsistencyScore,
+    confidence,
+    deepDiveNeeded,
+    reviewNotes: buildReviewNotes({ recentEvidence, matchedTerms, riskHits, visualQualitySignals, visualRiskSignals, activitySignals, dataRiskSignals, confidence, deepDiveNeeded })
+  };
+}
+
+function inferRecentCount(values) {
+  const joined = values.join(' ');
+  const match = joined.match(/近\s*(\d+)\s*篇/);
+  return match ? Number(match[1]) : 0;
+}
+
+function inferVisualQualitySignals(text) {
+  const signals = [];
+  const candidates = [
+    '画面清晰',
+    '像素清晰',
+    '封面统一',
+    '背景统一',
+    '风格统一',
+    '排版整齐',
+    '真人出镜',
+    '产品实拍',
+    '生活化',
+    '精致',
+    '画面精致',
+    '真实家居',
+    '母婴家庭场景',
+    '高质感',
+    '产品合集',
+    '步骤图',
+    '步骤教程',
+    'Vlog',
+    'vlog',
+    '生活记录',
+    '生活分享',
+    '风景',
+    '社交场景'
+  ];
+  for (const item of candidates) {
+    if (includesTerm(text, item)) signals.push(item);
+  }
+  return signals;
+}
+
+function inferVisualRiskSignals(text, criteria = {}) {
+  const signals = [];
+  const candidates = [
+    '封面下沉',
+    '封面混乱',
+    '画面模糊',
+    '低像素',
+    '排版混乱',
+    '低质',
+    '低质量',
+    '搬运',
+    '粗糙',
+    '杂乱',
+    '背景杂乱',
+    '硬广感'
+  ];
+  for (const item of candidates) {
+    if (includesTerm(text, item)) signals.push(item);
+  }
+  const criteriaText = [
+    criteria.rawText,
+    criteria.category,
+    ...(criteria.keywords || []),
+    ...(criteria.stylePreferences || [])
+  ].filter(Boolean).join(' ');
+  if (includesTerm(text, '纯自拍') && !/颜值|自拍|美妆|妆容/.test(criteriaText)) signals.push('纯自拍');
+  return signals;
+}
+
+function inferActivitySignals(creator = {}, text = '') {
+  const signals = [];
+  const days = Number(creator.lastPostDaysAgo ?? creator.daysSinceLastPost ?? 0);
+  if (days > 0 && days <= 30) signals.push(`近${days}天有更新`);
+  if (Number(creator.recentPostCount || 0) > 0) signals.push(`近30天更新${creator.recentPostCount}篇`);
+  if (/近\s*30\s*天有更新|近一个月有更新|最近更新|持续更新/.test(text)) signals.push('近30天有更新');
+  return [...new Set(signals)].slice(0, 4);
+}
+
+function inferDataRiskSignals(creator = {}, text = '') {
+  const signals = [];
+  const days = Number(creator.lastPostDaysAgo ?? creator.daysSinceLastPost ?? 0);
+  if (days > 30) signals.push('近30天无更新');
+  if (/停更|近一个月(?:没有|没|未)发|近\s*30\s*天(?:没有|没|未)更新|一个月未发布/.test(text)) signals.push('近30天无更新');
+  const averageLikes = Number(creator.recentAverageLikes ?? creator.averageLikes30d ?? creator.avgLikes30d ?? creator.averageLikes ?? 0);
+  if (averageLikes > 0 && averageLikes < 100) signals.push('近30天平均点赞低于100');
+  if (/百赞以下|100赞以下|平均点赞低于\s*100|点赞低于\s*100|低赞/.test(text)) signals.push('近30天平均点赞低于100');
+  if (Number(creator.repeatedCommenterCount || creator.repeatedCommenterHits || 0) > 0) signals.push('重复评论风险');
+  if (/重复评论|同一个人评论|同一批评论|刷评|数据造假|数据异常|数据波动/.test(text)) signals.push('重复评论/数据真实性风险');
+  return [...new Set(signals)].slice(0, 6);
+}
+
+function buildReviewNotes({ recentEvidence, matchedTerms, riskHits, visualQualitySignals, visualRiskSignals, activitySignals, dataRiskSignals, confidence, deepDiveNeeded }) {
+  const notes = [];
+  if (recentEvidence.length) notes.push(`已有最近内容证据 ${recentEvidence.length} 条`);
+  else notes.push('缺少最近 10 篇主页内容证据,需商务复核主页封面和标题');
+  if (matchedTerms.length) notes.push(`主页/标签命中:${matchedTerms.slice(0, 5).join('、')}`);
+  if (visualQualitySignals.length) notes.push(`视觉质感信号:${visualQualitySignals.slice(0, 4).join('、')}`);
+  if (visualRiskSignals.length) notes.push(`主页质感风险:${visualRiskSignals.slice(0, 4).join('、')}`);
+  if (activitySignals.length) notes.push(`近期更新信号:${activitySignals.slice(0, 3).join('、')}`);
+  if (dataRiskSignals.length) notes.push(`数据真实性/活跃风险:${dataRiskSignals.slice(0, 4).join('、')}`);
+  if (riskHits.length) notes.push(`疑似风险/不适配信号:${riskHits.slice(0, 4).join('、')}`);
+  if (deepDiveNeeded) notes.push('低置信度或存在风险,建议补图文正文、视频帧或 ASR 后再强推');
+  if (confidence === 'missing') notes.push('不能仅凭平台标签判定为强推荐');
+  return [...new Set(notes)];
+}
+
+function attachHomepageEvidenceToCreator(creator, criteria) {
+  const homepageEvidence = creator.homepageEvidence || buildHomepageEvidence(creator, criteria);
+  const recentContentFitScore = Math.round((
+    homepageEvidence.categoryConsistencyScore * 0.45 +
+    homepageEvidence.visualQualityScore * 0.25 +
+    homepageEvidence.toneConsistencyScore * 0.3
+  ));
+  return {
+    ...creator,
+    homepageEvidence,
+    recentContentFitScore,
+    visualQualityScore: homepageEvidence.visualQualityScore,
+    toneConsistencyScore: homepageEvidence.toneConsistencyScore
+  };
+}
+
+function includesTerm(text, term) {
+  return String(text || '').toLowerCase().includes(String(term || '').toLowerCase());
+}
+
+module.exports = {
+  buildHomepageEvidence,
+  attachHomepageEvidenceToCreator
+};

+ 501 - 0
claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/live-provider.js

@@ -0,0 +1,501 @@
+const { readTihaoToken } = require('../../core/credentials');
+const {
+  buildTihaoRechargeInfo,
+  buildMissingTokenMessage,
+  buildRechargeRequiredMessage,
+  resolveTihaoUserInfo
+} = require('../../core/payment-links');
+const { friendlyState } = require('../../core/result-envelope');
+
+const DEFAULT_BASE_URL = 'https://server.fmode.cn/api/voc-e-commerce';
+
+async function searchLiveCreators(criteria, input = {}) {
+  const token = readTihaoToken(input);
+  const recharge = await buildTihaoRechargeInfo({ sessionToken: token });
+  if (!token) {
+    return friendlyState('needs_token', buildMissingTokenMessage(recharge.paymentUrl), {
+      data: { recharge }
+    });
+  }
+
+  const baseUrl = input.baseUrl || process.env.TIHAO_ECOMMERCE_BASE_URL || DEFAULT_BASE_URL;
+  const identity = await resolveTihaoUserInfo(token);
+  const companyId = input.company || process.env.TIHAO_COMPANY || process.env.VOC_ECOMMERCE_COMPANY || identity.companyId || '';
+  const platforms = criteria.platforms.filter(platform => platform === 'xiaohongshu' || platform === 'douyin');
+  const recallKeywords = buildRecallKeywords(criteria, input);
+  const resultFirst = isResultFirstMode(input) && hasConcreteReferenceEvidence(criteria);
+  const keywordLimit = Math.max(1, Math.min(Number(input.keywordLimit || 5), 8));
+  const pagesPerKeyword = Math.max(1, Math.min(Number(input.pagesPerKeyword || 1), 5));
+  const effectiveKeywordLimit = resultFirst ? Math.max(keywordLimit, Math.min(4, recallKeywords.length || 4)) : keywordLimit;
+  const effectivePagesPerKeyword = resultFirst ? Math.max(pagesPerKeyword, 2) : pagesPerKeyword;
+  const creators = [];
+  const warnings = [];
+  const recallRecords = [];
+
+  for (const platform of platforms) {
+    for (const keyword of recallKeywords.slice(0, effectiveKeywordLimit)) {
+      for (let page = 1; page <= effectivePagesPerKeyword; page++) {
+        const path = resolveProxyPath(platform);
+        const url = new URL(`${baseUrl.replace(/\/+$/, '')}/${path}`);
+        const params = buildSearchParams(platform, criteria, keyword, page);
+        for (const [key, value] of Object.entries(params)) {
+          if (value !== undefined && value !== null && value !== '') {
+            url.searchParams.set(key, String(value));
+          }
+        }
+        if (companyId) url.searchParams.set('company', String(companyId));
+        if (input.isRefresh) url.searchParams.set('isRefresh', 'true');
+
+        const response = await fetchWithRetry(url, {
+          headers: {
+            Authorization: `Bearer ${token}`,
+            Accept: 'application/json'
+          }
+        });
+
+        if (!response) {
+          return friendlyState(
+            'live_network_error',
+            [
+              '真实 live 检索暂时无法连接到提号电商数据服务。',
+              '',
+              '这通常是网络抖动、网关临时断开或服务端连接被重置导致的。token 没有被写入报告或错误输出。',
+              '',
+              '建议稍后用相同参数重试,或先用 sample 模式校准 Brief。'
+            ].join('\n'),
+            {
+              warnings: [`network error while requesting ${platform}/${keyword}/page-${page}`],
+              data: { recharge }
+            }
+          );
+        }
+
+        if (response.status === 401) {
+          return friendlyState('needs_valid_token', buildMissingTokenMessage(recharge.paymentUrl), {
+            data: { recharge }
+          });
+        }
+        if (response.status === 403) {
+          return friendlyState('needs_recharge', buildRechargeRequiredMessage(recharge.paymentUrl), {
+            data: { recharge }
+          });
+        }
+        if (!response.ok) {
+          recallRecords.push(buildRecallRecord({ platform, keyword, page, path, status: 'http_error', httpStatus: response.status }));
+          warnings.push(`live provider returned http ${response.status} for ${platform}/${keyword}/page-${page}`);
+          continue;
+        }
+        const json = await response.json();
+        const normalized = normalizeProviderCreators(json, platform);
+        creators.push(...normalized);
+        recallRecords.push(buildRecallRecord({
+          platform,
+          keyword,
+          page,
+          path,
+          status: 'ok',
+          httpStatus: response.status,
+          returnedCount: countRawItems(json),
+          normalizedCount: normalized.length
+        }));
+      }
+    }
+  }
+
+  if (!creators.length && resultFirst && !input._fallbackAttempt) {
+    const fallback = await searchLiveCreators(criteria, {
+      ...input,
+      resultFirstMode: false,
+      optimizeForResults: false,
+      disableReferenceAwareRecall: true,
+      keywordLimit: Math.max(2, keywordLimit),
+      pagesPerKeyword: Math.max(1, pagesPerKeyword),
+      _fallbackAttempt: true
+    });
+    if (fallback.status !== 'ok') return fallback;
+    return {
+      ...fallback,
+      recallRecords: [...recallRecords, ...(fallback.recallRecords || [])],
+      warnings: [
+        ...warnings,
+        'result-first recall returned empty; used conservative brief-keyword fallback recall.',
+        ...(fallback.warnings || [])
+      ],
+      recharge
+    };
+  }
+
+  return {
+    status: 'ok',
+    creators,
+    recallRecords,
+    warnings,
+    recharge
+  };
+}
+
+function buildRecallKeywords(criteria, input = {}) {
+  const referenceAwareTerms = buildReferenceAwareRecallKeywords(criteria, input);
+  const categoryTerms = [criteria.category, ...(criteria.keywords || [])];
+  const personaTerms = [
+    ...(criteria.referenceStyleFingerprints || []).map(item => item.personaType),
+    ...(criteria.referenceSignals || []).filter(item => /妈妈|营养师|育儿师|素人|科普|测评|清单/.test(item))
+  ];
+  const sceneTerms = [
+    ...(criteria.referenceSignals || []).filter(item => /618|通勤|生活|母婴|宝宝|敏感肌|干货/.test(item)),
+    ...(criteria.stylePreferences || []).filter(item => /真实|生活|体验|科普|记录/.test(item))
+  ];
+  const contentTerms = [
+    ...(criteria.referenceStyleFingerprints || []).map(item => item.contentStructure),
+    ...(criteria.referenceStyleAnchors || []).map(item => item.contentType === 'video' ? '视频种草' : item.contentType === 'image_text' ? '图文种草' : '')
+  ];
+  const promotionTerms = [
+    ...(criteria.referenceSignals || []).filter(item => /618|合集|清单|囤货|节点/.test(item)),
+    '种草'
+  ];
+  return unique([
+    ...referenceAwareTerms,
+    ...[
+      ...categoryTerms,
+      ...personaTerms,
+      ...sceneTerms,
+      ...contentTerms,
+      ...promotionTerms
+    ].flatMap(splitKeyword)
+  ]).slice(0, 20);
+}
+
+function buildReferenceAwareRecallKeywords(criteria, input = {}) {
+  if (input.disableReferenceAwareRecall) return [];
+  if (!hasConcreteReferenceEvidence(criteria)) return [];
+  const primaryBriefTerms = unique([
+    ...(criteria.keywords || []),
+    criteria.category
+  ].flatMap(splitKeyword)).filter(isUsefulRecallTerm).slice(0, 4);
+  const referenceTerms = unique([
+    ...(criteria.referenceStyleFingerprints || []).flatMap(item => [
+      item.personaType,
+      item.contentStructure,
+      item.tone,
+      item.sellingPointBridge,
+      ...(item.visualElements || [])
+    ])
+  ].flatMap(splitKeyword)).filter(isUsefulRecallTerm).slice(0, 8);
+  const composites = [];
+  for (const briefTerm of primaryBriefTerms) {
+    for (const refTerm of referenceTerms) {
+      if (briefTerm === refTerm || briefTerm.includes(refTerm) || refTerm.includes(briefTerm)) continue;
+      composites.push(`${briefTerm} ${refTerm}`);
+    }
+  }
+  return unique([...composites, ...referenceTerms]).slice(0, 10);
+}
+
+function hasConcreteReferenceEvidence(criteria) {
+  return Boolean((criteria.referenceResources || []).length ||
+    (criteria.referenceStyleFingerprints || []).some(item => item && item.sourceLevel && item.sourceLevel !== 'video_analysis_pending' && item.sourceLevel !== 'reference_link_pending'));
+}
+
+function isResultFirstMode(input = {}) {
+  return Boolean(input.resultFirstMode || input.optimizeForResults || process.env.TIHAO_RESULT_FIRST_MODE === 'true');
+}
+
+function splitKeyword(value) {
+  return String(value || '')
+    .split(/[、,,;;\/|()\s]+/)
+    .map(item => item.replace(/围绕|做场景化承接|型|式|内容|结构|待补证/g, '').trim())
+    .map(item => item.replace(/^.*?(DHA).*$/i, '$1'))
+    .filter(item => item.length >= 2);
+}
+
+function isUsefulRecallTerm(value) {
+  const text = String(value || '').trim();
+  if (text.length < 2 || text.length > 12) return false;
+  if (/^(http|https|com|cn|www|mp4|jpg|webp)$/i.test(text)) return false;
+  if (/婚礼|摄影|宴|满月|派对|布置|仪式感|百日/.test(text)) return false;
+  return /DHA|母婴|妈妈|宝宝|育儿|育婴|营养|科普|清单|合集|测评|开箱|教程|真实|素人|生活|满月|百日|儿童|孩子|孕期/.test(text);
+}
+
+function buildRecallRecord({ platform, keyword, page, path, status, httpStatus = 0, returnedCount = 0, normalizedCount = 0 }) {
+  return {
+    platform,
+    keyword,
+    page,
+    path,
+    status,
+    httpStatus,
+    returnedCount,
+    normalizedCount
+  };
+}
+
+function countRawItems(json) {
+  const root = json && json.data && json.data.data ? json.data.data : json && json.data ? json.data : json;
+  const model = root && root.model ? root.model : root;
+  const rawList = model && (model.itemList || model.kols || model.authors || model.list || model.items || model.result);
+  return Array.isArray(rawList) ? rawList.length : 0;
+}
+
+async function fetchWithRetry(url, options) {
+  for (let attempt = 1; attempt <= 3; attempt++) {
+    try {
+      return await fetch(url, options);
+    } catch {
+      if (attempt === 3) return null;
+      await new Promise(resolve => setTimeout(resolve, attempt * 750));
+    }
+  }
+  return null;
+}
+
+function resolveProxyPath(platform) {
+  if (platform === 'douyin') return 'douyin-xingtu/gw/api/gsearch/search_for_author_square/v1';
+  return 'xiaohongshu-pgy/api/solar/cooperator/blogger/v2/v1';
+}
+
+function buildSearchParams(platform, criteria, keyword, page) {
+  const params = {
+    keyword,
+    page,
+    searchType: platform === 'douyin' ? 'CONTENT' : 'NOTE'
+  };
+  const fanRange = criteria.fanRange || {};
+  if (platform === 'douyin') {
+    const minWan = Math.floor(Number(fanRange.min || 0) / 10000);
+    const maxWan = fanRange.max ? Math.floor(Number(fanRange.max) / 10000) : 10000;
+    const safeMax = Math.min(10000, Math.max(maxWan, minWan + 1));
+    if (minWan > 0 || safeMax < 10000) params.followerRange = `${minWan}-${safeMax}`;
+    return params;
+  }
+  if (fanRange.min) params.fansNumberLower = Math.floor(Number(fanRange.min));
+  if (fanRange.max) params.fansNumberUpper = Math.floor(Number(fanRange.max));
+  if (criteria.gender) {
+    params.gender = criteria.gender === 'female' ? 'FEMALE' : criteria.gender === 'male' ? 'MALE' : 'ALL';
+  }
+  return params;
+}
+
+function normalizeProviderCreators(json, platform) {
+  const root = json && json.data && json.data.data ? json.data.data : json && json.data ? json.data : json;
+  const model = root && root.model ? root.model : root;
+  const rawList = model && (model.itemList || model.kols || model.authors || model.list || model.items || model.result);
+  const list = Array.isArray(rawList) ? rawList : [];
+  return list.map((item, index) => normalizeCreator(item, platform, index)).filter(item => item.displayName);
+}
+
+function normalizeCreator(item, platform, index) {
+  const attr = parseObject(item.attribute_datas || item.attributeDatas || {});
+  const taskPrices = parseDouyinTaskPrices(item.task_infos || item.taskInfos);
+  const sources = [item, attr];
+  const tags = [...new Set([
+    ...toArray(item.contentTags || item.content_tags || item.tags),
+    ...toArray(attr.content_theme_labels_180d),
+    ...toArray(item.featureTags || item.personalTags),
+    ...toArray(attr.tags_relation),
+    ...extractHashtagsFromLastItems(attr.last_10_items)
+  ].map(item => String(item).trim()).filter(Boolean))].slice(0, 12);
+  const secUid = String(item.secUid || item.sec_uid || item.secId || item.sec_id || item.authorSecUid || item.author_sec_uid || attr.secUid || attr.sec_uid || '');
+  const userId = String(secUid || item.userId || item.user_id || item.star_id || attr.core_user_id || attr.id || item.id || `live-${index}`);
+  const imagePrice = pickNumberDeep(sources, [
+    'picturePrice', 'picture_price', 'imagePrice', 'image_price', 'notePrice', 'note_price',
+    'graphicPrice', 'graphicsPrice', 'graphics_price', 'imageTextPrice', 'image_text_price',
+    'articlePrice', 'article_price', 'picPrice', 'pic_price', 'picTextPrice', 'pic_text_price'
+  ]) || taskPrices.imagePrice;
+  const videoPrice = pickNumberDeep(sources, [
+    'videoPrice', 'video_price', 'videoQuote', 'video_quote', 'videoTaskPrice', 'video_task_price',
+    'shortVideoPrice', 'short_video_price', 'price'
+  ]) || taskPrices.videoPrice;
+  const minPrice = pickNumberDeep(sources, [
+    'lowerPrice', 'lower_price', 'minPrice', 'min_price', 'lowestPrice', 'lowest_price',
+    'floorPrice', 'floor_price', 'quote', 'quotation', 'settlementPrice', 'settlement_price'
+  ]) || minPositive([imagePrice, videoPrice, taskPrices.minPrice]);
+  const hasQuoteField = hasDeepKey(sources, [
+    'price', 'quote', 'quotation', 'picturePrice', 'picture_price', 'imagePrice', 'image_price',
+    'videoPrice', 'video_price', 'lowerPrice', 'lower_price', 'minPrice', 'min_price'
+  ]);
+  return {
+    platform,
+    platformUserId: userId,
+    displayName: String(item.name || item.nickname || item.nickName || attr.nick_name || attr.nickname || attr.name || ''),
+    profileUrl: resolveProfileUrl(item, attr, platform, userId, secUid),
+    fansCount: toNumber(item.fansNum || item.fansCount || item.fans_count || item.followerCount || attr.follower || attr.fans_count),
+    minPrice,
+    imagePrice,
+    videoPrice,
+    quoteStatus: minPrice || imagePrice || videoPrice ? '已获取报价' : hasQuoteField ? '报价字段为空' : '未返回报价',
+    priceSource: minPrice || imagePrice || videoPrice ? 'api' : '',
+    city: String(item.city || item.location || attr.city || attr.province || ''),
+    location: String(item.location || item.address || item.area || attr.location || attr.city || attr.province || ''),
+    contentTags: tags,
+    personaTags: [...new Set([
+      ...toArray(item.personalTags || item.featureTags),
+      ...toArray(attr.author_thin_mid_word_association_index)
+    ].map(item => String(item).trim()).filter(Boolean))].slice(0, 12),
+    imageCpm: pickNumberDeep(sources, ['imageCpm', 'image_cpm', 'pictureCpm', 'picture_cpm', 'noteCpm', 'note_cpm', 'imgCpm', 'img_cpm']),
+    videoCpm: pickNumberDeep(sources, ['videoCpm', 'video_cpm', 'shortVideoCpm', 'short_video_cpm']),
+    imageCpe: pickNumberDeep(sources, ['imageCpe', 'image_cpe', 'pictureCpe', 'picture_cpe', 'noteCpe', 'note_cpe', 'imgCpe', 'img_cpe']),
+    videoCpe: pickNumberDeep(sources, ['videoCpe', 'video_cpe', 'shortVideoCpe', 'short_video_cpe']),
+    commercialOrderCount: pickNumberDeep(sources, ['commercialOrderCount', 'commercial_order_count', 'businessOrderCount', 'business_order_count', 'cooperationCount', 'cooperation_count', 'orderCount', 'order_count']),
+    commercialOrderCount30d: pickNumberDeep(sources, ['commercialOrderCount30d', 'commercial_order_count_30d', 'businessOrderCount30d', 'business_order_count_30d', 'orderCount30d', 'order_count_30d']),
+    commercialReadMedian: pickNumberDeep(sources, ['commercialReadMedian', 'commercial_read_median', 'businessReadMedian', 'business_read_median', 'readMedian', 'read_median']),
+    commercialInteractionMedian: pickNumberDeep(sources, ['commercialInteractionMedian', 'commercial_interaction_median', 'businessInteractionMedian', 'business_interaction_median', 'interactionMedian', 'interaction_median']),
+    commercialExposureMedian30d: pickNumberDeep(sources, ['commercialExposureMedian30d', 'commercial_exposure_median_30d', 'businessExposureMedian30d', 'business_exposure_median_30d', 'exposureMedian30d', 'exposure_median_30d']),
+    fanGrowthRate30d: pickValueDeep(sources, ['fanGrowthRate30d', 'fan_growth_rate_30d', 'fansGrowthRate30d', 'fans_growth_rate_30d', 'followerGrowthRate30d', 'follower_growth_rate_30d']),
+    inviteReplyRate48h: pickValueDeep(sources, ['inviteReplyRate48h', 'invite_reply_rate_48h', 'replyRate48h', 'reply_rate_48h', 'responseRate48h', 'response_rate_48h']),
+    lowActivity: pickBooleanDeep(sources, ['lowActivity', 'low_activity', 'isLowActivity', 'is_low_activity']),
+    sourceProvider: 'voc-e-commerce',
+    recentEvidence: ['来自公司 voc-e-commerce 代理接口的实时候选数据,需商务复核主页和报价有效期']
+  };
+}
+
+function resolveProfileUrl(item, attr, platform, userId, secUid = '') {
+  const direct = item.profileUrl || item.profile_url || item.homepageUrl || item.homepage_url ||
+    item.shareUrl || item.share_url || item.douyinUrl || item.douyin_url || item.xiaohongshuUrl ||
+    attr.profileUrl || attr.profile_url || attr.homepageUrl || attr.homepage_url || attr.shareUrl || attr.share_url;
+  if (direct) return String(direct);
+  if (platform === 'xiaohongshu' && userId) return `https://www.xiaohongshu.com/user/profile/${userId}`;
+  if (platform === 'douyin' && (secUid || userId)) return `https://www.douyin.com/user/${secUid || userId}`;
+  return '';
+}
+
+function parseObject(value) {
+  if (!value) return {};
+  if (typeof value === 'object' && !Array.isArray(value)) return value;
+  if (typeof value === 'string') {
+    try {
+      const parsed = JSON.parse(value);
+      return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
+    } catch {
+      return {};
+    }
+  }
+  return {};
+}
+
+function parseDouyinTaskPrices(value) {
+  const prices = toArrayOfObjects(value)
+    .flatMap(item => [item.price, item.amount, item.origin_price, item.discount_price, item.quote])
+    .map(toNumber)
+    .filter(Boolean);
+  return {
+    minPrice: prices.length ? Math.min(...prices) : 0,
+    imagePrice: 0,
+    videoPrice: prices.length ? Math.min(...prices) : 0
+  };
+}
+
+function pickNumberDeep(sources, keys) {
+  const value = pickValueDeep(sources, keys);
+  return toNumber(value);
+}
+
+function pickBooleanDeep(sources, keys) {
+  const value = pickValueDeep(sources, keys);
+  if (value === true || value === false) return value;
+  const text = String(value || '').trim().toLowerCase();
+  if (!text) return '';
+  if (['true', '1', 'yes', 'y', '是'].includes(text)) return true;
+  if (['false', '0', 'no', 'n', '否'].includes(text)) return false;
+  return '';
+}
+
+function pickValueDeep(sources, keys) {
+  const wanted = new Set(keys.map(normalizeFieldName));
+  for (const source of sources) {
+    const hit = findValueByNormalizedKey(source, wanted, new Set());
+    if (hit !== undefined && hit !== null && hit !== '') return hit;
+  }
+  return '';
+}
+
+function hasDeepKey(sources, keys) {
+  const wanted = new Set(keys.map(normalizeFieldName));
+  return sources.some(source => findValueByNormalizedKey(source, wanted, new Set(), true) !== undefined);
+}
+
+function findValueByNormalizedKey(value, wanted, seen, keyOnly = false) {
+  if (!value || typeof value !== 'object') return undefined;
+  if (seen.has(value)) return undefined;
+  seen.add(value);
+  if (Array.isArray(value)) {
+    for (const item of value.slice(0, 50)) {
+      const hit = findValueByNormalizedKey(item, wanted, seen, keyOnly);
+      if (hit !== undefined) return hit;
+    }
+    return undefined;
+  }
+  for (const [key, nested] of Object.entries(value)) {
+    if (wanted.has(normalizeFieldName(key))) return keyOnly ? true : nested;
+    if (nested && typeof nested === 'object') {
+      const hit = findValueByNormalizedKey(nested, wanted, seen, keyOnly);
+      if (hit !== undefined) return hit;
+    }
+  }
+  return undefined;
+}
+
+function normalizeFieldName(value) {
+  return String(value || '').replace(/[^a-z0-9\u4e00-\u9fa5]/gi, '').toLowerCase();
+}
+
+function minPositive(values) {
+  const numbers = values.map(toNumber).filter(value => value > 0);
+  return numbers.length ? Math.min(...numbers) : 0;
+}
+
+function toArrayOfObjects(value) {
+  if (Array.isArray(value)) return value.filter(item => item && typeof item === 'object');
+  if (typeof value === 'string') {
+    try {
+      const parsed = JSON.parse(value);
+      return Array.isArray(parsed) ? parsed.filter(item => item && typeof item === 'object') : [];
+    } catch {
+      return [];
+    }
+  }
+  return [];
+}
+
+function extractHashtagsFromLastItems(value) {
+  const tags = [];
+  for (const item of toArrayOfObjects(value).slice(0, 5)) {
+    const title = String(item.item_title || item.title || '');
+    for (const match of title.matchAll(/#([^\s#]+)/g)) tags.push(match[1]);
+  }
+  return tags;
+}
+
+function toArray(value) {
+  if (!value) return [];
+  if (Array.isArray(value)) {
+    return value.flatMap(item => {
+      if (typeof item === 'string') return item;
+      if (item && typeof item === 'object') return item.taxonomy1Tag || item.name || item.label || item.value || '';
+      return '';
+    }).filter(Boolean);
+  }
+  if (typeof value === 'string') {
+    try {
+      const parsed = JSON.parse(value);
+      if (Array.isArray(parsed)) return parsed.map(String);
+      if (parsed && typeof parsed === 'object') return Object.keys(parsed);
+    } catch {
+      return value.split(/[,,、\s]+/).filter(Boolean);
+    }
+  }
+  if (typeof value === 'object') return Object.keys(value);
+  return [];
+}
+
+function toNumber(value) {
+  return Number(String(value || '').replace(/[,%¥¥元]/g, '')) || 0;
+}
+
+function unique(items) {
+  return [...new Set(items.map(item => String(item || '').trim()).filter(Boolean))];
+}
+
+module.exports = {
+  searchLiveCreators,
+  normalizeProviderCreators
+};

+ 536 - 0
claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/multimodal-evidence.js

@@ -0,0 +1,536 @@
+const fs = require('fs');
+const path = require('path');
+const { readEvidenceProviderToken } = require('../../core/credentials');
+const { buildSocialRechargeInfo } = require('../../core/payment-links');
+
+// 视频/视觉分析默认走公司豆包模型(fmode 网关, OpenAI 兼容 chat completions)。
+const DEFAULT_VIDEO_ANALYSIS_BASE_URL = 'https://api.fmode.cn';
+const DEFAULT_VIDEO_ANALYSIS_MODEL = 'doubao-seed-2-0-pro-260215';
+
+async function loadMultimodalEvidence(criteria, creators, input = {}) {
+  const cached = [
+    ...(Array.isArray(input.evidenceCards) ? input.evidenceCards : []),
+    ...readEvidenceCards(input.evidenceCardsPath || input.evidenceCardPath || '')
+  ].map(normalizeEvidenceCard).filter(Boolean);
+
+  const providerResult = await fetchEvidenceCards(criteria, creators, input);
+  const evidenceCards = dedupe([...cached, ...providerResult.evidenceCards]);
+  return {
+    evidenceCards,
+    referenceStyleFingerprints: providerResult.referenceStyleFingerprints || [],
+    warnings: providerResult.warnings,
+    providerStatus: providerResult.status
+  };
+}
+
+async function fetchEvidenceCards(criteria, creators, input = {}) {
+  const provider = resolveEvidenceProvider(input);
+  const baseUrl = provider.baseUrl;
+  if (!baseUrl) return { evidenceCards: [], warnings: [], status: providerStatus('not_requested') };
+  const token = readEvidenceProviderToken(input);
+  const url = buildProviderUrl(baseUrl, provider.path);
+  const batches = buildEvidenceBatches(creators, input);
+  const results = [];
+  for (const batch of batches) {
+    results.push(await requestEvidenceBatch({ url, provider, criteria, creators: batch, input, token }));
+  }
+  const evidenceCards = dedupe(results.flatMap(item => item.evidenceCards || []));
+  const referenceStyleFingerprints = dedupeFingerprints(results.flatMap(item => item.referenceStyleFingerprints || []));
+  const referenceResources = dedupeResources(results.flatMap(item => item.referenceResources || []));
+  const warnings = results.flatMap(item => item.warnings || []);
+  const hardFailure = results.find(item => ['unauthorized_or_quota', 'http_error', 'network_error', 'parse_error'].includes(item.status.status));
+  const warning = '多模态证据 provider 未返回可用 evidenceCards;本轮只输出基础提号证据。';
+  const hasUsefulReferenceAnalysis = referenceStyleFingerprints.length || referenceResources.length;
+  if (!evidenceCards.length && !hasUsefulReferenceAnalysis && hardFailure) {
+    return { evidenceCards, referenceStyleFingerprints, referenceResources, warnings: hardFailure.warnings, status: hardFailure.status };
+  }
+  return {
+    evidenceCards,
+    referenceStyleFingerprints,
+    referenceResources,
+    warnings: evidenceCards.length || hasUsefulReferenceAnalysis ? warnings.filter(item => !String(item).includes('未返回可用 evidenceCards')) : [warning],
+    status: providerStatus(evidenceCards.length || hasUsefulReferenceAnalysis ? 'ok' : 'empty_result', evidenceCards.length || hasUsefulReferenceAnalysis ? '' : warning)
+  };
+}
+
+async function requestEvidenceBatch({ url, provider, criteria, creators, input, token }) {
+  const payload = buildProviderPayload(provider, criteria, creators, input);
+  let response;
+  try {
+    response = await fetch(url, {
+      method: 'POST',
+      headers: {
+        Accept: 'application/json',
+        'Content-Type': 'application/json',
+        ...(token ? { Authorization: `Bearer ${token}` } : {})
+      },
+      body: JSON.stringify(payload)
+    });
+  } catch {
+    const warning = '多模态证据 provider 暂时无法连接;本轮保留基础提号结果,后续再补帧图/ASR/视觉证据。';
+    return {
+      evidenceCards: [],
+      warnings: [warning],
+      status: providerStatus('network_error', warning)
+    };
+  }
+
+  if (response.status === 401 || response.status === 403) {
+    const recharge = await buildSocialRechargeInfo({
+      sessionToken: (input.sessionToken || input.tihaoToken || input.vocToken || token)
+    });
+    const warning = `社媒分析服务(视频分析)暂时不可用(未开通或余额不足),本轮先不使用帧图/ASR/视觉证据。可在此开通/充值后重试:${recharge.paymentUrl}`;
+    return {
+      evidenceCards: [],
+      warnings: [warning],
+      status: providerStatus('unauthorized_or_quota', warning, response.status, recharge)
+    };
+  }
+  if (!response.ok) {
+    const warning = `多模态证据 provider 返回 http ${response.status};本轮不使用帧图/ASR/视觉证据。`;
+    return {
+      evidenceCards: [],
+      warnings: [warning],
+      status: providerStatus('http_error', warning, response.status)
+    };
+  }
+
+  try {
+    const json = await response.json();
+    const evidenceCards = extractEvidenceCards(json).map(normalizeEvidenceCard).filter(Boolean);
+    const referenceStyleFingerprints = extractReferenceStyleFingerprints(json);
+    const referenceResources = extractReferenceResources(json);
+    const warning = '多模态证据 provider 未返回可用 evidenceCards;本轮只输出基础提号证据。';
+    const hasUsefulReferenceAnalysis = referenceStyleFingerprints.length || referenceResources.length;
+    return {
+      evidenceCards,
+      referenceStyleFingerprints,
+      referenceResources,
+      warnings: evidenceCards.length || hasUsefulReferenceAnalysis ? [] : [warning],
+      status: providerStatus(evidenceCards.length || hasUsefulReferenceAnalysis ? 'ok' : 'empty_result', evidenceCards.length || hasUsefulReferenceAnalysis ? '' : warning)
+    };
+  } catch {
+    const warning = '多模态证据 provider 返回内容无法解析;本轮不使用帧图/ASR/视觉证据。';
+    return {
+      evidenceCards: [],
+      warnings: [warning],
+      status: providerStatus('parse_error', warning)
+    };
+  }
+}
+
+function buildEvidenceBatches(creators, input = {}) {
+  const limit = resolveEvidenceCreatorLimit(input);
+  const selected = (creators || []).slice(0, limit);
+  const resultFirst = input.resultFirstMode || input.optimizeForResults || process.env.TIHAO_RESULT_FIRST_MODE === 'true';
+  const batchSize = resultFirst ? Number(input.evidenceBatchSize || 5) : selected.length || 1;
+  const result = [];
+  for (let index = 0; index < selected.length; index += batchSize) {
+    result.push(selected.slice(index, index + batchSize));
+  }
+  return result.length ? result : [[]];
+}
+
+function providerStatus(status, warning = '', httpStatus = 0, recharge = null) {
+  return {
+    provider: 'multimodal_evidence',
+    requested: status !== 'not_requested',
+    status,
+    httpStatus,
+    warning,
+    ...(recharge ? { recharge } : {})
+  };
+}
+
+function buildProviderPayload(provider, criteria, creators, input = {}) {
+  const contractPayload = buildEvidenceContractPayload(provider, criteria, creators, input);
+  if (provider.mode !== 'openai_chat') return contractPayload;
+  return {
+    model: provider.model,
+    temperature: Number(input.doubaoVisionTemperature ?? input.videoAnalysisTemperature ?? 0),
+    messages: [
+      {
+        role: 'system',
+        content: [
+          'You are a creator video-analysis evidence-card service for Tihao SOP.',
+          'Return JSON only. Do not include markdown.',
+          'Schema: {"referenceStyleFingerprints":[{"referenceId":"","url":"","platform":"","contentType":"video","accountName":"","title":"","bodySummary":"","authorInfo":"","coverOrVideoResource":"","personaType":"","contentStructure":"","tone":"","visualElements":[],"sellingPointBridge":"","commercialIntensity":"","complianceRisks":[],"evidenceAvailability":{"titleText":false,"asr":false,"frameVisual":false},"missingEvidenceNotes":[]}],"evidenceCards":[{"creatorId":"","platform":"","displayName":"","sourceLevel":"doubao_vision_verified","contentEvidence":{"textSignals":[],"asrSignals":[],"visualSignals":[],"frameEvidence":[],"transcriptSnippets":[]},"referenceFit":{"score":0,"level":"","hitPoints":[]},"riskEvidence":{"hits":[],"visualRisks":[],"needsManualReview":[]}}]}',
+          'For evidenceCards, return exactly one evidenceCard for every supplied creator. Do not stop at the best two creators. If evidence is weak, still return a card with lower referenceFit.score and needsManualReview notes.',
+          'For every supplied reference video, return one structured referenceStyleFingerprint. Use title/body, ASR, and frame/visual evidence only when supplied or actually analyzed. If no actual frames/transcripts are supplied, mark missingEvidenceNotes with 待补帧图证据 and 待补口播证据.'
+        ].join('\n')
+      },
+      {
+        role: 'user',
+        content: JSON.stringify(contractPayload)
+      }
+    ]
+  };
+}
+
+function buildEvidenceContractPayload(provider, criteria, creators, input = {}) {
+  return {
+    contractVersion: 'tihao-provider-v1',
+    providerType: provider.providerType,
+    model: provider.model,
+    requestedCapabilities: ['reference_title_text', 'reference_asr', 'reference_frame_visual', 'text_signals', 'asr_signals', 'visual_signals', 'frame_evidence', 'risk_review'],
+    criteria: {
+      brand: criteria.brand,
+      category: criteria.category,
+      keywords: criteria.keywords,
+      referenceLinks: criteria.referenceLinks,
+      referenceStyleAnchors: criteria.referenceStyleAnchors,
+      referenceStyleFingerprints: criteria.referenceStyleFingerprints,
+      referenceResources: criteria.referenceResources,
+      referenceBaselines: criteria.referenceBaselines
+    },
+    expectedEvidenceCardCount: (creators || []).slice(0, resolveEvidenceCreatorLimit(input)).length,
+    resultFirstMode: Boolean(input.resultFirstMode || input.optimizeForResults || process.env.TIHAO_RESULT_FIRST_MODE === 'true'),
+    creators: (creators || []).slice(0, resolveEvidenceCreatorLimit(input)).map(creator => ({
+      platform: creator.platform,
+      platformUserId: creator.platformUserId,
+      displayName: creator.displayName,
+      profileUrl: creator.profileUrl,
+      preliminaryRank: creator.rank,
+      preliminaryScore: creator.score,
+      briefFitScore: creator.briefFitScore,
+      referenceStyleFitScore: creator.referenceStyleFitScore,
+      contentTags: creator.contentTags,
+      personaTags: creator.personaTags,
+      recentEvidence: creator.recentEvidence
+    }))
+  };
+}
+
+function resolveEvidenceCreatorLimit(input = {}) {
+  if (input.evidenceCreatorsLimit) return Number(input.evidenceCreatorsLimit);
+  if (input.resultFirstMode || input.optimizeForResults || process.env.TIHAO_RESULT_FIRST_MODE === 'true') return 20;
+  return 6;
+}
+
+function readEvidenceCards(file) {
+  if (!file) return [];
+  const resolved = path.resolve(file);
+  if (!fs.existsSync(resolved)) return [];
+  try {
+    return extractEvidenceCards(JSON.parse(fs.readFileSync(resolved, 'utf8')));
+  } catch {
+    return [];
+  }
+}
+
+function extractEvidenceCards(json) {
+  if (Array.isArray(json)) return json;
+  if (Array.isArray(json.evidenceCards)) return json.evidenceCards;
+  if (Array.isArray(json.cards)) return json.cards;
+  const content = json?.choices?.[0]?.message?.content || json?.choices?.[0]?.text || json?.outputText || json?.output;
+  if (typeof content === 'string') {
+    const parsed = parseJsonContent(content);
+    if (parsed) return extractEvidenceCards(parsed);
+  }
+  if (json.data) return extractEvidenceCards(json.data);
+  return [];
+}
+
+function extractReferenceStyleFingerprints(json) {
+  if (!json || typeof json !== 'object') return [];
+  if (Array.isArray(json.referenceStyleFingerprints)) return json.referenceStyleFingerprints;
+  if (Array.isArray(json.fingerprints)) return json.fingerprints;
+  const content = json?.choices?.[0]?.message?.content || json?.choices?.[0]?.text || json?.outputText || json?.output;
+  if (typeof content === 'string') {
+    const parsed = parseJsonContent(content);
+    if (parsed) return extractReferenceStyleFingerprints(parsed);
+  }
+  if (json.data) return extractReferenceStyleFingerprints(json.data);
+  return [];
+}
+
+function extractReferenceResources(json) {
+  if (!json || typeof json !== 'object') return [];
+  if (Array.isArray(json.referenceResources)) return json.referenceResources;
+  if (Array.isArray(json.referenceDetails)) return json.referenceDetails;
+  if (json.data) return extractReferenceResources(json.data);
+  const content = json?.choices?.[0]?.message?.content || json?.choices?.[0]?.text || json?.outputText || json?.output;
+  if (typeof content === 'string') {
+    const parsed = parseJsonContent(content);
+    if (parsed) return extractReferenceResources(parsed);
+  }
+  return [];
+}
+
+function resolveEvidenceProvider(input = {}) {
+  // 显式配置的自定义 evidence-card provider(合同接口)优先生效。
+  const explicitEvidenceBaseUrl = input.multimodalEvidenceBaseUrl || input.evidenceProviderBaseUrl || process.env.TIHAO_EVIDENCE_BASE_URL || '';
+  const explicitDoubaoBaseUrl = input.doubaoVisionBaseUrl || input.videoAnalysisBaseUrl || process.env.DOUBAO_VISION_BASE_URL || process.env.VIDEO_ANALYSIS_BASE_URL || '';
+  // sample/演示模式保持无消耗(不发真实请求);live 模式默认走公司豆包视频分析,客户无需自填 baseUrl/Token。
+  const isSampleMode = String(input.collectionMode || input.collection_mode || '').toLowerCase() === 'sample';
+  const allowCompanyDefault = !isSampleMode && process.env.TIHAO_DISABLE_DEFAULT_VIDEO_ANALYSIS !== 'true';
+  const doubaoBaseUrl = explicitDoubaoBaseUrl || (explicitEvidenceBaseUrl || !allowCompanyDefault ? '' : DEFAULT_VIDEO_ANALYSIS_BASE_URL);
+  if (doubaoBaseUrl) {
+    const providerPath = input.doubaoVisionPath || input.videoAnalysisPath || process.env.DOUBAO_VISION_PATH || process.env.VIDEO_ANALYSIS_PATH || defaultDoubaoPath(doubaoBaseUrl) || '';
+    return {
+      baseUrl: doubaoBaseUrl,
+      path: providerPath,
+      providerType: 'doubao_vision_video_analysis',
+      model: input.doubaoVisionModel || input.videoAnalysisModel || process.env.DOUBAO_VISION_MODEL || DEFAULT_VIDEO_ANALYSIS_MODEL,
+      mode: resolveDoubaoMode(doubaoBaseUrl, providerPath, input)
+    };
+  }
+  return {
+    baseUrl: explicitEvidenceBaseUrl,
+    path: input.multimodalEvidencePath || 'evidence-cards',
+    providerType: 'multimodal_evidence_provider',
+    model: input.multimodalEvidenceModel || input.evidenceProviderModel || ''
+  };
+}
+
+function defaultDoubaoPath(baseUrl) {
+  return isFmodeOpenAIBaseUrl(baseUrl) ? 'v1/chat/completions' : '';
+}
+
+function resolveDoubaoMode(baseUrl, providerPath, input = {}) {
+  const explicit = input.doubaoVisionMode || input.videoAnalysisMode || process.env.DOUBAO_VISION_MODE || process.env.VIDEO_ANALYSIS_MODE || '';
+  if (explicit) return String(explicit);
+  return isFmodeOpenAIBaseUrl(baseUrl) || /chat\/completions/i.test(String(providerPath || ''))
+    ? 'openai_chat'
+    : 'contract';
+}
+
+function isFmodeOpenAIBaseUrl(baseUrl) {
+  try {
+    const url = new URL(String(baseUrl || ''));
+    return url.hostname === 'api.fmode.cn';
+  } catch {
+    return false;
+  }
+}
+
+function buildProviderUrl(baseUrl, providerPath) {
+  const cleaned = String(baseUrl || '').replace(/\/+$/, '');
+  const pathPart = String(providerPath || '').replace(/^\/+/, '');
+  return new URL(pathPart ? `${cleaned}/${pathPart}` : cleaned);
+}
+
+function parseJsonContent(content) {
+  const text = String(content || '').trim();
+  if (!text) return null;
+  const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
+  const candidate = fenced ? fenced[1].trim() : text;
+  try {
+    return JSON.parse(candidate);
+  } catch {
+    const objectMatch = candidate.match(/\{[\s\S]*\}/);
+    if (!objectMatch) return null;
+    try {
+      return JSON.parse(objectMatch[0]);
+    } catch {
+      return null;
+    }
+  }
+}
+
+function normalizeEvidenceCard(card) {
+  if (!card || typeof card !== 'object') return null;
+  const content = card.contentEvidence || {};
+  const risk = card.riskEvidence || {};
+  const referenceFit = card.referenceFit || {};
+  return {
+    creatorId: String(card.creatorId || card.platformUserId || card.userId || ''),
+    platform: String(card.platform || ''),
+    displayName: String(card.displayName || card.name || ''),
+    sourceLevel: String(card.sourceLevel || 'evidence_provider'),
+    profileEvidence: card.profileEvidence || {},
+    contentEvidence: {
+      titles: textList(content.titles),
+      textSignals: textList(content.textSignals),
+      asrSignals: textList(content.asrSignals),
+      visualSignals: textList(content.visualSignals),
+      frameEvidence: Array.isArray(content.frameEvidence) ? content.frameEvidence.slice(0, 12) : [],
+      transcriptSnippets: textList(content.transcriptSnippets),
+      engagementMedian: content.engagementMedian || {}
+    },
+    referenceFit: {
+      score: Number(referenceFit.score || 0),
+      level: String(referenceFit.level || ''),
+      hitPoints: textList(referenceFit.hitPoints)
+    },
+    riskEvidence: {
+      hits: textList(risk.hits),
+      visualRisks: textList(risk.visualRisks),
+      needsManualReview: textList(risk.needsManualReview)
+    }
+  };
+}
+
+function attachEvidenceCards(candidates, evidenceCards) {
+  if (!evidenceCards || !evidenceCards.length) return candidates;
+  return candidates.map(candidate => {
+    const card = findCard(candidate, evidenceCards);
+    if (!card) return candidate;
+    const signals = [
+      ...card.contentEvidence.textSignals,
+      ...card.contentEvidence.asrSignals,
+      ...card.contentEvidence.visualSignals
+    ];
+    const riskHints = [
+      ...card.riskEvidence.hits,
+      ...card.riskEvidence.visualRisks,
+      ...card.riskEvidence.needsManualReview
+    ];
+    const adjusted = {
+      ...candidate,
+      evidenceCard: card,
+      evidenceSignals: signals.slice(0, 10),
+      evidenceRiskHints: riskHints.slice(0, 10),
+      sourceLevel: card.sourceLevel || candidate.sourceProvider || '',
+      referenceSimilarity: Math.max(candidate.referenceSimilarity || 0, card.referenceFit.score || 0),
+      referenceStyleFitScore: Math.max(candidate.referenceStyleFitScore || 0, card.referenceFit.score || 0),
+      referenceFitLevel: card.referenceFit.level || candidate.referenceFitLevel,
+      referenceHitPoints: [...new Set([...(candidate.referenceHitPoints || []), ...card.referenceFit.hitPoints])].slice(0, 10),
+      referenceStyleHitPoints: [...new Set([...(candidate.referenceStyleHitPoints || []), ...card.referenceFit.hitPoints])].slice(0, 10),
+      riskNote: appendRiskHints(candidate.riskNote, riskHints)
+    };
+    return applyEvidenceScoreAdjustment(adjusted, card);
+  }).sort((a, b) => b.score - a.score).map((candidate, index) => ({ ...candidate, rank: index + 1 }));
+}
+
+function applyEvidenceScoreAdjustment(candidate, card) {
+  const evidenceReferenceScore = Number(card?.referenceFit?.score || 0);
+  if (!evidenceReferenceScore) return candidate;
+  const baseScore = Math.max(0, Math.min(100, Math.round(
+    Number(candidate.briefFitScore || 0) * 0.4 +
+    Number(candidate.referenceStyleFitScore || evidenceReferenceScore || 0) * 0.25 +
+    Number(candidate.dataQualityScore || 0) * 0.2 +
+    Number(candidate.budgetFitScore || 0) * 0.1 -
+    Number(candidate.riskPenalty || 0) * 0.05
+  )));
+  const evidenceBoost = computeEvidenceScoreBoost(candidate, card);
+  const evidenceRiskPenalty = computeEvidenceRiskPenalty(card);
+  const score = Math.max(Number(candidate.score || 0), Math.min(100, baseScore + evidenceBoost - evidenceRiskPenalty));
+  const adjusted = {
+    ...candidate,
+    score,
+    evidenceScoreBoost: evidenceBoost,
+    evidenceRiskPenalty
+  };
+  return {
+    ...adjusted,
+    recommendStatus: adjustRecommendStatus(adjusted)
+  };
+}
+
+function adjustRecommendStatus(candidate) {
+  if (candidate.recommendStatus === '已剔除' || (candidate.exclusionHits || []).length) return candidate.recommendStatus;
+  const score = Number(candidate.score || 0);
+  const brief = Number(candidate.briefFitScore || 0);
+  const reference = Number(candidate.referenceStyleFitScore || candidate.referenceSimilarity || 0);
+  const boost = Number(candidate.evidenceScoreBoost || 0);
+  const risk = Number(candidate.riskPenalty || 0);
+  const evidenceRisk = Number(candidate.evidenceRiskPenalty || 0);
+  const hits = (candidate.referenceStyleHitPoints || candidate.referenceHitPoints || []).length;
+  if (candidate.recommendStatus === '强推荐' && score >= 75 && brief >= 70 && reference >= 65 && risk < 35 && evidenceRisk <= 2) return '强推荐';
+  if (score >= 78 && brief >= 70 && reference >= 68 && boost > 0 && hits >= 2 && risk < 35) return '强推荐';
+  if (score >= 78 && brief >= 75 && reference >= 70 && boost > 0 && hits >= 1 && risk < 35 && evidenceRisk <= 2) return '强推荐';
+  if (score >= 77 && brief >= 75 && reference >= 70 && risk < 25 && evidenceRisk <= 1) return '强推荐';
+  if (score >= 65 && brief >= 65) return '备选';
+  if (score >= 55) return '需复核';
+  return candidate.recommendStatus || '需复核';
+}
+
+function computeEvidenceRiskPenalty(card) {
+  const risk = card.riskEvidence || {};
+  const text = [
+    ...(risk.hits || []),
+    ...(risk.visualRisks || []),
+    ...(risk.needsManualReview || [])
+  ].join(' ');
+  let penalty = 0;
+  if (/不适配|不符合|偏离|跑偏/.test(text)) penalty += 4;
+  if (/宠物|非母婴|占比/.test(text)) penalty += 3;
+  if (/合规|医疗|功效|风险/.test(text)) penalty += 2;
+  return Math.min(8, penalty);
+}
+
+function computeEvidenceScoreBoost(candidate, card) {
+  if (Number(candidate.briefFitScore || 0) < 65) return 0;
+  const content = card.contentEvidence || {};
+  const risk = card.riskEvidence || {};
+  const signalCount = [
+    ...(content.textSignals || []),
+    ...(content.asrSignals || []),
+    ...(content.visualSignals || [])
+  ].filter(Boolean).length;
+  const hitCount = (card.referenceFit?.hitPoints || []).filter(Boolean).length;
+  const riskCount = [
+    ...(risk.hits || []),
+    ...(risk.visualRisks || []),
+    ...(risk.needsManualReview || [])
+  ].filter(Boolean).length;
+  const referenceBonus = Number(card.referenceFit?.score || 0) >= 75 ? 2 : Number(card.referenceFit?.score || 0) >= 68 ? 1 : 0;
+  return Math.max(0, Math.min(6, Math.round(signalCount * 0.5 + hitCount * 0.8 + referenceBonus - riskCount * 0.8)));
+}
+
+function findCard(candidate, cards) {
+  const keys = [
+    candidate.platformUserId,
+    candidate.userId,
+    candidate.displayName
+  ].filter(Boolean).map(String);
+  return cards.find(card => keys.includes(card.creatorId) || keys.includes(card.displayName));
+}
+
+function appendRiskHints(riskNote, riskHints) {
+  const hints = riskHints.filter(Boolean).slice(0, 3);
+  if (!hints.length) return riskNote;
+  const suffix = `多模态复核:${hints.join('、')}`;
+  if (!riskNote || riskNote === '暂无明显风险') return suffix;
+  return `${riskNote};${suffix}`;
+}
+
+function textList(values) {
+  return (Array.isArray(values) ? values : [values])
+    .flatMap(item => String(item || '').split(/[、,;;]/))
+    .map(item => item.trim())
+    .filter(Boolean);
+}
+
+function dedupe(cards) {
+  const seen = new Set();
+  const result = [];
+  for (const card of cards) {
+    const key = `${card.platform}:${card.creatorId || card.displayName}`;
+    if (seen.has(key)) continue;
+    seen.add(key);
+    result.push(card);
+  }
+  return result;
+}
+
+function dedupeFingerprints(items) {
+  const seen = new Set();
+  const result = [];
+  for (const item of items.filter(Boolean)) {
+    const key = item.url || item.referenceId || `${item.platform || ''}:${item.title || item.accountName || ''}`;
+    if (seen.has(key)) continue;
+    seen.add(key);
+    result.push(item);
+  }
+  return result;
+}
+
+function dedupeResources(items) {
+  const seen = new Set();
+  const result = [];
+  for (const item of items.filter(Boolean)) {
+    const key = item.url || item.videoUrl || item.coverUrl || item.referenceId || JSON.stringify(item).slice(0, 80);
+    if (seen.has(key)) continue;
+    seen.add(key);
+    result.push(item);
+  }
+  return result;
+}
+
+module.exports = {
+  attachEvidenceCards,
+  loadMultimodalEvidence,
+  normalizeEvidenceCard
+};

+ 301 - 0
claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/preference-memory.js

@@ -0,0 +1,301 @@
+const fs = require('fs');
+const path = require('path');
+const { ensureDir, writeJson } = require('../../core/files');
+const { okResult } = require('../../core/result-envelope');
+
+function readMemory(memoryPath) {
+  if (!memoryPath || !fs.existsSync(memoryPath)) return defaultMemory();
+  try {
+    return { ...defaultMemory(), ...JSON.parse(fs.readFileSync(memoryPath, 'utf8')) };
+  } catch {
+    return defaultMemory();
+  }
+}
+
+function defaultMemory() {
+  return {
+    preferredStyles: [],
+    blockedStyles: [],
+    preferredPlatforms: [],
+    preferredReferenceAccounts: [],
+    blockedCreators: [],
+    pricingNotes: [],
+    clientFeedback: [],
+    feedbackRecords: [],
+    personalPreferences: [],
+    clientBrandPreferences: [],
+    teamRuleCandidates: [],
+    teamRuleSuggestions: [],
+    feedbackSummary: {},
+    updatedAt: ''
+  };
+}
+
+function applyMemory(criteria, memoryPath) {
+  const memory = readMemory(memoryPath);
+  const blockedCreators = merge(criteria.blockedCreators || [], memory.blockedCreators || []);
+  return {
+    ...criteria,
+    stylePreferences: merge(criteria.stylePreferences || [], memory.preferredStyles || []),
+    referenceAccounts: [
+      ...(criteria.referenceAccounts || []),
+      ...(memory.preferredReferenceAccounts || []).map(name => ({ name, sourceText: 'preference-memory', needsEnrichment: true }))
+    ],
+    blockedCreators,
+    exclusions: merge(criteria.exclusions || [], memory.blockedStyles || [], blockedCreators),
+    memory
+  };
+}
+
+function applyFeedbackRecordsToMemory(memory, records = []) {
+  const normalized = records.map(normalizeFeedbackRecord).filter(item => item.feedbackLabel || item.feedbackReason || item.creatorName);
+  const blockedCreators = [];
+  const blockedStyles = [];
+  const personalPreferences = [];
+  const clientBrandPreferences = [];
+  const teamRuleCandidates = [];
+
+  for (const record of normalized) {
+    const label = record.feedbackLabel || '';
+    const reason = record.feedbackReason || '';
+    const scope = record.scope || '';
+    const name = record.creatorName || record.profileUrl || '';
+    const combined = `${label} ${reason} ${scope}`;
+    if (/拉黑|硬性规则违规|跑偏|不可投|不要|剔除|调性不符|主页质感不符|参考账号不像/.test(combined)) {
+      if (name) blockedCreators.push(name);
+    }
+    if (/硬广|下沉|泛美妆|男性泛账号|调性不符|主页质感不符|参考账号不像/.test(combined)) {
+      blockedStyles.push(reason || label);
+    }
+    if (/个人|我的|我这边/.test(scope || reason)) personalPreferences.push(reason || label);
+    if (/客户|品牌/.test(scope || reason)) clientBrandPreferences.push(reason || label);
+    if (record.promoteToTeamRule || /团队规则|通用规则|连续/.test(scope || reason)) {
+      teamRuleCandidates.push(reason || label);
+    }
+  }
+
+  memory.feedbackRecords = [...(memory.feedbackRecords || []), ...normalized].slice(-500);
+  memory.blockedCreators = merge(memory.blockedCreators || [], blockedCreators);
+  memory.blockedStyles = merge(memory.blockedStyles || [], blockedStyles);
+  memory.personalPreferences = merge(memory.personalPreferences || [], personalPreferences);
+  memory.clientBrandPreferences = merge(memory.clientBrandPreferences || [], clientBrandPreferences);
+  memory.teamRuleCandidates = merge(memory.teamRuleCandidates || [], teamRuleCandidates);
+  memory.feedbackSummary = summarizeFeedback(memory.feedbackRecords);
+  memory.teamRuleSuggestions = buildTeamRuleSuggestions(memory.feedbackRecords);
+  memory.updatedAt = new Date().toISOString();
+  return memory;
+}
+
+function updatePreferenceMemory(input = {}) {
+  const memoryPath = path.resolve(input.memory || input.memoryPath || 'memory/tihao-preference-memory.json');
+  const memory = readMemory(memoryPath);
+  const message = normalizeMessage(input.message || input.feedback || '');
+
+  memory.preferredStyles = merge(memory.preferredStyles, input.preferredStyles, extractHits(message, ['素人感', '真实体验', '敏感肌', '成分党', '生活化', '空瓶记录', '专业科普']));
+  memory.blockedStyles = merge(memory.blockedStyles, input.blockedStyles, extractHits(message, ['硬广', '泛美妆', '医美', '整形', '虚假宣传', '搬运', '下沉']));
+  memory.preferredPlatforms = merge(memory.preferredPlatforms, input.preferredPlatforms, extractHits(message, ['小红书', '抖音', 'B站']));
+  memory.preferredReferenceAccounts = merge(memory.preferredReferenceAccounts, input.preferredReferenceAccounts, extractNamedAccounts(message, ['像', '参考', '对标', '类似']));
+  memory.blockedCreators = merge(memory.blockedCreators, input.blockedCreators, extractNamedAccounts(message, ['不要', '避开', '排除', '拉黑', '屏蔽']));
+  if (message) memory.clientFeedback = [...memory.clientFeedback, message].slice(-20);
+  if (input.pricingNote) memory.pricingNotes = [...memory.pricingNotes, String(input.pricingNote)].slice(-20);
+  memory.updatedAt = new Date().toISOString();
+
+  ensureDir(path.dirname(memoryPath));
+  writeJson(memoryPath, memory);
+
+  return okResult({
+    assistantMessage: [
+      '已更新提号偏好记忆。',
+      '',
+      `记忆文件:${memoryPath}`,
+      `偏好风格:${memory.preferredStyles.join('、') || '暂无'}`,
+      `排除风格:${memory.blockedStyles.join('、') || '暂无'}`,
+      `参考账号偏好:${memory.preferredReferenceAccounts.join('、') || '暂无'}`,
+      `排除账号:${memory.blockedCreators.join('、') || '暂无'}`,
+      '',
+      '下次按 brief 生成博主名单时,会把这些偏好并入筛选和风险提示。'
+    ].join('\n'),
+    summary: {
+      memoryPath,
+      preferredStyles: memory.preferredStyles,
+      blockedStyles: memory.blockedStyles,
+      preferredReferenceAccounts: memory.preferredReferenceAccounts,
+      blockedCreators: memory.blockedCreators
+    },
+    data: { memory },
+    files: [memoryPath],
+    nextActions: ['用同一个 memory 路径重新运行 tihao_brief_sourcing_run']
+  });
+}
+
+function normalizeFeedbackRecord(record = {}) {
+  return {
+    briefId: record.briefId || record['brief编号'] || record.brief || '',
+    creatorName: record.creatorName || record['博主名称'] || record['账号名称'] || record.name || '',
+    platform: record.platform || record['平台'] || '',
+    profileUrl: record.profileUrl || record['主页链接'] || record.url || '',
+    feedbackLabel: record.feedbackLabel || record['反馈标签'] || record['人工复核标签'] || record.label || '',
+    feedbackReason: record.feedbackReason || record['反馈原因'] || record['风险提示'] || record.reason || '',
+    attribution: record.attribution || record['归因类型'] || '',
+    scope: record.scope || record['影响范围'] || '',
+    nextAction: record.nextAction || record['下一轮动作'] || '',
+    promoteToTeamRule: Boolean(record.promoteToTeamRule || /是|true|1/i.test(String(record['是否升级为团队规则'] || ''))),
+    savedAt: record.savedAt || new Date().toISOString()
+  };
+}
+
+function summarizeFeedback(records = []) {
+  const byLabel = {};
+  const byAttribution = {};
+  const teamRuleSuggestionCount = buildTeamRuleSuggestions(records).length;
+  for (const item of records) {
+    const label = item.feedbackLabel || '未标注';
+    const attribution = item.attribution || '未归因';
+    byLabel[label] = (byLabel[label] || 0) + 1;
+    byAttribution[attribution] = (byAttribution[attribution] || 0) + 1;
+  }
+  return {
+    total: records.length,
+    byLabel,
+    byAttribution,
+    teamRuleSuggestionCount,
+    updatedAt: new Date().toISOString()
+  };
+}
+
+function buildTeamRuleSuggestions(records = []) {
+  const buckets = new Map();
+  for (const record of records.map(normalizeFeedbackRecord)) {
+    const key = teamRuleKey(record);
+    if (!key) continue;
+    const bucket = buckets.get(key) || {
+      key,
+      label: record.feedbackLabel || '',
+      attribution: record.attribution || '',
+      scope: inferRuleScope(record),
+      count: 0,
+      reasons: [],
+      sources: [],
+      suggestedRule: ''
+    };
+    bucket.count += 1;
+    if (record.feedbackReason) bucket.reasons.push(record.feedbackReason);
+    bucket.sources.push({
+      briefId: record.briefId || '',
+      creatorName: record.creatorName || '',
+      platform: record.platform || '',
+      profileUrl: record.profileUrl || '',
+      feedbackLabel: record.feedbackLabel || '',
+      feedbackReason: record.feedbackReason || '',
+      attribution: record.attribution || '',
+      scope: record.scope || '',
+      savedAt: record.savedAt || ''
+    });
+    buckets.set(key, bucket);
+  }
+
+  return [...buckets.values()]
+    .filter(item => item.count >= 3)
+    .map(item => ({
+      key: item.key,
+      status: '建议升级为团队规则',
+      count: item.count,
+      scope: item.scope,
+      label: item.label,
+      attribution: item.attribution,
+      suggestedRule: buildSuggestedRule(item),
+      reasons: unique(item.reasons).slice(0, 6),
+      sources: item.sources.slice(-10)
+    }))
+    .sort((a, b) => b.count - a.count || a.key.localeCompare(b.key));
+}
+
+function teamRuleKey(record = {}) {
+  const combined = `${record.feedbackLabel || ''} ${record.feedbackReason || ''} ${record.attribution || ''} ${record.scope || ''}`;
+  if (!record.promoteToTeamRule && !/团队规则|通用规则|连续|客户品牌级|团队级|硬性规则违规|跑偏|调性不符|主页质感不符|参考账号不像/.test(combined)) {
+    return '';
+  }
+  const normalizedReason = normalizeRuleReason(record.feedbackReason || record.feedbackLabel || '');
+  const attribution = record.attribution || inferAttribution(combined);
+  const label = record.feedbackLabel || inferLabel(combined);
+  return [attribution || '未归因', label || '未标注', normalizedReason || '未写原因'].join('|');
+}
+
+function normalizeRuleReason(text) {
+  return String(text || '')
+    .replace(/https?:\/\/\S+/g, '')
+    .replace(/[0-9]+/g, '')
+    .replace(/[,。;;、,.!!??\s]/g, '')
+    .replace(/这个|账号|博主|达人|不应|不要|不能|不适合|需要|建议/g, '')
+    .slice(0, 32);
+}
+
+function inferRuleScope(record = {}) {
+  const text = `${record.scope || ''} ${record.feedbackReason || ''}`;
+  if (/客户|品牌/.test(text)) return '客户/品牌规则';
+  if (/个人|我这边|我的/.test(text)) return '个人偏好';
+  return '团队通用候选';
+}
+
+function inferAttribution(text) {
+  if (/主页|封面|质感/.test(text)) return '主页证据';
+  if (/参考|不像|调性/.test(text)) return '参考风格';
+  if (/硬性|规则|粉丝|预算|地域|平台/.test(text)) return '硬性规则';
+  if (/跑偏|需求|brief/i.test(text)) return '需求解析';
+  return '';
+}
+
+function inferLabel(text) {
+  if (/硬性规则违规/.test(text)) return '硬性规则违规';
+  if (/跑偏/.test(text)) return '跑偏';
+  if (/调性不符/.test(text)) return '调性不符';
+  if (/主页质感不符/.test(text)) return '主页质感不符';
+  if (/参考账号不像/.test(text)) return '参考账号不像';
+  return '';
+}
+
+function buildSuggestedRule(item) {
+  const reason = unique(item.reasons)[0] || item.label || item.attribution || item.key;
+  const prefix = item.scope === '客户/品牌规则' ? '同客户/品牌需求下' : '后续提号中';
+  return `${prefix},遇到“${reason}”同类情况时,默认降级或剔除,并在报告中提示人工复核。`;
+}
+
+function unique(values) {
+  return [...new Set((values || []).filter(Boolean).map(String))];
+}
+
+function merge(...groups) {
+  return [...new Set(groups.flat().filter(Boolean).map(String))];
+}
+
+function normalizeMessage(message) {
+  return Array.isArray(message) ? message.filter(Boolean).join(';') : String(message || '');
+}
+
+function extractHits(text, vocabulary) {
+  return vocabulary.filter(item => text.includes(item));
+}
+
+function extractNamedAccounts(text, triggers) {
+  const result = [];
+  const lines = String(text || '').split(/\r?\n|[。;;]/).map(item => item.trim()).filter(Boolean);
+  for (const line of lines) {
+    if (!triggers.some(trigger => line.includes(trigger))) continue;
+    for (const match of line.matchAll(/[“"「『]([^”"」』]{2,30})[”"」』]/g)) {
+      result.push(match[1].trim());
+    }
+    const likeMatch = line.match(/像\s*([^,。;;]{2,30}?)\s*(?:这种|这类|一样|类似)/);
+    if (likeMatch) result.push(likeMatch[1].trim());
+    const accountMatch = line.match(/(?:账号|博主|达人)\s*[::]?\s*([\u4e00-\u9fa5A-Za-z0-9_()() -]{2,30})/);
+    if (accountMatch) result.push(accountMatch[1].trim());
+  }
+  return result.map(item => item.replace(/^(不要|避开|排除|拉黑|屏蔽)/, '').trim()).filter(Boolean);
+}
+
+module.exports = {
+  applyMemory,
+  readMemory,
+  updatePreferenceMemory,
+  applyFeedbackRecordsToMemory,
+  buildTeamRuleSuggestions
+};

+ 313 - 0
claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/ranker.js

@@ -0,0 +1,313 @@
+const { attachHomepageEvidenceToCreator } = require('./homepage-evidence');
+const { evaluateImplicitRuleFit } = require('./experience-rules');
+
+function rankCreators(creators, criteria) {
+  const seen = new Set();
+  return (creators || [])
+    .filter(creator => {
+      const key = `${creator.platform}:${creator.platformUserId || creator.profileUrl || creator.displayName}`;
+      if (seen.has(key)) return false;
+      seen.add(key);
+      return (criteria.platforms || []).includes(creator.platform);
+    })
+    .map(creator => scoreCreator(attachHomepageEvidenceToCreator(creator, criteria), criteria))
+    .sort((a, b) => b.score - a.score)
+    .map((creator, index) => ({ ...creator, rank: index + 1 }));
+}
+
+function scoreCreator(creator, criteria) {
+  const price = creator.minPrice || creator.imagePrice || creator.videoPrice || 0;
+  const hasPrice = Boolean(price);
+  const fanScore = scoreRange(creator.fansCount, criteria.fanRange, 50);
+  const priceScore = price ? scoreRange(price, criteria.budgetRange, 45) : 35;
+  const text = evidenceText(creator);
+  const riskHits = (criteria.exclusions || []).filter(item => hasRiskHit(text, item));
+  const blockedCreatorHits = (criteria.blockedCreators || []).filter(item => hasHit(text, item));
+  const hardExclusionHits = [...new Set([...riskHits, ...blockedCreatorHits])];
+  const briefFit = scoreBriefFit(creator, criteria, fanScore, priceScore);
+  const referenceFit = scoreReferenceFit(creator, criteria);
+  const dataQualityScore = scoreDataQuality(creator);
+  const homepageEvidence = creator.homepageEvidence || {};
+  const recentContentFitScore = creator.recentContentFitScore || homepageEvidence.recentContentFitScore || 60;
+  const visualQualityScore = creator.visualQualityScore || homepageEvidence.visualQualityScore || 0;
+  const toneConsistencyScore = creator.toneConsistencyScore || homepageEvidence.toneConsistencyScore || 0;
+  const homepageEvidenceHitPoints = buildHomepageEvidenceHitPoints(homepageEvidence);
+  const homepageQualityRisks = homepageEvidence.visualRiskSignals || [];
+  const homepageDataRisks = homepageEvidence.dataRiskSignals || [];
+  const highPriceVisualGap = price >= 300 && visualQualityScore > 0 && visualQualityScore < 65;
+  const explainableStyleEvidence = referenceFit.hitPoints.length || homepageEvidenceHitPoints.length;
+  const implicitRuleFit = evaluateImplicitRuleFit(creator, criteria.categoryRules || []);
+  const implicitViolations = implicitRuleFit.violations || [];
+  const overBudget = price && criteria.budgetRange?.max && price > criteria.budgetRange.max;
+  const riskPenalty = Math.min(100, hardExclusionHits.length * 35 + implicitViolations.length * 35 + homepageDataRisks.length * 12 + (highPriceVisualGap ? 12 : 0) + (!hasPrice ? 18 : 0) + (overBudget ? 28 : 0) + buildRiskFlags(creator, criteria).length * 8);
+  const score = clamp(Math.round(
+    briefFit.score * 0.32 +
+    referenceFit.score * 0.22 +
+    recentContentFitScore * 0.16 +
+    dataQualityScore * 0.15 +
+    priceScore * 0.1 +
+    implicitRuleFit.score * 0.08 -
+    riskPenalty * 0.05
+  ));
+  const strongEligible = !hardExclusionHits.length &&
+    hasPrice &&
+    !implicitViolations.length &&
+    !overBudget &&
+    !homepageQualityRisks.length &&
+    !homepageDataRisks.length &&
+    !highPriceVisualGap &&
+    briefFit.hitConditions.length >= 2 &&
+    explainableStyleEvidence >= 1 &&
+    briefFit.score >= 70 &&
+    referenceFit.score >= 60 &&
+    recentContentFitScore >= 58 &&
+    visualQualityScore >= 55;
+  const recommendStatus = hardExclusionHits.length ? '已剔除'
+    : strongEligible && score >= 78 ? '强推荐'
+    : !hasPrice && score >= 60 ? '需复核'
+    : overBudget && score >= 60 ? '需复核'
+    : score >= 65 && briefFit.score >= 65 ? '备选'
+    : score >= 55 ? '需复核'
+    : '已剔除';
+  const manualReviewLabel = resolveManualReviewLabel({
+    recommendStatus,
+    hardExclusionHits,
+    implicitViolations,
+    homepageQualityRisks,
+    homepageDataRisks,
+    highPriceVisualGap,
+    referenceFit
+  });
+
+  return {
+    ...creator,
+    score,
+    briefFitScore: briefFit.score,
+    referenceStyleFitScore: referenceFit.score,
+    dataQualityScore,
+    recentContentFitScore,
+    visualQualityScore,
+    toneConsistencyScore,
+    homepageEvidence,
+    implicitRuleFit,
+    implicitRuleHits: implicitRuleFit.hitPoints || [],
+    implicitRuleViolations: implicitViolations,
+    budgetFitScore: priceScore,
+    riskPenalty,
+    styleMatch: briefFit.score,
+    referenceSimilarity: referenceFit.score,
+    referenceFitLevel: referenceFit.level,
+    referenceHitPoints: referenceFit.hitPoints,
+    referenceStyleHitPoints: referenceFit.hitPoints,
+    referenceFallbackHitPoints: referenceFit.fallbackHitPoints,
+    referenceEvidenceSource: referenceFit.evidenceSource,
+    referenceEvidenceConcrete: referenceFit.concrete,
+    homepageEvidenceHitPoints,
+    homepageQualityRisks,
+    homepageDataRisks,
+    referenceMatchedSignals: (criteria.referenceSignals || []).filter(item => hasHit(text, item)).slice(0, 6),
+    briefHitConditions: briefFit.hitConditions,
+    missingKeyConditions: briefFit.missingConditions,
+    manualReviewLabel,
+    manualReviewFields: buildManualReviewFields(creator, criteria, [...hardExclusionHits, ...implicitViolations, ...homepageQualityRisks, ...homepageDataRisks, highPriceVisualGap ? '报价300元以上视觉质感不足' : '']),
+    exclusionHits: hardExclusionHits,
+    recommendStatus,
+    recommendReason: buildReason(creator, criteria, score, briefFit.score, referenceFit, homepageEvidenceHitPoints),
+    riskNote: hardExclusionHits.length || implicitViolations.length
+      ? `命中排除/隐性规则风险:${[...hardExclusionHits, ...implicitViolations].join('、')}`
+      : buildRiskNote(creator, criteria)
+  };
+}
+
+function resolveManualReviewLabel({ recommendStatus, hardExclusionHits, implicitViolations, homepageQualityRisks, homepageDataRisks, highPriceVisualGap, referenceFit }) {
+  if (recommendStatus === '强推荐') return '可直接发客户';
+  if (hardExclusionHits.length || implicitViolations.length) return '硬性规则违约';
+  if (homepageQualityRisks.length || homepageDataRisks.length || highPriceVisualGap) return '主页质感不符';
+  if (referenceFit?.evidenceSource === 'none' && referenceFit?.score < 60) return '参考账号不像';
+  if (recommendStatus === '已剔除') return '跑偏';
+  return '商务复核';
+}
+
+function buildHomepageEvidenceHitPoints(homepageEvidence = {}) {
+  const points = [
+    ...(homepageEvidence.matchedTerms || []).map(item => `主页/标签命中=${item}`),
+    ...(homepageEvidence.reviewNotes || [])
+      .filter(item => !/缺少|低置信度|不能仅凭|风险/.test(String(item || '')))
+  ];
+  if (homepageEvidence.source === 'provider') points.push('真实主页 provider 证据');
+  return [...new Set(points)].slice(0, 8);
+}
+
+function scoreBriefFit(creator, criteria, fanScore, priceScore) {
+  const text = evidenceText(creator);
+  const hitConditions = [];
+  const missingConditions = [];
+  if ((criteria.platforms || []).includes(creator.platform)) hitConditions.push(`平台=${labelPlatform(creator.platform)}`);
+  else missingConditions.push('平台不在 brief 要求内');
+  const keywordHits = (criteria.keywords || []).filter(term => hasHit(text, term)).slice(0, 5);
+  if (keywordHits.length) hitConditions.push(`品类/关键词=${keywordHits.join('、')}`);
+  else missingConditions.push('品类关键词未命中');
+  const personaHits = [...(criteria.stylePreferences || []), ...(criteria.referenceSignals || [])]
+    .filter(term => hasHit(text, term))
+    .slice(0, 5);
+  if (personaHits.length) hitConditions.push(`账号/内容类型=${personaHits.join('、')}`);
+  else missingConditions.push('账号类型或内容风格待复核');
+  if (creator.fansCount && creator.fansCount >= (criteria.fanRange?.min || 0) && creator.fansCount <= (criteria.fanRange?.max || Number.MAX_SAFE_INTEGER)) hitConditions.push('粉丝范围匹配');
+  else missingConditions.push('粉丝范围不完全匹配');
+  const price = creator.minPrice || creator.imagePrice || creator.videoPrice || 0;
+  if (price && price <= (criteria.budgetRange?.max || Number.MAX_SAFE_INTEGER)) hitConditions.push('报价在预算内');
+  else missingConditions.push(price ? '报价超过预算或需确认' : '报价缺失');
+  const score = clamp(Math.round(fanScore * 0.25 + priceScore * 0.25 + Math.min(keywordHits.length, 3) * 12 + Math.min(personaHits.length, 3) * 10 + hitConditions.length * 4));
+  return { score, hitConditions, missingConditions };
+}
+
+function scoreReferenceFit(creator, criteria) {
+  const text = evidenceText(creator);
+  const referenceSignals = [...(criteria.referenceSignals || []), ...(criteria.referenceBaselineSignals || [])];
+  const matched = referenceSignals.filter(signal => hasHit(text, signal)).slice(0, 8);
+  const baselineBoost = Math.min(18, (criteria.referenceBaselines || []).length * 4);
+  const fingerprintBoost = Math.min(12, (criteria.referenceStyleFingerprints || []).length * 3);
+  const score = clamp(50 + matched.length * 8 + baselineBoost + fingerprintBoost);
+  const fallbackHitPoints = matched.length ? [] : fallbackReferencePoints(creator, criteria);
+  return {
+    score,
+    level: score >= 75 ? 'high' : score >= 60 ? 'medium' : 'low',
+    hitPoints: matched,
+    fallbackHitPoints,
+    concrete: matched.length > 0,
+    evidenceSource: matched.length ? 'matched_reference_signal' : fallbackHitPoints.length ? 'fallback_reference_hint' : 'none'
+  };
+}
+
+function fallbackReferencePoints(creator, criteria) {
+  const points = [];
+  if ((creator.contentTags || []).length) points.push(`内容标签=${creator.contentTags.slice(0, 3).join('、')}`);
+  if ((creator.personaTags || []).length) points.push(`人设标签=${creator.personaTags.slice(0, 3).join('、')}`);
+  if ((criteria.referenceLinks || []).length) points.push('有参考链接,需补相似账号证据');
+  return points.slice(0, 4);
+}
+
+function scoreDataQuality(creator) {
+  let score = 45;
+  if (creator.profileUrl) score += 10;
+  if (creator.fansCount) score += 10;
+  if (creator.minPrice || creator.imagePrice || creator.videoPrice) score += 10;
+  if ((creator.contentTags || []).length) score += 10;
+  if ((creator.personaTags || []).length) score += 10;
+  if ((creator.recentEvidence || []).length) score += 10;
+  const averageLikes = Number(creator.recentAverageLikes ?? creator.averageLikes30d ?? creator.avgLikes30d ?? creator.averageLikes ?? 0);
+  if (averageLikes > 0 && averageLikes < 100) score -= 18;
+  const lastPostDaysAgo = Number(creator.lastPostDaysAgo ?? creator.daysSinceLastPost ?? 0);
+  if (lastPostDaysAgo > 30) score -= 20;
+  if (Number(creator.repeatedCommenterCount || creator.repeatedCommenterHits || 0) > 0) score -= 18;
+  return clamp(score);
+}
+
+function buildManualReviewFields(creator, criteria, riskHits) {
+  const fields = [];
+  if (!creator.profileUrl) fields.push('主页链接');
+  if (!(creator.minPrice || creator.imagePrice || creator.videoPrice)) fields.push('报价');
+  if (!(creator.recentEvidence || []).length) fields.push('近期内容');
+  if ((criteria.referenceLinks || []).length && !(criteria.referenceBaselines || []).length) fields.push('参考账号相似度');
+  if (requiresRecentActivityReview(criteria) || (creator.homepageEvidence?.dataRiskSignals || []).some(item => /近30天|停更/.test(item))) fields.push('近30天更新');
+  if (requiresAverageLikesReview(criteria) || (creator.homepageEvidence?.dataRiskSignals || []).some(item => /点赞/.test(item))) fields.push('平均点赞');
+  if ((creator.homepageEvidence?.dataRiskSignals || []).some(item => /重复评论|数据真实性|刷评/.test(item))) fields.push('重复评论/数据真实性');
+  const price = creator.minPrice || creator.imagePrice || creator.videoPrice || 0;
+  if (price >= 300 || (creator.homepageEvidence?.visualRiskSignals || []).length) fields.push('封面清晰度/背景统一');
+  if (riskHits.length) fields.push('排除/隐性规则');
+  return [...new Set(fields.filter(Boolean))];
+}
+
+function buildReason(creator, criteria, score, briefScore, referenceFit, homepageEvidenceHitPoints = []) {
+  const parts = [
+    `综合分 ${score}`,
+    `Brief匹配 ${briefScore}`,
+    referenceFit.hitPoints.length ? `参考/风格命中:${referenceFit.hitPoints.slice(0, 3).join('、')}` : '',
+    !referenceFit.hitPoints.length && referenceFit.fallbackHitPoints?.length ? `参考待补:${referenceFit.fallbackHitPoints.slice(0, 2).join('、')}` : '',
+    homepageEvidenceHitPoints.length ? `主页证据命中:${homepageEvidenceHitPoints.slice(0, 2).join('、')}` : '',
+    (creator.contentTags || []).length ? `内容标签:${creator.contentTags.slice(0, 3).join('、')}` : '',
+    (creator.homepageEvidence?.reviewNotes || []).length ? `主页证据:${creator.homepageEvidence.reviewNotes.slice(0, 2).join('、')}` : ''
+  ].filter(Boolean);
+  return parts.join(';');
+}
+
+function buildRiskNote(creator, criteria) {
+  const flags = buildRiskFlags(creator, criteria);
+  return flags.length ? flags.join(';') : '暂无明显风险,仍需商务复核主页近作、报价有效期和合作状态';
+}
+
+function buildRiskFlags(creator, criteria) {
+  const flags = [];
+  const price = creator.minPrice || creator.imagePrice || creator.videoPrice || 0;
+  if (!price) flags.push(creator.quoteStatus && creator.quoteStatus !== '已获取报价' ? creator.quoteStatus : '报价未返回');
+  if (price && criteria.budgetRange?.max && price > criteria.budgetRange.max) flags.push('报价高于预算');
+  if (!creator.profileUrl) flags.push('缺主页链接');
+  if (!(creator.recentEvidence || []).length && !creator.homepageEvidence) flags.push('缺近期内容证据');
+  if ((creator.homepageEvidence?.visualRiskSignals || []).length) flags.push(`主页质感风险:${creator.homepageEvidence.visualRiskSignals.slice(0, 4).join('、')}`);
+  if ((creator.homepageEvidence?.dataRiskSignals || []).length) flags.push(`数据真实性/活跃风险:${creator.homepageEvidence.dataRiskSignals.slice(0, 4).join('、')}`);
+  if (Number(creator.visualQualityScore || creator.homepageEvidence?.visualQualityScore || 0) > 0 &&
+    Number(creator.visualQualityScore || creator.homepageEvidence?.visualQualityScore || 0) < 55) flags.push('视觉质感分低于强推荐门槛');
+  if (price >= 300 && Number(creator.visualQualityScore || creator.homepageEvidence?.visualQualityScore || 0) > 0 &&
+    Number(creator.visualQualityScore || creator.homepageEvidence?.visualQualityScore || 0) < 65) flags.push('报价300元以上但封面/背景/精致度证据不足');
+  if ((criteria.referenceLinks || []).length && !(criteria.referenceBaselines || []).length) flags.push('参考账号未补详情基线');
+  return flags;
+}
+
+function requiresRecentActivityReview(criteria = {}) {
+  return (criteria.performanceRequirements || []).some(item => /近30天|更新/.test(item));
+}
+
+function requiresAverageLikesReview(criteria = {}) {
+  return (criteria.performanceRequirements || []).some(item => /点赞|CPE|CPM|CP1|阅读/.test(item));
+}
+
+function scoreRange(value, range = {}, fallback = 50) {
+  const numeric = Number(value || 0);
+  if (!numeric) return fallback;
+  const min = Number(range.min || 0);
+  const max = Number(range.max || Number.MAX_SAFE_INTEGER);
+  if (numeric >= min && numeric <= max) return 90;
+  if (numeric < min) return clamp(Math.round(65 * (numeric / Math.max(min, 1))));
+  return clamp(Math.round(80 - Math.min(45, ((numeric - max) / Math.max(max, 1)) * 60)));
+}
+
+function evidenceText(creator) {
+  return [
+    creator.displayName,
+    creator.nickname,
+    creator.platformUserId,
+    ...(creator.contentTags || []),
+    ...(creator.personaTags || []),
+    ...(creator.recentEvidence || []),
+    ...(creator.homepageEvidence?.reviewNotes || [])
+  ].filter(Boolean).join(' ');
+}
+
+function hasHit(text, term) {
+  if (!term) return false;
+  return String(text || '').toLowerCase().includes(String(term).toLowerCase());
+}
+
+function hasRiskHit(text, term) {
+  if (!hasHit(text, term)) return false;
+  const value = String(text || '');
+  if (term === '硬广' && /低硬广|非硬广|不要硬广|避免硬广|不做硬广/.test(value)) return false;
+  return true;
+}
+
+function clamp(value) {
+  return Math.max(0, Math.min(100, Number(value || 0)));
+}
+
+function labelPlatform(platform) {
+  return {
+    xiaohongshu: '小红书',
+    douyin: '抖音',
+    bilibili: 'B站'
+  }[platform] || platform || '';
+}
+
+module.exports = {
+  rankCreators,
+  scoreCreator
+};

+ 120 - 0
claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/reference-baselines.js

@@ -0,0 +1,120 @@
+const fs = require('fs');
+const path = require('path');
+
+function attachReferenceBaselines(criteria, input = {}) {
+  const baselines = loadReferenceBaselines(input);
+  const providerStatus = input.referenceProviderStatus || {};
+  if (!baselines.length) {
+    return {
+      ...criteria,
+      referenceBaselines: [],
+      referenceEvidenceStatus: buildEvidenceStatus(criteria, [], providerStatus)
+    };
+  }
+
+  return {
+    ...criteria,
+    referenceBaselines: baselines,
+    referenceBaselineSignals: [...new Set(baselines.flatMap(item => item.topSignals || []))],
+    referenceEvidenceStatus: buildEvidenceStatus(criteria, baselines, providerStatus)
+  };
+}
+
+function loadReferenceBaselines(input = {}) {
+  const fromInput = Array.isArray(input.referenceBaselines) ? input.referenceBaselines : [];
+  const baselinePath = input.referenceBaselinePath || input.referenceBaselinesPath || '';
+  const fromPath = readBaselineFile(baselinePath);
+  return dedupe([...fromInput, ...fromPath].map(normalizeBaseline).filter(Boolean));
+}
+
+function readBaselineFile(file) {
+  if (!file) return [];
+  const resolved = path.resolve(file);
+  if (!fs.existsSync(resolved)) return [];
+  try {
+    const parsed = JSON.parse(fs.readFileSync(resolved, 'utf8'));
+    if (Array.isArray(parsed)) return parsed;
+    if (Array.isArray(parsed.referenceBaselines)) return parsed.referenceBaselines;
+    if (Array.isArray(parsed.data)) return parsed.data;
+    return [];
+  } catch {
+    return [];
+  }
+}
+
+function normalizeBaseline(item) {
+  if (!item || typeof item !== 'object') return null;
+  const topSignals = normalizeSignals(item.topSignals || item.signals || item.referenceSignals || []);
+  return {
+    bucket: String(item.bucket || item.referenceBucket || '参考账号'),
+    platform: item.platform || 'xiaohongshu',
+    name: String(item.name || item.nickname || item.displayName || '').trim(),
+    redId: String(item.redId || item.red_id || '').trim(),
+    userId: String(item.userId || item.user_id || '').trim(),
+    fans: Number(item.fans || item.fansCount || item.fansNum || 0),
+    location: String(item.location || item.ipLocation || ''),
+    profileDesc: String(item.profileDesc || item.desc || ''),
+    sampleCount: Number(item.sampleCount || item.notesCount || 0),
+    typeMix: String(item.typeMix || ''),
+    collectMedian: toNullableNumber(item.collectMedian),
+    commentMedian: toNullableNumber(item.commentMedian),
+    topSignals,
+    sampleTitles: normalizeTextList(item.sampleTitles || item.titles || []),
+    source: item.source || 'reference-baseline-cache'
+  };
+}
+
+function normalizeSignals(values) {
+  return normalizeTextList(values)
+    .map(item => item.replace(/\(\d+\)\s*$/, '').trim())
+    .filter(Boolean);
+}
+
+function normalizeTextList(values) {
+  return (Array.isArray(values) ? values : [values])
+    .flatMap(item => String(item || '').split(/[、,;;]/))
+    .map(item => item.trim())
+    .filter(Boolean);
+}
+
+function buildEvidenceStatus(criteria, baselines, providerStatus = {}) {
+  const linkCount = (criteria.referenceLinks || []).length;
+  const status = baselines.length ? 'baseline_loaded' : linkCount ? 'needs_social_analysis_enrichment' : 'no_reference_links';
+  return {
+    referenceLinkCount: linkCount,
+    baselineCount: baselines.length,
+    status,
+    providerRequested: Boolean(providerStatus.requested),
+    providerStatus: providerStatus.status || 'not_requested',
+    providerHttpStatus: providerStatus.httpStatus || 0,
+    providerWarning: providerStatus.warning || '',
+    note: providerStatus.warning || (baselines.length
+      ? '已加载参考账号基线,可用于相似度与报告复核。'
+      : linkCount
+        ? '已识别参考链接,但尚未通过社媒分析服务加载账号详情。'
+        : 'Brief 未提供明确参考链接。')
+  };
+}
+
+function dedupe(items) {
+  const seen = new Set();
+  const result = [];
+  for (const item of items) {
+    const key = `${item.platform}:${item.userId || item.redId || item.name}:${item.bucket}`;
+    if (seen.has(key)) continue;
+    seen.add(key);
+    result.push(item);
+  }
+  return result;
+}
+
+function toNullableNumber(value) {
+  const number = Number(value);
+  return Number.isFinite(number) ? number : null;
+}
+
+module.exports = {
+  attachReferenceBaselines,
+  loadReferenceBaselines,
+  normalizeBaseline
+};

+ 321 - 0
claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/reference-enrichment-provider.js

@@ -0,0 +1,321 @@
+const { readSocialAnalysisToken, readTihaoToken } = require('../../core/credentials');
+const { buildSocialRechargeInfo } = require('../../core/payment-links');
+const { normalizeBaseline } = require('./reference-baselines');
+
+async function enrichReferenceBaselines(criteria, input = {}) {
+  const baseUrl = input.referenceEnrichmentBaseUrl || input.socialAnalysisBaseUrl || input.tikhubBaseUrl || process.env.SOCIAL_ANALYSIS_BASE_URL || process.env.TIKHUB_BASE_URL || '';
+  const vocSocialBaseUrl = input.vocSocialBaseUrl || process.env.VOC_SOCIAL_BASE_URL || 'https://server.fmode.cn/api/voc-social';
+  const token = readSocialAnalysisToken(input);
+  const useVocSocial = Boolean(input.enableVocSocialReferenceEnrichment || input.useVocSocialReferenceEnrichment || process.env.TIHAO_USE_VOC_SOCIAL_REFERENCE === 'true' || (!baseUrl && token && (criteria.referenceLinks || []).length));
+  const shouldRun = Boolean(baseUrl) || Boolean(useVocSocial) || Boolean(token && input.enableReferenceEnrichment);
+  if (!shouldRun) {
+    return { baselines: [], warnings: [], status: providerStatus('not_requested') };
+  }
+
+  if (useVocSocial) {
+    return enrichReferenceResourcesFromVocSocial(criteria, input, { token, baseUrl: vocSocialBaseUrl });
+  }
+
+  if (!baseUrl) {
+    const warning = '社媒分析服务参考账号补证未配置接口地址,已保留参考链接并继续生成候选名单。';
+    return {
+      baselines: [],
+      warnings: [warning],
+      status: providerStatus('missing_base_url', warning)
+    };
+  }
+
+  const url = new URL(`${baseUrl.replace(/\/+$/, '')}/${String(input.referenceEnrichmentPath || 'reference-baselines').replace(/^\/+/, '')}`);
+  const payload = {
+    contractVersion: 'tihao-provider-v1',
+    referenceLinks: criteria.referenceLinks || [],
+    referenceAccounts: criteria.referenceAccounts || [],
+    referenceStyleAnchors: criteria.referenceStyleAnchors || [],
+    criteria: {
+      brand: criteria.brand,
+      category: criteria.category,
+      platforms: criteria.platforms,
+      keywords: criteria.keywords,
+      referenceSignals: criteria.referenceSignals
+    },
+    maxPostsPerAccount: Number(input.referencePostsLimit || 20)
+  };
+
+  let response;
+  try {
+    response = await fetch(url, {
+      method: 'POST',
+      headers: {
+        Accept: 'application/json',
+        'Content-Type': 'application/json',
+        ...(token ? { Authorization: `Bearer ${token}` } : {})
+      },
+      body: JSON.stringify(payload)
+    });
+  } catch {
+    const warning = '社媒分析服务参考账号补证暂时无法连接,已保留参考链接并继续生成候选名单。';
+    return {
+      baselines: [],
+      warnings: [warning],
+      status: providerStatus('network_error', warning)
+    };
+  }
+
+  if (response.status === 401 || response.status === 403) {
+    const recharge = await buildSocialRechargeInfo({ sessionToken: (readTihaoToken(input) || token) });
+    const warning = `社媒分析服务暂时不可用(未开通或余额不足),已保留参考链接继续生成候选名单。可在此开通/充值后重试:${recharge.paymentUrl}`;
+    return {
+      baselines: [],
+      warnings: [warning],
+      status: providerStatus('unauthorized_or_quota', warning, response.status, recharge)
+    };
+  }
+  if (!response.ok) {
+    const warning = `社媒分析服务参考账号补证返回 http ${response.status},已保留参考链接并继续生成候选名单。`;
+    return {
+      baselines: [],
+      warnings: [warning],
+      status: providerStatus('http_error', warning, response.status)
+    };
+  }
+
+  try {
+    const json = await response.json();
+    const baselines = extractBaselines(json).map(normalizeBaseline).filter(Boolean);
+    const referenceResources = extractReferenceResources(json).map(normalizeReferenceResource).filter(Boolean);
+    const warning = '社媒分析服务参考账号补证未返回可用账号基线,已保留参考链接继续提号。';
+    return {
+      baselines,
+      referenceResources,
+      warnings: baselines.length || referenceResources.length ? [] : [warning],
+      status: providerStatus(baselines.length || referenceResources.length ? 'ok' : 'empty_result', baselines.length || referenceResources.length ? '' : warning)
+    };
+  } catch {
+    const warning = '社媒分析服务参考账号补证结果无法解析,已保留参考链接并继续生成候选名单。';
+    return {
+      baselines: [],
+      warnings: [warning],
+      status: providerStatus('parse_error', warning)
+    };
+  }
+}
+
+async function enrichReferenceResourcesFromVocSocial(criteria, input = {}, provider = {}) {
+  const token = provider.token;
+  if (!token) {
+    const recharge = await buildSocialRechargeInfo({ sessionToken: (readTihaoToken(input)) });
+    const warning = `尚未检测到社媒分析服务的开通信息,已保留参考链接继续生成候选名单。可在此开通/充值:${recharge.paymentUrl}`;
+    return { baselines: [], referenceResources: [], warnings: [warning], status: providerStatus('missing_token', warning, 0, recharge) };
+  }
+  const resources = [];
+  const warnings = [];
+  for (const link of (criteria.referenceLinks || []).filter(item => item.contentType === 'video' || /video|视频/i.test(`${item.sourceText || ''} ${item.url || ''}`)).slice(0, Number(input.referenceVideoLimit || 3))) {
+    try {
+      const noteId = link.noteId || extractXhsNoteIdFromUrl(await resolveShortUrl(link.url));
+      const detail = await fetchVocSocialVideoDetail({ baseUrl: provider.baseUrl, token, noteId, shareText: link.url });
+      resources.push(...extractReferenceResources(detail)
+        .map(item => normalizeReferenceResource({
+          ...item,
+          url: item.url || link.url,
+          platform: item.platform || link.platform || 'xiaohongshu',
+          contentType: 'video'
+        }))
+        .filter(Boolean));
+    } catch (error) {
+      warnings.push(`社媒分析服务参考视频详情补证失败:${safeErrorMessage(error)}`);
+    }
+  }
+  const status = resources.length ? providerStatus('ok') : providerStatus(warnings.length ? 'empty_result' : 'not_requested', warnings[0] || '');
+  return { baselines: [], referenceResources: resources, warnings, status };
+}
+
+async function fetchVocSocialVideoDetail({ baseUrl, token, noteId, shareText }) {
+  const url = new URL(`${String(baseUrl || '').replace(/\/+$/, '')}/xiaohongshu/app_v2/get_video_note_detail`);
+  if (noteId) url.searchParams.set('note_id', noteId);
+  else if (shareText) url.searchParams.set('share_text', shareText);
+  const response = await fetchWithRetry(url, {
+    headers: {
+      Authorization: `Bearer ${token}`,
+      Accept: 'application/json'
+    }
+  });
+  const text = await response.text();
+  let json;
+  try {
+    json = JSON.parse(text);
+  } catch {
+    throw new Error(`http ${response.status} parse_error`);
+  }
+  if (!response.ok || (Number(json.code) && Number(json.code) !== 200)) {
+    throw new Error(`http ${response.status} provider_code_${json.code || 0}`);
+  }
+  return json;
+}
+
+async function fetchWithRetry(url, options) {
+  let lastError;
+  for (let attempt = 1; attempt <= 3; attempt++) {
+    try {
+      return await fetch(url, options);
+    } catch (error) {
+      lastError = error;
+      if (attempt < 3) await new Promise(resolve => setTimeout(resolve, attempt * 600));
+    }
+  }
+  throw lastError;
+}
+
+async function resolveShortUrl(url) {
+  if (!url || /xiaohongshu\.com\/(?:discovery\/item|explore)\//i.test(url)) return url || '';
+  const response = await fetch(url, { redirect: 'manual', headers: { 'user-agent': 'Mozilla/5.0' } });
+  const location = response.headers.get('location');
+  return location ? new URL(location, url).toString() : url;
+}
+
+function providerStatus(status, warning = '', httpStatus = 0, recharge = null) {
+  return {
+    provider: 'reference_enrichment',
+    requested: status !== 'not_requested',
+    status,
+    httpStatus,
+    warning,
+    ...(recharge ? { recharge } : {})
+  };
+}
+
+function extractBaselines(json) {
+  if (Array.isArray(json)) return json;
+  if (Array.isArray(json.referenceBaselines)) return json.referenceBaselines;
+  if (Array.isArray(json.baselines)) return json.baselines;
+  if (json.data) return extractBaselines(json.data);
+  return [];
+}
+
+function extractReferenceResources(json) {
+  if (!json || typeof json !== 'object') return [];
+  if (Array.isArray(json.referenceResources)) return json.referenceResources;
+  if (Array.isArray(json.referenceDetails)) return json.referenceDetails;
+  if (Array.isArray(json.noteDetails)) return json.noteDetails;
+  if (Array.isArray(json.notes)) return json.notes;
+  if (Array.isArray(json.items)) return json.items;
+  if (Array.isArray(json.data)) return json.data;
+  if (json.data) return extractReferenceResources(json.data);
+  return [];
+}
+
+function normalizeReferenceResource(item) {
+  if (!item || typeof item !== 'object') return null;
+  const note = item.note || item.noteInfo || item.note_info || item.aweme_detail || item.aweme || item.item || item;
+  const videoUrl = firstText(
+    note.videoUrl,
+    note.video_url,
+    note.video?.url,
+    note.video?.url_list,
+    note.video?.media?.stream?.h264?.master_url,
+    note.video?.media?.stream?.h265?.master_url,
+    note.video_info_v2?.media?.stream?.h264?.[0]?.master_url,
+    note.video_info_v2?.media?.stream?.h265?.[0]?.master_url,
+    note.video_info_v2?.media?.stream?.h264,
+    note.video_info_v2?.media?.stream?.h265,
+    note.video?.url,
+    note.video?.play_addr?.url_list,
+    note.video?.download_addr?.url_list,
+    note.video?.media?.stream?.h264?.[0]?.master_url,
+    note.video?.media?.stream?.h265?.[0]?.master_url,
+    note.video?.consumer?.origin_video_key,
+    note.video?.origin_video_key
+  );
+  const coverUrl = firstText(
+    note.coverUrl,
+    note.cover_url,
+    note.cover,
+    note.cover?.url_list,
+    note.image_list?.[0]?.url,
+    note.image_list?.[0]?.url_default,
+    note.images_list?.[0]?.url,
+    note.image,
+    note.images,
+    note.imageList,
+    note.image_list,
+    note.video_info_v2?.image?.first_frame,
+    note.video_info_v2?.image?.thumbnail,
+    note.video_info_v2?.image?.thumbnail_dim,
+    note.video?.cover?.url_list,
+    note.video?.origin_cover?.url_list,
+    note.video?.dynamic_cover?.url_list
+  );
+  const title = firstText(note.title, note.display_title, note.desc, note.description, note.share_info?.title);
+  const bodySummary = firstText(note.desc, note.description, note.content, note.text, note.share_info?.desc);
+  const author = note.author || note.user || note.user_info || note.authorInfo || {};
+  const sourceUrl = firstText(item.url, item.link, note.url, note.share_url, note.shareInfo?.link, note.share_info?.link);
+  const contentType = videoUrl ? 'video' : coverUrl ? 'image_text' : String(item.contentType || item.type || note.type || '');
+  return {
+    referenceId: String(item.referenceId || item.id || note.id || note.note_id || note.aweme_id || sourceUrl || title || ''),
+    url: sourceUrl,
+    platform: String(item.platform || note.platform || inferPlatform(sourceUrl) || 'xiaohongshu'),
+    contentType,
+    accountName: firstText(item.accountName, item.authorName, author.nickname, author.nick_name, author.name),
+    title,
+    bodySummary,
+    authorInfo: firstText(author.desc, author.description, author.signature, author.nickname, author.name),
+    coverOrVideoResource: videoUrl || coverUrl,
+    videoUrl,
+    coverUrl,
+    asrText: firstText(item.asrText, item.transcript, note.asrText, note.transcript),
+    transcriptSnippets: textList(item.transcriptSnippets || note.transcriptSnippets || note.subtitle || note.subtitles || note.video_info_v2?.media?.video?.subtitles),
+    frameUrls: textList(item.frameUrls || item.frames || note.frameUrls || note.frames || note.video_info_v2?.image?.first_frame || note.video_info_v2?.image?.thumbnail),
+    sourceLevel: 'social_analysis_reference_detail'
+  };
+}
+
+function firstText(...values) {
+  for (const value of values.flat(Infinity)) {
+    if (value == null) continue;
+    if (typeof value === 'object') {
+      const nested = firstText(value.url, value.url_default, value.master_url, value.src, value.link);
+      if (nested) return nested;
+      continue;
+    }
+    const text = String(value).trim();
+    if (text) return text;
+  }
+  return '';
+}
+
+function textList(values) {
+  return (Array.isArray(values) ? values : [values])
+    .flatMap(item => {
+      if (item && typeof item === 'object' && !Array.isArray(item)) {
+        return Object.values(item).flatMap(value => textList(value));
+      }
+      if (typeof item === 'object' && item) return [firstText(item.url, item.src, item.path, item.observation)];
+      return String(item || '').split(/[、,,;;|]/);
+    })
+    .map(item => item.trim())
+    .filter(Boolean);
+}
+
+function inferPlatform(url) {
+  const value = String(url || '');
+  if (/xhslink|xiaohongshu|小红书/i.test(value)) return 'xiaohongshu';
+  if (/douyin|iesdouyin|抖音/i.test(value)) return 'douyin';
+  if (/bilibili|b23\.tv|B站/i.test(value)) return 'bilibili';
+  return '';
+}
+
+function extractXhsNoteIdFromUrl(url) {
+  const match = String(url || '').match(/\/(?:discovery\/item|explore)\/([0-9a-f]{16,32})/i);
+  return match ? match[1] : '';
+}
+
+function safeErrorMessage(error) {
+  return String(error && error.message ? error.message : error || '')
+    .replace(/Bearer\s+[^"'\s,}]+/gi, 'Bearer [REDACTED]')
+    .replace(/\br:[A-Za-z0-9]{4,}\b/g, 'r:[REDACTED]')
+    .replace(/\bsk-[A-Za-z0-9_-]{8,}\b/g, 'sk-[REDACTED]')
+    .slice(0, 120);
+}
+
+module.exports = {
+  enrichReferenceBaselines
+};

+ 250 - 0
claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/reference-style-fingerprint.js

@@ -0,0 +1,250 @@
+const fs = require('fs');
+const path = require('path');
+
+function attachReferenceStyleFingerprints(criteria, input = {}, providerResult = {}) {
+  const provided = [
+    ...(Array.isArray(input.referenceStyleFingerprints) ? input.referenceStyleFingerprints : []),
+    ...readFingerprintFile(input.referenceStyleFingerprintPath || input.referenceStyleFingerprintsPath || ''),
+    ...(Array.isArray(providerResult.referenceStyleFingerprints) ? providerResult.referenceStyleFingerprints : [])
+  ];
+  const resources = [
+    ...(Array.isArray(input.referenceResources) ? input.referenceResources : []),
+    ...(Array.isArray(criteria.referenceResources) ? criteria.referenceResources : []),
+    ...(Array.isArray(providerResult.referenceResources) ? providerResult.referenceResources : [])
+  ];
+  const links = criteria.referenceLinks || [];
+  const anchors = criteria.referenceStyleAnchors || [];
+  const baselines = criteria.referenceBaselines || [];
+  const fingerprints = dedupe([
+    ...provided.map(item => normalizeFingerprint(item, criteria)),
+    ...resources.map(item => normalizeFingerprint({ ...item, sourceLevel: item.sourceLevel || 'reference_resource' }, criteria)),
+    ...links.map((link, index) => buildFallbackFingerprint(link, anchors[index], criteria, index)),
+    ...anchors
+      .filter(anchor => !(anchor.links || []).length)
+      .map((anchor, index) => buildFallbackFingerprint({ url: '', platform: anchor.platform || '' }, anchor, criteria, links.length + index))
+  ].filter(Boolean));
+
+  return {
+    ...criteria,
+    referenceStyleFingerprints: fingerprints,
+    referenceResources: dedupeResources(resources),
+    referenceFingerprintStatus: buildFingerprintStatus(criteria, fingerprints, providerResult.providerStatus || providerResult.status || {})
+  };
+}
+
+function readFingerprintFile(file) {
+  if (!file) return [];
+  const resolved = path.resolve(file);
+  if (!fs.existsSync(resolved)) return [];
+  try {
+    const json = JSON.parse(fs.readFileSync(resolved, 'utf8'));
+    if (Array.isArray(json)) return json;
+    if (Array.isArray(json.referenceStyleFingerprints)) return json.referenceStyleFingerprints;
+    if (Array.isArray(json.fingerprints)) return json.fingerprints;
+    if (json.data) return readNestedFingerprints(json.data);
+  } catch {
+    return [];
+  }
+  return [];
+}
+
+function readNestedFingerprints(value) {
+  if (Array.isArray(value)) return value;
+  if (value && Array.isArray(value.referenceStyleFingerprints)) return value.referenceStyleFingerprints;
+  if (value && Array.isArray(value.fingerprints)) return value.fingerprints;
+  return [];
+}
+
+function normalizeFingerprint(item, criteria) {
+  if (!item || typeof item !== 'object') return null;
+  const evidence = item.evidenceAvailability || item.evidence || {};
+  const contentType = normalizeContentType(item.contentType || item.type || item.mediaType || inferContentType(item.url || item.sourceText || ''));
+  const titleTextEvidence = hasConcreteText(item.title || item.bodySummary || item.desc || item.description || item.noteTitle);
+  const asrEvidence = Boolean(item.asrText || item.transcript || item.transcriptText || textList(item.asrSignals || item.transcriptSnippets).length);
+  const frameVisualEvidence = Boolean(item.coverOrVideoResource || item.coverUrl || item.videoUrl || textList(item.frameEvidence || item.frameSignals || item.frameUrls || item.frames).length);
+  const evidenceAvailability = {
+    titleText: Boolean(titleTextEvidence),
+    asr: Boolean(asrEvidence),
+    frameVisual: Boolean(frameVisualEvidence)
+  };
+  const missing = normalizeMissingEvidence(item.missingEvidence || item.missingEvidenceNotes || [], evidence, contentType);
+  const guardedMissing = normalizeMissingEvidence(missing, evidenceAvailability, contentType);
+  return {
+    referenceId: String(item.referenceId || item.id || fingerprintId(item.url || item.sourceText || item.accountName || 'reference')),
+    url: String(item.url || item.link || ''),
+    platform: String(item.platform || inferPlatform(item.url || item.sourceText || '') || ''),
+    contentType,
+    accountName: String(item.accountName || item.authorName || item.name || ''),
+    title: String(item.title || item.noteTitle || ''),
+    bodySummary: String(item.bodySummary || item.desc || item.description || ''),
+    authorInfo: String(item.authorInfo || item.author || item.accountProfile || ''),
+    coverOrVideoResource: String(item.coverOrVideoResource || item.coverUrl || item.videoUrl || ''),
+    personaType: textList(item.personaType || item.persona || item.accountType).join('、') || inferPersona(criteria),
+    contentStructure: textList(item.contentStructure || item.structure).join('、') || inferStructure(contentType, criteria),
+    tone: textList(item.tone || item.tonality || item.styleTone).join('、') || inferTone(criteria),
+    visualElements: textList(item.visualElements || item.visual || item.frameSignals),
+    sellingPointBridge: textList(item.sellingPointBridge || item.sellingPoint || item.productBridge).join('、') || inferSellingPoint(criteria),
+    commercialIntensity: String(item.commercialIntensity || item.commercialLevel || '待补证'),
+    complianceRisks: textList(item.complianceRisks || item.risks || item.riskPoints),
+    evidenceAvailability,
+    missingEvidenceNotes: guardedMissing,
+    sourceLevel: String(item.sourceLevel || item.source || 'reference_fingerprint_provider')
+  };
+}
+
+function buildFallbackFingerprint(link, anchor = {}, criteria, index = 0) {
+  const contentType = anchor?.contentType || link?.contentType || inferContentType(`${anchor?.sourceText || ''} ${link?.url || ''}`);
+  const evidenceAvailability = {
+    titleText: Boolean(anchor?.sourceText),
+    asr: false,
+    frameVisual: false
+  };
+  return {
+    referenceId: fingerprintId(link?.url || anchor?.sourceText || `reference-${index + 1}`),
+    url: link?.url || '',
+    platform: link?.platform || anchor?.platform || inferPlatform(link?.url || anchor?.sourceText || ''),
+    contentType,
+    accountName: (anchor?.accountHints || [])[0] || '',
+    title: '',
+    bodySummary: anchor?.sourceText || '',
+    authorInfo: '',
+    coverOrVideoResource: '',
+    personaType: inferPersona(criteria),
+    contentStructure: inferStructure(contentType, criteria),
+    tone: inferTone(criteria),
+    visualElements: [],
+    sellingPointBridge: inferSellingPoint(criteria),
+    commercialIntensity: '待补证',
+    complianceRisks: ['功效表达、绝对化承诺、医疗化表述需人工复核'],
+    evidenceAvailability,
+    missingEvidenceNotes: normalizeMissingEvidence([], evidenceAvailability, contentType),
+    sourceLevel: contentType === 'video' ? 'video_analysis_pending' : 'reference_link_pending'
+  };
+}
+
+function buildFingerprintStatus(criteria, fingerprints, providerStatus = {}) {
+  const videos = fingerprints.filter(item => item.contentType === 'video');
+  const unresolved = fingerprints.filter(item => (item.missingEvidenceNotes || []).length);
+  return {
+    referenceLinkCount: (criteria.referenceLinks || []).length,
+    fingerprintCount: fingerprints.length,
+    videoFingerprintCount: videos.length,
+    unresolvedCount: unresolved.length,
+    providerRequested: Boolean(providerStatus.requested),
+    providerStatus: providerStatus.status || 'not_requested',
+    providerHttpStatus: providerStatus.httpStatus || 0,
+    note: fingerprints.length
+      ? `已生成 ${fingerprints.length} 条参考风格指纹,其中视频 ${videos.length} 条;缺证项已标为待补证。`
+      : '本轮未识别到参考链接或参考账号,未生成参考风格指纹。'
+  };
+}
+
+function normalizeMissingEvidence(notes, evidence, contentType) {
+  const result = textList(notes).filter(item => {
+    if (evidence.titleText && /标题|正文/.test(item)) return false;
+    if (evidence.asr && /口播|ASR|字幕/.test(item)) return false;
+    if (evidence.frameVisual && /帧图|画面|视觉/.test(item)) return false;
+    return true;
+  });
+  if (!evidence.titleText) result.push('待补标题/正文证据');
+  if (contentType === 'video' && !evidence.asr) result.push('待补口播证据');
+  if (contentType === 'video' && !evidence.frameVisual) result.push('待补帧图证据');
+  return [...new Set(result)];
+}
+
+function inferPersona(criteria) {
+  const text = `${criteria.rawText || ''} ${(criteria.referenceSignals || []).join(' ')}`;
+  if (/营养师|育儿师|医生|专家|科普/.test(text)) return '专业科普型';
+  if (/妈妈|母婴|宝宝|育儿/.test(text)) return '真实妈妈/家庭体验型';
+  if (/素人|真实|通勤|生活/.test(text)) return '素人生活方式型';
+  return '待补证';
+}
+
+function inferStructure(contentType, criteria) {
+  const signals = criteria.referenceSignals || [];
+  if (signals.some(item => /合集|618/.test(item))) return '节点合集/清单式推荐';
+  if (signals.some(item => /科普|成分|营养/.test(item))) return '问题引入-知识解释-选品建议';
+  if (contentType === 'video') return '开场痛点-体验过程-卖点承接-行动引导';
+  return '标题钩子-正文种草-评论互动';
+}
+
+function inferTone(criteria) {
+  const signals = [...(criteria.stylePreferences || []), ...(criteria.referenceSignals || [])].join(' ');
+  if (/真实|素人|体验/.test(signals)) return '真实体验、轻种草';
+  if (/科普|成分|营养/.test(signals)) return '理性科普、解释型';
+  return '待补证';
+}
+
+function inferSellingPoint(criteria) {
+  const keywords = (criteria.keywords || []).slice(0, 4).join('、');
+  return keywords ? `围绕 ${keywords} 做场景化承接` : '待补证';
+}
+
+function inferContentType(text) {
+  const value = String(text || '');
+  if (/video|视频|douyin|bilibili|b23\.tv/i.test(value)) return 'video';
+  if (/图文|note|xhslink|xiaohongshu/i.test(value)) return 'image_text';
+  return 'unknown';
+}
+
+function normalizeContentType(value) {
+  const text = String(value || '').toLowerCase();
+  if (['video', 'image_text', 'account_home', 'unknown'].includes(text)) return text;
+  if (/视频|video|douyin|bilibili|b23\.tv/i.test(String(value || ''))) return 'video';
+  if (/图文|image|note|xhslink|xiaohongshu/i.test(String(value || ''))) return 'image_text';
+  if (/主页|home|profile|account/i.test(String(value || ''))) return 'account_home';
+  return 'unknown';
+}
+
+function inferPlatform(text) {
+  const value = String(text || '');
+  if (/xhslink|xiaohongshu|小红书/i.test(value)) return 'xiaohongshu';
+  if (/douyin|抖音/i.test(value)) return 'douyin';
+  if (/bilibili|b23\.tv|B站/i.test(value)) return 'bilibili';
+  return '';
+}
+
+function fingerprintId(value) {
+  return `ref-${Buffer.from(String(value || 'reference')).toString('base64url').slice(0, 12)}`;
+}
+
+function textList(values) {
+  return (Array.isArray(values) ? values : [values])
+    .flatMap(item => String(item || '').split(/[、,,;;|]/))
+    .map(item => item.trim())
+    .filter(Boolean);
+}
+
+function hasConcreteText(value) {
+  const text = String(value || '').trim();
+  if (!text) return false;
+  return !/(待补|未提供|未知|暂无|N\/A|null|none)/i.test(text);
+}
+
+function dedupe(items) {
+  const seen = new Set();
+  const result = [];
+  for (const item of items) {
+    const key = item.url || `${item.referenceId}:${item.accountName}`;
+    if (seen.has(key)) continue;
+    seen.add(key);
+    result.push(item);
+  }
+  return result;
+}
+
+function dedupeResources(items) {
+  const seen = new Set();
+  const result = [];
+  for (const item of items.filter(Boolean)) {
+    const key = item.url || item.videoUrl || item.coverUrl || item.referenceId || JSON.stringify(item).slice(0, 80);
+    if (seen.has(key)) continue;
+    seen.add(key);
+    result.push(item);
+  }
+  return result;
+}
+
+module.exports = {
+  attachReferenceStyleFingerprints
+};

+ 673 - 0
claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/report.js

@@ -0,0 +1,673 @@
+const path = require('path');
+const { writeJson, writeText } = require('../../core/files');
+
+const SOFTWARE_TABLE_HEADER = [
+  'brief编号',
+  '策略',
+  '序号',
+  '目标城市',
+  '预算规则',
+  '命中价格类型',
+  '命中价格',
+  '平台',
+  '账号名称',
+  '账号ID',
+  '主页链接',
+  '粉丝数',
+  '图文报价',
+  '视频报价',
+  '最低报价',
+  '报价状态',
+  '地区/定位',
+  '内容标签',
+  '人设标签',
+  '推荐等级',
+  '综合分',
+  'brief匹配分',
+  '参考风格分',
+  '主页证据分',
+  '视觉质感分',
+  '调性一致分',
+  '推荐理由',
+  '风险提示',
+  '蒲公英判断',
+  '图文CPM',
+  '视频CPM',
+  '图文CPE',
+  '视频CPE',
+  '商单数',
+  '近30天商单数',
+  '商单阅读中位数',
+  '商单互动中位数',
+  '近30天商单曝光中位数',
+  '粉丝30日增长率',
+  '48h邀约回复率',
+  '是否低活跃',
+  '数据来源',
+  '人工复核标签',
+  '人工复核项'
+];
+
+function buildSourcingReport({ criteria, candidates, collectionMode, outputDir, warnings = [] }) {
+  const jsonPath = path.join(outputDir, 'tihao-sourcing-result.json');
+  const mdPath = path.join(outputDir, 'tihao-sourcing-report.md');
+  const csvPath = path.join(outputDir, 'tihao-sourcing-client-list.csv');
+
+  const dedupedCandidates = rerank(dedupeCandidates(candidates, criteria));
+  const strong = dedupedCandidates.filter(item => item.recommendStatus === '强推荐');
+  const backup = dedupedCandidates.filter(item => item.recommendStatus === '备选');
+  const review = dedupedCandidates.filter(item => item.recommendStatus === '需复核');
+  const excluded = dedupedCandidates.filter(item => item.recommendStatus === '已剔除');
+  const reviewPoolTarget = getReviewPoolTarget(criteria);
+  const clientList = rerank(dedupedCandidates
+    .filter(item => item.recommendStatus !== '已剔除')
+    .slice(0, reviewPoolTarget));
+  const coverage = buildCoverage(criteria, dedupedCandidates);
+  const mergedWarnings = [...warnings];
+  const duplicateCount = Math.max(0, candidates.length - dedupedCandidates.length);
+  if (duplicateCount) mergedWarnings.push(`已按 brief + 平台 + 主页链接/博主名称去重 ${duplicateCount} 条,软件端表格不再保留重复账号。`);
+  if (criteria.targetCount && clientList.length < reviewPoolTarget) {
+    mergedWarnings.push(`当前可复核候选 ${clientList.length} 位,低于 brief 目标 ${criteria.targetCount} 位的 1.5 倍候选池 ${reviewPoolTarget} 位;建议继续补量或放宽召回条件。`);
+  }
+  for (const item of coverage) {
+    if (item.target > 0 && item.actual < item.target) {
+      mergedWarnings.push(`${labelPlatform(item.platform)} 当前 ${item.actual}/${item.target},需要继续补量或人工资源库补充。`);
+    }
+  }
+
+  const calibrationQuestions = buildCalibrationQuestions({ criteria, clientList, strong, review, excluded, coverage, collectionMode });
+  const reportNextActions = buildReportNextActions({ criteria, calibrationQuestions, collectionMode, clientList, coverage });
+
+  const lines = [
+    `# 提号博主推荐名单 - ${criteria.brand || '未命名Brief'}`,
+    '',
+    '## Brief 解析摘要',
+    '',
+    `- 模式:${collectionMode}`,
+    `- 类目:${criteria.category || '未指定'}`,
+    `- 平台:${(criteria.platforms || []).map(labelPlatform).join('、') || '未指定'}`,
+    `- 关键词:${(criteria.keywords || []).join('、') || '待补充'}`,
+    `- 预算:${criteria.budgetRange?.min || 0}-${criteria.budgetRange?.max || 0}`,
+    `- 粉丝:${criteria.fanRange?.min || 0}-${criteria.fanRange?.max || 0}`,
+    `- 数据要求:${(criteria.performanceRequirements || []).join(';') || '未指定'}`,
+    `- 目标人数:${criteria.targetCount || '未指定'};当前可复核候选:${clientList.length}`,
+    `- 首轮候选:${dedupedCandidates.length} 位;强推荐 ${strong.length},备选 ${backup.length},需复核 ${review.length},已剔除 ${excluded.length}`,
+    `- 去重:原始 ${candidates.length} 条,去重后 ${dedupedCandidates.length} 条,重复 ${duplicateCount} 条`,
+    '',
+    '## 需求三分层与隐性规则',
+    '',
+    ...formatRequirementLayers(criteria),
+    '',
+    '## 参考账号可参考性判断',
+    '',
+    ...formatReferenceUsability(criteria),
+    '',
+    '## 参考视频风格指纹',
+    '',
+    ...formatReferenceFingerprints(criteria),
+    '',
+    '## 参考账号/风格锚点',
+    '',
+    ...formatReferenceAnchors(criteria),
+    '',
+    '## 候选召回记录',
+    '',
+    ...formatRecallRecords(criteria, dedupedCandidates),
+    '',
+    '## 主页最近内容证据',
+    '',
+    ...formatHomepageEvidence(criteria, clientList),
+    '',
+    '## 多模态证据卡',
+    '',
+    ...formatEvidenceCards(criteria, clientList),
+    '',
+    ...formatFeedbackMemory(criteria),
+    '## 商务可用名单',
+    '',
+    '以下名单按软件端固定表头输出,已完成重复账号去重和连续排名。',
+    '',
+    '## 软件端交付表',
+    '',
+    markdownTable(softwareRows(criteria, clientList)),
+    '',
+    ...formatExcludedItems(excluded),
+    '## 复核建议',
+    '',
+    '- 强推荐账号可进入商务复核和报价有效期确认。',
+    '- 备选账号适合用于补足平台、预算或风格结构。',
+    '- 需复核账号不要直接发客户,先确认主页链接、近期内容和合作状态。',
+    '- 命中客户明确排除项或关键隐性规则违规的账号不进入商务可用名单。',
+    '- 首轮输出是候选假设,不是最终投放名单;客户反馈应沉淀为下轮偏好记忆。',
+    ''
+  ];
+
+  if (calibrationQuestions.length) lines.push('## 下一轮校准问题', '', ...calibrationQuestions.map((item, index) => `${index + 1}. ${item}`), '');
+  if (mergedWarnings.length) lines.push('## 缺口与警告', '', ...[...new Set(mergedWarnings)].map(item => `- ${item}`), '');
+  lines.push('## 生成文件', '', `- Markdown:${mdPath}`, `- JSON:${jsonPath}`, `- CSV:${csvPath}`, '');
+
+  const markdown = lines.join('\n');
+  writeJson(jsonPath, {
+    criteria,
+    candidates: dedupedCandidates,
+    collectionMode,
+    warnings: mergedWarnings,
+    coverage,
+    duplicateCount,
+    calibrationQuestions,
+    nextActions: reportNextActions
+  });
+  writeText(mdPath, markdown);
+  writeText(csvPath, toCsv(softwareRows(criteria, clientList)));
+
+  return {
+    assistantMessage: markdown,
+    files: [mdPath, jsonPath, csvPath],
+    summary: {
+      brand: criteria.brand,
+      collectionMode,
+      total: dedupedCandidates.length,
+      originalTotal: candidates.length,
+      duplicateCount,
+      strong: strong.length,
+      backup: backup.length,
+      review: review.length,
+      excluded: excluded.length,
+      targetCount: criteria.targetCount || 0,
+      reviewPoolTarget,
+      clientReadyCount: clientList.length,
+      coverage,
+      calibrationQuestions
+    },
+    data: { criteria, candidates: clientList, calibrationQuestions, nextActions: reportNextActions },
+    nextActions: reportNextActions
+  };
+}
+
+function dedupeCandidates(candidates, criteria) {
+  const briefId = getBriefId(criteria);
+  const best = new Map();
+  for (const item of candidates || []) {
+    const key = dedupeKey(briefId, item);
+    const previous = best.get(key);
+    if (!previous || candidatePriority(item) > candidatePriority(previous)) best.set(key, item);
+  }
+  return [...best.values()];
+}
+
+function dedupeKey(briefId, item) {
+  const platform = normalize(item.platform);
+  const url = normalizeUrl(item.profileUrl || item.homepageUrl || item.url);
+  const name = normalize(item.platformUserId || item.displayName || item.nickname || item.name);
+  return url ? `${briefId}|${platform}|url:${url}` : `${briefId}|${platform}|name:${name}`;
+}
+
+function candidatePriority(item) {
+  const statusWeight = { '强推荐': 40, '备选': 30, '需复核': 20, '已剔除': 0 }[item.recommendStatus] || 10;
+  return statusWeight * 1000 + Number(item.score || 0) * 10 + Number(item.briefFitScore || 0);
+}
+
+function rerank(candidates) {
+  return candidates.map((item, index) => ({ ...item, rank: index + 1 }));
+}
+
+function getReviewPoolTarget(criteria) {
+  const targetCount = Number(criteria.targetCount || 0);
+  if (!targetCount) return 10;
+  return Math.ceil(targetCount * 1.5);
+}
+
+function softwareRows(criteria, rows) {
+  const briefId = getBriefId(criteria);
+  const strategy = criteria.strategy || criteria.sourcingStrategyName || criteria.collectionStrategy || 'tihao-sop';
+  return rows.map(item => {
+    const city = resolveTargetCity(criteria, item);
+    const price = resolvePriceMatch(item, criteria);
+    return {
+      brief编号: briefId,
+      策略: strategy,
+      序号: item.rank,
+      目标城市: city,
+      预算规则: resolveBudgetRule(criteria, city),
+      命中价格类型: price.type,
+      命中价格: formatNumber(price.value),
+      平台: labelPlatform(item.platform),
+      账号名称: item.displayName || item.name || '',
+      账号ID: item.platformUserId || item.userId || '',
+      主页链接: item.profileUrl || item.homepageUrl || '',
+      粉丝数: formatNumber(item.fansCount),
+      图文报价: formatNumber(item.imagePrice || item.picturePrice),
+      视频报价: formatNumber(item.videoPrice),
+      最低报价: formatNumber(item.minPrice || price.lowest),
+      报价状态: price.status,
+      '地区/定位': item.location || item.city || item.region || '',
+      内容标签: joinTags(item.contentTags),
+      人设标签: joinTags(item.personaTags),
+      推荐等级: item.recommendStatus || item.manualReviewLabel || '',
+      综合分: item.score || 0,
+      brief匹配分: item.briefFitScore || 0,
+      参考风格分: item.referenceStyleFitScore || item.referenceSimilarity || 0,
+      主页证据分: item.recentContentFitScore || 0,
+      视觉质感分: item.visualQualityScore || 0,
+      调性一致分: item.toneConsistencyScore || 0,
+      推荐理由: item.recommendReason || '',
+      风险提示: item.riskNote || '',
+      蒲公英判断: buildPugongyingJudgement(item, price),
+      图文CPM: formatNumber(item.imageCpm || item.pictureCpm),
+      视频CPM: formatNumber(item.videoCpm),
+      图文CPE: formatNumber(item.imageCpe || item.pictureCpe),
+      视频CPE: formatNumber(item.videoCpe),
+      商单数: formatNumber(item.commercialOrderCount),
+      近30天商单数: formatNumber(item.commercialOrderCount30d),
+      商单阅读中位数: formatNumber(item.commercialReadMedian),
+      商单互动中位数: formatNumber(item.commercialInteractionMedian),
+      近30天商单曝光中位数: formatNumber(item.commercialExposureMedian30d),
+      粉丝30日增长率: formatPercent(item.fanGrowthRate30d),
+      '48h邀约回复率': formatPercent(item.inviteReplyRate48h),
+      是否低活跃: formatBoolean(item.lowActivity),
+      数据来源: item.sourceProvider || item.priceSource || '',
+      人工复核标签: item.manualReviewLabel || '',
+      人工复核项: joinTags(item.manualReviewFields)
+    };
+  });
+}
+
+function resolveTargetCity(criteria, item) {
+  const locationText = [item.targetCity, item.city, item.location, item.region].filter(Boolean).join(' ');
+  const regionHits = (criteria.region || []).filter(city => locationText.includes(city));
+  if (regionHits.length) return regionHits[0];
+  const firstLocation = String(item.city || item.location || '')
+    .split(/[\/,,;;\s]+/)
+    .map(part => part.trim())
+    .find(Boolean);
+  return firstLocation || (criteria.region || []).join('、') || '';
+}
+
+function resolveBudgetRule(criteria, city) {
+  const cityRules = criteria.cityBudgetRules || {};
+  const matched = city && (cityRules[city] || cityRules[String(city).replace(/市$/, '')]);
+  if (matched) return formatBudgetRange(matched);
+  return formatBudgetRange(criteria.budgetRange);
+}
+
+function formatBudgetRange(range) {
+  const min = Number(range?.min || 0);
+  const max = Number(range?.max || 0);
+  if (!min && !max) return '';
+  if (min && max) return `${min}-${max}`;
+  return max ? `≤${max}` : `≥${min}`;
+}
+
+function resolvePriceMatch(item, criteria) {
+  const prices = [
+    { type: '图文报价', value: number(item.imagePrice || item.picturePrice) },
+    { type: '视频报价', value: number(item.videoPrice) },
+    { type: '最低报价', value: number(item.minPrice) }
+  ].filter(price => price.value > 0);
+  const lowest = prices.length ? Math.min(...prices.map(price => price.value)) : 0;
+  if (!prices.length) {
+    return {
+      type: item.quoteStatus || '未返回报价',
+      value: 0,
+      lowest: 0,
+      status: item.quoteStatus || '未返回报价'
+    };
+  }
+
+  const min = Number(criteria.budgetRange?.min || 0);
+  const max = Number(criteria.budgetRange?.max || 0);
+  const inBudget = prices.find(price => (!min || price.value >= min) && (!max || price.value <= max));
+  const picked = inBudget || prices.slice().sort((a, b) => a.value - b.value)[0];
+  return {
+    ...picked,
+    lowest,
+    status: inBudget || !max ? '已获取报价' : '已获取报价,预算外'
+  };
+}
+
+function buildPugongyingJudgement(item, price) {
+  const parts = [];
+  if (item.pugongyingJudgement) parts.push(item.pugongyingJudgement);
+  if (item.imageCpm || item.pictureCpm) parts.push(`图文CPM ${formatNumber(item.imageCpm || item.pictureCpm)}`);
+  if (item.videoCpm) parts.push(`视频CPM ${formatNumber(item.videoCpm)}`);
+  if (item.imageCpe || item.pictureCpe) parts.push(`图文CPE ${formatNumber(item.imageCpe || item.pictureCpe)}`);
+  if (item.videoCpe) parts.push(`视频CPE ${formatNumber(item.videoCpe)}`);
+  if (item.commercialOrderCount) parts.push(`商单${formatNumber(item.commercialOrderCount)}`);
+  if (item.lowActivity === true) parts.push('低活跃');
+  if (!parts.length && price.status !== '已获取报价') parts.push('报价/效率字段未返回,需商务复核');
+  return parts.join(';');
+}
+
+function joinTags(values) {
+  return (values || []).filter(Boolean).join('、');
+}
+
+function number(value) {
+  return Number(String(value ?? '').replace(/[,%¥¥元]/g, '')) || 0;
+}
+
+function formatNumber(value) {
+  const numeric = number(value);
+  return numeric ? String(numeric) : '';
+}
+
+function formatPercent(value) {
+  if (value === undefined || value === null || value === '') return '';
+  const text = String(value);
+  if (/%$/.test(text)) return text;
+  const numeric = Number(text);
+  if (!Number.isFinite(numeric)) return text;
+  if (numeric > 0 && numeric <= 1) return `${Math.round(numeric * 1000) / 10}%`;
+  return String(numeric);
+}
+
+function formatBoolean(value) {
+  if (value === true) return '是';
+  if (value === false) return '否';
+  return '';
+}
+
+function markdownTable(rows) {
+  if (!rows.length) return '- 本轮没有可进入软件端表格的候选。';
+  const header = SOFTWARE_TABLE_HEADER;
+  const lines = [
+    `| ${header.join(' | ')} |`,
+    `| ${header.map(() => '---').join(' | ')} |`
+  ];
+  for (const row of rows) {
+    lines.push(`| ${header.map(key => escapeCell(row[key])).join(' | ')} |`);
+  }
+  return lines.join('\n');
+}
+
+function formatRequirementLayers(criteria) {
+  const layers = criteria.requirementLayers || {};
+  const rules = criteria.categoryRules || [];
+  const strategy = criteria.sourcingStrategy || [];
+  const lines = ['- 硬性量化指标:'];
+  pushList(lines, layers.hardConstraints, '本轮未解析到明确硬性指标。');
+  lines.push('- 输入上下文完整性:');
+  pushList(lines, layers.contextChecklist, '本轮未记录输入上下文来源。');
+  lines.push('- 产品/人群隐性规则:');
+  pushList(lines, layers.implicitRules, '本轮未命中品类隐性规则,需人工复核是否有客户默认偏好。');
+  if (rules.length) {
+    lines.push('- 品类规则库命中:');
+    for (const rule of rules.slice(0, 6)) lines.push(`  - ${rule.category || '品类规则'}:${rule.description || rule.name || ''}`);
+  }
+  lines.push('- 风格调性证据:');
+  pushList(lines, layers.toneEvidence, '本轮未提取到明确参考调性,需更多参考账号或主页证据校准。');
+  lines.push('- 数据真实性与主页复核:');
+  pushList(lines, layers.dataReviewFocus, '本轮未生成数据真实性复核项。');
+  lines.push('- 强推荐复核重点:');
+  pushList(lines, layers.reviewFocus, '本轮未生成强推荐复核重点。');
+  if (strategy.length) {
+    lines.push('- 平台找号策略:');
+    for (const item of strategy) lines.push(`  - ${labelPlatform(item.platform)}:${item.note || item.strategy || ''}`);
+  }
+  return lines;
+}
+
+function formatReferenceUsability(criteria) {
+  const usability = criteria.referenceUsability || {};
+  const lines = [
+    `- 状态:${usability.status || 'not_evaluated'}`,
+    `- 类型锚点:${yesNo(usability.typeAnchor)};调性锚点:${yesNo(usability.toneAnchor)};风格冲突:${yesNo(usability.conflict)}`,
+    `- 判断:${usability.note || '本轮未完成参考账号可参考性判断。'}`
+  ];
+  const anchors = usability.anchors || [];
+  if (anchors.length) {
+    lines.push('| 锚点 | 类型锚点 | 调性锚点 | 内容类型 | 说明 |');
+    lines.push('| --- | --- | --- | --- | --- |');
+    for (const item of anchors.slice(0, 8)) {
+      lines.push(`| ${escapeCell(item.id)} | ${yesNo(item.typeAnchor)} | ${yesNo(item.toneAnchor)} | ${escapeCell(item.contentType || '')} | ${escapeCell(item.note || item.tone || '')} |`);
+    }
+  }
+  return lines;
+}
+
+function formatFeedbackMemory(criteria) {
+  const memory = criteria.memory || {};
+  const total = Number(memory.feedbackSummary?.total || 0);
+  const suggestions = memory.teamRuleSuggestions || [];
+  const candidates = memory.teamRuleCandidates || [];
+  if (!total && !suggestions.length && !candidates.length) return [];
+
+  const lines = [
+    '## 反馈记忆与团队规则建议',
+    '',
+    `- 累计反馈:${total}`,
+    `- 拉黑/降权账号:${(memory.blockedCreators || []).length}`,
+    `- 个人偏好:${(memory.personalPreferences || []).length}`,
+    `- 客户/品牌偏好:${(memory.clientBrandPreferences || []).length}`,
+    `- 团队规则候选:${candidates.length}`,
+    `- 达到升级阈值的团队规则建议:${suggestions.length}`,
+    ''
+  ];
+  if (suggestions.length) {
+    lines.push('| 次数 | 范围 | 标签 | 归因 | 建议规则 | 来源样本 |');
+    lines.push('| ---: | --- | --- | --- | --- | --- |');
+    for (const item of suggestions.slice(0, 6)) {
+      const sources = (item.sources || [])
+        .slice(0, 3)
+        .map(source => `${source.briefId || '未知brief'}:${source.creatorName || source.profileUrl || '未知账号'}`)
+        .join(';');
+      lines.push(`| ${item.count || 0} | ${escapeCell(item.scope || '')} | ${escapeCell(item.label || '')} | ${escapeCell(item.attribution || '')} | ${escapeCell(item.suggestedRule || '')} | ${escapeCell(sources)} |`);
+    }
+    lines.push('');
+    lines.push('- 以上是“建议升级”,需要团队确认后再固化为正式规则;本轮会作为风险提示和降级参考,不会静默替代人工判断。');
+  } else if (candidates.length) {
+    lines.push('- 已有团队规则候选,但同类反馈未达到 3 次升级阈值,暂不建议固化为团队规则。');
+  }
+  lines.push('');
+  return lines;
+}
+
+function formatHomepageEvidence(criteria, candidates) {
+  const status = criteria.homepageEvidenceStatus || {};
+  const lines = [
+    `- provider状态:${formatProviderStatus(status.status || status.providerStatus || 'not_requested')};证据数:${status.evidenceCount || 0}`,
+    `- 说明:${status.note || status.warning || '未配置真实主页最近内容 provider 时,使用候选标签和已有 recentEvidence 做轻量判断。'}`
+  ];
+  if (!candidates.length) return lines;
+  lines.push('| 排名 | 账号 | 最近内容分 | 视觉质感分 | 调性一致分 | 置信度 | 复核提示 |');
+  lines.push('| ---: | --- | ---: | ---: | ---: | --- | --- |');
+  for (const item of candidates.slice(0, 12)) {
+    const evidence = item.homepageEvidence || {};
+    lines.push(`| ${item.rank} | ${escapeCell(item.displayName)} | ${item.recentContentFitScore || 0} | ${item.visualQualityScore || 0} | ${item.toneConsistencyScore || 0} | ${evidence.confidence || 'missing'} | ${escapeCell((evidence.reviewNotes || []).slice(0, 3).join(';'))} |`);
+  }
+  return lines;
+}
+
+function formatProviderStatus(status) {
+  const value = String(status || 'not_requested');
+  const labels = {
+    ok: '已补证',
+    not_requested: '未请求',
+    fallback: '本地轻量判断',
+    unauthorized_or_quota: '鉴权或额度不可用,已回退',
+    http_error: '服务异常,已回退',
+    empty_result: '无可用证据,已回退',
+    network_error: '网络不可用,已回退'
+  };
+  return labels[value] || value.replace(/_/g, ' ');
+}
+
+function formatReferenceFingerprints(criteria) {
+  const fingerprints = criteria.referenceStyleFingerprints || [];
+  const status = criteria.referenceFingerprintStatus || {};
+  if (!fingerprints.length) return [`- ${status.note || '本轮未生成参考风格指纹;有参考视频但无真实资源时,只能标记待补证。'}`];
+  const lines = [];
+  if (status.note) lines.push(`- 指纹状态:${status.note}`);
+  lines.push('| 参考 | 类型 | 人设类型 | 内容结构 | 语气调性 | 画面元素 | 缺证 | 链接 |');
+  lines.push('| --- | --- | --- | --- | --- | --- | --- | --- |');
+  for (const item of fingerprints.slice(0, 10)) {
+    lines.push(`| ${escapeCell(item.referenceId)} | ${escapeCell(item.contentType || '')} | ${escapeCell(item.personaType || '待补证')} | ${escapeCell(item.contentStructure || '待补证')} | ${escapeCell(item.tone || '待补证')} | ${escapeCell((item.visualElements || []).join('、') || '待补证')} | ${escapeCell((item.missingEvidenceNotes || []).join('、') || '无')} | ${item.url || ''} |`);
+  }
+  return lines;
+}
+
+function formatReferenceAnchors(criteria) {
+  const anchors = criteria.referenceStyleAnchors || [];
+  const signals = criteria.referenceSignals || [];
+  const baselines = criteria.referenceBaselines || [];
+  const evidenceStatus = criteria.referenceEvidenceStatus || {};
+  if (!anchors.length && !signals.length && !baselines.length) {
+    return ['- Brief 未提供明确参考账号或链接;本轮按类目、关键词、风格偏好和排除规则提号。'];
+  }
+  const lines = [];
+  if (evidenceStatus.note) lines.push(`- 补证状态:${evidenceStatus.note}`);
+  if (signals.length) lines.push(`- 已识别参考信号:${signals.slice(0, 12).join('、')}`);
+  if (baselines.length) lines.push(`- 已加载参考账号基线:${baselines.length} 个,可用于相似度复核。`);
+  for (const anchor of anchors.slice(0, 8)) {
+    const parts = [
+      anchor.bucket || '参考账号',
+      anchor.contentType ? `内容形态:${anchor.contentType}` : '',
+      anchor.platform ? `平台:${labelPlatform(anchor.platform)}` : '',
+      anchor.accountHints?.length ? `账号线索:${anchor.accountHints.join('、')}` : '',
+      anchor.links?.length ? `链接:${anchor.links.join(' ')}` : ''
+    ].filter(Boolean);
+    lines.push(`- ${parts.join(';')}`);
+  }
+  return lines;
+}
+
+function formatRecallRecords(criteria, candidates) {
+  const records = criteria.recallRecords || [];
+  const target = Number(criteria.targetCount || 10);
+  if (!records.length) return [`- sample 模式未请求 live 接口;当前候选池 ${candidates.length} 位,目标 ${target} 位。`];
+  const totalNormalized = records.reduce((sum, item) => sum + Number(item.normalizedCount || 0), 0);
+  const lines = [
+    `- live 实际请求 ${records.length} 次,归一化候选 ${totalNormalized} 条;目标 ${target} 位。`,
+    '| 平台 | 关键词 | 页码 | API path | 状态 | HTTP | 返回数 | 归一化数 |',
+    '| --- | --- | ---: | --- | --- | ---: | ---: | ---: |'
+  ];
+  for (const item of records.slice(0, 30)) {
+    lines.push(`| ${labelPlatform(item.platform)} | ${escapeCell(item.keyword)} | ${item.page || 0} | ${escapeCell(item.path)} | ${item.status || ''} | ${item.httpStatus || 0} | ${item.returnedCount || 0} | ${item.normalizedCount || 0} |`);
+  }
+  return lines;
+}
+
+function formatEvidenceCards(criteria, candidates) {
+  const cards = criteria.evidenceCards || [];
+  const status = criteria.evidenceStatus || {};
+  if (!cards.length) return [`- ${status.note || '本轮未加载多模态证据卡。'}`, '- 证据卡用于辅助复核,不替代人工合规判断。', '- 只有拿到视频 URL、封面、字幕或帧图资源后,才能声明视频分析完成。'];
+  const lines = [`- ${status.note || `已加载 ${cards.length} 张证据卡。`}`, '- 证据卡用于辅助复核,不替代人工合规判断。'];
+  for (const item of candidates.filter(candidate => candidate.evidenceCard).slice(0, 6)) {
+    const card = item.evidenceCard;
+    const content = card.contentEvidence || {};
+    const risk = card.riskEvidence || {};
+    const signals = [...(content.textSignals || []), ...(content.asrSignals || []), ...(content.visualSignals || [])].slice(0, 6);
+    const risks = [...(risk.hits || []), ...(risk.visualRisks || []), ...(risk.needsManualReview || [])].slice(0, 4);
+    lines.push(`- ${item.displayName}:来源 ${card.sourceLevel || item.sourceLevel || 'evidence_provider'};信号 ${signals.join('、') || '待补充'};复核 ${risks.join('、') || '暂无额外提示'}`);
+  }
+  return lines;
+}
+
+function formatExcludedItems(excluded) {
+  if (!excluded.length) return [
+    '## 剔除/降级原因',
+    '',
+    '- 本轮无已剔除账号;需复核账号请按风险提示补证后再决定是否发客户。',
+    ''
+  ];
+  return [
+    '## 剔除/降级原因',
+    '',
+    '| 平台 | 账号 | 原因 | 链接 |',
+    '| --- | --- | --- | --- |',
+    ...excluded.slice(0, 12).map(item => `| ${labelPlatform(item.platform)} | ${escapeCell(item.displayName)} | ${escapeCell(item.riskNote || '命中排除规则')} | ${item.profileUrl || ''} |`),
+    '',
+    '这些项目用于内部复盘;已剔除账号不进入商务可用名单。',
+    ''
+  ];
+}
+
+function buildCoverage(criteria, candidates) {
+  const platforms = [...new Set([...(criteria.platforms || []), ...Object.keys(criteria.platformTargets || {})])];
+  return platforms.map(platform => ({
+    platform,
+    target: Number(criteria.platformTargets?.[platform] || 0),
+    actual: candidates.filter(item => item.platform === platform && item.recommendStatus !== '已剔除').length
+  }));
+}
+
+function buildCalibrationQuestions({ criteria, clientList, strong, review, excluded, coverage, collectionMode }) {
+  const questions = [];
+  const reviewPoolTarget = getReviewPoolTarget(criteria);
+  if (criteria.targetCount && clientList.length < reviewPoolTarget) questions.push(`本轮可复核候选 ${clientList.length}/${reviewPoolTarget},未达到 brief 目标 ${criteria.targetCount} 的 1.5 倍候选池,是否先交首批名单,还是继续补量后再发客户?`);
+  const gap = coverage.find(item => item.target > 0 && item.actual < item.target);
+  if (gap) questions.push(`${labelPlatform(gap.platform)} 当前 ${gap.actual}/${gap.target},下一轮优先放宽粉丝、预算还是关键词来补量?`);
+  if ((criteria.referenceLinks || []).length && !(criteria.referenceBaselines || []).length) questions.push('Brief 已给参考博主链接,是否优先用社媒分析服务补出作者主页、近作和互动中位数后再重排?');
+  if (!criteria.contextSources?.product) questions.push('本轮未单独提供产品介绍;宽类目 Brief 是否需要补产品卖点/使用场景后再细分账号类型?');
+  if (!criteria.contextSources?.chat) questions.push('本轮未提供客户聊天记录;是否有 Brief 外的隐藏偏好、禁区或参考账号需要并入?');
+  if (!criteria.contextSources?.reference && !(criteria.referenceLinks || []).length && !(criteria.referenceAccounts || []).length) questions.push('本轮没有参考账号/链接,是否补 1-3 个客户认可的对标账号来校准调性?');
+  if ((criteria.performanceRequirements || []).length) questions.push('数据要求已解析到报告中,商务复核时优先核近30天更新、平均点赞、CPE/CPM/CP1和重复评论风险。');
+  if (!(criteria.evidenceCards || []).length) questions.push('本轮未加载多模态证据卡,是否要对强推荐账号补视频抽帧、ASR 和视觉识别后再发客户?');
+  if (review.length) questions.push(`本轮有 ${review.length} 个需复核账号,商务优先核主页近期内容、报价有效期还是竞品/合规风险?`);
+  if (excluded.length) questions.push(`已剔除 ${excluded.length} 个账号,这些排除规则是否要长期写入该客户记忆?`);
+  if (collectionMode === 'sample') questions.push('sample 模式只验证 SOP 和名单结构;是否切到 live 小规模检索,用真实库存复核强推荐账号?');
+  if (!strong.length) questions.push('本轮没有强推荐账号,是否补充参考账号或放宽风格匹配门槛后重跑?');
+  return [...new Set(questions)].slice(0, 8);
+}
+
+function buildReportNextActions({ criteria, collectionMode, clientList, coverage }) {
+  const actions = ['商务复核强推荐账号的主页、报价有效期和近期内容', '根据校准问题确认下一轮放宽/补证策略', '把客户剔除/偏好反馈写入 tihao_preference_update'];
+  if ((criteria.referenceLinks || []).length && !(criteria.referenceBaselines || []).length) actions.push('用社媒分析服务补参考账号基线后重跑');
+  if (!(criteria.evidenceCards || []).length) actions.push('对强推荐账号补 evidenceCards,再复核视频/ASR/视觉证据');
+  if (coverage.some(item => item.target > 0 && item.actual < item.target) || (criteria.targetCount && clientList.length < getReviewPoolTarget(criteria))) actions.push('按平台缺口补量或放宽关键词/粉丝/预算条件,补到目标人数 1.5 倍候选池');
+  if (collectionMode === 'sample') actions.push('确认 SOP 后切换 live 小规模检索');
+  return [...new Set(actions)].slice(0, 8);
+}
+
+function toCsv(rows) {
+  return [
+    SOFTWARE_TABLE_HEADER.join(','),
+    ...rows.map(row => SOFTWARE_TABLE_HEADER.map(key => csvCell(row[key])).join(','))
+  ].join('\n');
+}
+
+function getBriefId(criteria) {
+  return criteria.briefId || criteria.projectId || criteria.brand || 'brief-未命名';
+}
+
+function pushList(lines, values, emptyText) {
+  const items = (values || []).filter(Boolean).slice(0, 10);
+  if (!items.length) lines.push(`  - ${emptyText}`);
+  else for (const item of items) lines.push(`  - ${item}`);
+}
+
+function csvCell(value) {
+  const text = String(value ?? '');
+  return /[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
+}
+
+function escapeCell(value) {
+  return String(value ?? '').replace(/\|/g, '/').replace(/\n/g, ' ');
+}
+
+function normalize(value) {
+  return String(value || '').trim().toLowerCase();
+}
+
+function normalizeUrl(value) {
+  return normalize(value).replace(/^https?:\/\//, '').replace(/[?#].*$/, '').replace(/\/$/, '');
+}
+
+function yesNo(value) {
+  return value ? '是' : '否';
+}
+
+function labelPlatform(platform) {
+  return {
+    xiaohongshu: '小红书',
+    douyin: '抖音',
+    bilibili: 'B站'
+  }[platform] || platform || '';
+}
+
+module.exports = {
+  SOFTWARE_TABLE_HEADER,
+  buildSourcingReport,
+  dedupeCandidates,
+  softwareRows
+};

+ 111 - 0
claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/sample-data.js

@@ -0,0 +1,111 @@
+const SAMPLE_CREATORS = [
+  {
+    platform: 'xiaohongshu',
+    platformUserId: 'xhs-dha-001',
+    displayName: '营养师妈妈DHA笔记',
+    profileUrl: 'https://www.xiaohongshu.com/user/profile/xhs-dha-001',
+    fansCount: 84000,
+    minPrice: 4200,
+    imagePrice: 4200,
+    videoPrice: 6800,
+    city: '广州',
+    contentTags: ['DHA', '儿童营养', '母婴', '干货科普'],
+    personaTags: ['营养师妈妈', '专业科普', '记录生活'],
+    sourceProvider: 'sample',
+    recentEvidence: ['近 10 篇中 6 篇围绕儿童营养和补剂选择', '评论区问题集中在年龄段、剂量和 618 囤货']
+  },
+  {
+    platform: 'xiaohongshu',
+    platformUserId: 'xhs-dha-002',
+    displayName: '育婴师小满的成长记录',
+    profileUrl: 'https://www.xiaohongshu.com/user/profile/xhs-dha-002',
+    fansCount: 56000,
+    minPrice: 3000,
+    imagePrice: 3000,
+    videoPrice: 5200,
+    city: '山东',
+    contentTags: ['育婴师', '母婴', '萌娃', '记录生活'],
+    personaTags: ['育婴师', '妈妈博主', '真实日常'],
+    sourceProvider: 'sample',
+    recentEvidence: ['账号人设符合育婴师/妈妈优先要求', '适合做 DHA 选择经验和宝宝成长记录场景']
+  },
+  {
+    platform: 'xiaohongshu',
+    platformUserId: 'xhs-dha-003',
+    displayName: '618母婴好物清单',
+    profileUrl: 'https://www.xiaohongshu.com/user/profile/xhs-dha-003',
+    fansCount: 118000,
+    minPrice: 5000,
+    imagePrice: 5000,
+    videoPrice: 9000,
+    city: '河南',
+    contentTags: ['618合集', '母婴好物', '测评', '清单'],
+    personaTags: ['清单型', '妈妈博主', '性价比'],
+    sourceProvider: 'sample',
+    recentEvidence: ['近期多篇 618 清单内容,适合促销节点', '预算接近上限,需确认报价有效期']
+  },
+  {
+    platform: 'xiaohongshu',
+    platformUserId: 'xhs-skincare-001',
+    displayName: '敏感肌成分研究所',
+    profileUrl: 'https://www.xiaohongshu.com/user/profile/xhs-skincare-001',
+    fansCount: 92000,
+    minPrice: 4500,
+    imagePrice: 4500,
+    videoPrice: 7600,
+    city: '上海',
+    contentTags: ['敏感肌', '屏障修护', '成分党', '护肤测评'],
+    personaTags: ['专业测评', '真实体验', '低硬广'],
+    sourceProvider: 'sample',
+    recentEvidence: ['主页近期围绕敏感肌屏障和精简护肤', '封面统一,适合护肤品牌种草']
+  },
+  {
+    platform: 'douyin',
+    platformUserId: 'dy-life-001',
+    displayName: '精致生活小鹿',
+    profileUrl: 'https://www.douyin.com/user/dy-life-001',
+    fansCount: 150000,
+    minPrice: 6500,
+    imagePrice: 0,
+    videoPrice: 6500,
+    city: '杭州',
+    contentTags: ['生活方式', '精致日常', '好物分享'],
+    personaTags: ['女性场景', '高质感', '轻种草'],
+    sourceProvider: 'sample',
+    recentEvidence: ['画面质感较好,适合高客单生活方式产品', '报价略高,需确认预算']
+  },
+  {
+    platform: 'xiaohongshu',
+    platformUserId: 'xhs-home-001',
+    displayName: '真实家居清洁实验室',
+    profileUrl: 'https://www.xiaohongshu.com/user/profile/xhs-home-001',
+    fansCount: 64000,
+    minPrice: 2800,
+    imagePrice: 2800,
+    videoPrice: 4600,
+    city: '成都',
+    contentTags: ['家清', '清洁测评', '家居收纳', '真实场景'],
+    personaTags: ['真实家庭', '测评型', '实用主义'],
+    sourceProvider: 'sample',
+    recentEvidence: ['近期内容集中在家居清洁和真实对比测评', '适合家清家居类 Brief']
+  },
+  {
+    platform: 'xiaohongshu',
+    platformUserId: 'xhs-food-001',
+    displayName: '办公室健康零食测评',
+    profileUrl: 'https://www.xiaohongshu.com/user/profile/xhs-food-001',
+    fansCount: 73000,
+    minPrice: 3200,
+    imagePrice: 3200,
+    videoPrice: 5600,
+    city: '深圳',
+    contentTags: ['健康零食', '饮品测评', '办公室场景', '轻负担'],
+    personaTags: ['白领', '测评型', '生活方式'],
+    sourceProvider: 'sample',
+    recentEvidence: ['近期内容适合食品饮料和健康零食', '场景集中在办公室和日常加餐']
+  }
+];
+
+module.exports = {
+  SAMPLE_CREATORS
+};

+ 185 - 0
claude-code-tihao-sourcing/mcp/src/features/tihao-sourcing/sourcing-workflow.js

@@ -0,0 +1,185 @@
+const path = require('path');
+const { okResult } = require('../../core/result-envelope');
+const { parseBrief } = require('./brief-parser');
+const { SAMPLE_CREATORS } = require('./sample-data');
+const { searchLiveCreators } = require('./live-provider');
+const { applyMemory } = require('./preference-memory');
+const { attachReferenceBaselines } = require('./reference-baselines');
+const { enrichReferenceBaselines } = require('./reference-enrichment-provider');
+const { attachReferenceStyleFingerprints } = require('./reference-style-fingerprint');
+const { attachEvidenceCards, loadMultimodalEvidence } = require('./multimodal-evidence');
+const { enrichCriteriaWithExperience } = require('./experience-rules');
+const { enrichHomepageEvidence } = require('./homepage-evidence-provider');
+const { rankCreators } = require('./ranker');
+const { buildSourcingReport } = require('./report');
+
+async function runTihaoSourcing(input = {}) {
+  const collectionMode = resolveCollectionMode(input);
+  const effectiveInput = collectionMode === 'live' ? withLiveSmallRunDefaults(input) : input;
+  const outputDir = path.resolve(input.output || input.outputDir || `outputs/tihao-sourcing-${Date.now()}`);
+  let criteria = parseBrief(input);
+  criteria = applyMemory(criteria, effectiveInput.memory || effectiveInput.memoryPath);
+  const enrichment = await enrichReferenceBaselines(criteria, effectiveInput);
+  criteria = attachReferenceBaselines(criteria, {
+    ...effectiveInput,
+    referenceBaselines: [
+      ...(Array.isArray(effectiveInput.referenceBaselines) ? effectiveInput.referenceBaselines : []),
+      ...enrichment.baselines
+    ],
+    referenceProviderStatus: enrichment.status
+  });
+  criteria = attachReferenceStyleFingerprints(criteria, effectiveInput, {
+    status: enrichment.status,
+    referenceStyleFingerprints: enrichment.referenceStyleFingerprints || [],
+    referenceResources: enrichment.referenceResources || []
+  });
+  criteria = enrichCriteriaWithExperience(criteria);
+
+  let creators = SAMPLE_CREATORS;
+  let warnings = [...(enrichment.warnings || [])];
+  let recallRecords = [];
+  if (collectionMode === 'live') {
+    const liveResult = await searchLiveCreators(criteria, effectiveInput);
+    if (liveResult.status !== 'ok') return liveResult;
+    creators = liveResult.creators.length ? liveResult.creators : [];
+    recallRecords = liveResult.recallRecords || [];
+    warnings = [...warnings, ...(liveResult.warnings || [])];
+    if (creators.length === 0) {
+      warnings.push('live 模式没有返回候选,建议放宽关键词、检查 company,或先用 sample 模式校准 brief。');
+      if (!effectiveInput.allowSampleFallback) {
+        return okResult({
+          status: 'no_live_samples',
+          assistantMessage: [
+            '真实 live 检索已完成,但没有返回可用候选。',
+            '',
+            `检索平台:${criteria.platforms.map(labelPlatform).join('、')}`,
+            `关键词:${criteria.keywords.join('、') || '未解析到关键词'}`,
+            '',
+            '建议下一步:',
+            '- 确认 company objectId 是否需要传入;',
+            '- 换一个更宽的关键词,例如品类词而不是品牌词;',
+            '- 先用 sample 模式校准 Brief,再扩大 live 检索页数。'
+          ].join('\n'),
+          summary: {
+            brand: criteria.brand,
+            collectionMode,
+            total: 0,
+            targetCount: criteria.targetCount || 0
+          },
+          data: { criteria: { ...criteria, recallRecords } },
+          warnings,
+          errors: [],
+          nextActions: [
+            '传入 company 后重跑 live',
+            '改用更宽关键词重跑 live',
+            '设置 allowSampleFallback=true 生成演示名单'
+          ]
+        });
+      }
+      creators = SAMPLE_CREATORS.filter(item => criteria.platforms.includes(item.platform));
+    }
+  }
+
+  const homepageEvidence = await enrichHomepageEvidence(criteria, creators, effectiveInput);
+  creators = homepageEvidence.creators || creators;
+  warnings = [...warnings, ...(homepageEvidence.warnings || [])];
+  criteria = {
+    ...criteria,
+    homepageEvidenceStatus: buildHomepageEvidenceStatus(homepageEvidence.status || {})
+  };
+
+  const preliminaryCandidates = rankCreators(creators, criteria);
+  const multimodalEvidence = await loadMultimodalEvidence(criteria, preliminaryCandidates, effectiveInput);
+  warnings = [...warnings, ...(multimodalEvidence.warnings || [])];
+  criteria = {
+    ...criteria,
+    evidenceCards: multimodalEvidence.evidenceCards || [],
+    evidenceStatus: buildEvidenceStatus(multimodalEvidence.evidenceCards || [], multimodalEvidence.providerStatus),
+    recallRecords,
+    referenceResources: [
+      ...(criteria.referenceResources || []),
+      ...(multimodalEvidence.referenceResources || [])
+    ]
+  };
+  criteria = attachReferenceStyleFingerprints(criteria, effectiveInput, {
+    providerStatus: multimodalEvidence.providerStatus,
+    referenceStyleFingerprints: multimodalEvidence.referenceStyleFingerprints || [],
+    referenceResources: multimodalEvidence.referenceResources || []
+  });
+  criteria = enrichCriteriaWithExperience(criteria);
+
+  const candidates = attachEvidenceCards(preliminaryCandidates, criteria.evidenceCards);
+  const report = buildSourcingReport({
+    criteria,
+    candidates,
+    collectionMode,
+    outputDir,
+    warnings
+  });
+
+  return okResult({
+    assistantMessage: report.assistantMessage,
+    summary: report.summary,
+    data: report.data,
+    files: report.files,
+    warnings,
+    nextActions: report.nextActions || [
+      '商务复核强推荐账号的主页、报价有效期和近期内容',
+      '把客户剔除/偏好反馈写入 tihao_preference_update',
+      collectionMode === 'sample' ? '确认 SOP 后切换 live 小规模检索' : '确认额度后逐步扩大 keywordLimit/pagesPerKeyword'
+    ]
+  });
+}
+
+function resolveCollectionMode(input = {}) {
+  const explicitMode = input.collectionMode || input.collection_mode;
+  return explicitMode === 'sample' ? 'sample' : 'live';
+}
+
+function withLiveSmallRunDefaults(input = {}) {
+  return {
+    ...input,
+    keywordLimit: input.keywordLimit || input.keyword_limit || 1,
+    pagesPerKeyword: input.pagesPerKeyword || input.pages_per_keyword || 1
+  };
+}
+
+function buildEvidenceStatus(evidenceCards, providerStatus = {}) {
+  return {
+    cardCount: evidenceCards.length,
+    status: evidenceCards.length ? 'evidence_cards_loaded' : 'no_multimodal_evidence',
+    providerRequested: Boolean(providerStatus.requested),
+    providerStatus: providerStatus.status || 'not_requested',
+    providerHttpStatus: providerStatus.httpStatus || 0,
+    providerWarning: providerStatus.warning || '',
+    note: providerStatus.warning || (evidenceCards.length
+      ? '已加载多模态证据卡,可用于帧图/ASR/视觉复核。'
+      : '本轮未加载多模态证据卡。')
+  };
+}
+
+function buildHomepageEvidenceStatus(status = {}) {
+  return {
+    requested: Boolean(status.requested),
+    status: status.status || 'not_requested',
+    providerStatus: status.status || 'not_requested',
+    source: status.source || '',
+    httpStatus: status.httpStatus || 0,
+    evidenceCount: status.evidenceCount || 0,
+    note: status.note || status.warning || ''
+  };
+}
+
+function labelPlatform(platform) {
+  return {
+    xiaohongshu: '小红书',
+    douyin: '抖音',
+    bilibili: 'B站'
+  }[platform] || platform;
+}
+
+module.exports = {
+  runTihaoSourcing,
+  resolveCollectionMode,
+  withLiveSmallRunDefaults
+};

+ 298 - 0
claude-code-tihao-sourcing/mcp/src/server.js

@@ -0,0 +1,298 @@
+#!/usr/bin/env node
+const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
+const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
+const { z } = require('zod');
+const { runTihaoSourcing } = require('./tools/tihao-brief-sourcing-run');
+const { updatePreferenceMemory } = require('./tools/tihao-preference-update');
+const { analyzeFmodeImage } = require('./tools/fmode-image-analysis');
+const { readTihaoToken } = require('./core/credentials');
+const { buildTihaoRechargeInfo, buildMissingTokenMessage } = require('./core/payment-links');
+
+function asToolResult(result) {
+  return {
+    content: [
+      {
+        type: 'text',
+        text: result.assistantMessage || JSON.stringify(result, null, 2)
+      }
+    ],
+    structuredContent: result,
+    isError: !['ok', 'needs_token', 'needs_recharge', 'needs_valid_token'].includes(result.status)
+  };
+}
+
+function createServer() {
+  const server = new McpServer({
+    name: 'tihao',
+    version: '1.0.9'
+  });
+
+  server.registerTool(
+    'fmode_image_analysis',
+    {
+      title: 'Analyze Images With Fmode Doubao Vision',
+      description: [
+        'Analyze screenshots, product images, UI images, charts, posters, or local image files through Fmode OpenAI-compatible Doubao vision.',
+        'Use when the user asks for 图片识别, 识图, 看图, 分析截图, 读取图片文字, UI 截图分析, or image-to-brief context.',
+        'Default model is doubao-seed-2-0-pro-260215 and billing uses the configured platform user token instead of a separate model key.'
+      ].join(' '),
+      inputSchema: {
+        imagePath: z.string().optional(),
+        imagePaths: z.array(z.string()).optional(),
+        imageUrl: z.string().optional(),
+        imageUrls: z.array(z.string()).optional(),
+        image: z.union([z.string(), z.object({}).passthrough()]).optional(),
+        images: z.array(z.union([z.string(), z.object({}).passthrough()])).optional(),
+        prompt: z.string().optional(),
+        question: z.string().optional(),
+        text: z.string().optional(),
+        context: z.string().optional(),
+        outputFormat: z.enum(['text', 'json']).optional(),
+        responseFormat: z.enum(['text', 'json']).optional(),
+        detail: z.enum(['auto', 'low', 'high']).optional(),
+        temperature: z.number().optional(),
+        maxTokens: z.number().int().min(64).max(8192).optional(),
+        maxImageBytes: z.number().int().min(1024).optional(),
+        output: z.string().optional(),
+        outputPath: z.string().optional(),
+        outputDir: z.string().optional(),
+        baseUrl: z.string().optional(),
+        path: z.string().optional(),
+        fmodeBaseUrl: z.string().optional(),
+        fmodePath: z.string().optional(),
+        fmodeModel: z.string().optional(),
+        model: z.string().optional(),
+        doubaoVisionBaseUrl: z.string().optional(),
+        doubaoVisionPath: z.string().optional(),
+        doubaoVisionModel: z.string().optional(),
+        videoAnalysisBaseUrl: z.string().optional(),
+        videoAnalysisPath: z.string().optional(),
+        videoAnalysisModel: z.string().optional(),
+        fmodeToken: z.string().optional(),
+        imageAnalysisToken: z.string().optional(),
+        doubaoVisionToken: z.string().optional(),
+        videoAnalysisToken: z.string().optional(),
+        tihaoToken: z.string().optional(),
+        sessionToken: z.string().optional(),
+        vocToken: z.string().optional(),
+        allowEnvToken: z.boolean().optional()
+      },
+      outputSchema: {
+        status: z.string(),
+        assistantMessage: z.string(),
+        summary: z.object({}).passthrough().optional(),
+        data: z.object({}).passthrough().optional(),
+        files: z.array(z.string()).optional(),
+        nextActions: z.array(z.string()).optional(),
+        warnings: z.array(z.any()).optional(),
+        errors: z.array(z.any()).optional()
+      }
+    },
+    async input => asToolResult(await analyzeFmodeImage(input))
+  );
+
+  server.registerTool(
+    'tihao_brief_sourcing_run',
+    {
+      title: 'Run Tihao Brief Creator Sourcing',
+      description: [
+        'Read or accept a client brief and generate a business-ready creator/blogger recommendation list.',
+        'Use when the user asks to 上传brief, 根据brief提号, 找博主, 选号, 推荐达人名单, or export a client-ready blogger list.',
+        'Default normal brief-to-list requests to live small-run mode with keywordLimit=1 and pagesPerKeyword=1.',
+        'Use sample only when the user explicitly asks for sample/demo/no-cost flow.',
+        'Live mode uses the company voc-e-commerce proxy and hides raw auth/recharge errors.'
+      ].join(' '),
+      inputSchema: {
+        collectionMode: z.enum(['sample', 'live']).optional(),
+        brief: z.string().optional(),
+        briefPath: z.string().optional(),
+        briefText: z.string().optional(),
+        productIntro: z.string().optional(),
+        productDescription: z.string().optional(),
+        productContext: z.string().optional(),
+        product: z.union([z.string(), z.object({}).passthrough()]).optional(),
+        chatText: z.string().optional(),
+        chatRecords: z.union([z.string(), z.array(z.union([z.string(), z.object({}).passthrough()]))]).optional(),
+        customerChat: z.union([z.string(), z.array(z.union([z.string(), z.object({}).passthrough()]))]).optional(),
+        conversation: z.union([z.string(), z.array(z.union([z.string(), z.object({}).passthrough()]))]).optional(),
+        brand: z.string().optional(),
+        category: z.string().optional(),
+        platforms: z.array(z.string()).optional(),
+        keywords: z.array(z.string()).optional(),
+        keyword: z.string().optional(),
+        targetCount: z.number().int().min(1).max(300).optional(),
+        budgetRange: z.object({ min: z.number().optional(), max: z.number().optional() }).optional(),
+        fanRange: z.object({ min: z.number().optional(), max: z.number().optional() }).optional(),
+        region: z.union([z.string(), z.array(z.string())]).optional(),
+        gender: z.string().optional(),
+        preferredStyles: z.array(z.string()).optional(),
+        stylePreferences: z.array(z.string()).optional(),
+        referenceLinks: z.array(z.union([
+          z.string(),
+          z.object({}).passthrough()
+        ])).optional(),
+        referenceAccounts: z.array(z.union([
+          z.string(),
+          z.object({}).passthrough()
+        ])).optional(),
+        referenceBaselines: z.array(z.object({}).passthrough()).optional(),
+        referenceBaselinePath: z.string().optional(),
+        referenceBaselinesPath: z.string().optional(),
+        referenceEnrichmentBaseUrl: z.string().optional(),
+        referenceEnrichmentPath: z.string().optional(),
+        enableReferenceEnrichment: z.boolean().optional(),
+        referencePostsLimit: z.number().int().min(1).max(50).optional(),
+        socialAnalysisBaseUrl: z.string().optional(),
+        socialAnalysisToken: z.string().optional(),
+        tikhubBaseUrl: z.string().optional(),
+        tikhubToken: z.string().optional(),
+        tikHubToken: z.string().optional(),
+        vocSocialBaseUrl: z.string().optional(),
+        vocSocialToken: z.string().optional(),
+        enableVocSocialReferenceEnrichment: z.boolean().optional(),
+        useVocSocialReferenceEnrichment: z.boolean().optional(),
+        referenceVideoLimit: z.number().int().min(1).max(10).optional(),
+        evidenceCards: z.array(z.object({}).passthrough()).optional(),
+        evidenceCardsPath: z.string().optional(),
+        evidenceCardPath: z.string().optional(),
+        multimodalEvidenceBaseUrl: z.string().optional(),
+        multimodalEvidencePath: z.string().optional(),
+        multimodalEvidenceModel: z.string().optional(),
+        evidenceProviderBaseUrl: z.string().optional(),
+        evidenceProviderModel: z.string().optional(),
+        evidenceProviderToken: z.string().optional(),
+        multimodalEvidenceToken: z.string().optional(),
+        homepageEvidenceBaseUrl: z.string().optional(),
+        homepageProviderBaseUrl: z.string().optional(),
+        homepageEvidencePath: z.string().optional(),
+        homepageEvidenceFile: z.string().optional(),
+        homepageEvidenceJsonPath: z.string().optional(),
+        homepageEvidenceToken: z.string().optional(),
+        homepageProviderToken: z.string().optional(),
+        homepageEvidenceLimit: z.number().int().min(1).max(50).optional(),
+        recentContentsLimit: z.number().int().min(1).max(50).optional(),
+        homepageEvidenceCreatorsLimit: z.number().int().min(1).max(100).optional(),
+        doubaoVisionBaseUrl: z.string().optional(),
+        doubaoVisionPath: z.string().optional(),
+        doubaoVisionModel: z.string().optional(),
+        doubaoVisionToken: z.string().optional(),
+        videoAnalysisBaseUrl: z.string().optional(),
+        videoAnalysisPath: z.string().optional(),
+        videoAnalysisModel: z.string().optional(),
+        videoAnalysisToken: z.string().optional(),
+        evidenceCreatorsLimit: z.number().int().min(1).max(100).optional(),
+        resultFirstMode: z.boolean().optional(),
+        optimizeForResults: z.boolean().optional(),
+        excludeTags: z.array(z.string()).optional(),
+        exclusions: z.array(z.string()).optional(),
+        blockedCreators: z.array(z.string()).optional(),
+        memory: z.string().optional(),
+        memoryPath: z.string().optional(),
+        output: z.string().optional(),
+        outputDir: z.string().optional(),
+        keywordLimit: z.number().int().min(1).max(5).optional(),
+        pagesPerKeyword: z.number().int().min(1).max(5).optional(),
+        company: z.string().optional(),
+        isRefresh: z.boolean().optional(),
+        allowSampleFallback: z.boolean().optional(),
+        tihaoToken: z.string().optional(),
+        sessionToken: z.string().optional(),
+        vocToken: z.string().optional()
+      },
+      outputSchema: {
+        status: z.string(),
+        assistantMessage: z.string(),
+        summary: z.object({}).passthrough().optional(),
+        data: z.object({}).passthrough().optional(),
+        files: z.array(z.string()).optional(),
+        nextActions: z.array(z.string()).optional(),
+        warnings: z.array(z.any()).optional(),
+        errors: z.array(z.any()).optional()
+      }
+    },
+    async input => asToolResult(await runTihaoSourcing(input))
+  );
+
+  server.registerTool(
+    'tihao_token_check',
+    {
+      title: 'Check Tihao VOC E-Commerce Token',
+      description: 'Check whether a Tihao sessionToken is configured for live creator sourcing without echoing it.',
+      inputSchema: {
+        tihaoToken: z.string().optional(),
+        sessionToken: z.string().optional(),
+        vocToken: z.string().optional()
+      },
+      outputSchema: {
+        status: z.string(),
+        assistantMessage: z.string(),
+        configured: z.boolean(),
+        data: z.object({}).passthrough().optional(),
+        errors: z.array(z.any()).optional()
+      }
+    },
+    async input => {
+      const token = readTihaoToken(input);
+      const recharge = token ? null : await buildTihaoRechargeInfo();
+      return asToolResult({
+        status: token ? 'ok' : 'needs_token',
+        configured: Boolean(token),
+        assistantMessage: token
+          ? '提号 live 检索 sessionToken 已配置,可以先用小规模参数运行真实检索。'
+          : buildMissingTokenMessage(recharge.paymentUrl),
+        data: recharge ? { recharge } : {},
+        errors: []
+      });
+    }
+  );
+
+  server.registerTool(
+    'tihao_preference_update',
+    {
+      title: 'Update Tihao Creator Sourcing Preference',
+      description: 'Save client or business feedback as preference memory for future brief-to-blogger-list sourcing rounds.',
+      inputSchema: {
+        message: z.string().optional(),
+        feedback: z.string().optional(),
+        preferredStyles: z.array(z.string()).optional(),
+        blockedStyles: z.array(z.string()).optional(),
+        preferredPlatforms: z.array(z.string()).optional(),
+        preferredReferenceAccounts: z.array(z.string()).optional(),
+        blockedCreators: z.array(z.string()).optional(),
+        pricingNote: z.string().optional(),
+        memory: z.string().optional(),
+        memoryPath: z.string().optional()
+      },
+      outputSchema: {
+        status: z.string(),
+        assistantMessage: z.string(),
+        summary: z.object({}).passthrough().optional(),
+        data: z.object({}).passthrough().optional(),
+        files: z.array(z.string()).optional(),
+        nextActions: z.array(z.string()).optional(),
+        warnings: z.array(z.any()).optional(),
+        errors: z.array(z.any()).optional()
+      }
+    },
+    async input => asToolResult(updatePreferenceMemory(input))
+  );
+
+  return server;
+}
+
+async function main() {
+  const server = createServer();
+  const transport = new StdioServerTransport();
+  await server.connect(transport);
+}
+
+if (require.main === module) {
+  main().catch(error => {
+    console.error(error);
+    process.exit(1);
+  });
+}
+
+module.exports = {
+  createServer
+};

+ 37 - 0
claude-code-tihao-sourcing/mcp/src/tools/cli-args.js

@@ -0,0 +1,37 @@
+function parseArgs(argv = process.argv.slice(2)) {
+  const out = {};
+  for (let i = 0; i < argv.length; i++) {
+    const arg = argv[i];
+    if (!arg.startsWith('--')) continue;
+    const key = camel(arg.slice(2));
+    const next = argv[i + 1];
+    if (!next || next.startsWith('--')) {
+      out[key] = true;
+      continue;
+    }
+    i += 1;
+    out[key] = coerce(next, key);
+  }
+  return out;
+}
+
+function camel(value) {
+  return value.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
+}
+
+function coerce(value, key = '') {
+  if (value === 'true') return true;
+  if (value === 'false') return false;
+  if (/^\d+$/.test(value)) return Number(value);
+  if (isLongTextKey(key)) return value;
+  if (value.includes(',') || value.includes(',')) return value.split(/[,,]/).map(item => item.trim()).filter(Boolean);
+  return value;
+}
+
+function isLongTextKey(key) {
+  return ['message', 'feedback', 'briefText', 'text', 'prompt', 'question', 'context', 'pricingNote'].includes(key);
+}
+
+module.exports = {
+  parseArgs
+};

+ 26 - 0
claude-code-tihao-sourcing/mcp/src/tools/fmode-image-analysis.js

@@ -0,0 +1,26 @@
+#!/usr/bin/env node
+const { analyzeFmodeImage } = require('../features/fmode-image-analysis/image-analysis');
+const { parseArgs } = require('./cli-args');
+const { redactSecrets } = require('../core/result-envelope');
+
+async function main() {
+  const input = parseArgs();
+  const result = await analyzeFmodeImage(input);
+  if (input.assistantMessageOnly) {
+    console.log(result.assistantMessage);
+  } else {
+    console.log(`${input.resultPrefix || 'FMODE_IMAGE_ANALYSIS_RESULT'}=${JSON.stringify(result)}`);
+  }
+  process.exit(result.status === 'error' ? 1 : 0);
+}
+
+if (require.main === module) {
+  main().catch(error => {
+    console.error(redactSecrets(error && error.stack ? error.stack : String(error)));
+    process.exit(1);
+  });
+}
+
+module.exports = {
+  analyzeFmodeImage
+};

+ 26 - 0
claude-code-tihao-sourcing/mcp/src/tools/tihao-brief-sourcing-run.js

@@ -0,0 +1,26 @@
+#!/usr/bin/env node
+const { runTihaoSourcing } = require('../features/tihao-sourcing/sourcing-workflow');
+const { parseArgs } = require('./cli-args');
+const { redactSecrets } = require('../core/result-envelope');
+
+async function main() {
+  const input = parseArgs();
+  const result = await runTihaoSourcing(input);
+  if (input.assistantMessageOnly) {
+    console.log(result.assistantMessage);
+  } else {
+    console.log(`${input.resultPrefix || 'TIHAO_SOURCING_RESULT'}=${JSON.stringify(result)}`);
+  }
+  process.exit(result.status === 'error' ? 1 : 0);
+}
+
+if (require.main === module) {
+  main().catch(error => {
+    console.error(redactSecrets(error && error.stack ? error.stack : String(error)));
+    process.exit(1);
+  });
+}
+
+module.exports = {
+  runTihaoSourcing
+};

+ 24 - 0
claude-code-tihao-sourcing/mcp/src/tools/tihao-preference-update.js

@@ -0,0 +1,24 @@
+#!/usr/bin/env node
+const { updatePreferenceMemory } = require('../features/tihao-sourcing/preference-memory');
+const { parseArgs } = require('./cli-args');
+
+async function main() {
+  const input = parseArgs();
+  const result = updatePreferenceMemory(input);
+  if (input.assistantMessageOnly) {
+    console.log(result.assistantMessage);
+  } else {
+    console.log(`${input.resultPrefix || 'TIHAO_PREF_RESULT'}=${JSON.stringify(result)}`);
+  }
+}
+
+if (require.main === module) {
+  main().catch(error => {
+    console.error(error && error.stack ? error.stack : String(error));
+    process.exit(1);
+  });
+}
+
+module.exports = {
+  updatePreferenceMemory
+};

+ 19 - 0
claude-code-tihao-sourcing/memory-templates/tihao-sourcing-profile.json

@@ -0,0 +1,19 @@
+{
+  "preferredStyles": [
+    "真实体验",
+    "素人感",
+    "成分拆解"
+  ],
+  "blockedStyles": [
+    "硬广",
+    "泛美妆",
+    "医美"
+  ],
+  "preferredPlatforms": [
+    "小红书",
+    "抖音"
+  ],
+  "blockedCreators": [],
+  "pricingNotes": [],
+  "clientFeedback": []
+}

+ 1161 - 0
claude-code-tihao-sourcing/package-lock.json

@@ -0,0 +1,1161 @@
+{
+  "name": "@vocmarket/tihao",
+  "version": "1.0.11",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "@vocmarket/tihao",
+      "version": "1.0.11",
+      "license": "ISC",
+      "dependencies": {
+        "@modelcontextprotocol/sdk": "^1.29.0",
+        "zod": "^4.4.3"
+      },
+      "bin": {
+        "tihao": "bin/tihao.js",
+        "tihao-sourcing": "bin/tihao.js"
+      }
+    },
+    "node_modules/@hono/node-server": {
+      "version": "1.19.14",
+      "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz",
+      "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18.14.1"
+      },
+      "peerDependencies": {
+        "hono": "^4"
+      }
+    },
+    "node_modules/@modelcontextprotocol/sdk": {
+      "version": "1.29.0",
+      "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz",
+      "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@hono/node-server": "^1.19.9",
+        "ajv": "^8.17.1",
+        "ajv-formats": "^3.0.1",
+        "content-type": "^1.0.5",
+        "cors": "^2.8.5",
+        "cross-spawn": "^7.0.5",
+        "eventsource": "^3.0.2",
+        "eventsource-parser": "^3.0.0",
+        "express": "^5.2.1",
+        "express-rate-limit": "^8.2.1",
+        "hono": "^4.11.4",
+        "jose": "^6.1.3",
+        "json-schema-typed": "^8.0.2",
+        "pkce-challenge": "^5.0.0",
+        "raw-body": "^3.0.0",
+        "zod": "^3.25 || ^4.0",
+        "zod-to-json-schema": "^3.25.1"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@cfworker/json-schema": "^4.1.1",
+        "zod": "^3.25 || ^4.0"
+      },
+      "peerDependenciesMeta": {
+        "@cfworker/json-schema": {
+          "optional": true
+        },
+        "zod": {
+          "optional": false
+        }
+      }
+    },
+    "node_modules/accepts": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+      "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-types": "^3.0.0",
+        "negotiator": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/ajv": {
+      "version": "8.20.0",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
+      "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
+      "license": "MIT",
+      "dependencies": {
+        "fast-deep-equal": "^3.1.3",
+        "fast-uri": "^3.0.1",
+        "json-schema-traverse": "^1.0.0",
+        "require-from-string": "^2.0.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/epoberezkin"
+      }
+    },
+    "node_modules/ajv-formats": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
+      "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ajv": "^8.0.0"
+      },
+      "peerDependencies": {
+        "ajv": "^8.0.0"
+      },
+      "peerDependenciesMeta": {
+        "ajv": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/body-parser": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
+      "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "^3.1.2",
+        "content-type": "^1.0.5",
+        "debug": "^4.4.3",
+        "http-errors": "^2.0.0",
+        "iconv-lite": "^0.7.0",
+        "on-finished": "^2.4.1",
+        "qs": "^6.14.1",
+        "raw-body": "^3.0.1",
+        "type-is": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/bytes": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/call-bound": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+      "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "get-intrinsic": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/content-disposition": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
+      "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/content-type": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+      "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie": {
+      "version": "0.7.2",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+      "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie-signature": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+      "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.6.0"
+      }
+    },
+    "node_modules/cors": {
+      "version": "2.8.6",
+      "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+      "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+      "license": "MIT",
+      "dependencies": {
+        "object-assign": "^4",
+        "vary": "^1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/cross-spawn": {
+      "version": "7.0.6",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+      "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+      "license": "MIT",
+      "dependencies": {
+        "path-key": "^3.1.0",
+        "shebang-command": "^2.0.0",
+        "which": "^2.0.1"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/debug": {
+      "version": "4.4.3",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+      "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+      "license": "MIT"
+    },
+    "node_modules/encodeurl": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+      "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-object-atoms": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
+      "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+      "license": "MIT"
+    },
+    "node_modules/etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/eventsource": {
+      "version": "3.0.7",
+      "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
+      "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
+      "license": "MIT",
+      "dependencies": {
+        "eventsource-parser": "^3.0.1"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/eventsource-parser": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz",
+      "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/express": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+      "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+      "license": "MIT",
+      "dependencies": {
+        "accepts": "^2.0.0",
+        "body-parser": "^2.2.1",
+        "content-disposition": "^1.0.0",
+        "content-type": "^1.0.5",
+        "cookie": "^0.7.1",
+        "cookie-signature": "^1.2.1",
+        "debug": "^4.4.0",
+        "depd": "^2.0.0",
+        "encodeurl": "^2.0.0",
+        "escape-html": "^1.0.3",
+        "etag": "^1.8.1",
+        "finalhandler": "^2.1.0",
+        "fresh": "^2.0.0",
+        "http-errors": "^2.0.0",
+        "merge-descriptors": "^2.0.0",
+        "mime-types": "^3.0.0",
+        "on-finished": "^2.4.1",
+        "once": "^1.4.0",
+        "parseurl": "^1.3.3",
+        "proxy-addr": "^2.0.7",
+        "qs": "^6.14.0",
+        "range-parser": "^1.2.1",
+        "router": "^2.2.0",
+        "send": "^1.1.0",
+        "serve-static": "^2.2.0",
+        "statuses": "^2.0.1",
+        "type-is": "^2.0.1",
+        "vary": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 18"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/express-rate-limit": {
+      "version": "8.5.2",
+      "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz",
+      "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==",
+      "license": "MIT",
+      "dependencies": {
+        "ip-address": "^10.2.0"
+      },
+      "engines": {
+        "node": ">= 16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/express-rate-limit"
+      },
+      "peerDependencies": {
+        "express": ">= 4.11"
+      }
+    },
+    "node_modules/fast-deep-equal": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+      "license": "MIT"
+    },
+    "node_modules/fast-uri": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
+      "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/fastify"
+        },
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/fastify"
+        }
+      ],
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/finalhandler": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+      "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "^4.4.0",
+        "encodeurl": "^2.0.0",
+        "escape-html": "^1.0.3",
+        "on-finished": "^2.4.1",
+        "parseurl": "^1.3.3",
+        "statuses": "^2.0.1"
+      },
+      "engines": {
+        "node": ">= 18.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/forwarded": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fresh": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+      "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "es-define-property": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.1.1",
+        "function-bind": "^1.1.2",
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "has-symbols": "^1.1.0",
+        "hasown": "^2.0.2",
+        "math-intrinsics": "^1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "license": "MIT",
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+      "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
+      "license": "MIT",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/hono": {
+      "version": "4.12.23",
+      "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.23.tgz",
+      "integrity": "sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=16.9.0"
+      }
+    },
+    "node_modules/http-errors": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+      "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+      "license": "MIT",
+      "dependencies": {
+        "depd": "~2.0.0",
+        "inherits": "~2.0.4",
+        "setprototypeof": "~1.2.0",
+        "statuses": "~2.0.2",
+        "toidentifier": "~1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/iconv-lite": {
+      "version": "0.7.2",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+      "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+      "license": "MIT",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "license": "ISC"
+    },
+    "node_modules/ip-address": {
+      "version": "10.2.0",
+      "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
+      "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 12"
+      }
+    },
+    "node_modules/ipaddr.js": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/is-promise": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+      "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+      "license": "MIT"
+    },
+    "node_modules/isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+      "license": "ISC"
+    },
+    "node_modules/jose": {
+      "version": "6.2.3",
+      "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz",
+      "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/panva"
+      }
+    },
+    "node_modules/json-schema-traverse": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+      "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+      "license": "MIT"
+    },
+    "node_modules/json-schema-typed": {
+      "version": "8.0.2",
+      "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz",
+      "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==",
+      "license": "BSD-2-Clause"
+    },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/media-typer": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+      "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/merge-descriptors": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+      "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.54.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+      "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+      "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "^1.54.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/negotiator": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+      "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/object-inspect": {
+      "version": "1.13.4",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+      "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/on-finished": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+      "license": "MIT",
+      "dependencies": {
+        "ee-first": "1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+      "license": "ISC",
+      "dependencies": {
+        "wrappy": "1"
+      }
+    },
+    "node_modules/parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/path-key": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/path-to-regexp": {
+      "version": "8.4.2",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+      "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
+      "license": "MIT",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/pkce-challenge": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz",
+      "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=16.20.0"
+      }
+    },
+    "node_modules/proxy-addr": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+      "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+      "license": "MIT",
+      "dependencies": {
+        "forwarded": "0.2.0",
+        "ipaddr.js": "1.9.1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/qs": {
+      "version": "6.15.2",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
+      "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "side-channel": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=0.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/raw-body": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+      "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "~3.1.2",
+        "http-errors": "~2.0.1",
+        "iconv-lite": "~0.7.0",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/require-from-string": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+      "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/router": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+      "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "^4.4.0",
+        "depd": "^2.0.0",
+        "is-promise": "^4.0.0",
+        "parseurl": "^1.3.3",
+        "path-to-regexp": "^8.0.0"
+      },
+      "engines": {
+        "node": ">= 18"
+      }
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "license": "MIT"
+    },
+    "node_modules/send": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+      "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "^4.4.3",
+        "encodeurl": "^2.0.0",
+        "escape-html": "^1.0.3",
+        "etag": "^1.8.1",
+        "fresh": "^2.0.0",
+        "http-errors": "^2.0.1",
+        "mime-types": "^3.0.2",
+        "ms": "^2.1.3",
+        "on-finished": "^2.4.1",
+        "range-parser": "^1.2.1",
+        "statuses": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 18"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/serve-static": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+      "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
+      "license": "MIT",
+      "dependencies": {
+        "encodeurl": "^2.0.0",
+        "escape-html": "^1.0.3",
+        "parseurl": "^1.3.3",
+        "send": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 18"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/setprototypeof": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+      "license": "ISC"
+    },
+    "node_modules/shebang-command": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+      "license": "MIT",
+      "dependencies": {
+        "shebang-regex": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/shebang-regex": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/side-channel": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+      "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "object-inspect": "^1.13.3",
+        "side-channel-list": "^1.0.0",
+        "side-channel-map": "^1.0.1",
+        "side-channel-weakmap": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-list": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+      "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "object-inspect": "^1.13.4"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-map": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+      "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.5",
+        "object-inspect": "^1.13.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-weakmap": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+      "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.5",
+        "object-inspect": "^1.13.3",
+        "side-channel-map": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/statuses": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+      "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/toidentifier": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
+    "node_modules/type-is": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
+      "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
+      "license": "MIT",
+      "dependencies": {
+        "content-type": "^2.0.0",
+        "media-typer": "^1.1.0",
+        "mime-types": "^3.0.0"
+      },
+      "engines": {
+        "node": ">= 18"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/type-is/node_modules/content-type": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
+      "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/which": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+      "license": "ISC",
+      "dependencies": {
+        "isexe": "^2.0.0"
+      },
+      "bin": {
+        "node-which": "bin/node-which"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+      "license": "ISC"
+    },
+    "node_modules/zod": {
+      "version": "4.4.3",
+      "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
+      "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/colinhacks"
+      }
+    },
+    "node_modules/zod-to-json-schema": {
+      "version": "3.25.2",
+      "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz",
+      "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==",
+      "license": "ISC",
+      "peerDependencies": {
+        "zod": "^3.25.28 || ^4"
+      }
+    }
+  }
+}

+ 162 - 0
claude-code-tihao-sourcing/package.json

@@ -0,0 +1,162 @@
+{
+  "name": "@vocmarket/tihao",
+  "version": "1.0.11",
+  "description": "Claude Code skill package for Tihao AI creator sourcing and Fmode image analysis: upload/read briefs, call VOC e-commerce proxy, analyze images with Doubao vision, rank creators, export business-ready blogger lists, and keep preference memory.",
+  "type": "commonjs",
+  "bin": {
+    "tihao": "bin/tihao.js",
+    "tihao-sourcing": "bin/tihao.js"
+  },
+  "scripts": {
+    "install:check": "node install.js --check",
+    "install:smoke": "node install.js --smoke",
+    "sample": "node mcp/src/tools/tihao-brief-sourcing-run.js --collection-mode sample --brief fixtures/sample-briefs/skincare-sample.md --output ../outputs/claude-code-tihao-sample --assistant-message-only",
+    "fmode:image": "node mcp/src/tools/fmode-image-analysis.js",
+    "fmode:image:smoke": "node scripts/fmode-image-analysis-smoke.js",
+    "live": "node mcp/src/tools/tihao-brief-sourcing-run.js --collection-mode live --brief fixtures/sample-briefs/skincare-sample.md --output ../outputs/claude-code-tihao-live --keyword-limit 1 --pages-per-keyword 1 --assistant-message-only",
+    "live:preflight": "node scripts/live-preflight.js",
+    "live:acceptance": "node scripts/live-acceptance.js",
+    "live:normalization:smoke": "node scripts/live-provider-normalization-smoke.js",
+    "longrun:readiness": "node scripts/long-run-readiness-audit.js",
+    "longrun:readiness:smoke": "node scripts/long-run-readiness-smoke.js",
+    "local-seed-material:index": "node scripts/local-seed-material-index.js",
+    "local-seed-material:index:smoke": "node scripts/local-seed-material-index-smoke.js",
+    "local-seed-to-intake:worklist": "node scripts/local-seed-to-intake-worklist.js",
+    "local-seed-to-intake:worklist:smoke": "node scripts/local-seed-to-intake-worklist-smoke.js",
+    "experience-transcript:index": "node scripts/experience-transcript-index.js",
+    "experience-transcript:index:smoke": "node scripts/experience-transcript-index-smoke.js",
+    "real-gap:materials": "node scripts/real-gap-material-audit.js",
+    "real-gap:materials:smoke": "node scripts/real-gap-material-audit-smoke.js",
+    "evidence:index": "node scripts/evidence-index.js",
+    "evidence:index:smoke": "node scripts/evidence-index-smoke.js",
+    "preference-smoke": "node mcp/src/tools/tihao-preference-update.js --message \"客户更喜欢素人感、敏感肌真实记录,不要硬广和泛美妆号\" --memory ../outputs/claude-code-tihao-memory-smoke/tihao-preference-memory.json --assistant-message-only",
+    "feedback:import": "node scripts/import-feedback-memory.js",
+    "feedback:closure": "node scripts/feedback-loop-closure-audit.js",
+    "feedback:closure:smoke": "node scripts/feedback-loop-closure-smoke.js",
+    "review:metrics": "node scripts/review-metrics.js",
+    "review:metrics:smoke": "node scripts/review-metrics-smoke.js",
+    "manual-review:label-guide": "node scripts/manual-review-label-guide.js",
+    "manual-review:label-guide:smoke": "node scripts/manual-review-label-guide-smoke.js",
+    "skill:refs:smoke": "node scripts/skill-reference-readability-smoke.js",
+    "customer-effect:audit": "node scripts/customer-effect-audit.js",
+    "customer-effect:smoke": "node scripts/customer-effect-audit-smoke.js",
+    "software:export": "node scripts/export-software-table.js",
+    "software:markdown": "node scripts/export-software-markdown.js",
+    "software:refresh": "node scripts/refresh-software-client-list.js",
+    "software:smoke": "node scripts/software-table-smoke.js",
+    "proof-gap:software-form": "node scripts/export-proof-gap-software-form.js",
+    "proof-gap:software-form:smoke": "node scripts/export-proof-gap-software-form-smoke.js",
+    "plan:consistency:smoke": "node scripts/plan-consistency-smoke.js",
+    "plan:execution-status": "node scripts/plan-execution-status.js",
+    "plan:execution-status:smoke": "node scripts/plan-execution-status-smoke.js",
+    "hygiene:smoke": "node scripts/leak-mojibake-audit.js",
+    "candidate-pool:smoke": "node scripts/candidate-pool-smoke.js",
+    "history:audit": "node scripts/historical-dataset-audit.js",
+    "history:from-csv": "node scripts/history-dataset-from-csv.js",
+    "history:from-csv:smoke": "node scripts/history-dataset-from-csv-smoke.js",
+    "history:audit:smoke": "node scripts/historical-dataset-audit-smoke.js",
+    "data:intake-template": "node scripts/create-data-intake-pack.js",
+    "data:intake-template:smoke": "node scripts/create-data-intake-pack-smoke.js",
+    "intake:readiness": "node scripts/intake-readiness-audit.js",
+    "intake:readiness:smoke": "node scripts/intake-readiness-smoke.js",
+    "intake:field-checklist": "node scripts/intake-field-checklist.js",
+    "intake:field-checklist:smoke": "node scripts/intake-field-checklist-smoke.js",
+    "video:intake-template": "node scripts/create-video-intake-pack.js",
+    "video:intake-template:smoke": "node scripts/create-video-intake-pack-smoke.js",
+    "video:resource-readiness": "node scripts/video-resource-readiness-audit.js",
+    "video:resource-readiness:smoke": "node scripts/video-resource-readiness-smoke.js",
+    "optimization:status": "node scripts/optimization-status-audit.js",
+    "optimization:status:smoke": "node scripts/optimization-status-smoke.js",
+    "optimization:pipeline": "node scripts/optimization-pipeline.js",
+    "optimization:pipeline:smoke": "node scripts/optimization-pipeline-smoke.js",
+    "optimization:completion": "node scripts/optimization-completion-audit.js",
+    "optimization:completion:smoke": "node scripts/optimization-completion-smoke.js",
+    "handoff:summary": "node scripts/generate-optimization-handoff.js",
+    "handoff:summary:smoke": "node scripts/generate-optimization-handoff-smoke.js",
+    "proof-gap:request": "node scripts/generate-proof-gap-request.js",
+    "proof-gap:request:smoke": "node scripts/generate-proof-gap-request-smoke.js",
+    "proof-gap:closure": "node scripts/proof-gap-closure-audit.js",
+    "proof-gap:closure:smoke": "node scripts/proof-gap-closure-smoke.js",
+    "proof-gap:operator-pack": "node scripts/proof-gap-operator-pack.js",
+    "proof-gap:operator-pack:smoke": "node scripts/proof-gap-operator-pack-smoke.js",
+    "proof-gap:intake-bundle": "node scripts/real-proof-intake-bundle.js",
+    "proof-gap:intake-bundle:smoke": "node scripts/real-proof-intake-bundle-smoke.js",
+    "real-proof:work-order": "node scripts/real-proof-closure-work-order.js",
+    "real-proof:work-order:smoke": "node scripts/real-proof-closure-work-order-smoke.js",
+    "business-proof:progress": "node scripts/business-proof-progress.js",
+    "business-proof:progress:smoke": "node scripts/business-proof-progress-smoke.js",
+    "business-proof:next-actions": "node scripts/business-proof-next-actions.js",
+    "business-proof:next-actions:smoke": "node scripts/business-proof-next-actions-smoke.js",
+    "business-proof:next-actions:missing-proof:smoke": "node scripts/business-proof-next-actions-missing-proof-smoke.js",
+    "business-execution:index": "node scripts/business-execution-index.js",
+    "business-execution:index:smoke": "node scripts/business-execution-index-smoke.js",
+    "latest-form:index": "node scripts/latest-form-index.js",
+    "latest-form:index:smoke": "node scripts/latest-form-index-smoke.js",
+    "round:refresh": "node scripts/refresh-round-artifacts.js",
+    "round:refresh:smoke": "node scripts/refresh-round-artifacts-smoke.js",
+    "round:deposition": "node scripts/round-deposition-audit.js",
+    "round:deposition:smoke": "node scripts/round-deposition-smoke.js",
+    "task:handoff-index:smoke": "node scripts/task-handoff-index-smoke.js",
+    "feedback:smoke": "node scripts/feedback-import-smoke.js",
+    "feedback:roundtrip": "node scripts/feedback-roundtrip-smoke.js",
+    "sample-with-memory": "node mcp/src/tools/tihao-brief-sourcing-run.js --collection-mode sample --brief fixtures/sample-briefs/skincare-sample.md --memory ../outputs/claude-code-tihao-memory-smoke/tihao-preference-memory.json --output ../outputs/claude-code-tihao-memory-smoke/report --assistant-message-only",
+    "mcp:start": "node mcp/src/server.js",
+    "mcp:smoke": "node scripts/smoke-mcp.js",
+    "smoke:package": "node scripts/smoke-package.js",
+    "package:contents:smoke": "node scripts/package-contents-smoke.js",
+    "smoke:briefs": "node scripts/brief-fixture-smoke.js",
+    "hard-rules:smoke": "node scripts/hard-rules-smoke.js",
+    "reference-rules:smoke": "node scripts/reference-rules-smoke.js",
+    "platform:strategy:smoke": "node scripts/platform-sourcing-strategy-smoke.js",
+    "acceptance:pm": "node scripts/product-acceptance-audit.js",
+    "acceptance:providers": "node scripts/provider-contract-audit.js",
+    "acceptance:providers:mock": "node scripts/provider-contract-audit.js --mock",
+    "homepage:smoke": "node scripts/homepage-evidence-provider-smoke.js",
+    "homepage:readiness": "node scripts/homepage-evidence-readiness-audit.js",
+    "homepage:readiness:smoke": "node scripts/homepage-evidence-readiness-smoke.js",
+    "acceptance:video-ab:smoke": "node scripts/video-hit-rate-audit-smoke.js",
+    "acceptance:video-ab": "node scripts/video-hit-rate-audit.js",
+    "overnight:quality": "node scripts/overnight-quality-campaign.js",
+    "overnight:review:smoke": "node scripts/overnight-manual-review-smoke.js",
+    "acceptance": "node scripts/acceptance.js",
+    "publish:dry": "npm run acceptance && npm publish --dry-run --access public",
+    "publish:npm": "npm run acceptance && npm publish --access public"
+  },
+  "files": [
+    ".claude-plugin/",
+    ".mcp.json",
+    "bin/",
+    "docs/",
+    "fixtures/",
+    "install.js",
+    "mcp/",
+    "memory-templates/",
+    "package.json",
+    "README.md",
+    "scripts/",
+    "skill-package-manifest.json",
+    "skills/"
+  ],
+  "publishConfig": {
+    "access": "public"
+  },
+  "keywords": [
+    "claude-code",
+    "tihao",
+    "creator-sourcing",
+    "blogger-list",
+    "brief",
+    "mcp",
+    "justone"
+  ],
+  "dependencies": {
+    "@modelcontextprotocol/sdk": "^1.29.0",
+    "zod": "^4.4.3"
+  },
+  "main": "install.js",
+  "directories": {
+    "doc": "docs"
+  },
+  "author": "",
+  "license": "ISC"
+}

+ 139 - 0
claude-code-tihao-sourcing/scripts/acceptance.js

@@ -0,0 +1,139 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const { spawnSync } = require('child_process');
+
+const root = path.resolve(__dirname, '..');
+const npmCache = path.join(root, '.npm-cache-acceptance');
+
+function main() {
+  fs.mkdirSync(npmCache, { recursive: true });
+  run('npm', ['run', 'fmode:image:smoke'], root);
+  run('npm', ['run', 'smoke:package'], root);
+  run('npm', ['run', 'live:normalization:smoke'], root);
+  run('npm', ['run', 'smoke:briefs'], root);
+  run('npm', ['run', 'hard-rules:smoke'], root);
+  run('npm', ['run', 'reference-rules:smoke'], root);
+  run('npm', ['run', 'platform:strategy:smoke'], root);
+  run('npm', ['run', 'feedback:smoke'], root);
+  run('npm', ['run', 'feedback:roundtrip'], root);
+  run('npm', ['run', 'feedback:closure:smoke'], root);
+  run('npm', ['run', 'review:metrics:smoke'], root);
+  run('npm', ['run', 'manual-review:label-guide:smoke'], root);
+  run('npm', ['run', 'skill:refs:smoke'], root);
+  run('npm', ['run', 'customer-effect:smoke'], root);
+  run('npm', ['run', 'software:smoke'], root);
+  run('npm', ['run', 'proof-gap:software-form:smoke'], root);
+  run('npm', ['run', 'plan:consistency:smoke'], root);
+  run('npm', ['run', 'plan:execution-status:smoke'], root);
+  run('npm', ['run', 'hygiene:smoke'], root);
+  run('npm', ['run', 'candidate-pool:smoke'], root);
+  run('npm', ['run', 'history:from-csv:smoke'], root);
+  run('npm', ['run', 'history:audit:smoke'], root);
+  run('npm', ['run', 'data:intake-template:smoke'], root);
+  run('npm', ['run', 'video:intake-template:smoke'], root);
+  run('npm', ['run', 'intake:readiness:smoke'], root);
+  run('npm', ['run', 'intake:field-checklist:smoke'], root);
+  run('npm', ['run', 'video:resource-readiness:smoke'], root);
+  run('npm', ['run', 'longrun:readiness:smoke'], root);
+  run('npm', ['run', 'local-seed-material:index:smoke'], root);
+  run('npm', ['run', 'local-seed-to-intake:worklist:smoke'], root);
+  run('npm', ['run', 'experience-transcript:index:smoke'], root);
+  run('npm', ['run', 'real-gap:materials:smoke'], root);
+  run('npm', ['run', 'optimization:pipeline:smoke'], root);
+  run('npm', ['run', 'optimization:completion:smoke'], root);
+  run('npm', ['run', 'handoff:summary:smoke'], root);
+  run('npm', ['run', 'proof-gap:request:smoke'], root);
+  run('npm', ['run', 'proof-gap:closure:smoke'], root);
+  run('npm', ['run', 'proof-gap:intake-bundle:smoke'], root);
+  run('npm', ['run', 'real-proof:work-order:smoke'], root);
+  run('npm', ['run', 'business-proof:progress:smoke'], root);
+  run('npm', ['run', 'business-proof:next-actions:smoke'], root);
+  run('npm', ['run', 'business-proof:next-actions:missing-proof:smoke'], root);
+  run('npm', ['run', 'business-execution:index:smoke'], root);
+  run('npm', ['run', 'latest-form:index:smoke'], root);
+  run('npm', ['run', 'round:refresh:smoke'], root);
+  run('npm', ['run', 'round:deposition:smoke'], root);
+  run('npm', ['run', 'task:handoff-index:smoke'], root);
+  run('npm', ['run', 'evidence:index:smoke'], root);
+  run('npm', ['run', 'acceptance:video-ab:smoke'], root);
+  run('npm', ['run', 'overnight:review:smoke'], root);
+  run('npm', ['run', 'optimization:status:smoke'], root);
+  run('npm', ['run', 'homepage:smoke'], root);
+  run('npm', ['run', 'homepage:readiness:smoke'], root);
+  run('npm', ['run', 'mcp:smoke'], root);
+  run('npm', ['run', 'acceptance:pm'], root);
+  run('npm', ['run', 'acceptance:providers:mock'], root);
+  run('npm', ['run', 'package:contents:smoke'], root);
+  run('npm', ['pack', '--dry-run'], root);
+
+  const workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-workspace-install-'));
+  const legacyPluginDir = path.join(workspace, '.claude', 'plugins', 'tihao-sourcing');
+  const legacySkillDir = path.join(workspace, '.claude', 'skills', 'tihao-creator-sourcing');
+  fs.mkdirSync(legacyPluginDir, { recursive: true });
+  fs.writeFileSync(path.join(legacyPluginDir, 'legacy.txt'), 'legacy tihao-sourcing plugin\n', 'utf8');
+  fs.mkdirSync(legacySkillDir, { recursive: true });
+  fs.writeFileSync(path.join(legacySkillDir, 'SKILL.md'), 'name: tihao-creator-sourcing\n', 'utf8');
+  fs.writeFileSync(path.join(workspace, '.mcp.json'), JSON.stringify({
+    mcpServers: {
+      'tihao-sourcing': {
+        command: 'node',
+        args: [path.join(legacyPluginDir, 'mcp', 'src', 'server.js')]
+      }
+    }
+  }, null, 2), 'utf8');
+  run(process.execPath, [path.join(root, 'install.js'), '--workspace', '--smoke'], workspace);
+
+  const mcpFile = path.join(workspace, '.mcp.json');
+  const skillFile = path.join(workspace, '.claude', 'skills', 'tihao', 'SKILL.md');
+  const imageSkillFile = path.join(workspace, '.claude', 'skills', 'fmode-image-analysis', 'SKILL.md');
+  const pluginServer = path.join(workspace, '.claude', 'plugins', 'tihao', 'mcp', 'src', 'server.js');
+
+  assert(fs.existsSync(mcpFile), 'workspace install should write .mcp.json');
+  assert(fs.existsSync(skillFile), 'workspace install should write skill entry');
+  assert(fs.existsSync(imageSkillFile), 'workspace install should write fmode image analysis skill entry');
+  assert(!fs.existsSync(legacySkillDir), 'workspace install should remove legacy tihao-creator-sourcing skill entry');
+  assert(!fs.existsSync(legacyPluginDir), 'workspace install should remove legacy tihao-sourcing plugin entry');
+  assert(fs.existsSync(pluginServer), 'workspace install should copy plugin server');
+
+  const mcp = JSON.parse(fs.readFileSync(mcpFile, 'utf8'));
+  assert(mcp.mcpServers && mcp.mcpServers.tihao, 'workspace .mcp.json should register tihao server');
+  assert(!mcp.mcpServers['tihao-sourcing'], 'workspace .mcp.json should remove legacy tihao-sourcing server');
+  assert(path.isAbsolute(mcp.mcpServers.tihao.args[0]), 'workspace MCP server path should be absolute');
+
+  console.log(`acceptance ok: ${workspace}`);
+}
+
+function run(command, args, cwd) {
+  const resolved = resolveCommand(command, args);
+  const result = spawnSync(resolved.command, resolved.args, {
+    cwd,
+    stdio: 'inherit',
+    shell: resolved.shell,
+    env: {
+      ...process.env,
+      npm_config_cache: npmCache,
+      NPM_CONFIG_CACHE: npmCache
+    }
+  });
+  if (result.status !== 0) {
+    throw new Error(`${command} ${args.join(' ')} failed in ${cwd}`);
+  }
+}
+
+function resolveCommand(command, args) {
+  if (command === 'npm' && process.env.npm_execpath) {
+    return { command: process.execPath, args: [process.env.npm_execpath, ...args], shell: false };
+  }
+  if (command === 'npm' && process.platform === 'win32') {
+    return { command: 'npm.cmd', args, shell: true };
+  }
+  return { command, args, shell: false };
+}
+
+function assert(condition, message) {
+  if (!condition) throw new Error(message);
+}
+
+main();

+ 218 - 0
claude-code-tihao-sourcing/scripts/brief-fixture-smoke.js

@@ -0,0 +1,218 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const path = require('path');
+const { parseBrief } = require('../mcp/src/features/tihao-sourcing/brief-parser');
+
+function main() {
+  const repoRoot = path.resolve(__dirname, '..', '..');
+  const docsDir = path.join(repoRoot, 'docs');
+
+  const dhaBrief = path.join(repoRoot, 'dha_brief.xlsx');
+  if (fs.existsSync(dhaBrief)) {
+    const parsed = parseBrief({ brief: dhaBrief });
+    assert(parsed.brand === 'DHA', 'DHA xlsx should parse product as brand');
+    assert(parsed.platforms.length === 1 && parsed.platforms[0] === 'xiaohongshu', 'DHA xlsx should parse Xiaohongshu platform');
+    assert(parsed.keywords.includes('DHA') && parsed.keywords.includes('营养师'), 'DHA xlsx should parse maternal nutrition keywords');
+    assert(parsed.performanceRequirements.includes('CPE≤3'), 'DHA xlsx should parse CPE requirement');
+    assert(parsed.referenceLinks.length === 4, 'DHA xlsx should parse 4 reference account links');
+    assert(parsed.referenceStyleAnchors.length >= 4, 'DHA xlsx should build reference style anchors');
+    assert(parsed.referenceSignals.includes('干货科普') && parsed.referenceSignals.includes('618合集'), 'DHA xlsx should parse reference signals');
+  }
+
+  const markdownBrief = findBriefFile(docsDir, '.md');
+  if (markdownBrief) {
+    const parsed = parseBrief({ brief: markdownBrief });
+    assert(parsed.brand.includes('花漾肌研'), 'markdown brief should parse brand');
+    assert(parsed.targetCount === 32, 'markdown brief should parse target count sum');
+    assert(parsed.platformTargets.xiaohongshu === 20, 'markdown brief should parse Xiaohongshu target');
+    assert(parsed.platformTargets.douyin === 8, 'markdown brief should parse Douyin target');
+    assert(parsed.platformTargets.bilibili === 4, 'markdown brief should parse Bilibili target');
+    assert(parsed.fanRange.min === 20000 && parsed.fanRange.max === 500000, 'markdown brief should parse fan range');
+    assert(parsed.referenceSignals.includes('素人感'), 'markdown brief should parse reference style preference');
+  }
+
+  const htmlXlsBrief = findBriefFile(docsDir, '.xls');
+  if (htmlXlsBrief) {
+    const parsed = parseBrief({ brief: htmlXlsBrief });
+    assert(parsed.brand.includes('花漾肌研'), 'html xls brief should parse brand from key-value cells');
+    assert(parsed.targetCount === 32, 'html xls brief should parse target count sum');
+    assert(parsed.platformTargets.xiaohongshu === 20, 'html xls brief should parse Xiaohongshu target');
+    assert(parsed.platformTargets.douyin === 8, 'html xls brief should parse Douyin target');
+    assert(parsed.platformTargets.bilibili === 4, 'html xls brief should parse Bilibili target');
+    assert(parsed.fanRange.min === 20000 && parsed.fanRange.max === 500000, 'html xls brief should parse horizontal fan range table');
+    assert(parsed.referenceSignals.includes('真实测评') && parsed.referenceSignals.includes('成分党'), 'html xls brief should parse reference keyword signals');
+  }
+
+  assertNaturalLanguageBriefCoverage();
+
+  console.log('brief fixture smoke ok');
+}
+
+function findBriefFile(dir, ext) {
+  if (!fs.existsSync(dir)) return '';
+  return fs.readdirSync(dir)
+    .map(name => path.join(dir, name))
+    .find(file => fs.statSync(file).isFile() && path.basename(file).includes('Brief') && file.endsWith(ext)) || '';
+}
+
+function assertNaturalLanguageBriefCoverage() {
+  const cases = [
+    {
+      name: 'skincare',
+      text: [
+        '客户名称:明澈肌研',
+        '产品:敏感肌屏障修护精华',
+        '平台与人数:小红书 6 人,抖音 2 人',
+        '关键词:敏感肌、修护、成分、真实测评',
+        '粉丝范围:2万-50万',
+        '预算范围:3000-18000',
+        '偏好真实体验、素人感,不要硬广。'
+      ].join('\n'),
+      assert(parsed) {
+        assert(parsed.brand === '明澈肌研', 'skincare should parse brand');
+        assert(parsed.category === '护肤美妆', 'skincare should infer beauty category');
+        assert(parsed.platformTargets.xiaohongshu === 6 && parsed.platformTargets.douyin === 2, 'skincare should parse platform targets');
+        assert(parsed.fanRange.min === 20000 && parsed.fanRange.max === 500000, 'skincare should parse fan range');
+        assert(parsed.keywords.includes('敏感肌') && parsed.keywords.includes('修护'), 'skincare should parse skincare keywords');
+      }
+    },
+    {
+      name: 'real-douyin-beauty-brief',
+      text: [
+        '平台:抖音',
+        '账号类型:美妆护肤',
+        '粉丝要求:1k-10w',
+        '单个预算:2000内',
+        '数据要求:主页互动数据200+',
+        '备注:产品是客单价比较高的面膜,想找调性高的美妆护肤博主,想要主页之前合作过大牌护肤品的',
+        '',
+        '参考账号:',
+        'https://www.douyin.com/user/MS4wLjABAAAAZ31qrM27_cxcps8cc2sGKlnUWIOUDuVmaGB7BhNEpYAcZfhxjgicQ3VVXfRNgcWv'
+      ].join('\n'),
+      assert(parsed) {
+        assert(parsed.category === '护肤美妆', 'real douyin beauty brief should infer beauty category');
+        assert(parsed.platforms.length === 1 && parsed.platforms[0] === 'douyin', 'real douyin beauty brief should parse Douyin only');
+        assert(parsed.fanRange.min === 1000 && parsed.fanRange.max === 100000, 'real douyin beauty brief should parse 1k-10w fan range');
+        assert(parsed.budgetRange.min === 0 && parsed.budgetRange.max === 2000, 'real douyin beauty brief should parse 2000 upper-bound budget');
+        assert(parsed.performanceRequirements.includes('主页互动数据≥200'), 'real douyin beauty brief should parse homepage interaction requirement');
+        assert(parsed.keywords.includes('护肤') && parsed.keywords.includes('美妆') && parsed.keywords.includes('面膜'), 'real douyin beauty brief should keep beauty keywords');
+        assert(parsed.referenceLinks.length === 1, 'real douyin beauty brief should parse reference account link');
+      }
+    },
+    {
+      name: 'healthy-snack',
+      text: [
+        '品牌:谷物星球',
+        '本次推广产品:高蛋白低脂燕麦棒',
+        '目标:找小红书 5 个、抖音 3 个健身/控糖/办公室零食博主',
+        '粉丝数范围:1万-30万',
+        '单篇预算:1000-8000',
+        '风格偏生活化开箱、真实吃法、成分党,不要医美和夸大减肥。'
+      ].join('\n'),
+      assert(parsed) {
+        assert(parsed.brand === '谷物星球', 'healthy snack should parse brand');
+        assert(parsed.category === '食品餐饮', 'healthy snack should infer food category instead of beauty');
+        assert(parsed.targetCount === 8, 'healthy snack should parse platform target sum');
+        assert(parsed.fanRange.min === 10000 && parsed.fanRange.max === 300000, 'healthy snack should parse fan range');
+        assert(parsed.keywords.includes('控糖') && parsed.keywords.includes('办公室零食'), 'healthy snack should parse food creator keywords');
+        assert(parsed.exclusions.includes('医美') && parsed.exclusions.includes('夸大减肥'), 'healthy snack should parse exclusions');
+      }
+    },
+    {
+      name: 'home-cleaning',
+      text: [
+        '客户:净住研究所',
+        '产品:厨房重油污清洁喷雾',
+        '需求人数:小红书 8 人',
+        '要求:家居清洁、收纳、租房生活、妈妈人群,粉丝 5000-20万,预算 800-5000',
+        '排除:硬广、低质搬运、争议账号。'
+      ].join('\n'),
+      assert(parsed) {
+        assert(parsed.brand === '净住研究所', 'home cleaning should parse brand');
+        assert(parsed.category === '家居生活', 'home cleaning should infer home category');
+        assert(parsed.platforms.length === 1 && parsed.platforms[0] === 'xiaohongshu', 'home cleaning should parse Xiaohongshu only');
+        assert(parsed.fanRange.min === 5000 && parsed.fanRange.max === 200000, 'home cleaning should not read budget as fan range');
+        assert(parsed.budgetRange.min === 800 && parsed.budgetRange.max === 5000, 'home cleaning should parse budget on same line');
+        assert(parsed.keywords.includes('清洁') && parsed.keywords.includes('收纳'), 'home cleaning should parse home keywords');
+      }
+    },
+    {
+      name: 'b2b-saas',
+      text: [
+        '品牌:FlowOps',
+        '产品:企业项目管理 SaaS',
+        '需求:找 B站 4 个、抖音 2 个效率工具/创业/企业管理类内容创作者',
+        '粉丝:1万-40万',
+        '预算:3000-20000',
+        '偏好教程型、案例型、职场效率,不要纯娱乐账号。'
+      ].join('\n'),
+      assert(parsed) {
+        assert(parsed.brand === 'FlowOps', 'B2B SaaS should parse brand');
+        assert(parsed.category === 'B2B软件', 'B2B SaaS should infer software category');
+        assert(parsed.platformTargets.bilibili === 4 && parsed.platformTargets.douyin === 2, 'B2B SaaS should parse Bilibili and Douyin targets');
+        assert(parsed.keywords.includes('效率工具') && parsed.keywords.includes('企业管理'), 'B2B SaaS should parse software keywords');
+        assert(parsed.exclusions.includes('纯娱乐') || parsed.exclusions.includes('纯娱乐账号'), 'B2B SaaS should parse entertainment exclusion');
+      }
+    },
+    {
+      name: 'local-food',
+      text: [
+        '品牌:山城小面加盟',
+        '任务:找本地生活探店、餐饮创业类达人,重庆/成都优先',
+        '平台:抖音 10 人',
+        '粉丝 2万-80万,预算 2000-12000',
+        '重点看同城转化、门店拍摄经验,排除泛娱乐流量号。'
+      ].join('\n'),
+      assert(parsed) {
+        assert(parsed.brand === '山城小面加盟', 'local food should parse brand');
+        assert(parsed.category === '食品餐饮', 'local food should infer food/local category');
+        assert(parsed.platforms.length === 1 && parsed.platforms[0] === 'douyin', 'local food should parse Douyin only');
+        assert(parsed.targetCount === 10, 'local food should parse target count');
+        assert(parsed.fanRange.min === 20000 && parsed.fanRange.max === 800000, 'local food should parse fan range and ignore budget');
+        assert(parsed.region.includes('重庆') && parsed.region.includes('成都'), 'local food should parse city priority');
+        assert(parsed.keywords.includes('本地生活') && parsed.keywords.includes('同城转化'), 'local food should parse local conversion keywords');
+      }
+    },
+    {
+      name: 'fashion-product-chat-refinement',
+      text: [
+        '客户:南栖衣橱',
+        'Brief:找精致生活/时尚穿搭类账号,小红书 4 人',
+        '粉丝:1万-20万,预算:300-3000',
+        '数据要求:CPE≤3,CP1≤20,CPM≤80',
+        '不要纯自拍。'
+      ].join('\n'),
+      input: {
+        productIntro: '产品是港风通勤女装,偏成熟风、轻熟高级感,不是泛生活方式。',
+        chatRecords: [
+          '客户说要看最近一个月有更新,平均点赞不要百赞以下。',
+          '如果多篇都是同一个人评论,怀疑刷评,先不要强推。'
+        ],
+        referenceAccounts: ['港风穿搭Alice']
+      },
+      assert(parsed) {
+        assert(parsed.brand === '南栖衣橱', 'fashion should parse brand');
+        assert(parsed.category === '服饰穿搭', 'fashion product/chat should refine broad lifestyle into apparel category');
+        assert(parsed.contextSources.product && parsed.contextSources.chat && parsed.contextSources.reference, 'fashion should mark product/chat/reference context sources');
+        assert(parsed.platformTargets.xiaohongshu === 4, 'fashion should parse Xiaohongshu target');
+        assert(parsed.keywords.includes('港风') && parsed.keywords.includes('成熟风'), 'fashion should extract subtype keywords from product/chat context');
+        assert(parsed.keywords.includes('时尚穿搭') || parsed.keywords.includes('穿搭'), 'fashion should keep broad fashion keyword');
+        assert(parsed.exclusions.includes('纯自拍') && !parsed.exclusions.includes('穿搭'), 'fashion should not treat wearing/fashion as an exclusion');
+        assert(parsed.performanceRequirements.includes('CPE≤3') && parsed.performanceRequirements.includes('CP1≤20') && parsed.performanceRequirements.includes('CPM≤80'), 'fashion should parse cost metrics');
+        assert(parsed.performanceRequirements.includes('近30天平均点赞≥100'), 'fashion should turn low-like warning into average-like requirement');
+        assert(parsed.performanceRequirements.includes('复核数据真实性/重复评论风险'), 'fashion should parse repeated-comment authenticity review');
+      }
+    }
+  ];
+
+  for (const item of cases) {
+    const parsed = parseBrief({ briefText: item.text, ...(item.input || {}) });
+    item.assert(parsed);
+  }
+}
+
+function assert(condition, message) {
+  if (!condition) throw new Error(message);
+}
+
+main();

+ 257 - 0
claude-code-tihao-sourcing/scripts/business-execution-index-smoke.js

@@ -0,0 +1,257 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const { buildBusinessExecutionIndex } = require('./business-execution-index');
+
+function main() {
+  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-business-execution-index-'));
+  const outputs = path.join(root, 'outputs');
+  seed(outputs);
+
+  const summary = buildBusinessExecutionIndex({ root, outputsDir: outputs });
+  assert(summary.passed === true, 'business execution index should pass with seeded artifacts');
+  assert(summary.directCustomerProof === false, 'business execution index should not be direct customer proof');
+  assert(summary.proofLevel === 'smoke_or_local', 'business execution index should stay smoke/local evidence');
+  assert(summary.proofOpenCount === 5, 'should expose proof open count');
+  assert(summary.actionRowCount === 3, 'should expose owner action rows');
+  assert(summary.repairRowCount === 2, 'should expose repair action rows');
+  assert(summary.operatorPackOwnerArtifactRowCount === 3, 'should expose operator pack owner attachment rows');
+  assert(summary.operatorPackOwnerArtifactCount === 3, 'should expose operator pack owner attachment count');
+  assert(Array.isArray(summary.operatorPackOwnerArtifacts) && summary.operatorPackOwnerArtifacts.length === 3, 'should carry operator pack owner attachments');
+  assert(summary.fieldOwnerRowCount === 2, 'should expose field checklist owner group rows');
+  assert(summary.recheckCommandRowCount === 4, 'should expose field checklist recheck command rows');
+  assert(summary.fileRowCount >= 2, 'should expose primary file rows');
+  assert(summary.rows.some(row => row.type === '修复清单' && row.repairFile.includes('intake-readiness-repair-actions.csv') && row.actionCount === 6), 'should include business intake repair file');
+  assert(summary.rows.some(row => row.type === '修复清单' && row.repairFile.includes('video-resource-repair-actions.csv') && row.actionCount === 1), 'should include video repair file');
+  assert(summary.sourceFiles.proofGapOperatorPack?.includes('proof-gap-operator-pack-summary.json'), 'should expose proof-gap operator pack source file');
+  assert(summary.rows.some(row => row.type === '操作包负责人附件' && row.owner === '商务' && row.mainFile.includes('by-owner/business.md') && row.csv.includes('by-owner/business.csv') && row.acceptance.includes('historical-dataset')), 'should include business proof-gap operator owner attachment');
+  assert(summary.rows.some(row => row.type === '操作包负责人附件' && row.owner === '商务/投放' && row.mainFile.includes('by-owner/business-media.md') && row.csv.includes('by-owner/business-media.csv')), 'should include business/media proof-gap operator owner attachment');
+  assert(summary.rows.some(row => row.type === '操作包负责人附件' && row.owner === '技术/AI' && row.mainFile.includes('by-owner/tech-ai.md') && row.csv.includes('by-owner/tech-ai.csv')), 'should include tech/AI proof-gap operator owner attachment');
+  assert(summary.rows.some(row => row.type === '字段补齐负责人' && row.title.includes('历史 Brief') && row.repairFile.includes('history-data-template.csv')), 'should include history field owner row');
+  assert(summary.rows.some(row => row.type === '字段补齐负责人' && row.title.includes('视频资源') && row.repairFile.includes('video-resource-template.csv')), 'should include video field owner row');
+  assert(summary.rows.some(row => row.type === '补齐后复验命令' && row.command.includes('intake:readiness')), 'should include intake readiness recheck row');
+  assert(summary.rows.some(row => row.type === '补齐后复验命令' && row.command.includes('acceptance:video-ab')), 'should include video A/B recheck row');
+  assert(summary.rows.some(row => row.type === '补齐后复验命令' && row.command.includes('customer-effect:audit')), 'should include customer effect recheck row');
+  assert(summary.sourceFiles.longRunReadiness?.includes('long-run-readiness-summary.json'), 'should expose long-run readiness source file');
+  assert(summary.sourceFiles.optimizationCompletion?.includes('optimization-completion-summary.json'), 'should expose optimization completion source file');
+  assert(summary.rows.some(row => row.type === '关键文件' && row.title.includes('长跑前置门禁') && row.mainFile.includes('long-run-readiness-report.md') && row.acceptance.includes('ready=false')), 'should include long-run readiness key file row');
+  assert(summary.rows.some(row => row.title.includes('长跑前置门禁') && row.boundary.includes('不能启动')), 'long-run readiness row should keep startup boundary');
+  assert(summary.rows.some(row => row.type === '关键文件' && row.title.includes('优化完成度审计') && row.mainFile.includes('optimization-completion-report.md') && row.acceptance.includes('readyForClaim=false')), 'should include optimization completion key file row');
+  assert(summary.rows.some(row => row.title.includes('优化完成度审计') && row.acceptance.includes('missingProofRequirementCount=2')), 'optimization completion row should expose missing proof requirement count');
+  assert(summary.rows.some(row => row.title.includes('优化完成度审计') && row.acceptance.includes('videoAbPreflightReadyForVideoAb=false')), 'optimization completion row should expose video A/B preflight readiness');
+  assert(summary.rows.some(row => row.title.includes('优化完成度审计') && row.acceptance.includes('videoAbPreflightFailureCount=4')), 'optimization completion row should expose video A/B preflight failure count');
+  assert(summary.rows.some(row => row.title.includes('优化完成度审计') && row.boundary.includes('不得宣称提号率提升')), 'optimization completion row should keep forbidden-claim boundary');
+  const missingBoundaryRows = summary.rows.filter(row => !boundaryOk(row.boundary));
+  assert(missingBoundaryRows.length === 0, `each row should carry proof boundary: ${JSON.stringify(missingBoundaryRows.map(row => ({ type: row.type, title: row.title, boundary: row.boundary })))}`);
+  assert(summary.rows.some(row => row.title.includes('A/B') && row.acceptance.includes('proofContext.mode=live')), 'video A/B row should expose missing proof requirements');
+  console.log(JSON.stringify({
+    ok: true,
+    rowCount: summary.rowCount,
+    repairRowCount: summary.repairRowCount,
+    fieldOwnerRowCount: summary.fieldOwnerRowCount,
+    recheckCommandRowCount: summary.recheckCommandRowCount
+  }, null, 2));
+}
+
+function seed(outputs) {
+  const nextActionsDir = path.join(outputs, 'business-proof-next-actions-latest');
+  const latestFormDir = path.join(outputs, 'latest-form-index-latest');
+  const proofFormDir = path.join(outputs, 'tihao-proof-gap-software-form-latest');
+  const closureDir = path.join(outputs, 'proof-gap-closure-latest');
+  const evidenceDir = path.join(outputs, 'evidence-index-latest');
+  const fieldChecklistDir = path.join(outputs, 'intake-field-checklist-latest');
+  const longRunReadinessDir = path.join(outputs, 'long-run-readiness-latest');
+  const optimizationCompletionDir = path.join(outputs, 'optimization-completion-latest');
+  const proofGapOperatorPackDir = path.join(outputs, 'proof-gap-operator-pack-latest');
+  for (const dir of [nextActionsDir, latestFormDir, proofFormDir, closureDir, evidenceDir, fieldChecklistDir, longRunReadinessDir, optimizationCompletionDir, proofGapOperatorPackDir]) fs.mkdirSync(dir, { recursive: true });
+  fs.mkdirSync(path.join(nextActionsDir, 'by-owner'), { recursive: true });
+  fs.mkdirSync(path.join(proofGapOperatorPackDir, 'by-owner'), { recursive: true });
+  fs.writeFileSync(path.join(nextActionsDir, 'by-owner', 'business.md'), '# 商务\n', 'utf8');
+  fs.writeFileSync(path.join(nextActionsDir, 'by-owner', 'business.csv'), '标题\n', 'utf8');
+  fs.writeFileSync(path.join(nextActionsDir, 'by-owner', 'business-media.md'), '# 商务/投放\n', 'utf8');
+  fs.writeFileSync(path.join(nextActionsDir, 'by-owner', 'business-media.csv'), '标题\n', 'utf8');
+  fs.writeFileSync(path.join(proofGapOperatorPackDir, 'by-owner', 'business.md'), '# 商务 proof-gap\n', 'utf8');
+  fs.writeFileSync(path.join(proofGapOperatorPackDir, 'by-owner', 'business.csv'), 'gapId\n', 'utf8');
+  fs.writeFileSync(path.join(proofGapOperatorPackDir, 'by-owner', 'business-media.md'), '# 商务/投放 proof-gap\n', 'utf8');
+  fs.writeFileSync(path.join(proofGapOperatorPackDir, 'by-owner', 'business-media.csv'), 'gapId\n', 'utf8');
+  fs.writeFileSync(path.join(proofGapOperatorPackDir, 'by-owner', 'tech-ai.md'), '# 技术/AI proof-gap\n', 'utf8');
+  fs.writeFileSync(path.join(proofGapOperatorPackDir, 'by-owner', 'tech-ai.csv'), 'gapId\n', 'utf8');
+
+  writeJson(path.join(nextActionsDir, 'business-proof-next-actions-summary.json'), {
+    complete: false,
+    sourceState: { proofGapOpenCount: 5 },
+    actions: [
+      { owner: '商务', title: '填真实历史 Brief', priority: 1, status: 'blocked_by_external_data', command: '填写 history-data-template.csv', expectedArtifact: 'history-data-template.csv', acceptance: 'historyReady=true' },
+      { owner: '商务/投放', title: '填真实视频资源', priority: 2, status: 'blocked_by_external_data', command: '填写 video-resource-template.csv', expectedArtifact: 'video-resource-template.csv', acceptance: 'readyForVideoAbPreflight=true' },
+      { owner: '技术/AI', title: '真实视频 A/B 验收', priority: 24, status: 'open', command: 'npm run acceptance:video-ab', expectedArtifact: 'video-hit-rate-summary.json', acceptance: '真实 provider 和真实视频资源下通过', missingProofRequirements: ['proofContext.mode=live', 'proofContext.generatedBy=acceptance:video-ab'] }
+    ],
+    ownerArtifacts: [
+      {
+        owner: '商务',
+        actionCount: 1,
+        markdown: 'by-owner/business.md',
+        csv: 'by-owner/business.csv',
+        repairArtifacts: [
+          { title: '历史数据/商务复核修复清单', csv: 'outputs/intake-readiness-latest/intake-readiness-repair-actions.csv', actionCount: 6, topPriority: 1 }
+        ]
+      },
+      {
+        owner: '商务/投放',
+        actionCount: 1,
+        markdown: 'by-owner/business-media.md',
+        csv: 'by-owner/business-media.csv',
+        repairArtifacts: [
+          { title: '视频资源修复清单', csv: 'outputs/video-resource-readiness-latest/video-resource-repair-actions.csv', actionCount: 1, topPriority: 1 }
+        ]
+      }
+    ]
+  });
+  writeJson(path.join(latestFormDir, 'latest-form-index-summary.json'), {
+    passed: true,
+    primaryForm: { csv: 'outputs/tihao-proof-gap-software-form-latest/tihao-proof-gap-software-form.csv', summary: 'outputs/tihao-proof-gap-software-form-latest/tihao-proof-gap-software-form-summary.json' },
+    clientList: { exists: true, csv: 'outputs/software-client-latest/software-client-list.final.csv', summary: 'outputs/software-client-latest/software-client-list.final.summary.json', rowCount: 28 },
+    handoff: { complete: false, report: 'outputs/optimization-handoff-latest/optimization-handoff-report.md', summary: 'outputs/optimization-handoff-latest/optimization-handoff-summary.json' }
+  });
+  fs.writeFileSync(path.join(latestFormDir, 'latest-form-index.md'), '# 最新提号表单索引\n', 'utf8');
+  writeJson(path.join(proofFormDir, 'tihao-proof-gap-software-form-summary.json'), {
+    passed: true,
+    rowCount: 5,
+    duplicateIdCount: 0
+  });
+  writeJson(path.join(fieldChecklistDir, 'intake-field-checklist-summary.json'), {
+    passed: true,
+    proofLevel: 'smoke_or_local',
+    directCustomerProof: false,
+    itemCount: 6,
+    missingRequiredCount: 2,
+    placeholderCount: 4,
+    ownerGroups: [
+      {
+        owner: '商务',
+        section: 'history',
+        itemCount: 3,
+        missingRequiredCount: 1,
+        placeholderCount: 2,
+        files: ['outputs/data-intake-pack-latest/history-data-template.csv'],
+        nextAction: '补真实历史 Brief',
+        acceptance: 'historyReady=true',
+        boundary: '补齐材料,不证明客户效果'
+      },
+      {
+        owner: '商务/投放',
+        section: 'video',
+        itemCount: 3,
+        missingRequiredCount: 1,
+        placeholderCount: 2,
+        files: ['outputs/video-intake-pack-latest/video-resource-template.csv'],
+        nextAction: '补真实视频 URL',
+        acceptance: 'videoReady=true',
+        boundary: '补齐材料,不证明客户效果'
+      }
+    ],
+    recheckCommands: [
+      { stage: '真实材料预审', owner: '技术/AI', command: 'npm run intake:readiness -- --data-pack outputs\\data-intake-pack-latest --video-pack outputs\\video-intake-pack-latest --output outputs\\intake-readiness-latest', acceptance: 'overallReady=true', boundary: '预审通过不证明客户效果' },
+      { stage: '刷新本轮交接', owner: '技术/AI', command: 'npm run round:refresh -- --output-root outputs --strict', acceptance: 'round-deposition.passed=true', boundary: '刷新交接不证明业务效果' },
+      { stage: '客户效果审计', owner: '技术/AI', command: 'npm run customer-effect:audit -- --review-csv <已标注CSV> --history-audit <historical-dataset-audit.json> --current-manual-supplement-count <本轮人工补号量> --output <客户效果输出目录> --strict', acceptance: 'overallPass=true', boundary: '缺客户选择不得宣称效果' },
+      { stage: '视频 A/B 验收', owner: '技术/AI', command: 'npm run acceptance:video-ab', acceptance: '真实 provider 下通过', boundary: 'sample 不证明视频提升提号率' }
+    ],
+    items: []
+  });
+  fs.writeFileSync(path.join(fieldChecklistDir, 'intake-field-checklist.md'), '# 真实材料字段级补齐核对表\n', 'utf8');
+  fs.writeFileSync(path.join(fieldChecklistDir, 'intake-field-checklist.csv'), 'ID,负责人,区域\n', 'utf8');
+  writeJson(path.join(closureDir, 'proof-gap-closure-summary.json'), {
+    complete: false,
+    openCount: 5
+  });
+  writeJson(path.join(evidenceDir, 'evidence-index-summary.json'), {
+    counts: { real_evidence: 1, smoke_or_local: 3, not_business_proof: 2 }
+  });
+  writeJson(path.join(proofGapOperatorPackDir, 'proof-gap-operator-pack-summary.json'), {
+    passed: true,
+    complete: false,
+    directCustomerProof: false,
+    proofLevel: 'not_business_proof',
+    openCount: 5,
+    rowCount: 5,
+    ownerArtifactCount: 3,
+    ownerArtifacts: [
+      {
+        owner: '商务',
+        actionCount: 2,
+        topPriority: 1,
+        markdown: 'by-owner/business.md',
+        csv: 'by-owner/business.csv',
+        gapIds: ['historical-dataset', 'manual-review-and-customer-effect'],
+        evidenceRowCount: 2,
+        nextActionCount: 2
+      },
+      {
+        owner: '商务/投放',
+        actionCount: 1,
+        topPriority: 2,
+        markdown: 'by-owner/business-media.md',
+        csv: 'by-owner/business-media.csv',
+        gapIds: ['video-real-candidate'],
+        evidenceRowCount: 1,
+        nextActionCount: 1
+      },
+      {
+        owner: '技术/AI',
+        actionCount: 2,
+        topPriority: 3,
+        markdown: 'by-owner/tech-ai.md',
+        csv: 'by-owner/tech-ai.csv',
+        gapIds: ['video-ab-live-proof', 'live-provider-overnight-proof'],
+        evidenceRowCount: 2,
+        nextActionCount: 2
+      }
+    ]
+  });
+  writeJson(path.join(longRunReadinessDir, 'long-run-readiness-summary.json'), {
+    mode: 'full-matrix',
+    ready: false,
+    counts: { pass: 1, fail: 5, warn: 0 }
+  });
+  fs.writeFileSync(path.join(longRunReadinessDir, 'long-run-readiness-report.md'), '# 提号长跑就绪度审计\n', 'utf8');
+  writeJson(path.join(optimizationCompletionDir, 'optimization-completion-summary.json'), {
+    complete: false,
+    readyForClaim: false,
+    proofGapOpenCount: 5,
+    blockingReasonCount: 2,
+    blockingReasonsWithMissingProofRequirements: 2,
+    missingProofRequirementCount: 2,
+    videoAbPreflightReadyForVideoAb: false,
+    videoAbPreflightFailureCount: 4,
+    blockingReasons: [
+      { id: 'proof-gap-closure', status: 'blocked', missingProofRequirements: ['proofGapClosure.openCount=0'] },
+      { id: 'longrun-readiness', status: 'blocked', missingProofRequirements: ['longRunReadiness.ready=true'] }
+    ],
+    canClaim: {
+      tihaoRateImproved: false,
+      customerEffectAchieved: false,
+      manualSupplementReduced: false
+    }
+  });
+  fs.writeFileSync(path.join(optimizationCompletionDir, 'optimization-completion-report.md'), '# 优化完成度审计\n', 'utf8');
+}
+
+function boundaryOk(value) {
+  const text = String(value || '');
+  return text.includes('不证明') ||
+    text.includes('不能证明') ||
+    text.includes('补证') ||
+    text.includes('只证明') ||
+    text.includes('不得宣称');
+}
+
+function writeJson(file, value) {
+  fs.mkdirSync(path.dirname(file), { recursive: true });
+  fs.writeFileSync(file, JSON.stringify(value, null, 2), 'utf8');
+}
+
+function assert(condition, message) {
+  if (!condition) throw new Error(message);
+}
+
+if (require.main === module) main();

+ 543 - 0
claude-code-tihao-sourcing/scripts/business-execution-index.js

@@ -0,0 +1,543 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const path = require('path');
+
+const ROOT = path.resolve(__dirname, '..');
+const DEFAULT_OUTPUT = path.join(ROOT, 'outputs', 'business-execution-index-latest');
+
+function main() {
+  const args = parseArgs(process.argv.slice(2));
+  const outputsDir = path.resolve(args.outputs || path.join(ROOT, 'outputs'));
+  const outputDir = path.resolve(args.output || DEFAULT_OUTPUT);
+  const summary = buildBusinessExecutionIndex({ root: ROOT, outputsDir });
+
+  fs.mkdirSync(outputDir, { recursive: true });
+  const jsonPath = path.join(outputDir, 'business-execution-index-summary.json');
+  const reportPath = path.join(outputDir, 'business-execution-index.md');
+  const csvPath = path.join(outputDir, 'business-execution-index.csv');
+  fs.writeFileSync(jsonPath, JSON.stringify(summary, null, 2), 'utf8');
+  fs.writeFileSync(reportPath, withBom(renderReport(summary)), 'utf8');
+  fs.writeFileSync(csvPath, withBom(renderCsv(summary.rows)), 'utf8');
+
+  console.log(JSON.stringify({
+    outputDir,
+    json: jsonPath,
+    report: reportPath,
+    csv: csvPath,
+    passed: summary.passed,
+    rowCount: summary.rowCount,
+    proofOpenCount: summary.proofOpenCount
+  }, null, 2));
+
+  if (args.strict && !summary.passed) process.exitCode = 1;
+}
+
+function buildBusinessExecutionIndex({ root, outputsDir }) {
+  const nextActionsDir = pickDir(outputsDir, 'business-proof-next-actions-latest', /^business-proof-next-actions-/);
+  const latestFormDir = pickDir(outputsDir, 'latest-form-index-latest', /^latest-form-index-/);
+  const proofFormDir = pickDir(outputsDir, 'tihao-proof-gap-software-form-latest', /^tihao-proof-gap-software-form-/);
+  const proofClosureDir = pickDir(outputsDir, 'proof-gap-closure-latest', /^proof-gap-closure-/);
+  const evidenceDir = pickDir(outputsDir, 'evidence-index-latest', /^evidence-index-/);
+  const fieldChecklistDir = pickDir(outputsDir, 'intake-field-checklist-latest', /^intake-field-checklist-/);
+  const longRunReadinessDir = pickDir(outputsDir, 'long-run-readiness-latest', /^long-run-readiness-/);
+  const optimizationCompletionDir = pickDir(outputsDir, 'optimization-completion-latest', /^optimization-completion-/);
+  const proofGapOperatorPackDir = pickDir(outputsDir, 'proof-gap-operator-pack-latest', /^proof-gap-operator-pack-/);
+
+  const nextActions = readJson(path.join(nextActionsDir, 'business-proof-next-actions-summary.json'));
+  const latestForm = readJson(path.join(latestFormDir, 'latest-form-index-summary.json'));
+  const proofForm = readJson(path.join(proofFormDir, 'tihao-proof-gap-software-form-summary.json'));
+  const closure = readJson(path.join(proofClosureDir, 'proof-gap-closure-summary.json'));
+  const evidence = readJson(path.join(evidenceDir, 'evidence-index-summary.json'));
+  const fieldChecklist = readJson(path.join(fieldChecklistDir, 'intake-field-checklist-summary.json'));
+  const longRunReadiness = readJson(path.join(longRunReadinessDir, 'long-run-readiness-summary.json'));
+  const optimizationCompletion = readJson(path.join(optimizationCompletionDir, 'optimization-completion-summary.json'));
+  const proofGapOperatorPack = readJson(path.join(proofGapOperatorPackDir, 'proof-gap-operator-pack-summary.json'));
+  const operatorPackOwnerArtifacts = normalizeProofGapOperatorOwnerArtifacts(root, proofGapOperatorPackDir, proofGapOperatorPack);
+
+  const rows = [
+    ...buildOwnerActionRows(root, nextActionsDir, nextActions),
+    ...buildRepairRows(root, nextActionsDir, nextActions),
+    ...buildProofGapOperatorOwnerRows(operatorPackOwnerArtifacts),
+    ...buildFieldChecklistOwnerRows(root, fieldChecklistDir, fieldChecklist),
+    ...buildFieldChecklistRecheckRows(root, fieldChecklistDir, fieldChecklist),
+    ...buildPrimaryFileRows(root, latestFormDir, latestForm, proofForm, fieldChecklistDir, fieldChecklist, longRunReadinessDir, longRunReadiness, optimizationCompletionDir, optimizationCompletion)
+  ].map(row => ({
+    ...row,
+    boundary: normalizeProofBoundary(row.boundary)
+  }));
+  const proofOpenCount = Number(nextActions?.sourceState?.proofGapOpenCount ?? closure?.openCount ?? latestForm?.nextActions?.proofOpenCount ?? 0);
+  const passed = rows.length > 0 && rows.every(row => row.owner && row.title && row.acceptance && row.boundary);
+
+  return {
+    generatedAt: new Date().toISOString(),
+    root,
+    outputsDir,
+    passed,
+    rowCount: rows.length,
+    actionRowCount: rows.filter(row => row.type === '负责人行动').length,
+    repairRowCount: rows.filter(row => row.type === '修复清单').length,
+    operatorPackOwnerArtifactRowCount: rows.filter(row => row.type === '操作包负责人附件').length,
+    operatorPackOwnerArtifactCount: operatorPackOwnerArtifacts.length,
+    operatorPackOwnerArtifacts,
+    fieldOwnerRowCount: rows.filter(row => row.type === '字段补齐负责人').length,
+    recheckCommandRowCount: rows.filter(row => row.type === '补齐后复验命令').length,
+    fileRowCount: rows.filter(row => row.type === '关键文件').length,
+    proofOpenCount,
+    closureComplete: Boolean(closure?.complete),
+    status: proofOpenCount === 0 && closure?.complete ? 'ready_for_claim_audit' : 'actions_open',
+    evidenceCounts: evidence?.counts || {},
+    proofLevel: 'smoke_or_local',
+    directCustomerProof: false,
+    files: {
+      report: 'outputs/business-execution-index-latest/business-execution-index.md',
+      csv: 'outputs/business-execution-index-latest/business-execution-index.csv',
+      summary: 'outputs/business-execution-index-latest/business-execution-index-summary.json'
+    },
+    sourceFiles: {
+      nextActions: rel(root, path.join(nextActionsDir, 'business-proof-next-actions-summary.json')),
+      latestFormIndex: rel(root, path.join(latestFormDir, 'latest-form-index-summary.json')),
+      proofGapSoftwareForm: rel(root, path.join(proofFormDir, 'tihao-proof-gap-software-form-summary.json')),
+      proofGapClosure: rel(root, path.join(proofClosureDir, 'proof-gap-closure-summary.json')),
+      intakeFieldChecklist: rel(root, path.join(fieldChecklistDir, 'intake-field-checklist-summary.json')),
+      longRunReadiness: rel(root, path.join(longRunReadinessDir, 'long-run-readiness-summary.json')),
+      optimizationCompletion: rel(root, path.join(optimizationCompletionDir, 'optimization-completion-summary.json')),
+      proofGapOperatorPack: rel(root, path.join(proofGapOperatorPackDir, 'proof-gap-operator-pack-summary.json')),
+      evidenceIndex: rel(root, path.join(evidenceDir, 'evidence-index-summary.json'))
+    },
+    guardrails: [
+      '本表只用于商务补证执行分派,不证明客户效果或命中率提升。',
+      'sample、smoke、模板、provider fallback、接口 200 都不能作为业务证明。',
+      '只有 proof-gap-closure openCount=0 且 customer-effect/video/live 真实审计通过后,才能进入效果宣称。',
+      'optimization-completion readyForClaim=false 时,本表不证明客户效果,不得宣称提号率提升、客户效果达标或人工补号量下降。',
+      '本表不得包含 sessionToken、Authorization、模型 token 或 npm token。'
+    ],
+    rows
+  };
+}
+
+function buildOwnerActionRows(root, nextActionsDir, nextActions) {
+  const actions = Array.isArray(nextActions?.actions) ? nextActions.actions : [];
+  return actions.map(action => {
+    const ownerArtifact = findOwnerArtifact(nextActions, action.owner);
+    return {
+      owner: action.owner || '未分配',
+      type: '负责人行动',
+      title: action.title || '',
+      priority: Number(action.priority || action.order || 0),
+      status: action.status || 'open',
+      mainFile: ownerArtifact?.markdown ? rel(root, path.join(nextActionsDir, ownerArtifact.markdown)) : rel(root, path.join(nextActionsDir, 'business-proof-next-actions-report.md')),
+      csv: ownerArtifact?.csv ? rel(root, path.join(nextActionsDir, ownerArtifact.csv)) : rel(root, path.join(nextActionsDir, 'business-proof-next-actions.csv')),
+      repairFile: '',
+      actionCount: 1,
+      command: action.command || '',
+      expectedArtifact: action.expectedArtifact || '',
+      acceptance: appendMissingProofRequirements(action.acceptance || '', action.missingProofRequirements),
+      boundary: '补证任务,不证明业务效果'
+    };
+  });
+}
+
+function buildRepairRows(root, nextActionsDir, nextActions) {
+  const ownerArtifacts = Array.isArray(nextActions?.ownerArtifacts) ? nextActions.ownerArtifacts : [];
+  return ownerArtifacts.flatMap(owner => {
+    const repairs = Array.isArray(owner.repairArtifacts) ? owner.repairArtifacts : [];
+    const ownerMarkdown = owner.markdown ? rel(root, path.join(nextActionsDir, owner.markdown)) : '';
+    const ownerCsv = owner.csv ? rel(root, path.join(nextActionsDir, owner.csv)) : '';
+    return repairs.map(repair => ({
+      owner: owner.owner || repair.owner || '未分配',
+      type: '修复清单',
+      title: repair.title || '修复清单',
+      priority: Number(repair.topPriority || 0),
+      status: Number(repair.actionCount || 0) > 0 ? 'actions_open' : 'empty',
+      mainFile: ownerMarkdown,
+      csv: ownerCsv,
+      repairFile: repair.csv || '',
+      actionCount: Number(repair.actionCount || 0),
+      command: '按修复清单补齐字段后重新运行 round:refresh',
+      expectedArtifact: repair.csv || '',
+      acceptance: Number(repair.actionCount || 0) > 0 ? `修复动作数=${Number(repair.actionCount || 0)},补齐后对应 readiness failureCount=0` : '无修复动作',
+      boundary: '修复清单是补证执行材料,不证明客户效果'
+    }));
+  });
+}
+
+function buildProofGapOperatorOwnerRows(ownerArtifacts) {
+  return ownerArtifacts.map(item => ({
+    owner: item.owner || '未分配',
+    type: '操作包负责人附件',
+    title: 'proof-gap 操作包负责人附件',
+    priority: Number(item.topPriority || 0),
+    status: Number(item.actionCount || 0) > 0 ? 'actions_open' : 'empty',
+    mainFile: item.markdown || '',
+    csv: item.csv || '',
+    repairFile: item.csv || '',
+    actionCount: Number(item.actionCount || 0),
+    command: '打开负责人附件,按 gapId 补齐当前证据和下一步动作后重新运行 proof-gap:operator-pack 与 round:refresh',
+    expectedArtifact: [item.markdown, item.csv].filter(Boolean).join(';'),
+    acceptance: [
+      `actionCount=${Number(item.actionCount || 0)}`,
+      `evidenceRowCount=${Number(item.evidenceRowCount || 0)}`,
+      `nextActionCount=${Number(item.nextActionCount || 0)}`,
+      `gapIds=${(item.gapIds || []).join(';') || 'none'}`
+    ].join(';'),
+    boundary: '负责人附件只用于补证派工,不证明客户效果'
+  }));
+}
+
+function buildFieldChecklistOwnerRows(root, fieldChecklistDir, fieldChecklist) {
+  const groups = Array.isArray(fieldChecklist?.ownerGroups) ? fieldChecklist.ownerGroups : [];
+  return groups.map((group, index) => ({
+    owner: group.owner || '未分配',
+    type: '字段补齐负责人',
+    title: `${sectionLabel(group.section)}字段补齐`,
+    priority: index + 1,
+    status: Number(group.missingRequiredCount || 0) > 0 || Number(group.placeholderCount || 0) > 0 ? 'actions_open' : 'ready',
+    mainFile: rel(root, path.join(fieldChecklistDir, 'intake-field-checklist.md')),
+    csv: rel(root, path.join(fieldChecklistDir, 'intake-field-checklist.csv')),
+    repairFile: Array.isArray(group.files) ? group.files.join(';') : '',
+    actionCount: Number(group.itemCount || 0),
+    command: group.nextAction || '按字段核对表补齐真实材料',
+    expectedArtifact: Array.isArray(group.files) ? group.files.join(';') : rel(root, path.join(fieldChecklistDir, 'intake-field-checklist-summary.json')),
+    acceptance: [
+      `字段项=${Number(group.itemCount || 0)}`,
+      `缺必填=${Number(group.missingRequiredCount || 0)}`,
+      `占位=${Number(group.placeholderCount || 0)}`,
+      group.acceptance || ''
+    ].filter(Boolean).join(';'),
+    boundary: group.boundary || '补齐材料,不证明客户效果'
+  }));
+}
+
+function buildFieldChecklistRecheckRows(root, fieldChecklistDir, fieldChecklist) {
+  const commands = Array.isArray(fieldChecklist?.recheckCommands) ? fieldChecklist.recheckCommands : [];
+  return commands.map((item, index) => ({
+    owner: item.owner || '技术/AI',
+    type: '补齐后复验命令',
+    title: item.stage || `复验命令 ${index + 1}`,
+    priority: index + 1,
+    status: 'pending_after_fill',
+    mainFile: rel(root, path.join(fieldChecklistDir, 'intake-field-checklist.md')),
+    csv: rel(root, path.join(fieldChecklistDir, 'intake-field-checklist.csv')),
+    repairFile: '',
+    actionCount: 1,
+    command: item.command || '',
+    expectedArtifact: rel(root, path.join(fieldChecklistDir, 'intake-field-checklist-summary.json')),
+    acceptance: item.acceptance || '复验命令通过',
+    boundary: item.boundary || '复验通过不证明客户效果'
+  }));
+}
+
+function buildPrimaryFileRows(root, latestFormDir, latestForm, proofForm, fieldChecklistDir, fieldChecklist, longRunReadinessDir, longRunReadiness, optimizationCompletionDir, optimizationCompletion) {
+  const rows = [];
+  if (latestForm?.primaryForm?.csv || proofForm) {
+    rows.push({
+      owner: '商务',
+      type: '关键文件',
+      title: '最新提号补证表单',
+      priority: 0,
+      status: proofForm?.passed ? 'pass' : 'not_ready',
+      mainFile: latestForm?.primaryForm?.csv || 'outputs/tihao-proof-gap-software-form-latest/tihao-proof-gap-software-form.csv',
+      csv: latestForm?.primaryForm?.csv || '',
+      repairFile: '',
+      actionCount: Number(proofForm?.rowCount || 0),
+      command: '打开 CSV 分派补证任务',
+      expectedArtifact: latestForm?.primaryForm?.summary || '',
+      acceptance: `rowCount=${Number(proofForm?.rowCount || 0)},duplicateId=${Number(proofForm?.duplicateIdCount || 0)}`,
+      boundary: '补证表单不证明客户效果'
+    });
+  }
+  if (latestForm?.clientList?.csv) {
+    rows.push({
+      owner: '商务',
+      type: '关键文件',
+      title: '最新软件端候选名单',
+      priority: 0,
+      status: latestForm.clientList.exists === false ? 'missing' : 'ready',
+      mainFile: latestForm.clientList.csv,
+      csv: latestForm.clientList.csv,
+      repairFile: '',
+      actionCount: Number(latestForm.clientList.rowCount || 0),
+      command: '用于商务复核候选博主',
+      expectedArtifact: latestForm.clientList.summary || '',
+      acceptance: `候选行数=${Number(latestForm.clientList.rowCount || 0)},仍需人工复核和客户反馈`,
+      boundary: '候选名单不证明命中率提升'
+    });
+  }
+  if (fieldChecklist?.passed) {
+    rows.push({
+      owner: '商务',
+      type: '关键文件',
+      title: '真实材料字段级补齐核对表',
+      priority: 0,
+      status: Number(fieldChecklist.itemCount || 0) > 0 ? 'actions_open' : 'empty',
+      mainFile: rel(root, path.join(fieldChecklistDir, 'intake-field-checklist.md')),
+      csv: rel(root, path.join(fieldChecklistDir, 'intake-field-checklist.csv')),
+      repairFile: rel(root, path.join(fieldChecklistDir, 'intake-field-checklist.csv')),
+      actionCount: Number(fieldChecklist.itemCount || 0),
+      command: '按字段级核对表逐格补齐真实材料后重新运行 round:refresh',
+      expectedArtifact: rel(root, path.join(fieldChecklistDir, 'intake-field-checklist-summary.json')),
+      acceptance: `itemCount=${Number(fieldChecklist.itemCount || 0)},placeholder=${Number(fieldChecklist.placeholderCount || 0)},missingRequired=${Number(fieldChecklist.missingRequiredCount || 0)}`,
+      boundary: '字段级核对表只指导补齐材料,不证明客户效果'
+    });
+  }
+  if (longRunReadiness || fs.existsSync(path.join(longRunReadinessDir, 'long-run-readiness-report.md'))) {
+    const failCount = Number(longRunReadiness?.counts?.fail ?? longRunReadiness?.failCount ?? 0);
+    rows.push({
+      owner: '技术/AI',
+      type: '关键文件',
+      title: '长跑前置门禁报告',
+      priority: 0,
+      status: longRunReadiness?.ready ? 'ready' : 'blocked',
+      mainFile: rel(root, path.join(longRunReadinessDir, 'long-run-readiness-report.md')),
+      csv: '',
+      repairFile: '',
+      actionCount: failCount,
+      command: '补齐真实材料后运行 longrun:readiness,再判断是否允许启动 full-matrix/live 长跑',
+      expectedArtifact: rel(root, path.join(longRunReadinessDir, 'long-run-readiness-summary.json')),
+      acceptance: `ready=${Boolean(longRunReadiness?.ready)},failCount=${failCount};ready=true 且 failCount=0 后才允许进入真实 live/provider 长跑启动判断。`,
+      boundary: '长跑前置门禁只判断启动条件,不证明客户效果;ready=false 时不能启动或宣称 full-matrix/live 长跑完成。'
+    });
+  }
+  if (optimizationCompletion || fs.existsSync(path.join(optimizationCompletionDir, 'optimization-completion-report.md'))) {
+    const blockingReasonCount = Number(optimizationCompletion?.blockingReasonCount ?? (Array.isArray(optimizationCompletion?.blockingReasons)
+      ? optimizationCompletion.blockingReasons.length
+      : 0));
+    const missingProofRequirementCount = Number(optimizationCompletion?.missingProofRequirementCount ?? uniqueMissingProofRequirementCount(optimizationCompletion?.blockingReasons));
+    const proofGapOpenCount = Number(optimizationCompletion?.proofGapOpenCount || 0);
+    const videoAbPreflightReadyForVideoAb = Boolean(optimizationCompletion?.videoAbPreflightReadyForVideoAb);
+    const videoAbPreflightFailureCount = Number.isFinite(Number(optimizationCompletion?.videoAbPreflightFailureCount))
+      ? Number(optimizationCompletion.videoAbPreflightFailureCount)
+      : null;
+    rows.push({
+      owner: '技术/AI',
+      type: '关键文件',
+      title: '优化完成度审计报告',
+      priority: 0,
+      status: optimizationCompletion?.readyForClaim ? 'ready_for_claim' : 'blocked',
+      mainFile: rel(root, path.join(optimizationCompletionDir, 'optimization-completion-report.md')),
+      csv: '',
+      repairFile: '',
+      actionCount: blockingReasonCount,
+      command: '补齐真实材料和证明缺口后运行 optimization:completion,再判断是否允许声明长期目标完成',
+      expectedArtifact: rel(root, path.join(optimizationCompletionDir, 'optimization-completion-summary.json')),
+      acceptance: `complete=${Boolean(optimizationCompletion?.complete)},readyForClaim=${Boolean(optimizationCompletion?.readyForClaim)},proofGapOpenCount=${proofGapOpenCount},blockingReasons=${blockingReasonCount},missingProofRequirementCount=${missingProofRequirementCount},videoAbPreflightReadyForVideoAb=${videoAbPreflightReadyForVideoAb},videoAbPreflightFailureCount=${videoAbPreflightFailureCount ?? 'unknown'}`,
+      boundary: '优化完成度审计只判断能否进入完成声明,不证明客户效果;readyForClaim=false 时不得宣称提号率提升、客户效果达标或人工补号量下降。'
+    });
+  }
+  if (latestForm?.handoff?.report) {
+    rows.push({
+      owner: '技术/AI',
+      type: '关键文件',
+      title: '下一轮交接摘要',
+      priority: 0,
+      status: latestForm.handoff.complete ? 'complete_reported' : 'incomplete_reported',
+      mainFile: latestForm.handoff.report,
+      csv: '',
+      repairFile: '',
+      actionCount: 1,
+      command: '下一轮 AI 先读 handoff 和本执行总表',
+      expectedArtifact: latestForm.handoff.summary || '',
+      acceptance: `handoff.complete=${Boolean(latestForm.handoff.complete)}`,
+      boundary: '交接摘要不证明客户效果'
+    });
+  } else if (fs.existsSync(path.join(latestFormDir, 'latest-form-index.md'))) {
+    rows.push({
+      owner: '技术/AI',
+      type: '关键文件',
+      title: '最新表单索引',
+      priority: 0,
+      status: latestForm?.passed ? 'pass' : 'not_ready',
+      mainFile: rel(root, path.join(latestFormDir, 'latest-form-index.md')),
+      csv: '',
+      repairFile: '',
+      actionCount: 1,
+      command: '定位最新补证表单、候选名单和负责人文件',
+      expectedArtifact: rel(root, path.join(latestFormDir, 'latest-form-index-summary.json')),
+      acceptance: `latestFormIndex.passed=${Boolean(latestForm?.passed)}`,
+      boundary: '索引不证明客户效果'
+    });
+  }
+  return rows;
+}
+
+function renderReport(summary) {
+  const lines = [
+    '# 商务补证执行总表',
+    '',
+    `- 生成时间:${summary.generatedAt}`,
+    `- 是否通过:${summary.passed ? '是' : '否'}`,
+    `- 当前状态:${summary.status}`,
+    `- 未关闭证明缺口:${summary.proofOpenCount}`,
+    `- 行数:${summary.rowCount}`,
+    '',
+    '## 执行总表',
+    '',
+    '| 负责人 | 类型 | 标题 | 优先级 | 状态 | 主文件 | 修复清单 | 动作数 | 验收标准 | 边界 |',
+    '| --- | --- | --- | ---: | --- | --- | --- | ---: | --- | --- |',
+    ...summary.rows.map(row => tableRow([
+      row.owner,
+      row.type,
+      row.title,
+      row.priority,
+      row.status,
+      row.mainFile || row.csv || '',
+      row.repairFile || '',
+      row.actionCount,
+      row.acceptance,
+      row.boundary
+    ])),
+    '',
+    '## 来源',
+    '',
+    ...Object.entries(summary.sourceFiles).map(([key, value]) => `- ${key}:${value || '缺失'}`),
+    '',
+    '## 防误用边界',
+    '',
+    ...summary.guardrails.map(item => `- ${item}`)
+  ];
+  return lines.join('\n');
+}
+
+function renderCsv(rows) {
+  const headers = ['负责人', '类型', '标题', '优先级', '状态', '主文件', '负责人CSV', '修复清单', '动作数', '命令/动作', '预期产物', '验收标准', '边界'];
+  return [
+    headers.join(','),
+    ...rows.map(row => [
+      row.owner,
+      row.type,
+      row.title,
+      row.priority,
+      row.status,
+      row.mainFile,
+      row.csv,
+      row.repairFile,
+      row.actionCount,
+      row.command,
+      row.expectedArtifact,
+      row.acceptance,
+      row.boundary
+    ].map(csvCell).join(','))
+  ].join('\n');
+}
+
+function appendMissingProofRequirements(acceptance, missingProofRequirements) {
+  const items = Array.isArray(missingProofRequirements) ? missingProofRequirements.filter(Boolean) : [];
+  if (!items.length) return acceptance;
+  return `${acceptance}${acceptance ? ';' : ''}缺失证明:${items.join(';')}`;
+}
+
+function normalizeProofBoundary(boundary) {
+  const text = String(boundary || '').trim();
+  const fallback = '不证明客户效果';
+  if (!text) return fallback;
+  if (text.includes('不证明') || text.includes('不能证明')) return text;
+  if (text.includes('不得宣称') || text.includes('不得宣布') || text.includes('只证明')) {
+    return `${text};${fallback}`;
+  }
+  return `${text};${fallback}`;
+}
+
+function uniqueMissingProofRequirementCount(rows) {
+  if (!Array.isArray(rows)) return 0;
+  const values = new Set();
+  for (const row of rows) {
+    const requirements = Array.isArray(row.missingProofRequirements) ? row.missingProofRequirements : [];
+    for (const requirement of requirements) {
+      if (requirement) values.add(String(requirement));
+    }
+  }
+  return values.size;
+}
+
+function sectionLabel(section) {
+  const value = String(section || '');
+  if (value === 'history') return '历史 Brief';
+  if (value === 'video') return '视频资源';
+  if (value === 'review') return '商务复核';
+  return value || '真实材料';
+}
+
+function findOwnerArtifact(summary, owner) {
+  return (summary?.ownerArtifacts || []).find(item => item.owner === owner);
+}
+
+function normalizeProofGapOperatorOwnerArtifacts(root, dir, summary) {
+  const artifacts = Array.isArray(summary?.ownerArtifacts) ? summary.ownerArtifacts : [];
+  return artifacts.map(item => ({
+    owner: item.owner || '',
+    actionCount: Number(item.actionCount || 0),
+    topPriority: Number(item.topPriority || 0),
+    markdown: artifactRef(root, dir, item.markdown || ''),
+    csv: artifactRef(root, dir, item.csv || ''),
+    gapIds: Array.isArray(item.gapIds) ? item.gapIds.filter(Boolean) : [],
+    evidenceRowCount: Number(item.evidenceRowCount || 0),
+    nextActionCount: Number(item.nextActionCount || 0)
+  }));
+}
+
+function artifactRef(root, dir, file) {
+  const value = String(file || '').replace(/\\/g, '/');
+  if (!value) return '';
+  if (path.isAbsolute(file)) return rel(root, file);
+  if (value.startsWith('outputs/')) return value;
+  return rel(root, path.join(dir, file));
+}
+
+function pickDir(outputsDir, latestName, pattern) {
+  const latest = path.join(outputsDir, latestName);
+  if (fs.existsSync(latest)) return latest;
+  if (!fs.existsSync(outputsDir)) return latest;
+  const dirs = fs.readdirSync(outputsDir, { withFileTypes: true })
+    .filter(entry => entry.isDirectory() && pattern.test(entry.name))
+    .map(entry => path.join(outputsDir, entry.name))
+    .sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
+  return dirs[0] || latest;
+}
+
+function readJson(file) {
+  if (!fs.existsSync(file)) return null;
+  return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, ''));
+}
+
+function rel(root, file) {
+  return file ? path.relative(root, file).replace(/\\/g, '/') : '';
+}
+
+function tableRow(cells) {
+  return `| ${cells.map(escapeCell).join(' | ')} |`;
+}
+
+function escapeCell(value) {
+  return String(value ?? '').replace(/\|/g, '/').replace(/\r?\n/g, ' ');
+}
+
+function csvCell(value) {
+  const text = String(value ?? '');
+  return /[",\r\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
+}
+
+function parseArgs(argv) {
+  const result = {};
+  for (let index = 0; index < argv.length; index += 1) {
+    const arg = argv[index];
+    if (!arg.startsWith('--')) continue;
+    const key = arg.slice(2).replace(/-([a-z])/g, (_, char) => char.toUpperCase());
+    const next = argv[index + 1];
+    if (!next || next.startsWith('--')) result[key] = true;
+    else {
+      result[key] = next;
+      index += 1;
+    }
+  }
+  return result;
+}
+
+function withBom(text) {
+  return `\uFEFF${text}`;
+}
+
+if (require.main === module) main();
+
+module.exports = {
+  buildBusinessExecutionIndex
+};

+ 132 - 0
claude-code-tihao-sourcing/scripts/business-proof-next-actions-missing-proof-smoke.js

@@ -0,0 +1,132 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const { spawnSync } = require('child_process');
+
+const root = path.resolve(__dirname, '..');
+
+function main() {
+  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-next-actions-missing-proof-'));
+  const outputs = path.join(tmp, 'outputs');
+  fs.mkdirSync(outputs, { recursive: true });
+
+  writeJson(path.join(outputs, 'business-proof-progress-latest', 'business-proof-progress-summary.json'), {
+    complete: false,
+    counts: { pass: 0, blocked_by_external_data: 1, fail: 0, pending: 0 },
+    rows: []
+  });
+  writeJson(path.join(outputs, 'proof-gap-closure-latest', 'proof-gap-closure-summary.json'), {
+    complete: false,
+    openCount: 1,
+    rows: [
+      {
+        id: 'video-ab-live-proof',
+        title: '真实视频 A/B 验收',
+        status: 'open',
+        evidence: 'passed=true, mode=unknown, generatedBy=unknown, provider=ok',
+        command: 'npm run acceptance:video-ab',
+        expectedArtifact: '<视频A/B输出目录>/video-hit-rate-summary.json',
+        required: 'acceptance:video-ab 必须在真实 provider 和真实视频资源下通过。',
+        missingProofRequirements: [
+          'proofContext.mode=live',
+          'proofContext.collectionMode=live',
+          'proofContext.generatedBy=acceptance:video-ab'
+        ]
+      }
+    ]
+  });
+  writeJson(path.join(outputs, 'optimization-pipeline-latest', 'optimization-pipeline-summary.json'), {
+    generatedAt: '2026-06-07T08:59:39.964Z',
+    readyForClaim: false,
+    counts: { fail: 1 },
+    nextActions: [
+      {
+        priority: 1,
+        owner: '技术/AI',
+        title: '过期 pipeline 视频 A/B 动作',
+        step: 'video-ab-proof',
+        command: 'stale pipeline command should not dispatch',
+        expectedArtifact: 'stale/video-hit-rate-summary.json',
+        acceptance: 'stale pipeline should be rerun first'
+      }
+    ]
+  });
+  writeJson(path.join(outputs, 'optimization-completion-latest', 'optimization-completion-summary.json'), {
+    complete: false,
+    readyForClaim: false,
+    proofGapOpenCount: 1,
+    blockingReasons: [
+      {
+        id: 'optimization-pipeline',
+        evidence: 'readyForClaim=false, stale=true, stale optimization-pipeline-summary.json: generatedAt=2026-06-07T08:59:39.964Z older than latest proof source=2026-06-12T04:21:28.840Z',
+        missingProofRequirements: [
+          'optimizationPipeline.generatedAt>=currentProofSources',
+          'optimizationPipeline.readyForClaim=true'
+        ],
+        command: 'npm run optimization:pipeline -- --data-pack outputs\\data-intake-pack-latest --video-pack outputs\\video-intake-pack-latest --current-manual-supplement-count <本轮人工补号量> --output <输出目录> --strict'
+      }
+    ]
+  });
+
+  const outputDir = path.join(outputs, 'business-proof-next-actions-latest');
+  const result = spawnSync(process.execPath, [
+    path.join(root, 'scripts', 'business-proof-next-actions.js'),
+    '--outputs',
+    outputs,
+    '--output',
+    outputDir
+  ], { cwd: root, encoding: 'utf8' });
+  if (result.status !== 0) {
+    process.stderr.write(result.stderr || result.stdout || '');
+    throw new Error('business-proof-next-actions should run');
+  }
+
+  const summary = readJson(path.join(outputDir, 'business-proof-next-actions-summary.json'));
+  const report = readText(path.join(outputDir, 'business-proof-next-actions-report.md'));
+  const csv = readText(path.join(outputDir, 'business-proof-next-actions.csv'));
+  const ownerReport = readText(path.join(outputDir, 'by-owner', 'tech-ai.md'));
+  const videoAction = summary.actions.find(action => action.id === 'gap-video-ab-live-proof');
+
+  assert(summary.actionCount === 1, 'summary should expose top-level actionCount for stale-pipeline-only fixture');
+  assert(Array.isArray(summary.topActions) && summary.topActions.length === 1, 'summary should expose top-level topActions for current dispatch');
+  assert(summary.topActions[0].id === 'gap-video-ab-live-proof', 'topActions should keep the current video A/B action');
+  assert(summary.sourceState.pipelineStale === true, 'next actions should expose stale pipeline state from completion audit');
+  assert(summary.sourceState.pipelineNextActionCount === 0, 'stale pipeline next actions should not count as current dispatch');
+  assert(summary.sourceState.pipelineRawNextActionCount === 1, 'next actions should retain raw stale pipeline action count for audit');
+  assert(summary.sourceState.pipelineSuppressedNextActionCount === 1, 'next actions should expose suppressed stale pipeline action count');
+  assert(String(summary.sourceState.pipelineStaleReason || '').includes('stale=true'), 'next actions should expose stale pipeline reason');
+  assert(!summary.actions.some(action => action.command === 'stale pipeline command should not dispatch'), 'stale pipeline next action should not dispatch');
+  assert(videoAction, 'video A/B action should exist');
+  assert(Array.isArray(videoAction.missingProofRequirements), 'video A/B action should keep missingProofRequirements');
+  assert(videoAction.missingProofRequirements.includes('proofContext.mode=live'), 'video A/B action should keep missing mode requirement');
+  assert(videoAction.next.includes('proofContext.generatedBy=acceptance:video-ab'), 'video A/B next step should expose generatedBy requirement');
+  assert(report.includes('proofContext.collectionMode=live'), 'main report should expose missing collectionMode requirement');
+  assert(report.includes('pipeline.stale:true'), 'main report should expose stale pipeline state');
+  assert(report.includes('pipeline.suppressedNextActionCount:1'), 'main report should expose suppressed stale pipeline count');
+  assert(ownerReport.includes('proofContext.mode=live'), 'owner report should expose missing mode requirement');
+  assert(csv.includes('proofContext.generatedBy=acceptance:video-ab'), 'CSV should expose missing generatedBy requirement');
+  assert(!/sk-[A-Za-z0-9_-]{10,}/.test(report + csv + ownerReport), 'outputs should not contain model token patterns');
+  assert(!/r:[A-Za-z0-9]{20,}/.test(report + csv + ownerReport), 'outputs should not contain Parse sessionToken patterns');
+
+  console.log(JSON.stringify({ ok: true, outputDir, missingCount: videoAction.missingProofRequirements.length }, null, 2));
+}
+
+function writeJson(file, value) {
+  fs.mkdirSync(path.dirname(file), { recursive: true });
+  fs.writeFileSync(file, JSON.stringify(value, null, 2), 'utf8');
+}
+
+function readJson(file) {
+  return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, ''));
+}
+
+function readText(file) {
+  return fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, '');
+}
+
+function assert(condition, message) {
+  if (!condition) throw new Error(message);
+}
+
+main();

+ 275 - 0
claude-code-tihao-sourcing/scripts/business-proof-next-actions-smoke.js

@@ -0,0 +1,275 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const { spawnSync } = require('child_process');
+
+const root = path.resolve(__dirname, '..');
+
+function main() {
+  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-business-proof-next-actions-'));
+  const outputs = path.join(tmp, 'outputs');
+  fs.mkdirSync(outputs, { recursive: true });
+  writeJson(path.join(outputs, 'business-proof-progress-latest', 'business-proof-progress-summary.json'), {
+    complete: false,
+    counts: { pass: 4, blocked_by_external_data: 7, fail: 2, pending: 2 },
+    rows: [
+      { order: 3, owner: '商务', title: '填真实历史 Brief', status: 'blocked_by_external_data', evidence: 'historyReady=false, issues=4', next: '补真实历史 Brief。' },
+      { order: 4, owner: '商务/投放', title: '填真实视频资源', status: 'blocked_by_external_data', evidence: 'realCandidate=0, failureCount=1', next: '补真实候选视频。' },
+      { order: 5, owner: '技术/AI', title: '真实材料预审', status: 'fail', evidence: 'overallReady=false, failureCount=9', next: '重跑 intake。' },
+      { order: 12, owner: '商务', title: '人工复核标注', status: 'blocked_by_external_data', evidence: 'reviewMetricsReady=false, customerEffectReady=false', next: '补人工复核。' },
+      { order: 14, owner: '技术/AI', title: '客户效果审计', status: 'blocked_by_external_data', evidence: 'missing customer-effect-summary.json', next: '补客户选择。' }
+    ]
+  });
+  writeJson(path.join(outputs, 'proof-gap-closure-latest', 'proof-gap-closure-summary.json'), {
+    complete: false,
+    openCount: 4,
+    rows: [
+      {
+        id: 'historical-dataset',
+        title: '真实历史 Brief 数据集',
+        status: 'open',
+        evidence: 'missing historical-dataset-audit.json',
+        command: 'npm run history:audit',
+        expectedArtifact: 'historical-dataset-audit.json',
+        required: 'readyForCustomerEffectProof=true',
+        missingProofRequirements: ['missing historical-dataset-audit.json']
+      },
+      {
+        id: 'video-real-candidate',
+        title: '真实候选视频资源',
+        status: 'open',
+        evidence: 'realCandidate=0',
+        command: 'npm run video:resource-readiness',
+        expectedArtifact: 'video-resource-readiness-summary.json',
+        required: 'readyForVideoAbPreflight=true',
+        missingProofRequirements: ['acceptance.readyForVideoAbPreflight=true', 'counts.realCandidateRows>=1']
+      },
+      {
+        id: 'manual-review-and-customer-effect',
+        title: '商务复核和客户效果证明',
+        status: 'open',
+        evidence: 'missing customer-effect-summary.json',
+        command: 'npm run customer-effect:audit',
+        expectedArtifact: 'customer-effect-summary.json',
+        required: 'customer-effect:audit overallPass=true',
+        missingProofRequirements: ['missing customer-effect-summary.json']
+      }
+    ]
+  });
+  writeJson(path.join(outputs, 'intake-readiness-latest', 'intake-readiness-summary.json'), {
+    failureCount: 3,
+    history: { issues: [{ message: '真实历史 Brief 数不足:1/5。' }] },
+    repairActions: [
+      {
+        priority: 1,
+        section: 'history',
+        type: 'min-briefs',
+        owner: '商务',
+        file: 'outputs/data-intake-pack-latest/history-data-template.csv',
+        field: 'brief编号',
+        action: '补齐至少 5 个真实历史 Brief。',
+        acceptance: 'historyReady=true。'
+      },
+      {
+        priority: 2,
+        section: 'review',
+        type: 'customer-decision',
+        owner: '商务',
+        file: 'outputs/data-intake-pack-latest/manual-review-template.csv',
+        field: '客户选择',
+        action: '补齐客户最终选中/拒绝记录。',
+        acceptance: 'customerEffectReady=true。'
+      }
+    ],
+    issues: [
+      { section: 'history', type: 'min-briefs', message: '真实历史 Brief 数不足:1/5。' },
+      { section: 'video', type: 'real-candidate', message: '缺少真实候选视频资源。' },
+      { section: 'review', type: 'customer-decision', message: '人工复核表至少需要客户最终选中/拒绝记录。' }
+    ]
+  });
+  writeJson(path.join(outputs, 'video-resource-readiness-latest', 'video-resource-readiness-summary.json'), {
+    failureCount: 1,
+    repairActions: [
+      {
+        priority: 1,
+        owner: '商务/投放',
+        type: 'missing-real-candidate',
+        file: 'outputs/video-intake-pack-latest/video-resource-template.csv',
+        field: '资源角色/是否真实资源',
+        action: '补真实候选视频。',
+        acceptance: 'hasRealCandidateResource=true。'
+      }
+    ],
+    issues: [{ message: '缺少标记为真实资源的候选视频。' }]
+  });
+  writeText(path.join(outputs, 'intake-readiness-latest', 'intake-readiness-repair-actions.csv'), '优先级,负责人,字段,修复动作\n1,商务,brief编号,补齐至少 5 个真实历史 Brief\n');
+  writeText(path.join(outputs, 'video-resource-readiness-latest', 'video-resource-repair-actions.csv'), '优先级,负责人,字段,修复动作\n1,商务/投放,资源角色/是否真实资源,补真实候选视频\n');
+  writeJson(path.join(outputs, 'homepage-evidence-readiness-latest', 'homepage-evidence-readiness-summary.json'), {
+    ready: false,
+    directCustomerProof: false,
+    proofLevel: 'not_business_proof',
+    failureCount: 2,
+    repairActions: [
+      {
+        priority: 1,
+        owner: '技术/AI',
+        type: 'missing-provider-evidence',
+        file: 'outputs/software-client-latest/software-client-list.final.csv',
+        field: 'homepageEvidence.source',
+        action: '接入真实主页近期内容 provider 或提供 path/local JSON 证据。',
+        acceptance: '至少 1 个 creator 的 evidenceSource 为 provider/path/api。'
+      },
+      {
+        priority: 2,
+        owner: '商务/投放',
+        type: 'missing-recent-posts',
+        file: 'outputs/software-client-latest/software-client-list.final.csv',
+        field: 'recentPosts',
+        action: '补最近 10/20 篇内容列表。',
+        acceptance: 'recentPosts 条数大于 0。'
+      }
+    ]
+  });
+  writeText(path.join(outputs, 'homepage-evidence-readiness-latest', 'homepage-evidence-repair-actions.csv'), '优先级,负责人,字段,修复动作\n1,技术/AI,homepageEvidence.source,接入真实主页近期内容 provider\n2,商务/投放,recentPosts,补最近 10/20 篇内容列表\n');
+  writeJson(path.join(outputs, 'optimization-pipeline-latest', 'optimization-pipeline-summary.json'), {
+    readyForClaim: false,
+    counts: { pass: 5, fail: 3 },
+    nextActions: [
+      {
+        priority: 3,
+        owner: '商务',
+        title: '补齐历史数据审计字段',
+        step: 'history-audit',
+        command: '补齐真实 Brief、参考链接、人工名单、客户选择、拒绝原因和历史人工补号量基线后重跑 history:audit --strict。',
+        expectedArtifact: 'history-audit/historical-dataset-audit.json',
+        acceptance: 'historical-dataset-audit readyForCustomerEffectProof=true。'
+      },
+      {
+        priority: 4,
+        owner: '商务',
+        title: '补齐客户效果证明',
+        step: 'customer-effect-audit',
+        command: '补齐客户最终选择、历史人工补号量基线和本轮人工补号量后重跑 customer-effect:audit --strict。',
+        expectedArtifact: 'customer-effect-summary.json',
+        acceptance: 'customer-effect:audit overallPass=true。'
+      },
+      {
+        priority: 4,
+        owner: '商务/投放',
+        title: '补齐真实视频资源',
+        step: 'video-resource-readiness',
+        command: '补齐真实参考视频、真实候选视频、视频 URL、封面、ASR、帧图或正文证据后重跑 video:resource-readiness --strict。',
+        expectedArtifact: 'video-resource-readiness-summary.json',
+        acceptance: 'video-resource-readiness readyForVideoAbPreflight=true。'
+      }
+    ]
+  });
+
+  const outputDir = path.join(outputs, 'business-proof-next-actions-latest');
+  const result = spawnSync(process.execPath, [
+    path.join(root, 'scripts', 'business-proof-next-actions.js'),
+    '--outputs',
+    outputs,
+    '--output',
+    outputDir
+  ], { cwd: root, encoding: 'utf8' });
+  if (result.status !== 0) {
+    process.stderr.write(result.stderr || result.stdout || '');
+    throw new Error('business-proof-next-actions should run');
+  }
+
+  const summary = JSON.parse(fs.readFileSync(path.join(outputDir, 'business-proof-next-actions-summary.json'), 'utf8'));
+  const report = fs.readFileSync(path.join(outputDir, 'business-proof-next-actions-report.md'), 'utf8').replace(/^\uFEFF/, '');
+  const csv = fs.readFileSync(path.join(outputDir, 'business-proof-next-actions.csv'), 'utf8').replace(/^\uFEFF/, '');
+  assert(summary.complete === false, 'next actions should not mark complete');
+  assert(summary.actionCount === summary.actions.length, 'summary should expose top-level actionCount matching actions');
+  assert(summary.ownerGroupCount === summary.ownerGroups.length, 'summary should expose top-level ownerGroupCount');
+  assert(summary.ownerRepairArtifactCount === summary.ownerRepairArtifacts.length, 'summary should expose top-level ownerRepairArtifactCount');
+  assert(Array.isArray(summary.topActions) && summary.topActions.length === Math.min(5, summary.actions.length), 'summary should expose top-level topActions');
+  assert(summary.topActions[0].id === summary.actions[0].id, 'topActions should follow the sorted action queue');
+  assert(summary.guardrails.some(item => item.includes('round:refresh')), 'next action guardrails should prefer ordered round refresh');
+  assert(!summary.guardrails.some(item => item.includes('intake:readiness、proof-gap:closure、business-proof:progress 和 round:deposition')), 'next action guardrails should not preserve stale manual refresh order');
+  assert(summary.actions.length >= 3, 'next actions should include actionable blockers after compacting duplicate validation steps');
+  assert(Array.isArray(summary.ownerGroups) && summary.ownerGroups.length >= 2, 'next actions should group actions by owner');
+  assert(Array.isArray(summary.ownerArtifacts) && summary.ownerArtifacts.length === summary.ownerGroups.length, 'next actions should write owner artifacts');
+  assert(summary.ownerArtifactCount === summary.ownerArtifacts.length, 'summary should expose top-level ownerArtifactCount after owner artifacts are written');
+  assert(Array.isArray(summary.ownerRepairArtifacts) && summary.ownerRepairArtifacts.length >= 2, 'next actions should expose owner repair artifacts');
+  assert(summary.ownerRepairArtifacts.some(item => item.owner === '商务' && item.csv.includes('intake-readiness-repair-actions.csv') && item.actionCount === 2), 'business owner should receive data intake repair artifact');
+  assert(summary.ownerRepairArtifacts.some(item => item.owner === '商务/投放' && item.csv.includes('video-resource-repair-actions.csv') && item.actionCount === 1), 'business/media owner should receive video repair artifact');
+  assert(summary.ownerGroups.some(group => group.owner === '商务' && group.actionCount >= 1), 'owner groups should include business owner');
+  assert(summary.ownerArtifacts.every(item => fs.existsSync(path.join(outputDir, item.markdown)) && fs.existsSync(path.join(outputDir, item.csv))), 'owner artifact files should exist');
+  assert(summary.ownerArtifacts.some(item => item.owner === '商务' && item.repairArtifacts.some(artifact => artifact.csv.includes('intake-readiness-repair-actions.csv'))), 'business owner artifact metadata should include data repair csv');
+  assert(summary.ownerArtifacts.some(item => item.owner === '商务/投放' && item.repairArtifacts.some(artifact => artifact.csv.includes('video-resource-repair-actions.csv'))), 'business/media owner artifact metadata should include video repair csv');
+  assert(summary.actions[0].title.includes('填真实历史 Brief') || summary.actions[0].title.includes('history'), 'history blocker should be first');
+  assert(summary.actions.some(action => action.title.includes('填真实视频资源')), 'video resource action should be present');
+  assert(!summary.actions.some(action => action.title.includes('修复 intake history')), 'history intake issues should be merged into the history action');
+  assert(!summary.actions.some(action => action.title.includes('修复 intake video')), 'video intake issues should be merged into the video action');
+  assert(!summary.actions.some(action => action.title.includes('修复 intake review')), 'review intake issues should be merged into the review action');
+  assert(!summary.actions.some(action => action.title.includes('真实材料预审')), 'intake readiness validation should merge into a primary business gap action');
+  assert(!summary.actions.some(action => action.title.includes('历史数据审计')), 'history audit validation should merge into the primary history action');
+  assert(!summary.actions.some(action => action.title.includes('视频资源就绪审计')), 'video readiness validation should merge into the primary video action');
+  assert(!summary.actions.some(action => action.title.includes('商务复核指标')), 'review metrics validation should merge into the primary review action');
+  assert(!summary.actions.some(action => ['生成统一收集包', '生成视频资源收集包', '刷新证据和交接'].some(title => action.title.includes(title))), 'setup and deposition steps should not appear as business proof actions');
+  assert(!summary.actions.some(action => action.source === 'proof-gap-closure' && action.title.includes('真实历史 Brief')), 'historical proof-gap action should not duplicate progress actions');
+  assert(summary.actions.some(action => action.title.includes('填真实历史 Brief') && action.next.includes('真实历史 Brief 数不足')), 'history action should retain issue details');
+  assert(summary.actions.some(action => action.title.includes('填真实视频资源') && action.next.includes('缺少真实候选视频资源')), 'video action should retain issue details');
+  assert(summary.actions.some(action => action.title.includes('人工复核标注') && action.next.includes('客户最终选中/拒绝记录')), 'review action should retain issue details');
+  assert(summary.actions.some(action => action.title.includes('填真实历史 Brief') && action.missingProofRequirements.includes('missing historical-dataset-audit.json')), 'history action should inherit proof-gap missing requirements');
+  assert(summary.actions.some(action => action.title.includes('填真实视频资源') && action.missingProofRequirements.includes('counts.realCandidateRows>=1')), 'video action should inherit proof-gap missing requirements');
+  assert(summary.actions.some(action => action.title.includes('人工复核标注') && action.missingProofRequirements.includes('missing customer-effect-summary.json')), 'review action should inherit proof-gap missing requirements');
+  assert(summary.sourceState.pipelineNextActionCount === 3, 'summary should include pipeline next action count');
+  assert(!summary.actions.some(action => action.title.includes('补齐历史数据审计字段')), 'pipeline history action should merge into the primary history action when intake is not ready');
+  assert(!summary.actions.some(action => action.title === '补齐真实视频资源'), 'pipeline video action should merge into the primary video action when video intake is not ready');
+  assert(!summary.actions.some(action => action.title.includes('补齐客户效果证明')), 'pipeline customer-effect action should merge into the primary review action when review intake is not ready');
+  assert(summary.actions.some(action => action.title.includes('填真实历史 Brief') && action.source.includes('optimization-pipeline') && action.next.includes('pipeline 当前动作')), 'history action should retain pipeline action details');
+  assert(summary.actions.some(action => action.title.includes('填真实视频资源') && action.source.includes('optimization-pipeline') && action.next.includes('pipeline 当前动作')), 'video action should retain pipeline action details');
+  assert(summary.actions.some(action => action.title.includes('人工复核标注') && action.source.includes('optimization-pipeline') && action.next.includes('pipeline 当前动作')), 'review action should retain pipeline customer-effect details');
+  assert(summary.actions.some(action => action.title.includes('填真实历史 Brief') && action.command === '填写 outputs\\data-intake-pack-latest\\history-data-template.csv'), 'history primary action should keep the fill-template command');
+  assert(summary.actions.some(action => action.title.includes('填真实视频资源') && action.command === '填写 outputs\\video-intake-pack-latest\\video-resource-template.csv'), 'video primary action should keep the fill-template command');
+  assert(summary.actions.some(action => action.title.includes('人工复核标注') && action.command === '填写 outputs\\data-intake-pack-latest\\manual-review-template.csv'), 'review primary action should keep the fill-template command');
+  assert(summary.ownerGroups.some(group => group.owner === '商务' && group.topTitles.some(title => title.includes('填真实历史 Brief'))), 'owner groups should keep primary business action visible');
+  assert(summary.ownerArtifacts.some(item => item.owner === '商务/投放' && item.markdown === 'by-owner/business-media.md'), 'business/media owner should use the normalized artifact name');
+  assert(!summary.ownerArtifacts.some(item => item.markdown === 'by-owner/media-business.md'), 'media/business duplicate owner artifact should not be generated');
+  assert(summary.actions.some(action => [action.next, action.command, action.acceptance].join(' ').includes('intake')), 'intake readiness validation should be retained in merged action details');
+  assert(report.includes('# 提号补证下一步行动队列'), 'report should be Chinese');
+  assert(report.includes('## 按负责人汇总'), 'report should include owner summary section');
+  assert(report.includes('## 负责人文件'), 'report should include owner artifact section');
+  assert(report.includes('修复清单附件'), 'report should include owner repair artifact links');
+  assert(report.includes('intake-readiness-repair-actions.csv'), 'report should include data intake repair csv');
+  assert(report.includes('video-resource-repair-actions.csv'), 'report should include video repair csv');
+  assert(report.includes('避免把“填一张表”拆成多条重复任务'), 'report should explain duplicate action merge rule');
+  assert(report.includes('下一步说明'), 'report should expose next-step details');
+  assert(report.includes('pipeline.nextActionCount'), 'report should expose pipeline next action count');
+  assert(report.includes('pipeline 当前动作'), 'report should include pipeline merged action details');
+  assert(report.includes('真实历史 Brief 数不足'), 'report should show merged history issue details');
+  assert(report.includes('missing historical-dataset-audit.json'), 'report should expose inherited historical proof requirement');
+  assert(report.includes('counts.realCandidateRows>=1'), 'report should expose inherited video proof requirement');
+  assert(report.includes('missing customer-effect-summary.json'), 'report should expose inherited customer-effect proof requirement');
+  assert(report.includes('不能把 sample、smoke'), 'report should preserve proof boundary');
+  assert(report.includes('round:refresh'), 'report should expose ordered round refresh after real materials are filled');
+  assert(csv.includes('优先级,负责人,动作'), 'csv should have Chinese headers');
+  const businessOwnerReport = fs.readFileSync(path.join(outputDir, 'by-owner', 'business.md'), 'utf8').replace(/^\uFEFF/, '');
+  const mediaOwnerReport = fs.readFileSync(path.join(outputDir, 'by-owner', 'business-media.md'), 'utf8').replace(/^\uFEFF/, '');
+  assert(businessOwnerReport.includes('修复清单附件') && businessOwnerReport.includes('intake-readiness-repair-actions.csv'), 'business owner report should link data repair csv');
+  assert(mediaOwnerReport.includes('修复清单附件') && mediaOwnerReport.includes('video-resource-repair-actions.csv'), 'business/media owner report should link video repair csv');
+  assert(!/sk-[A-Za-z0-9_-]{10,}/.test(report), 'report should not contain model token patterns');
+  assert(!/r:[A-Za-z0-9]{20,}/.test(report), 'report should not contain Parse sessionToken patterns');
+  console.log(JSON.stringify({ ok: true, outputDir, actionCount: summary.actions.length, topAction: summary.actions[0].title }, null, 2));
+}
+
+function writeJson(file, value) {
+  fs.mkdirSync(path.dirname(file), { recursive: true });
+  fs.writeFileSync(file, JSON.stringify(value, null, 2), 'utf8');
+}
+
+function writeText(file, value) {
+  fs.mkdirSync(path.dirname(file), { recursive: true });
+  fs.writeFileSync(file, value, 'utf8');
+}
+
+function assert(condition, message) {
+  if (!condition) throw new Error(message);
+}
+
+main();

+ 786 - 0
claude-code-tihao-sourcing/scripts/business-proof-next-actions.js

@@ -0,0 +1,786 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const path = require('path');
+
+const ROOT = path.resolve(__dirname, '..');
+const OUTPUTS = path.join(ROOT, 'outputs');
+
+function main() {
+  const args = parseArgs(process.argv.slice(2));
+  const outputDir = path.resolve(args.output || process.env.TIHAO_BUSINESS_PROOF_NEXT_ACTIONS_OUTPUT || path.join(OUTPUTS, `business-proof-next-actions-${Date.now()}`));
+  const summary = buildNextActions({
+    root: ROOT,
+    outputsDir: path.resolve(args.outputs || OUTPUTS),
+    limit: Number(args.limit || 8)
+  });
+
+  fs.mkdirSync(outputDir, { recursive: true });
+  const jsonPath = path.join(outputDir, 'business-proof-next-actions-summary.json');
+  const reportPath = path.join(outputDir, 'business-proof-next-actions-report.md');
+  const csvPath = path.join(outputDir, 'business-proof-next-actions.csv');
+  const ownerArtifacts = writeOwnerArtifacts(outputDir, summary.ownerGroups, summary.actions, summary.ownerRepairArtifacts);
+  summary.ownerArtifacts = ownerArtifacts;
+  summary.ownerArtifactCount = ownerArtifacts.length;
+  fs.writeFileSync(jsonPath, JSON.stringify(summary, null, 2), 'utf8');
+  fs.writeFileSync(reportPath, withBom(renderReport(summary, ownerArtifacts)), 'utf8');
+  fs.writeFileSync(csvPath, withBom(renderCsv(summary.actions)), 'utf8');
+
+  console.log(JSON.stringify({
+    outputDir,
+    json: jsonPath,
+    report: reportPath,
+    csv: csvPath,
+    ownerArtifacts: ownerArtifacts.length,
+    complete: summary.complete,
+    actionCount: summary.actionCount,
+    topAction: summary.topActions[0]?.title || ''
+  }, null, 2));
+
+  if (!summary.complete && args.strict) process.exitCode = 1;
+}
+
+function buildNextActions({ root, outputsDir, limit = 8 }) {
+  const progress = readJson(latestFile(outputsDir, 'business-proof-progress-summary.json'));
+  const closure = readJson(latestFile(outputsDir, 'proof-gap-closure-summary.json'));
+  const intake = readJson(latestFile(outputsDir, 'intake-readiness-summary.json'));
+  const videoReadiness = readJson(latestFile(outputsDir, 'video-resource-readiness-summary.json'));
+  const homepageReadiness = readJson(latestFile(outputsDir, 'homepage-evidence-readiness-summary.json'));
+  const pipeline = readJson(latestFile(outputsDir, 'optimization-pipeline-summary.json'));
+  const completion = readJson(latestFile(outputsDir, 'optimization-completion-summary.json'));
+  const pipelineFreshness = getPipelineFreshnessFromCompletion(completion);
+
+  const candidates = [];
+  const primarySections = new Set();
+  const primaryGapIds = new Set();
+  for (const row of progress?.rows || []) {
+    if (row.status === 'pass') continue;
+    const order = Number(row.order);
+    if (order === 3) {
+      primarySections.add('history');
+      primaryGapIds.add('historical-dataset');
+    }
+    if (order === 4) {
+      primarySections.add('video');
+      primaryGapIds.add('video-real-candidate');
+    }
+    if (order === 5) primaryGapIds.add('intake-readiness');
+    if (order === 12) {
+      primarySections.add('review');
+      primaryGapIds.add('manual-review-and-customer-effect');
+    }
+    if (order === 14) primaryGapIds.add('manual-review-and-customer-effect');
+    candidates.push(actionFromProgressRow(row, { intake, videoReadiness }));
+  }
+  for (const row of closure?.rows || []) {
+    if (row.status !== 'open') continue;
+    if (primaryGapIds.has(row.id)) {
+      mergeClosureRowIntoPrimaryAction(candidates, row);
+      continue;
+    }
+    candidates.push(actionFromClosureRow(row));
+  }
+  for (const issue of intake?.issues || []) {
+    if (primarySections.has(issue.section)) continue;
+    candidates.push(actionFromIntakeIssue(issue));
+  }
+  if (!pipelineFreshness.stale) {
+    mergePipelineNextActions(candidates, pipeline, { closure, videoReadiness });
+  }
+
+  const deduped = compactBusinessGapActions(dedupeActions(candidates))
+    .sort((a, b) => a.priority - b.priority || a.order - b.order)
+    .slice(0, limit);
+  const ownerGroups = groupByOwner(deduped);
+  const ownerRepairArtifacts = buildOwnerRepairActionArtifacts({
+    root,
+    outputsDir,
+    intake,
+    videoReadiness,
+    homepageReadiness
+  });
+
+  return {
+    generatedAt: new Date().toISOString(),
+    root,
+    outputsDir,
+    complete: Boolean(progress?.complete) && Number(closure?.openCount || 0) === 0,
+    actionCount: deduped.length,
+    ownerGroupCount: ownerGroups.length,
+    ownerRepairArtifactCount: ownerRepairArtifacts.length,
+    topActions: summarizeTopActions(deduped),
+    sourceState: {
+      progressComplete: Boolean(progress?.complete),
+      progressCounts: progress?.counts || {},
+      proofGapOpenCount: closure ? Number(closure.openCount || 0) : null,
+      intakeFailureCount: intake ? Number(intake.failureCount || 0) : null,
+      videoFailureCount: videoReadiness ? Number(videoReadiness.failureCount || 0) : null,
+      homepageFailureCount: homepageReadiness ? Number(homepageReadiness.failureCount || 0) : null,
+      pipelineReadyForClaim: pipeline ? Boolean(pipeline.readyForClaim) : null,
+      pipelineFailCount: pipeline ? Number(pipeline.counts?.fail || 0) : null,
+      pipelineNextActionCount: pipeline && !pipelineFreshness.stale ? Number(pipeline.nextActions?.length || 0) : 0,
+      pipelineRawNextActionCount: pipeline ? Number(pipeline.nextActions?.length || 0) : null,
+      pipelineSuppressedNextActionCount: pipelineFreshness.stale && pipeline ? Number(pipeline.nextActions?.length || 0) : 0,
+      pipelineStale: pipelineFreshness.stale,
+      pipelineStaleReason: pipelineFreshness.reason,
+      pipelineStaleMissingProofRequirements: pipelineFreshness.missingProofRequirements
+    },
+    guardrails: [
+      '行动队列只用于安排下一步补证,不证明客户效果完成。',
+      '不能把 sample、smoke、模板、provider fallback 或接口 200 当成业务证明。',
+      '补证动作不得包含 sessionToken、Authorization、模型 token 或 npm token。',
+      '补齐真实材料后优先运行 npm run round:refresh -- --output-root outputs --strict,按顺序刷新 proof-gap、业务补证进度、证据台账、交接摘要和沉淀审计。',
+      'stale optimization-pipeline nextActions are not current dispatch evidence; rerun optimization:pipeline after real materials are ready.'
+    ],
+    ownerGroups,
+    ownerRepairArtifacts,
+    actions: deduped
+  };
+}
+
+function summarizeTopActions(actions, limit = 5) {
+  if (!Array.isArray(actions)) return [];
+  return actions.slice(0, limit).map(action => ({
+    id: action.id || '',
+    source: action.source || '',
+    order: Number(action.order || 0),
+    priority: Number(action.priority || 0),
+    owner: action.owner || '',
+    title: action.title || '',
+    status: action.status || '',
+    evidence: action.evidence || '',
+    command: action.command || '',
+    expectedArtifact: action.expectedArtifact || '',
+    acceptance: action.acceptance || '',
+    missingProofRequirementCount: Array.isArray(action.missingProofRequirements) ? action.missingProofRequirements.length : 0,
+    missingProofRequirements: Array.isArray(action.missingProofRequirements) ? action.missingProofRequirements : []
+  }));
+}
+
+function getPipelineFreshnessFromCompletion(completion) {
+  const blocker = Array.isArray(completion?.blockingReasons)
+    ? completion.blockingReasons.find(item => item.id === 'optimization-pipeline')
+    : null;
+  const missingProofRequirements = Array.isArray(blocker?.missingProofRequirements)
+    ? blocker.missingProofRequirements.filter(Boolean).map(item => String(item))
+    : [];
+  const evidence = String(blocker?.evidence || '');
+  const stale = evidence.includes('stale=true') ||
+    missingProofRequirements.includes('optimizationPipeline.generatedAt>=currentProofSources');
+  return {
+    stale,
+    reason: stale ? (evidence || 'optimization pipeline is stale') : '',
+    missingProofRequirements
+  };
+}
+
+function mergePipelineNextActions(candidates, pipeline, context = {}) {
+  if (!pipeline || !Array.isArray(pipeline.nextActions)) return;
+  for (const pipelineAction of pipeline.nextActions) {
+    if (shouldSkipPipelineAction(pipelineAction, context)) continue;
+    const mappedId = pipelineStepActionId(pipelineAction.step, candidates);
+    const existing = candidates.find(action => action.id === mappedId);
+    if (!existing) {
+      candidates.push(actionFromPipelineNextAction(pipelineAction));
+      continue;
+    }
+    existing.source = `${existing.source}+optimization-pipeline`;
+    const keepPrimaryAction = ['step-03', 'step-04', 'step-12'].includes(existing.id);
+    if (!keepPrimaryAction) {
+      existing.owner = pipelineAction.owner || existing.owner;
+      existing.title = pipelineAction.title || existing.title;
+      existing.priority = Math.min(existing.priority || 99, Number(pipelineAction.priority || 99));
+      existing.command = pipelineAction.command || existing.command;
+      existing.expectedArtifact = pipelineAction.expectedArtifact || existing.expectedArtifact;
+      existing.acceptance = pipelineAction.acceptance || existing.acceptance;
+    }
+    existing.next = appendIssueMessages(existing.next, 'pipeline 当前动作', [pipelineAction.command]);
+  }
+}
+
+function shouldSkipPipelineAction(pipelineAction, { closure, videoReadiness } = {}) {
+  if (pipelineAction?.step !== 'video-resource-readiness') return false;
+  const videoReady = Boolean(videoReadiness?.acceptance?.readyForVideoAbPreflight) &&
+    Number(videoReadiness?.failureCount || 0) === 0;
+  const videoGapClosed = (Array.isArray(closure?.rows) ? closure.rows : [])
+    .some(row => row.id === 'video-real-candidate' && row.status === 'closed');
+  return videoReady || videoGapClosed;
+}
+
+function pipelineStepActionId(step, candidates = []) {
+  if (step === 'history-audit' && hasAction(candidates, 'step-03')) return 'step-03';
+  if (step === 'video-resource-readiness' && hasAction(candidates, 'step-04')) return 'step-04';
+  if (step === 'customer-effect-audit' && hasAction(candidates, 'step-12')) return 'step-12';
+  const map = {
+    'intake-readiness': 'step-05',
+    'history-from-csv': 'step-06',
+    'history-audit': 'step-07',
+    'video-resource-readiness': 'step-08',
+    'longrun-readiness': 'step-09',
+    'review-metrics': 'step-13',
+    'customer-effect-audit': 'step-14',
+    'evidence-index': 'step-15'
+  };
+  return map[step] || '';
+}
+
+function hasAction(candidates, id) {
+  return candidates.some(action => action.id === id);
+}
+
+function actionFromPipelineNextAction(action) {
+  return {
+    id: `pipeline-${action.step || action.title}`,
+    source: 'optimization-pipeline',
+    order: 300,
+    priority: Number(action.priority || 30),
+    owner: action.owner || '技术/AI',
+    title: action.title || 'Pipeline 下一步动作',
+    status: 'blocked_by_external_data',
+    evidence: `pipeline step=${action.step || 'unknown'}`,
+    next: action.command || '',
+    command: action.command || '',
+    expectedArtifact: action.expectedArtifact || '',
+    acceptance: action.acceptance || 'pipeline 对应 nextAction 完成。'
+  };
+}
+
+function actionFromProgressRow(row, { intake, videoReadiness }) {
+  const base = {
+    id: `step-${String(row.order).padStart(2, '0')}`,
+    source: 'business-proof-progress',
+    order: Number(row.order || 999),
+    owner: row.owner || '待分配',
+    title: row.title || `补证步骤 ${row.order}`,
+    status: row.status,
+    evidence: row.evidence || '',
+    next: row.next || '',
+    command: commandForStep(row.order),
+    expectedArtifact: artifactForStep(row.order),
+    acceptance: acceptanceForStep(row.order)
+  };
+  base.priority = priorityForStep(row.order, row.status);
+  if (row.order === 3 && intake?.history?.issues?.length) {
+    base.next = appendIssueMessages(base.next, '当前历史数据问题', [
+      ...intake.history.issues.map(item => item.message),
+      ...sectionIssueMessages(intake, 'history')
+    ]);
+  }
+  if (row.order === 4) {
+    base.next = appendIssueMessages(base.next, '当前视频资源问题', [
+      ...(videoReadiness?.issues || []).map(item => item.message),
+      ...sectionIssueMessages(intake, 'video')
+    ]);
+  }
+  if (row.order === 12) {
+    base.next = appendIssueMessages(base.next, '当前人工复核问题', sectionIssueMessages(intake, 'review'));
+  }
+  return base;
+}
+
+function sectionIssueMessages(intake, section) {
+  return (intake?.issues || [])
+    .filter(issue => issue.section === section)
+    .map(issue => issue.message)
+    .filter(Boolean);
+}
+
+function appendIssueMessages(next, label, messages) {
+  const uniqueMessages = Array.from(new Set(messages.filter(Boolean)));
+  if (!uniqueMessages.length) return next;
+  return `${next} ${label}:${uniqueMessages.join(';')}`;
+}
+
+function actionFromClosureRow(row) {
+  const missingProofRequirements = Array.isArray(row.missingProofRequirements) ? row.missingProofRequirements.filter(Boolean) : [];
+  return {
+    id: `gap-${row.id}`,
+    source: 'proof-gap-closure',
+    order: 100 + gapOrder(row.id),
+    priority: 20 + gapOrder(row.id),
+    owner: ownerForGap(row.id),
+    title: titleForGap(row.id, row.title),
+    status: row.status,
+    evidence: row.evidence || '',
+    missingProofRequirements,
+    next: appendIssueMessages(row.next || row.command || '', '缺失证明', missingProofRequirements),
+    command: row.command || '',
+    expectedArtifact: row.expectedArtifact || '',
+    acceptance: row.required || '对应 proof gap status=closed。'
+  };
+}
+
+function mergeClosureRowIntoPrimaryAction(candidates, row) {
+  const targetId = primaryActionIdForGap(row.id);
+  const target = candidates.find(action => action.id === targetId);
+  if (!target) return;
+  const gapAction = actionFromClosureRow(row);
+  target.source = mergeSource(target.source, gapAction.source);
+  target.evidence = mergeText(target.evidence || '', gapAction.evidence || '');
+  target.next = appendIssueMessages(target.next, '缺失证明', gapAction.missingProofRequirements || []);
+  target.next = appendIssueMessages(target.next, 'proof-gap 当前证据', [gapAction.evidence]);
+  target.acceptance = mergeText(target.acceptance, gapAction.acceptance);
+  target.missingProofRequirements = mergeList(target.missingProofRequirements, gapAction.missingProofRequirements);
+  if (!target.expectedArtifact.includes(gapAction.expectedArtifact || '__missing__')) {
+    target.expectedArtifact = [target.expectedArtifact, gapAction.expectedArtifact].filter(Boolean).join(';');
+  }
+}
+
+function primaryActionIdForGap(id) {
+  const map = {
+    'intake-readiness': 'step-05',
+    'historical-dataset': 'step-03',
+    'video-real-candidate': 'step-04',
+    'manual-review-and-customer-effect': 'step-12'
+  };
+  return map[id] || '';
+}
+
+function actionFromIntakeIssue(issue) {
+  return {
+    id: `intake-${issue.section}-${issue.type}`,
+    source: 'intake-readiness',
+    order: 200,
+    priority: issue.section === 'history' ? 1 : issue.section === 'video' ? 2 : 3,
+    owner: issue.section === 'video' ? '商务/投放' : '商务',
+    title: `修复 intake ${issue.section}:${issue.type}`,
+    status: 'blocked_by_external_data',
+    evidence: issue.message || '',
+    next: issue.message || '',
+    command: 'npm run intake:readiness -- --data-pack outputs\\data-intake-pack-latest --video-pack outputs\\video-intake-pack-latest --output outputs\\intake-readiness-latest',
+    expectedArtifact: 'outputs/intake-readiness-latest/intake-readiness-summary.json',
+    acceptance: 'intake readiness 对应 issue 消失,overallReady=true 且 failureCount=0。'
+  };
+}
+
+function commandForStep(order) {
+  const commands = {
+    3: '填写 outputs\\data-intake-pack-latest\\history-data-template.csv',
+    4: '填写 outputs\\video-intake-pack-latest\\video-resource-template.csv',
+    5: 'npm run intake:readiness -- --data-pack outputs\\data-intake-pack-latest --video-pack outputs\\video-intake-pack-latest --output outputs\\intake-readiness-latest',
+    6: 'npm run history:from-csv -- --input <历史数据CSV> --output <history-dataset目录>',
+    7: 'npm run history:audit -- --input <history-dataset目录> --output <历史审计输出目录> --strict',
+    8: 'npm run video:resource-readiness -- --input <video-resource-template.csv> --output <视频资源审计输出目录> --strict',
+    9: 'npm run longrun:readiness -- --mode full-matrix --intake-readiness outputs\\intake-readiness-latest\\intake-readiness-summary.json --proof-gap outputs\\proof-gap-request-latest\\proof-gap-request-summary.json --proof-gap-closure outputs\\proof-gap-closure-latest\\proof-gap-closure-summary.json --output outputs\\long-run-readiness-latest',
+    10: 'npm run optimization:pipeline -- --history-csv <历史数据CSV> --review-csv <已标注CSV> --current-manual-supplement-count <本轮人工补号量> --output <输出目录> --strict',
+    12: '填写 outputs\\data-intake-pack-latest\\manual-review-template.csv',
+    13: 'npm run review:metrics -- --input <已标注CSV> --output <复核指标输出目录> --strict',
+    14: 'npm run customer-effect:audit -- --review-csv <已标注CSV> --history-audit <historical-dataset-audit.json> --current-manual-supplement-count <本轮人工补号量> --output <客户效果输出目录> --strict'
+  };
+  return commands[order] || '';
+}
+
+function artifactForStep(order) {
+  const artifacts = {
+    3: 'outputs/data-intake-pack-latest/history-data-template.csv',
+    4: 'outputs/video-intake-pack-latest/video-resource-template.csv',
+    5: 'outputs/intake-readiness-latest/intake-readiness-summary.json',
+    6: '<history-dataset目录>/*.json',
+    7: '<历史审计输出目录>/historical-dataset-audit.json',
+    8: '<视频资源审计输出目录>/video-resource-readiness-summary.json',
+    9: 'outputs/long-run-readiness-latest/long-run-readiness-summary.json',
+    10: '<输出目录>/optimization-pipeline-summary.json',
+    12: 'outputs/data-intake-pack-latest/manual-review-template.csv',
+    13: '<复核指标输出目录>/review-metrics-summary.json',
+    14: '<客户效果输出目录>/customer-effect-summary.json'
+  };
+  return artifacts[order] || '';
+}
+
+function acceptanceForStep(order) {
+  const rules = {
+    3: 'historyReady=true,真实历史 Brief 数 >= 5,且无模板占位。',
+    4: 'videoReady=true,至少有真实参考视频和真实候选视频。',
+    5: 'overallReady=true 且 failureCount=0。',
+    6: '历史数据集 JSON 成功生成。',
+    7: 'readyForCustomerEffectProof=true。',
+    8: 'readyForVideoAbPreflight=true 且 failureCount=0。',
+    9: 'ready=true 且 fail=0。',
+    10: 'readyForClaim=true,且软件端重复键为 0、排名连续。',
+    12: '客户选择、负样本归因和本轮人工补号量齐全。',
+    13: 'review:metrics overallPass=true,负样本归因覆盖率=100%。',
+    14: 'customer-effect:audit overallPass=true。'
+  };
+  return rules[order] || '对应步骤状态为 pass。';
+}
+
+function priorityForStep(order, status) {
+  if (order === 3) return 1;
+  if (order === 4) return 2;
+  if (order === 5) return 3;
+  if (order === 12) return 4;
+  if (status === 'fail') return 10 + Number(order || 99);
+  if (status === 'blocked_by_external_data') return 20 + Number(order || 99);
+  return 40 + Number(order || 99);
+}
+
+function gapOrder(id) {
+  const order = {
+    'intake-readiness': 1,
+    'historical-dataset': 2,
+    'video-real-candidate': 3,
+    'video-ab-live-proof': 4,
+    'live-provider-overnight-proof': 5,
+    'manual-review-and-customer-effect': 6
+  };
+  return order[id] || 99;
+}
+
+function ownerForGap(id) {
+  if (id === 'video-real-candidate') return '商务/投放';
+  if (id === 'video-ab-live-proof') return '技术/AI';
+  if (id.includes('manual') || id.includes('customer') || id.includes('historical')) return '商务';
+  return '技术/AI';
+}
+
+function titleForGap(id, fallback) {
+  const titles = {
+    'intake-readiness': '真实材料预审',
+    'historical-dataset': '真实历史 Brief 数据集',
+    'video-real-candidate': '真实视频资源表',
+    'video-ab-live-proof': '真实视频 A/B 验收',
+    'live-provider-overnight-proof': '真实 live/provider 长跑证明',
+    'manual-review-and-customer-effect': '商务复核和客户效果'
+  };
+  return titles[id] || fallback || id;
+}
+
+function dedupeActions(actions) {
+  const seen = new Set();
+  const result = [];
+  for (const action of actions.filter(Boolean)) {
+    const key = `${action.owner}|${action.title}|${action.command}`;
+    if (seen.has(key)) continue;
+    seen.add(key);
+    result.push(action);
+  }
+  return result;
+}
+
+function compactBusinessGapActions(actions) {
+  const byId = new Map(actions.map(action => [action.id, action]));
+  mergeActionInto(byId, 'step-03', 'step-05', '统一预审');
+  mergeActionInto(byId, 'step-03', 'step-06', '转换动作');
+  mergeActionInto(byId, 'step-03', 'step-07', '验证动作');
+  mergeActionInto(byId, 'step-04', 'step-08', '验证动作');
+  mergeActionInto(byId, 'step-12', 'step-14', '验证动作');
+  mergeActionInto(byId, 'step-12', 'step-13', '复核指标');
+  mergeActionInto(byId, 'gap-live-provider-overnight-proof', 'step-09', '前置门禁');
+  mergeActionInto(byId, 'gap-live-provider-overnight-proof', 'step-10', '执行动作');
+  mergeActionInto(byId, 'gap-video-ab-live-proof', 'step-11', '验收动作');
+
+  const result = [];
+  const usedGapKeys = new Set();
+  for (const action of actions) {
+    if (isMergedTechnicalAction(action.id)) continue;
+    const gapKey = businessGapKey(action);
+    if (gapKey) {
+      if (usedGapKeys.has(gapKey)) continue;
+      usedGapKeys.add(gapKey);
+    }
+    result.push(action);
+  }
+  return result;
+}
+
+function mergeActionInto(byId, targetId, sourceId, label) {
+  const target = byId.get(targetId);
+  const source = byId.get(sourceId);
+  if (!target || !source) return;
+  target.source = mergeSource(target.source, source.source);
+  target.next = appendIssueMessages(target.next, label, [
+    `${source.title}:${source.next || source.command || source.acceptance || ''}`
+  ]);
+  target.acceptance = mergeText(target.acceptance, source.acceptance);
+  target.missingProofRequirements = mergeList(target.missingProofRequirements, source.missingProofRequirements);
+  if (!target.expectedArtifact.includes(source.expectedArtifact || '__missing__')) {
+    target.expectedArtifact = [target.expectedArtifact, source.expectedArtifact].filter(Boolean).join(';');
+  }
+}
+
+function mergeSource(left, right) {
+  return Array.from(new Set(String(`${left}+${right}`).split('+').filter(Boolean))).join('+');
+}
+
+function mergeText(left, right) {
+  if (!right || left.includes(right)) return left;
+  return `${left};${right}`;
+}
+
+function mergeList(left, right) {
+  return Array.from(new Set([
+    ...(Array.isArray(left) ? left : []),
+    ...(Array.isArray(right) ? right : [])
+  ].filter(Boolean)));
+}
+
+function isMergedTechnicalAction(id) {
+  return ['step-01', 'step-02', 'step-05', 'step-06', 'step-07', 'step-08', 'step-09', 'step-10', 'step-11', 'step-13', 'step-14', 'step-15'].includes(id);
+}
+
+function businessGapKey(action) {
+  if (['step-03', 'gap-historical-dataset'].includes(action.id)) return 'historical-dataset';
+  if (['step-04', 'gap-video-real-candidate'].includes(action.id)) return 'video-real-candidate';
+  if (['step-12', 'gap-manual-review-and-customer-effect'].includes(action.id)) return 'manual-review-and-customer-effect';
+  if (['gap-live-provider-overnight-proof'].includes(action.id)) return 'live-provider-overnight-proof';
+  if (['gap-video-ab-live-proof'].includes(action.id)) return 'video-ab-live-proof';
+  return '';
+}
+
+function renderReport(summary, ownerArtifacts = summary.ownerArtifacts || []) {
+  const lines = [
+    '# 提号补证下一步行动队列',
+    '',
+    `- 生成时间:${summary.generatedAt}`,
+    `- 是否完成:${summary.complete ? '是' : '否'}`,
+    `- 行动数:${summary.actions.length}`,
+    `- progress.complete:${summary.sourceState.progressComplete ? 'true' : 'false'}`,
+    `- proofGap.openCount:${summary.sourceState.proofGapOpenCount ?? 'unknown'}`,
+    `- intake.failureCount:${summary.sourceState.intakeFailureCount ?? 'unknown'}`,
+    `- video.failureCount:${summary.sourceState.videoFailureCount ?? 'unknown'}`,
+    `- homepage.failureCount:${summary.sourceState.homepageFailureCount ?? 'unknown'}`,
+    `- pipeline.readyForClaim:${summary.sourceState.pipelineReadyForClaim ?? 'unknown'}`,
+    `- pipeline.failCount:${summary.sourceState.pipelineFailCount ?? 'unknown'}`,
+    `- pipeline.nextActionCount:${summary.sourceState.pipelineNextActionCount ?? 'unknown'}`,
+    `- pipeline.rawNextActionCount:${summary.sourceState.pipelineRawNextActionCount ?? 'unknown'}`,
+    `- pipeline.suppressedNextActionCount:${summary.sourceState.pipelineSuppressedNextActionCount ?? 0}`,
+    `- pipeline.stale:${summary.sourceState.pipelineStale ? 'true' : 'false'}`,
+    summary.sourceState.pipelineStaleReason ? `- pipeline.staleReason:${summary.sourceState.pipelineStaleReason}` : '',
+    '',
+    '## 按负责人汇总',
+    '',
+    '| 负责人 | 行动数 | 最高优先级 | 重点动作 |',
+    '| --- | --- | --- | --- |',
+    ...summary.ownerGroups.map(group => `| ${escapeCell(group.owner)} | ${group.actionCount} | ${group.topPriority} | ${escapeCell(group.topTitles.join(';'))} |`),
+    '',
+    '## 负责人文件',
+    '',
+    '| 负责人 | Markdown | CSV | 修复清单附件 |',
+    '| --- | --- | --- | --- |',
+    ...ownerArtifacts.map(item => `| ${escapeCell(item.owner)} | ${escapeCell(item.markdown)} | ${escapeCell(item.csv)} | ${escapeCell((item.repairArtifacts || []).map(artifact => `${artifact.title}:${artifact.csv}`).join(';') || '无')} |`),
+    '',
+    '## 行动队列',
+    '',
+    '同一份业务材料的多个字段缺口会合并到主动作的下一步说明中,避免把“填一张表”拆成多条重复任务。',
+    '',
+    '| 优先级 | 负责人 | 动作 | 来源 | 当前证据 | 下一步说明 | 执行命令 | 预期产物 | 通过标准 |',
+    '| --- | --- | --- | --- | --- | --- | --- | --- | --- |',
+    ...summary.actions.map(action => `| ${action.priority} | ${escapeCell(action.owner)} | ${escapeCell(action.title)} | ${escapeCell(action.source)} | ${escapeCell(action.evidence)} | ${escapeCell(action.next)} | \`${escapeCell(action.command)}\` | ${escapeCell(action.expectedArtifact)} | ${escapeCell(action.acceptance)} |`),
+    '',
+    '## 边界',
+    '',
+    ...summary.guardrails.map(item => `- ${item}`)
+  ];
+  return lines.join('\n');
+}
+
+function writeOwnerArtifacts(outputDir, ownerGroups, actions, ownerRepairArtifacts = []) {
+  const ownerDir = path.join(outputDir, 'by-owner');
+  fs.mkdirSync(ownerDir, { recursive: true });
+  const artifacts = [];
+  for (const group of ownerGroups) {
+    const ownerActions = actions.filter(action => action.owner === group.owner);
+    const repairArtifacts = ownerRepairArtifacts.filter(item => item.owner === group.owner);
+    const slug = slugOwner(group.owner);
+    const markdownRel = path.join('by-owner', `${slug}.md`).replace(/\\/g, '/');
+    const csvRel = path.join('by-owner', `${slug}.csv`).replace(/\\/g, '/');
+    fs.writeFileSync(path.join(outputDir, markdownRel), withBom(renderOwnerReport(group, ownerActions, repairArtifacts)), 'utf8');
+    fs.writeFileSync(path.join(outputDir, csvRel), withBom(renderCsv(ownerActions)), 'utf8');
+    artifacts.push({
+      owner: group.owner,
+      actionCount: group.actionCount,
+      topPriority: group.topPriority,
+      markdown: markdownRel,
+      csv: csvRel,
+      repairArtifacts
+    });
+  }
+  return artifacts;
+}
+
+function renderOwnerReport(group, actions, repairArtifacts = []) {
+  const lines = [
+    `# ${group.owner}补证行动`,
+    '',
+    `- 行动数:${group.actionCount}`,
+    `- 最高优先级:${group.topPriority}`,
+    '',
+    '## 修复清单附件',
+    '',
+    repairArtifacts.length
+      ? renderRepairArtifactTable(repairArtifacts)
+      : '- 当前没有单独归属到本负责人的修复清单附件。',
+    '',
+    '## 行动明细',
+    '',
+    '| 优先级 | 动作 | 当前证据 | 下一步说明 | 执行命令 | 预期产物 | 通过标准 |',
+    '| --- | --- | --- | --- | --- | --- | --- |',
+    ...actions.map(action => `| ${action.priority} | ${escapeCell(action.title)} | ${escapeCell(action.evidence)} | ${escapeCell(action.next)} | \`${escapeCell(action.command)}\` | ${escapeCell(action.expectedArtifact)} | ${escapeCell(action.acceptance)} |`),
+    '',
+    '## 边界',
+    '',
+    '- 本文件只用于安排下一步补证,不证明客户效果完成。',
+    '- 不要在补证材料中写入 sessionToken、Authorization、模型 token 或 npm token。'
+  ];
+  return lines.join('\n');
+}
+
+function groupByOwner(actions) {
+  const map = new Map();
+  for (const action of actions) {
+    const owner = action.owner || '待分配';
+    if (!map.has(owner)) {
+      map.set(owner, {
+        owner,
+        actionCount: 0,
+        topPriority: action.priority,
+        topTitles: []
+      });
+    }
+    const group = map.get(owner);
+    group.actionCount += 1;
+    group.topPriority = Math.min(group.topPriority, action.priority);
+    if (group.topTitles.length < 3) group.topTitles.push(action.title);
+  }
+  return Array.from(map.values()).sort((a, b) => a.topPriority - b.topPriority || b.actionCount - a.actionCount);
+}
+
+function buildOwnerRepairActionArtifacts({ root, outputsDir, intake, videoReadiness, homepageReadiness }) {
+  const artifacts = [];
+  addRepairActionArtifacts(artifacts, {
+    root,
+    csv: latestFile(outputsDir, 'intake-readiness-repair-actions.csv'),
+    title: '历史数据/商务复核修复清单',
+    source: 'intake-readiness',
+    repairActions: intake?.repairActions || []
+  });
+  addRepairActionArtifacts(artifacts, {
+    root,
+    csv: latestFile(outputsDir, 'video-resource-repair-actions.csv'),
+    title: '视频资源修复清单',
+    source: 'video-resource-readiness',
+    repairActions: videoReadiness?.repairActions || []
+  });
+  addRepairActionArtifacts(artifacts, {
+    root,
+    csv: latestFile(outputsDir, 'homepage-evidence-repair-actions.csv'),
+    title: '主页近期内容证据修复清单',
+    source: 'homepage-evidence-readiness',
+    repairActions: homepageReadiness?.repairActions || []
+  });
+  return artifacts.sort((a, b) => a.topPriority - b.topPriority || a.owner.localeCompare(b.owner, 'zh-CN'));
+}
+
+function addRepairActionArtifacts(artifacts, { root, csv, title, source, repairActions }) {
+  if (!csv || !fs.existsSync(csv) || !Array.isArray(repairActions) || repairActions.length === 0) return;
+  const byOwner = new Map();
+  for (const action of repairActions) {
+    const owner = action.owner || '待分配';
+    if (!byOwner.has(owner)) {
+      byOwner.set(owner, {
+        owner,
+        title,
+        source,
+        csv: path.relative(root, csv).replace(/\\/g, '/'),
+        actionCount: 0,
+        topPriority: Number(action.priority || 999),
+        fields: []
+      });
+    }
+    const item = byOwner.get(owner);
+    item.actionCount += 1;
+    item.topPriority = Math.min(item.topPriority, Number(action.priority || 999));
+    if (action.field && item.fields.length < 5 && !item.fields.includes(action.field)) item.fields.push(action.field);
+  }
+  artifacts.push(...byOwner.values());
+}
+
+function renderRepairArtifactTable(repairArtifacts) {
+  return [
+    '| 修复清单 | CSV | 动作数 | 最高优先级 | 重点字段 |',
+    '| --- | --- | ---: | ---: | --- |',
+    ...repairArtifacts.map(item => `| ${escapeCell(item.title)} | ${escapeCell(item.csv)} | ${item.actionCount} | ${item.topPriority} | ${escapeCell(item.fields.join(';') || '见 CSV')} |`)
+  ].join('\n');
+}
+
+function renderCsv(actions) {
+  const header = ['优先级', '负责人', '动作', '来源', '当前状态', '当前证据', '执行命令', '预期产物', '通过标准', '下一步说明'];
+  const rows = actions.map(action => [
+    action.priority,
+    action.owner,
+    action.title,
+    action.source,
+    action.status,
+    action.evidence,
+    action.command,
+    action.expectedArtifact,
+    action.acceptance,
+    action.next
+  ]);
+  return [header, ...rows].map(row => row.map(csvCell).join(',')).join('\n');
+}
+
+function latestFile(outputsDir, fileName) {
+  if (!fs.existsSync(outputsDir)) return '';
+  return fs.readdirSync(outputsDir, { withFileTypes: true })
+    .filter(entry => entry.isDirectory())
+    .map(entry => path.join(outputsDir, entry.name, fileName))
+    .filter(file => fs.existsSync(file))
+    .sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs)[0] || '';
+}
+
+function readJson(file) {
+  if (!file || !fs.existsSync(file)) return null;
+  try {
+    return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, ''));
+  } catch {
+    return null;
+  }
+}
+
+function parseArgs(argv) {
+  const args = {};
+  for (let index = 0; index < argv.length; index += 1) {
+    const raw = argv[index];
+    if (!raw.startsWith('--')) continue;
+    const key = raw.slice(2).replace(/-([a-z])/g, (_, char) => char.toUpperCase());
+    const next = argv[index + 1];
+    if (!next || next.startsWith('--')) args[key] = true;
+    else {
+      args[key] = next;
+      index += 1;
+    }
+  }
+  return args;
+}
+
+function escapeCell(value) {
+  return String(value ?? '').replace(/\|/g, '/').replace(/\r?\n/g, ' ');
+}
+
+function csvCell(value) {
+  const text = String(value ?? '');
+  return /[",\r\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
+}
+
+function slugOwner(owner) {
+  const map = {
+    '商务': 'business',
+    '投放/商务': 'media-business',
+    '商务/投放': 'business-media',
+    '技术/AI': 'tech-ai'
+  };
+  if (map[owner]) return map[owner];
+  const ascii = String(owner || 'owner').replace(/[^\w]+/g, '-').replace(/^-|-$/g, '').toLowerCase();
+  return ascii || Buffer.from(String(owner || 'owner')).toString('hex').slice(0, 12);
+}
+
+function withBom(text) {
+  return `\uFEFF${text}`;
+}
+
+if (require.main === module) main();
+
+module.exports = {
+  buildNextActions,
+  renderReport
+};

+ 189 - 0
claude-code-tihao-sourcing/scripts/business-proof-progress-smoke.js

@@ -0,0 +1,189 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const { spawnSync } = require('child_process');
+
+const root = path.resolve(__dirname, '..');
+
+function main() {
+  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-business-proof-progress-'));
+  const outputs = path.join(tmp, 'outputs');
+  fs.mkdirSync(outputs, { recursive: true });
+
+  makeDir(path.join(outputs, 'data-intake-pack-latest'), {
+    'history-data-template.csv': 'brief编号,客户原始Brief\n',
+    'manual-review-template.csv': 'brief编号,人工复核标签\n',
+    'README.md': '# 数据收集包\n'
+  });
+  makeDir(path.join(outputs, 'video-intake-pack-latest'), {
+    'video-resource-template.csv': 'brief编号,视频链接\n',
+    'README.md': '# 视频资源收集包\n'
+  });
+  writeJson(path.join(outputs, 'intake-readiness-latest', 'intake-readiness-summary.json'), {
+    failureCount: 9,
+    acceptance: {
+      historyReady: false,
+      videoReady: false,
+      reviewMetricsReady: false,
+      customerEffectReady: false,
+      overallReady: false
+    },
+    history: { issues: [{ type: 'placeholder' }] }
+  });
+  writeJson(path.join(outputs, 'video-resource-readiness-latest', 'video-resource-readiness-summary.json'), {
+    failureCount: 1,
+    counts: { realCandidateRows: 0 },
+    acceptance: { readyForVideoAbPreflight: false }
+  });
+  writeJson(path.join(outputs, 'long-run-readiness-latest', 'long-run-readiness-summary.json'), {
+    ready: false,
+    counts: { pass: 2, fail: 5 }
+  });
+  writeJson(path.join(outputs, 'proof-gap-closure-latest', 'proof-gap-closure-summary.json'), {
+    complete: false,
+    openCount: 4,
+    rows: [
+      { id: 'video-ab-live-proof', status: 'closed', evidence: 'passed=true, strongDelta=5, evidenceDelta=20' }
+    ]
+  });
+  writeJson(path.join(outputs, 'historical-dataset-thin', 'historical-dataset-audit.json'), {
+    briefCount: 5,
+    categoryCount: 3,
+    withCustomerDecision: 5,
+    acceptance: {
+      readyForLongRun: true,
+      readyForCustomerEffectProof: true
+    }
+  });
+  const operatorOwnerArtifacts = [
+    {
+      owner: '商务',
+      markdown: 'outputs/proof-gap-operator-pack-latest/by-owner/business.md',
+      csv: 'outputs/proof-gap-operator-pack-latest/by-owner/business.csv',
+      actionCount: 2,
+      topPriority: 1,
+      gapIds: ['historical-dataset', 'manual-review-and-customer-effect'],
+      evidenceRowCount: 2,
+      nextActionCount: 2
+    },
+    {
+      owner: '商务/投放',
+      markdown: 'outputs/proof-gap-operator-pack-latest/by-owner/business-media.md',
+      csv: 'outputs/proof-gap-operator-pack-latest/by-owner/business-media.csv',
+      actionCount: 1,
+      topPriority: 2,
+      gapIds: ['video-real-candidate'],
+      evidenceRowCount: 1,
+      nextActionCount: 1
+    },
+    {
+      owner: '技术/AI',
+      markdown: 'outputs/proof-gap-operator-pack-latest/by-owner/tech-ai.md',
+      csv: 'outputs/proof-gap-operator-pack-latest/by-owner/tech-ai.csv',
+      actionCount: 2,
+      topPriority: 3,
+      gapIds: ['video-ab-live-proof', 'live-provider-overnight-proof'],
+      evidenceRowCount: 2,
+      nextActionCount: 2
+    }
+  ];
+  writeJson(path.join(outputs, 'proof-gap-operator-pack-latest', 'proof-gap-operator-pack-summary.json'), {
+    passed: true,
+    complete: false,
+    ownerArtifactCount: 3,
+    ownerArtifacts: operatorOwnerArtifacts
+  });
+  writeJson(path.join(outputs, 'business-execution-index-latest', 'business-execution-index-summary.json'), {
+    passed: true,
+    operatorPackOwnerArtifactCount: 3,
+    operatorPackOwnerArtifactRowCount: 3,
+    operatorPackOwnerArtifacts: operatorOwnerArtifacts
+  });
+  writeJson(path.join(outputs, 'optimization-handoff-latest', 'optimization-handoff-summary.json'), {
+    complete: false,
+    statusCounts: { passed: 25, ready_not_proven: 2, blocked_by_external_data: 2 }
+  });
+  writeJson(path.join(outputs, 'evidence-index-latest', 'evidence-index-summary.json'), {
+    counts: { real_evidence: 5, smoke_or_local: 23, not_business_proof: 102 }
+  });
+  writeJson(path.join(outputs, 'round-deposition-latest', 'round-deposition-summary.json'), {
+    passed: true,
+    counts: { pass: 40, fail: 0, warn: 0 }
+  });
+  writeJson(path.join(outputs, 'optimization-status-latest', 'optimization-status-summary.json'), {
+    complete: false,
+    counts: { passed: 27, ready_not_proven: 2, blocked_by_external_data: 2 },
+    items: [
+      {
+        id: 'video-ab-proof',
+        status: 'ready_not_proven',
+        evidence: 'acceptance:video-ab smoke is ready but live proof is missing'
+      }
+    ]
+  });
+
+  const outputDir = path.join(outputs, 'business-proof-progress-latest');
+  const result = spawnSync(process.execPath, [
+    path.join(root, 'scripts', 'business-proof-progress.js'),
+    '--outputs',
+    outputs,
+    '--output',
+    outputDir
+  ], { cwd: root, encoding: 'utf8' });
+
+  if (result.status !== 0) {
+    process.stderr.write(result.stderr || result.stdout || '');
+    throw new Error('business-proof-progress should run');
+  }
+
+  const summary = JSON.parse(fs.readFileSync(path.join(outputDir, 'business-proof-progress-summary.json'), 'utf8'));
+  const report = fs.readFileSync(path.join(outputDir, 'business-proof-progress-report.md'), 'utf8').replace(/^\uFEFF/, '');
+  assert(summary.complete === false, 'progress must not report complete with blocked proof data');
+  assert(summary.rows.length === 15, 'progress should include 15 steps');
+  assert(summary.counts.pass === 4, 'template packs, history import presence, and deposition refresh should pass in fixture');
+  assert(summary.counts.fail === 2, 'intake preaudit and longrun gate should fail in fixture');
+  assert(summary.counts.blocked_by_external_data === 7, 'external data blockers should be explicit');
+  assert(summary.counts.pending === 2, 'pipeline and unproven video A/B should remain pending');
+  assert(summary.rows.some(row => row.order === 6 && row.status === 'pass'), 'history import should pass when audit file exists');
+  assert(summary.rows.some(row => row.order === 7 && row.status === 'blocked_by_external_data' && row.evidence.includes('missing=')), 'thin history audit should stay blocked and expose missing details');
+  assert(summary.rows.some(row => row.title === '客户效果审计' && row.status === 'blocked_by_external_data'), 'customer effect should be blocked by real data');
+  assert(summary.rows.some(row => row.title === '视频 A/B 验收' && row.status === 'pending' && row.evidence.includes('ready_not_proven')), 'video A/B should stay pending when status audit is ready_not_proven');
+  assert(summary.rows.every(row => row.owner !== '投放/商务'), 'progress owner labels should be normalized');
+  assert(summary.observed.videoAbProofStatus === 'ready_not_proven', 'progress should expose video A/B status audit result');
+  assert(summary.operatorPackOwnerArtifactCount === 3, 'progress should expose proof-gap operator owner attachment count');
+  assert(Array.isArray(summary.operatorPackOwnerArtifacts) && summary.operatorPackOwnerArtifacts.length === 3, 'progress should carry proof-gap operator owner attachments');
+  assert(summary.operatorPackOwnerArtifacts.some(item => item.markdown.includes('by-owner/business.md') && item.csv.includes('by-owner/business.csv')), 'progress should carry business owner operator attachment');
+  assert(summary.observed.businessExecutionOperatorPackOwnerArtifactRowCount === 3, 'progress should expose business execution operator attachment rows');
+  assert(report.includes('# 提号补证 15 步进度表'), 'report should be Chinese');
+  assert(report.includes('blocked_by_external_data'), 'report should show blocked status');
+  assert(report.includes('by-owner/business-media.md'), 'report should show proof-gap operator owner attachments');
+  assert(report.includes('不能把 sample、smoke'), 'report should preserve proof boundary');
+  assert(!/sk-[A-Za-z0-9_-]{10,}/.test(report), 'report should not contain model token patterns');
+  assert(!/r:[A-Za-z0-9]{20,}/.test(report), 'report should not contain Parse sessionToken patterns');
+
+  console.log(JSON.stringify({
+    ok: true,
+    outputDir,
+    complete: summary.complete,
+    counts: summary.counts
+  }, null, 2));
+}
+
+function makeDir(dir, files) {
+  fs.mkdirSync(dir, { recursive: true });
+  for (const [name, content] of Object.entries(files)) {
+    fs.writeFileSync(path.join(dir, name), content, 'utf8');
+  }
+}
+
+function writeJson(file, value) {
+  fs.mkdirSync(path.dirname(file), { recursive: true });
+  fs.writeFileSync(file, JSON.stringify(value, null, 2), 'utf8');
+}
+
+function assert(condition, message) {
+  if (!condition) throw new Error(message);
+}
+
+main();

+ 289 - 0
claude-code-tihao-sourcing/scripts/business-proof-progress.js

@@ -0,0 +1,289 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const path = require('path');
+
+const ROOT = path.resolve(__dirname, '..');
+const OUTPUTS = path.join(ROOT, 'outputs');
+
+function main() {
+  const args = parseArgs(process.argv.slice(2));
+  const outputDir = path.resolve(args.output || process.env.TIHAO_BUSINESS_PROOF_PROGRESS_OUTPUT || path.join(OUTPUTS, `business-proof-progress-${Date.now()}`));
+  const summary = buildBusinessProofProgress({
+    root: ROOT,
+    outputsDir: path.resolve(args.outputs || OUTPUTS)
+  });
+
+  fs.mkdirSync(outputDir, { recursive: true });
+  const jsonPath = path.join(outputDir, 'business-proof-progress-summary.json');
+  const reportPath = path.join(outputDir, 'business-proof-progress-report.md');
+  fs.writeFileSync(jsonPath, JSON.stringify(summary, null, 2), 'utf8');
+  fs.writeFileSync(reportPath, withBom(renderReport(summary)), 'utf8');
+
+  console.log(JSON.stringify({
+    outputDir,
+    json: jsonPath,
+    report: reportPath,
+    complete: summary.complete,
+    pass: summary.counts.pass,
+    blocked: summary.counts.blocked_by_external_data,
+    pending: summary.counts.pending,
+    fail: summary.counts.fail
+  }, null, 2));
+
+  if (!summary.complete && args.strict) process.exitCode = 1;
+}
+
+function buildBusinessProofProgress({ root, outputsDir }) {
+  const dataPackDir = latestDir(outputsDir, 'data-intake-pack');
+  const videoPackDir = latestDir(outputsDir, 'video-intake-pack');
+  const intake = readJson(latestFile(outputsDir, 'intake-readiness-summary.json'));
+  const videoReadiness = readJson(latestFile(outputsDir, 'video-resource-readiness-summary.json'));
+  const longrun = readJson(latestFile(outputsDir, 'long-run-readiness-summary.json'));
+  const closure = readJson(latestFile(outputsDir, 'proof-gap-closure-summary.json'));
+  const handoff = readJson(latestFile(outputsDir, 'optimization-handoff-summary.json'));
+  const evidence = readJson(latestFile(outputsDir, 'evidence-index-summary.json'));
+  const round = readJson(latestFile(outputsDir, 'round-deposition-summary.json'));
+  const status = readJson(latestFile(outputsDir, 'optimization-status-summary.json'));
+  const history = readJson(latestFile(outputsDir, 'historical-dataset-audit.json'));
+  const review = readJson(latestFile(outputsDir, 'review-metrics-summary.json'));
+  const customerEffect = readJson(latestFile(outputsDir, 'customer-effect-summary.json'));
+  const pipeline = readJson(latestFile(outputsDir, 'optimization-pipeline-summary.json'));
+  const proofGapOperatorPack = readJson(latestFile(outputsDir, 'proof-gap-operator-pack-summary.json'));
+  const businessExecutionIndex = readJson(latestFile(outputsDir, 'business-execution-index-summary.json'));
+  const operatorPackOwnerArtifacts = normalizeOperatorPackOwnerArtifacts(
+    proofGapOperatorPack?.ownerArtifacts || businessExecutionIndex?.operatorPackOwnerArtifacts || []
+  );
+  const operatorPackOwnerArtifactCount = Number(proofGapOperatorPack?.ownerArtifactCount || operatorPackOwnerArtifacts.length || 0);
+
+  const closureRow = (id) => Array.isArray(closure?.rows) ? closure.rows.find(row => row.id === id) : null;
+  const intakeOverallReady = intake?.acceptance?.overallReady === true && Number(intake?.failureCount || 0) === 0;
+  const videoReady = videoReadiness?.acceptance?.readyForVideoAbPreflight === true && Number(videoReadiness?.failureCount || 0) === 0;
+  const historyMissingRequirements = getHistoryCustomerEffectProofMissingRequirements(history);
+  const historyReady = historyMissingRequirements.length === 0;
+  const reviewReady = review?.acceptance?.overallPass === true;
+  const customerEffectReady = customerEffect?.acceptance?.overallPass === true;
+  const videoAbStatus = statusItem(status, 'video-ab-proof');
+  const videoAbPassed = videoAbStatus?.status === 'passed' && closureRow('video-ab-live-proof')?.status === 'closed';
+  const videoAbEvidence = videoAbStatus
+    ? `status=${videoAbStatus.status}, closure=${closureRow('video-ab-live-proof')?.status || 'missing'}, evidence=${videoAbStatus.evidence || ''}`
+    : (closureRow('video-ab-live-proof')?.evidence || 'missing video-ab status row');
+
+  const rows = [
+    step(1, '商务', '生成统一收集包', dataPackDir ? 'pass' : 'pending', dataPackDir ? rel(root, dataPackDir) : 'missing data-intake-pack', '没有统一模板时先运行 data:intake-template。'),
+    step(2, '商务/投放', '生成视频资源收集包', videoPackDir ? 'pass' : 'pending', videoPackDir ? rel(root, videoPackDir) : 'missing video-intake-pack', '没有视频模板时先运行 video:intake-template。'),
+    step(3, '商务', '填真实历史 Brief', intake?.acceptance?.historyReady ? 'pass' : 'blocked_by_external_data', intake ? `historyReady=${Boolean(intake.acceptance?.historyReady)}, issues=${intake.history?.issues?.length || 0}` : 'missing intake-readiness', '补 5-10 个真实 Brief、人工名单、客户选择、拒绝原因和历史人工补号量基线。'),
+    step(4, '商务/投放', '填真实视频资源', intake?.acceptance?.videoReady || videoReady ? 'pass' : 'blocked_by_external_data', videoReadiness ? `realCandidate=${videoReadiness.counts?.realCandidateRows || 0}, failureCount=${videoReadiness.failureCount || 0}` : 'missing video-resource-readiness', '补真实参考视频和真实候选视频 URL、封面、ASR、帧图或正文证据。'),
+    step(5, '技术/AI', '真实材料预审', intakeOverallReady ? 'pass' : 'fail', intake ? `overallReady=${Boolean(intake.acceptance?.overallReady)}, failureCount=${intake.failureCount || 0}` : 'missing intake-readiness', '先修 intake readiness 失败项,不能跳过预审。'),
+    step(6, '技术/AI', '历史数据导入', history ? 'pass' : 'pending', history ? 'historical-dataset-audit.json present' : 'missing historical-dataset-audit.json', '真实历史 CSV 到位后运行 history:from-csv。'),
+    step(7, '技术/AI', '历史数据审计', historyReady ? 'pass' : 'blocked_by_external_data', history ? `readyForCustomerEffectProof=${Boolean(history.acceptance?.readyForCustomerEffectProof)}, missing=${historyMissingRequirements.length}` : 'missing historical-dataset-audit.json', '运行 history:audit --strict,直到可支撑客户效果证明。'),
+    step(8, '技术/AI', '视频资源就绪审计', videoReady ? 'pass' : 'blocked_by_external_data', videoReadiness ? `readyForVideoAbPreflight=${Boolean(videoReadiness.acceptance?.readyForVideoAbPreflight)}, failureCount=${videoReadiness.failureCount || 0}` : 'missing video-resource-readiness', '补齐真实候选视频后重跑 video:resource-readiness --strict。'),
+    step(9, '技术/AI', '长跑前门禁', longrun?.ready ? 'pass' : 'fail', longrun ? `ready=${Boolean(longrun.ready)}, fail=${longrun.counts?.fail || 0}` : 'missing long-run-readiness', 'longrun:readiness ready=true 前不要启动 overnight。'),
+    step(10, '技术/AI', '运行策略矩阵', pipeline?.readyForClaim ? 'pass' : 'pending', pipeline ? `readyForClaim=${Boolean(pipeline.readyForClaim)}, pass=${pipeline.counts?.pass || 0}, fail=${pipeline.counts?.fail || 0}` : 'missing optimization-pipeline-summary.json', '真实材料和长跑门禁通过后再跑 optimization:pipeline。'),
+    step(11, '技术/AI', '视频 A/B 验收', videoAbPassed ? 'pass' : 'pending', videoAbEvidence, '只有真实视频 A/B 通过且 status 审计为 passed 时才可用于视频提升结论。'),
+    step(12, '商务', '人工复核标注', intake?.acceptance?.reviewMetricsReady ? 'pass' : 'blocked_by_external_data', intake ? `reviewMetricsReady=${Boolean(intake.acceptance?.reviewMetricsReady)}, customerEffectReady=${Boolean(intake.acceptance?.customerEffectReady)}` : 'missing intake-readiness', '补商务复核标签、负样本归因、客户选择和本轮人工补号量。'),
+    step(13, '技术/AI', '商务复核指标', reviewReady ? 'pass' : 'blocked_by_external_data', review ? `overallPass=${Boolean(review.acceptance?.overallPass)}` : 'missing review-metrics-summary.json', '已标注 CSV 到位后运行 review:metrics --strict。'),
+    step(14, '技术/AI', '客户效果审计', customerEffectReady ? 'pass' : 'blocked_by_external_data', customerEffect ? `overallPass=${Boolean(customerEffect.acceptance?.overallPass)}` : 'missing customer-effect-summary.json', '客户选择、历史基线和本轮人工补号量到位后运行 customer-effect:audit --strict。'),
+    step(15, '技术/AI', '刷新证据和交接', round?.passed && handoff && evidence ? 'pass' : 'pending', `round=${Boolean(round?.passed)}, handoff=${Boolean(handoff)}, evidence=${Boolean(evidence)}, operatorPackOwnerArtifactCount=${operatorPackOwnerArtifactCount}`, '每轮结束刷新 evidence:index、proof-gap:closure、handoff:summary 和 round:deposition。')
+  ];
+
+  const counts = countRows(rows);
+  return {
+    generatedAt: new Date().toISOString(),
+    root,
+    outputsDir,
+    complete: rows.every(row => row.status === 'pass'),
+    counts,
+    observed: {
+      intakeReady: intakeOverallReady,
+      intakeFailureCount: intake ? Number(intake.failureCount || 0) : null,
+      videoReady,
+      videoFailureCount: videoReadiness ? Number(videoReadiness.failureCount || 0) : null,
+      longrunReady: Boolean(longrun?.ready),
+      longrunFailCount: longrun ? Number(longrun.counts?.fail || 0) : null,
+      proofGapOpenCount: closure ? Number(closure.openCount || 0) : null,
+      handoffComplete: Boolean(handoff?.complete),
+      videoAbProofStatus: videoAbStatus?.status || null,
+      proofGapOperatorPackOwnerArtifactCount: operatorPackOwnerArtifactCount,
+      businessExecutionOperatorPackOwnerArtifactCount: Number(businessExecutionIndex?.operatorPackOwnerArtifactCount || 0),
+      businessExecutionOperatorPackOwnerArtifactRowCount: Number(businessExecutionIndex?.operatorPackOwnerArtifactRowCount || 0),
+      evidenceCounts: evidence?.counts || {}
+    },
+    operatorPackOwnerArtifactCount,
+    operatorPackOwnerArtifacts,
+    guardrails: [
+      '本进度表只同步补证执行状态,不能把 sample、smoke、模板、provider fallback 或接口 200 计为客户效果证明。',
+      'blocked_by_external_data 表示需要真实历史 Brief、真实视频资源、商务复核或客户选择数据。',
+      '只有全部 15 步为 pass,且 customer-effect:audit 与 proof-gap:closure 通过后,才可进入长期完成判断。',
+      'proof-gap by-owner attachments are dispatch files only; they do not prove customer effect.',
+      '不得在任何输入、报告或日志中写入 sessionToken、Authorization、模型 token 或 npm token。'
+    ],
+    rows
+  };
+}
+
+function statusItem(status, id) {
+  return Array.isArray(status?.items) ? status.items.find(item => item.id === id) : null;
+}
+
+function getHistoryCustomerEffectProofMissingRequirements(history) {
+  if (!history) return ['missing historical-dataset-audit.json'];
+  const missing = [];
+  const acceptance = history.acceptance || {};
+  if (acceptance.readyForCustomerEffectProof !== true) missing.push('acceptance.readyForCustomerEffectProof=true');
+  if (Number(history.briefCount || 0) < 5) missing.push('briefCount>=5');
+  if (acceptance.parseOk !== true) missing.push('acceptance.parseOk=true');
+  if (acceptance.minBriefsMet !== true) missing.push('acceptance.minBriefsMet=true');
+  if (acceptance.allHaveBriefText !== true) missing.push('acceptance.allHaveBriefText=true');
+  if (acceptance.allHaveManualFinalList !== true) missing.push('acceptance.allHaveManualFinalList=true');
+  if (acceptance.allHaveCustomerDecision !== true) missing.push('acceptance.allHaveCustomerDecision=true');
+  if (acceptance.allHaveFeedbackReason !== true) missing.push('acceptance.allHaveFeedbackReason=true');
+  if (acceptance.allHaveManualSupplementBaseline !== true) missing.push('acceptance.allHaveManualSupplementBaseline=true');
+  if (acceptance.categoryCoverageMet !== true) missing.push('acceptance.categoryCoverageMet=true');
+  if (Number(history.missingCriticalCount || 0) !== 0) missing.push('missingCriticalCount=0');
+  if (!Array.isArray(history.items) || history.items.length < 5) missing.push('items.length>=5');
+  if (Number(history.withManualFinalList || 0) < 5) missing.push('withManualFinalList>=5');
+  if (Number(history.withCustomerDecision || 0) < 5) missing.push('withCustomerDecision>=5');
+  if (Number(history.withRejectionReason || 0) < 1) missing.push('withRejectionReason>=1');
+  if (Number(history.withManualSupplementBaseline || 0) < 5) missing.push('withManualSupplementBaseline>=5');
+  return uniqueStrings(missing);
+}
+
+function step(order, owner, title, status, evidence, next) {
+  return { order, owner, title, status, evidence, next };
+}
+
+function normalizeOperatorPackOwnerArtifacts(ownerArtifacts) {
+  if (!Array.isArray(ownerArtifacts)) return [];
+  return ownerArtifacts.map(item => ({
+    owner: item.owner || '',
+    actionCount: Number(item.actionCount || 0),
+    topPriority: Number(item.topPriority || 0),
+    markdown: String(item.markdown || '').replace(/\\/g, '/'),
+    csv: String(item.csv || '').replace(/\\/g, '/'),
+    gapIds: Array.isArray(item.gapIds) ? item.gapIds : [],
+    evidenceRowCount: Number(item.evidenceRowCount || 0),
+    nextActionCount: Number(item.nextActionCount || 0)
+  }));
+}
+
+function renderReport(summary) {
+  const lines = [
+    '# 提号补证 15 步进度表',
+    '',
+    `- 生成时间:${summary.generatedAt}`,
+    `- 是否完成:${summary.complete ? '是' : '否'}`,
+    `- pass:${summary.counts.pass || 0}`,
+    `- fail:${summary.counts.fail || 0}`,
+    `- pending:${summary.counts.pending || 0}`,
+    `- blocked_by_external_data:${summary.counts.blocked_by_external_data || 0}`,
+    '',
+    '## 当前观测',
+    '',
+    `- intakeReady:${summary.observed.intakeReady ? 'true' : 'false'}`,
+    `- intakeFailureCount:${summary.observed.intakeFailureCount ?? 'unknown'}`,
+    `- videoReady:${summary.observed.videoReady ? 'true' : 'false'}`,
+    `- videoFailureCount:${summary.observed.videoFailureCount ?? 'unknown'}`,
+    `- longrunReady:${summary.observed.longrunReady ? 'true' : 'false'}`,
+    `- longrunFailCount:${summary.observed.longrunFailCount ?? 'unknown'}`,
+    `- proofGapOpenCount:${summary.observed.proofGapOpenCount ?? 'unknown'}`,
+    `- handoffComplete:${summary.observed.handoffComplete ? 'true' : 'false'}`,
+    `- videoAbProofStatus:${summary.observed.videoAbProofStatus ?? 'unknown'}`,
+    `- proofGapOperatorPackOwnerArtifactCount:${summary.observed.proofGapOperatorPackOwnerArtifactCount ?? 'unknown'}`,
+    `- businessExecutionOperatorPackOwnerArtifactCount:${summary.observed.businessExecutionOperatorPackOwnerArtifactCount ?? 'unknown'}`,
+    `- businessExecutionOperatorPackOwnerArtifactRowCount:${summary.observed.businessExecutionOperatorPackOwnerArtifactRowCount ?? 'unknown'}`,
+    '',
+    '## 15 步进度',
+    '',
+    '| 顺序 | 负责人 | 目标 | 状态 | 证据 | 下一步 |',
+    '| --- | --- | --- | --- | --- | --- |',
+    ...summary.rows.map(row => `| ${row.order} | ${escapeCell(row.owner)} | ${escapeCell(row.title)} | ${row.status} | ${escapeCell(row.evidence)} | ${escapeCell(row.next)} |`),
+    '',
+    '## proof-gap 操作包负责人附件',
+    '',
+    '| 负责人 | 动作数 | Markdown | CSV | 缺口 |',
+    '| --- | ---: | --- | --- | --- |',
+    ...summary.operatorPackOwnerArtifacts.map(item => `| ${escapeCell(item.owner)} | ${item.actionCount} | ${escapeCell(item.markdown)} | ${escapeCell(item.csv)} | ${escapeCell((item.gapIds || []).join(';'))} |`),
+    '',
+    '## 边界',
+    '',
+    ...summary.guardrails.map(item => `- ${item}`)
+  ];
+  return lines.join('\n');
+}
+
+function latestDir(outputsDir, prefix) {
+  if (!fs.existsSync(outputsDir)) return '';
+  const dirs = fs.readdirSync(outputsDir, { withFileTypes: true })
+    .filter(entry => entry.isDirectory() && entry.name.startsWith(prefix))
+    .map(entry => path.join(outputsDir, entry.name))
+    .sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
+  return dirs[0] || '';
+}
+
+function latestFile(outputsDir, fileName) {
+  if (!fs.existsSync(outputsDir)) return '';
+  return fs.readdirSync(outputsDir, { withFileTypes: true })
+    .filter(entry => entry.isDirectory())
+    .map(entry => path.join(outputsDir, entry.name, fileName))
+    .filter(fileExists)
+    .sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs)[0] || '';
+}
+
+function readJson(file) {
+  if (!file || !fs.existsSync(file)) return null;
+  try {
+    return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, ''));
+  } catch {
+    return null;
+  }
+}
+
+function fileExists(file) {
+  return Boolean(file) && fs.existsSync(file);
+}
+
+function countRows(rows) {
+  return rows.reduce((acc, row) => {
+    acc[row.status] = (acc[row.status] || 0) + 1;
+    return acc;
+  }, {});
+}
+
+function uniqueStrings(values) {
+  return [...new Set((values || []).filter(Boolean).map(value => String(value)))];
+}
+
+function parseArgs(argv) {
+  const args = {};
+  for (let index = 0; index < argv.length; index += 1) {
+    const raw = argv[index];
+    if (!raw.startsWith('--')) continue;
+    const key = raw.slice(2).replace(/-([a-z])/g, (_, char) => char.toUpperCase());
+    const next = argv[index + 1];
+    if (!next || next.startsWith('--')) args[key] = true;
+    else {
+      args[key] = next;
+      index += 1;
+    }
+  }
+  return args;
+}
+
+function rel(root, file) {
+  return path.relative(root, file).replace(/\\/g, '/');
+}
+
+function escapeCell(value) {
+  return String(value ?? '').replace(/\|/g, '/').replace(/\r?\n/g, ' ');
+}
+
+function withBom(text) {
+  return `\uFEFF${text}`;
+}
+
+if (require.main === module) main();
+
+module.exports = {
+  buildBusinessProofProgress,
+  renderReport
+};

+ 136 - 0
claude-code-tihao-sourcing/scripts/candidate-pool-smoke.js

@@ -0,0 +1,136 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const { buildSourcingReport } = require('../mcp/src/features/tihao-sourcing/report');
+
+function main() {
+  const root = path.resolve(__dirname, '..');
+  const outputRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-candidate-pool-'));
+
+  const fullPool = buildSourcingReport({
+    criteria: {
+      briefId: '候选池验收',
+      brand: '候选池验收',
+      targetCount: 4,
+      platforms: ['xiaohongshu']
+    },
+    candidates: sampleCandidates(),
+    collectionMode: 'sample',
+    outputDir: path.join(outputRoot, 'full')
+  });
+
+  assert(fullPool.summary.reviewPoolTarget === 6, 'targetCount=4 should require a 1.5x review pool of 6');
+  assert(fullPool.summary.clientReadyCount === 6, 'software table should output 6 reviewable candidates');
+  assert(fullPool.summary.duplicateCount === 1, 'duplicate homepage URL should be reported');
+  assertNoDuplicateSoftwareRows(path.join(outputRoot, 'full', 'tihao-sourcing-client-list.csv'));
+
+  const shortPool = buildSourcingReport({
+    criteria: {
+      briefId: '候选池不足验收',
+      brand: '候选池不足验收',
+      targetCount: 10,
+      platforms: ['xiaohongshu']
+    },
+    candidates: sampleCandidates(),
+    collectionMode: 'sample',
+    outputDir: path.join(outputRoot, 'short')
+  });
+
+  const shortJson = JSON.parse(fs.readFileSync(path.join(outputRoot, 'short', 'tihao-sourcing-result.json'), 'utf8'));
+  assert(shortPool.summary.reviewPoolTarget === 15, 'targetCount=10 should require a 1.5x review pool of 15');
+  assert(shortPool.summary.clientReadyCount === 7, 'short software table should keep all available non-excluded candidates');
+  assert(shortJson.warnings.some(item => item.includes('1.5 倍候选池 15 位')), 'short pool should warn about 1.5x candidate pool gap');
+  assert(shortPool.summary.calibrationQuestions.some(item => item.includes('1.5 倍候选池')), 'calibration question should mention 1.5x pool');
+
+  console.log(JSON.stringify({
+    ok: true,
+    outputRoot,
+    fullPool: fullPool.summary,
+    shortPool: shortPool.summary
+  }, null, 2));
+}
+
+function sampleCandidates() {
+  return [
+    creator('账号A', 88, 'https://example.com/a'),
+    creator('账号A-高分重复', 93, 'https://example.com/a'),
+    creator('账号B', 87, 'https://example.com/b'),
+    creator('账号C', 86, 'https://example.com/c'),
+    creator('账号D', 85, 'https://example.com/d'),
+    creator('账号E', 84, 'https://example.com/e'),
+    creator('账号F', 83, 'https://example.com/f'),
+    creator('账号G', 82, 'https://example.com/g')
+  ];
+}
+
+function creator(displayName, score, profileUrl) {
+  return {
+    platform: 'xiaohongshu',
+    displayName,
+    profileUrl,
+    score,
+    briefFitScore: score,
+    referenceStyleFitScore: 80,
+    recentContentFitScore: 78,
+    visualQualityScore: 76,
+    toneConsistencyScore: 79,
+    recommendStatus: score >= 86 ? '强推荐' : '备选',
+    recommendReason: `${displayName} 命中 Brief 核心人群和参考风格。`,
+    riskNote: '需商务复核报价有效期。'
+  };
+}
+
+function assertNoDuplicateSoftwareRows(csvPath) {
+  const lines = fs.readFileSync(csvPath, 'utf8').replace(/^\uFEFF/, '').trim().split(/\r?\n/);
+  const header = lines[0].split(',');
+  const rows = lines.slice(1).map(line => parseCsvLine(line, header));
+  const keys = new Set();
+  for (const row of rows) {
+    const briefId = pick(row, ['brief编号']);
+    const platform = pick(row, ['平台']);
+    const profileUrl = pick(row, ['主页链接']);
+    const displayName = pick(row, ['账号名称', '博主名称']);
+    const key = profileUrl
+      ? `${briefId}|${platform}|${profileUrl.toLowerCase()}`
+      : `${briefId}|${platform}|${displayName.toLowerCase()}`;
+    assert(!keys.has(key), `duplicate software row key: ${key}`);
+    keys.add(key);
+  }
+  assert(rows.map(row => Number(pick(row, ['序号', '排名']))).join(',') === '1,2,3,4,5,6', 'software rows should be reranked continuously');
+}
+
+function pick(row, keys) {
+  for (const key of keys) {
+    if (row[key] !== undefined && row[key] !== '') return row[key];
+  }
+  return '';
+}
+
+function parseCsvLine(line, header) {
+  const cells = [];
+  let current = '';
+  let quoted = false;
+  for (let i = 0; i < line.length; i += 1) {
+    const ch = line[i];
+    if (ch === '"' && quoted && line[i + 1] === '"') {
+      current += '"';
+      i += 1;
+    } else if (ch === '"') {
+      quoted = !quoted;
+    } else if (ch === ',' && !quoted) {
+      cells.push(current);
+      current = '';
+    } else {
+      current += ch;
+    }
+  }
+  cells.push(current);
+  return Object.fromEntries(header.map((key, index) => [key, cells[index] || '']));
+}
+
+function assert(condition, message) {
+  if (!condition) throw new Error(message);
+}
+
+main();

+ 54 - 0
claude-code-tihao-sourcing/scripts/create-data-intake-pack-smoke.js

@@ -0,0 +1,54 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const { spawnSync } = require('child_process');
+const { HISTORY_HEADER, REVIEW_HEADER } = require('./create-data-intake-pack');
+
+const root = path.resolve(__dirname, '..');
+
+function main() {
+  const output = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-data-intake-'));
+  const result = spawnSync(process.execPath, [
+    path.join(root, 'scripts', 'create-data-intake-pack.js'),
+    '--output',
+    output
+  ], { cwd: root, encoding: 'utf8' });
+  if (result.status !== 0) {
+    process.stderr.write(result.stderr || result.stdout || '');
+    throw new Error('create-data-intake-pack failed');
+  }
+
+  const historyCsv = path.join(output, 'history-data-template.csv');
+  const reviewCsv = path.join(output, 'manual-review-template.csv');
+  const readme = path.join(output, 'README.md');
+  assert(fs.existsSync(historyCsv), 'history template should exist');
+  assert(fs.existsSync(reviewCsv), 'review template should exist');
+  assert(fs.existsSync(readme), 'README should exist');
+
+  assert(readHeader(historyCsv) === HISTORY_HEADER.join(','), 'history template header should match history:from-csv fields');
+  assert(readHeader(reviewCsv) === REVIEW_HEADER.join(','), 'review template header should match review/customer-effect fields');
+
+  const allText = [historyCsv, reviewCsv, readme].map(file => fs.readFileSync(file, 'utf8')).join('\n');
+  assert(!/(sk-[A-Za-z0-9_-]{20,}|r:[A-Za-z0-9]{20,}|Authorization\s*[:=]\s*Bearer)/i.test(allText), 'data intake pack should not contain secrets');
+  assert(allText.includes('客户选择'), 'templates should include customer decision field');
+  assert(allText.includes('历史人工补号量基线'), 'templates should include historical manual supplement baseline');
+  assert(allText.includes('本轮人工补号量'), 'templates should include current manual supplement count');
+  assert(allText.includes('字段填写验收表'), 'README should include field checklist');
+  assert(allText.includes('常见退回原因'), 'README should explain common rejection reasons');
+  assert(allText.includes('npm run intake:readiness'), 'README should tell business to run intake readiness first');
+  assert(allText.includes('客户拒绝样本必须填写'), 'README should explain rejection reason requirement');
+  assert(fs.readFileSync(readme, 'utf8').includes('才能宣称客户效果证明完成'), 'README should preserve proof boundary');
+
+  console.log(JSON.stringify({ ok: true, output, files: 3 }, null, 2));
+}
+
+function readHeader(file) {
+  return fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, '').split(/\r?\n/)[0];
+}
+
+function assert(condition, message) {
+  if (!condition) throw new Error(message);
+}
+
+main();

+ 207 - 0
claude-code-tihao-sourcing/scripts/create-data-intake-pack.js

@@ -0,0 +1,207 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const path = require('path');
+
+const HISTORY_HEADER = [
+  'brief编号',
+  '项目名称',
+  '类目',
+  '客户原始Brief',
+  '历史人工补号量基线',
+  '参考账号或视频',
+  '平台',
+  '博主名称',
+  '主页链接',
+  '人工复核标签',
+  '客户选择',
+  '拒绝原因'
+];
+
+const REVIEW_HEADER = [
+  'brief编号',
+  '策略',
+  '排名',
+  '平台',
+  '博主名称',
+  '综合分',
+  'brief匹配分',
+  '参考风格分',
+  '主页证据分',
+  '视觉质感分',
+  '调性一致分',
+  '证据加分',
+  '证据风险扣分',
+  '推荐理由',
+  '风险提示',
+  '主页链接',
+  '人工复核标签',
+  '客户选择',
+  '归因类型',
+  '反馈原因',
+  '本轮人工补号量'
+];
+
+function main() {
+  const args = parseArgs(process.argv.slice(2));
+  const output = path.resolve(args.output || path.join(__dirname, '..', 'outputs', `data-intake-pack-${Date.now()}`));
+  fs.mkdirSync(output, { recursive: true });
+
+  const historyCsv = path.join(output, 'history-data-template.csv');
+  const reviewCsv = path.join(output, 'manual-review-template.csv');
+  const readme = path.join(output, 'README.md');
+
+  writeCsv(historyCsv, [
+    HISTORY_HEADER,
+    [
+      'brief-001',
+      '示例项目',
+      '母婴',
+      '填写客户原始 Brief,不要写 token、报价明细或密钥。',
+      '6',
+      'https://example.com/reference-video',
+      '小红书',
+      '示例博主',
+      'https://example.com/profile',
+      '可直接发客户',
+      '客户选中',
+      ''
+    ]
+  ]);
+
+  writeCsv(reviewCsv, [
+    REVIEW_HEADER,
+    [
+      'brief-001',
+      'result-first-broad',
+      '1',
+      '小红书',
+      '示例博主',
+      '86',
+      '82',
+      '78',
+      '80',
+      '79',
+      '81',
+      '4',
+      '0',
+      '命中 Brief 人群和参考风格,可给商务复核。',
+      '需确认报价有效期、档期和近 30 天竞品合作。',
+      'https://example.com/profile',
+      '商务复核',
+      '待客户反馈',
+      '',
+      '',
+      '3'
+    ]
+  ]);
+
+  fs.writeFileSync(readme, withBom(renderReadme()), 'utf8');
+
+  console.log(JSON.stringify({ output, files: { historyCsv, reviewCsv, readme } }, null, 2));
+}
+
+function renderReadme() {
+  return [
+    '# 提号真实数据收集包',
+    '',
+    '用途:把商务历史 Brief、人工名单、客户选择和本轮复核结果整理成可审计数据。',
+    '',
+    '## 文件',
+    '',
+    '- `history-data-template.csv`:整理 5-10 个真实历史 Brief,用于 `history:from-csv` 和 `history:audit`。',
+    '- `manual-review-template.csv`:整理本轮 AI 产出的候选名单和商务/客户复核,用于 `review:metrics` 和 `customer-effect:audit`。',
+    '',
+    '## 填写规则',
+    '',
+    '- 不要写 Parse sessionToken、模型 token、鉴权头、npm token 或客户敏感报价明细。',
+    '- `历史人工补号量基线` 必须填写,才能证明人工补号量减少 50%。',
+    '- `客户选择` 建议填写 `客户选中`、`客户拒绝` 或 `待客户反馈`。',
+    '- 负样本必须填写 `归因类型`,例如 `需求解析错`、`隐性规则漏`、`召回关键词错`、`主页证据不足`、`视频证据误判`、`排序权重错`、`输出解释错`。',
+    '',
+    '## 字段填写验收表',
+    '',
+    '### 历史数据表',
+    '',
+    '| 字段 | 必填规则 | 常见退回原因 |',
+    '| --- | --- | --- |',
+    '| brief编号 | 同一项目保持稳定编号。 | 空值、多个项目混用同一编号。 |',
+    '| 客户原始Brief | 填客户原始需求,至少能看出类目、人群、平台、预算或内容方向。 | 只写“见附件”“种草合作”或少于 20 字。 |',
+    '| 历史人工补号量基线 | 填数字,表示历史同类需求人工额外补号数量。 | 空值、写“不知道”、写区间。 |',
+    '| 参考账号或视频 | 填真实主页链接或视频链接。 | example.com、截图占位、只写账号昵称无链接。 |',
+    '| 博主名称/主页链接 | 填人工最终名单里的真实博主和主页。 | 只有博主名、主页缺失、候选占位。 |',
+    '| 客户选择 | 填 `客户选中` 或 `客户拒绝`。 | 一直保留 `待客户反馈` 时不能进入客户效果审计。 |',
+    '| 拒绝原因 | 客户拒绝样本必须填写。 | 拒绝样本为空,无法归因优化。 |',
+    '',
+    '### 人工复核表',
+    '',
+    '| 字段 | 必填规则 | 常见退回原因 |',
+    '| --- | --- | --- |',
+    '| 人工复核标签 | 每个候选都要填,例如 `可直接发客户`、`商务复核`、`跑偏`、`调性不符`。 | 空值或只写“看情况”。 |',
+    '| 归因类型 | 负样本必须填固定归因类型。 | 跑偏、调性不符、硬性违约但没有归因。 |',
+    '| 反馈原因 | 写具体原因,能让下一轮优化规则或权重。 | 只写“不合适”。 |',
+    '| 客户选择 | 已有客户反馈时填 `客户选中` 或 `客户拒绝`。 | 全部待反馈时只能做复核指标,不能做客户效果。 |',
+    '| 本轮人工补号量 | 填数字,表示本轮 AI 后仍需人工补号数量。 | 空值或非数字,无法计算人工补号减少率。 |',
+    '',
+    '## 先跑预审',
+    '',
+    '商务/投放填完后先跑:',
+    '',
+    '```powershell',
+    'npm run intake:readiness -- --data-pack outputs\\data-intake-pack-latest --video-pack outputs\\video-intake-pack-latest --output outputs\\intake-readiness-latest',
+    '```',
+    '',
+    '预审 `failureCount=0` 后,再进入 history、review 和 customer-effect 审计。',
+    '',
+    '## 审计命令',
+    '',
+    '```powershell',
+    'npm run history:from-csv -- --input <history-data-template.csv> --output <history-dataset目录>',
+    'npm run history:audit -- --input <history-dataset目录> --output <历史审计输出目录> --strict',
+    'npm run review:metrics -- --input <manual-review-template.csv> --output <复核指标输出目录> --strict',
+    'npm run customer-effect:audit -- --review-csv <manual-review-template.csv> --history-audit <historical-dataset-audit.json> --output <客户效果输出目录> --strict',
+    'npm run evidence:index -- --output outputs\\evidence-index-latest',
+    '```',
+    '',
+    '## 边界',
+    '',
+    '- 模板生成不代表业务效果达标。',
+    '- 只有真实客户选择、真实历史补号基线和本轮补号量都通过审计,才能宣称客户效果证明完成。'
+  ].join('\n');
+}
+
+function writeCsv(file, rows) {
+  fs.writeFileSync(file, withBom(rows.map(row => row.map(csvCell).join(',')).join('\n')), 'utf8');
+}
+
+function csvCell(value) {
+  const text = String(value ?? '');
+  return /[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
+}
+
+function parseArgs(argv) {
+  const args = {};
+  for (let index = 0; index < argv.length; index += 1) {
+    const raw = argv[index];
+    if (!raw.startsWith('--')) continue;
+    const key = raw.slice(2).replace(/-([a-z])/g, (_, char) => char.toUpperCase());
+    const next = argv[index + 1];
+    if (!next || next.startsWith('--')) args[key] = true;
+    else {
+      args[key] = next;
+      index += 1;
+    }
+  }
+  return args;
+}
+
+function withBom(text) {
+  return `\uFEFF${text}`;
+}
+
+if (require.main === module) main();
+
+module.exports = {
+  HISTORY_HEADER,
+  REVIEW_HEADER,
+  renderReadme
+};

+ 53 - 0
claude-code-tihao-sourcing/scripts/create-video-intake-pack-smoke.js

@@ -0,0 +1,53 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const { spawnSync } = require('child_process');
+const { VIDEO_RESOURCE_HEADER } = require('./create-video-intake-pack');
+
+const root = path.resolve(__dirname, '..');
+
+function main() {
+  const output = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-video-intake-'));
+  const result = spawnSync(process.execPath, [
+    path.join(root, 'scripts', 'create-video-intake-pack.js'),
+    '--output',
+    output
+  ], { cwd: root, encoding: 'utf8' });
+  if (result.status !== 0) {
+    process.stderr.write(result.stderr || result.stdout || '');
+    throw new Error('create-video-intake-pack failed');
+  }
+
+  const videoCsv = path.join(output, 'video-resource-template.csv');
+  const readme = path.join(output, 'README.md');
+  assert(fs.existsSync(videoCsv), 'video resource template should exist');
+  assert(fs.existsSync(readme), 'README should exist');
+  assert(readHeader(videoCsv) === VIDEO_RESOURCE_HEADER.join(','), 'video resource header should match expected fields');
+
+  const allText = [videoCsv, readme].map(file => fs.readFileSync(file, 'utf8')).join('\n');
+  assert(allText.includes('视频链接'), 'template should include video URL field');
+  assert(allText.includes('封面链接'), 'template should include cover URL field');
+  assert(allText.includes('字幕或ASR文本'), 'template should include ASR/subtitle field');
+  assert(allText.includes('帧图链接'), 'template should include frame URL field');
+  assert(allText.includes('是否真实资源'), 'template should include real-resource flag');
+  assert(allText.includes('字段填写验收表'), 'README should include field checklist');
+  assert(allText.includes('常见退回原因'), 'README should explain common rejection reasons');
+  assert(allText.includes('npm run video:resource-readiness'), 'README should tell users to run video resource readiness first');
+  assert(allText.includes('把占位行标成真实资源'), 'README should warn against fake-real rows');
+  assert(allText.includes('不得声明视频分析完成'), 'README should preserve proof boundary');
+  assert(allText.includes('acceptance:video-ab'), 'README should point to video A/B audit');
+  assert(!/(sk-[A-Za-z0-9_-]{20,}|r:[A-Za-z0-9]{20,}|Authorization\s*[:=]\s*Bearer)/i.test(allText), 'video intake pack should not contain secrets');
+
+  console.log(JSON.stringify({ ok: true, output, files: 2 }, null, 2));
+}
+
+function readHeader(file) {
+  return fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, '').split(/\r?\n/)[0];
+}
+
+function assert(condition, message) {
+  if (!condition) throw new Error(message);
+}
+
+main();

+ 184 - 0
claude-code-tihao-sourcing/scripts/create-video-intake-pack.js

@@ -0,0 +1,184 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const path = require('path');
+
+const VIDEO_RESOURCE_HEADER = [
+  'brief编号',
+  '项目名称',
+  '平台',
+  '资源角色',
+  '博主名称',
+  '主页链接',
+  '视频链接',
+  '封面链接',
+  '字幕或ASR文本',
+  '帧图链接',
+  '标题',
+  '发布时间',
+  '内容摘要',
+  '风格调性标签',
+  '画面人设场景信号',
+  '风险提示',
+  '来源接口或备注',
+  '是否真实资源'
+];
+
+function main() {
+  const args = parseArgs(process.argv.slice(2));
+  const output = path.resolve(args.output || path.join(__dirname, '..', 'outputs', `video-intake-pack-${Date.now()}`));
+  fs.mkdirSync(output, { recursive: true });
+
+  const videoCsv = path.join(output, 'video-resource-template.csv');
+  const readme = path.join(output, 'README.md');
+  writeCsv(videoCsv, [
+    VIDEO_RESOURCE_HEADER,
+    [
+      'brief-001',
+      '示例项目',
+      '小红书',
+      '参考视频',
+      '示例博主',
+      'https://example.com/profile',
+      'https://example.com/video.mp4',
+      'https://example.com/cover.jpg',
+      '示例 ASR 文本,真实填写时不要写 token 或鉴权头。',
+      'https://example.com/frame1.jpg|https://example.com/frame2.jpg',
+      '示例标题',
+      '2026-06-01',
+      '说明视频内容、人群、场景、表达结构。',
+      '真实记录感|高质感女性场景|轻讲解',
+      '室内自然光、真人出镜、护肤前后对比、口播节奏稳定',
+      '需复核广告合规表达和报价档期。',
+      'TikHub/VOC social/人工补充均可,写来源但不要写密钥。',
+      '是'
+    ],
+    [
+      'brief-001',
+      '示例项目',
+      '小红书',
+      '候选视频',
+      '候选博主',
+      'https://example.com/candidate-profile',
+      '',
+      'https://example.com/candidate-cover.jpg',
+      '',
+      '',
+      '候选视频标题',
+      '2026-06-02',
+      '暂时只有封面和标题时,只能作为待补视频证据。',
+      '待确认',
+      '封面偏生活方式,缺 ASR 和帧图。',
+      '缺真实视频链接,不得声明视频分析完成。',
+      '人工待补',
+      '否'
+    ]
+  ]);
+  fs.writeFileSync(readme, withBom(renderReadme()), 'utf8');
+
+  console.log(JSON.stringify({ output, files: { videoCsv, readme } }, null, 2));
+}
+
+function renderReadme() {
+  return [
+    '# 提号视频资源收集包',
+    '',
+    '用途:为 `acceptance:video-ab` 和真实视频风格分析准备可审计资源,避免只凭接口 200 或占位提示判断视频能力有效。',
+    '',
+    '## 文件',
+    '',
+    '- `video-resource-template.csv`:收集参考视频和候选视频的真实资源。',
+    '',
+    '## 填写规则',
+    '',
+    '- `资源角色` 填 `参考视频` 或 `候选视频`。',
+    '- `视频链接` 优先填写真实可访问的视频 URL;没有视频 URL 时,必须至少补 `封面链接`、`字幕或ASR文本`、`帧图链接` 其中之一。',
+    '- `帧图链接` 多个链接用 `|` 分隔。',
+    '- `是否真实资源` 只能填 `是` 或 `否`;没有真实视频 URL、封面、字幕、ASR、帧图或正文证据时必须填 `否`。',
+    '- 不要写 Parse sessionToken、模型 token、鉴权头、npm token、报价密钥或内部鉴权参数。',
+    '',
+    '## 字段填写验收表',
+    '',
+    '| 字段 | 必填规则 | 常见退回原因 |',
+    '| --- | --- | --- |',
+    '| 资源角色 | 只能填 `参考视频` 或 `候选视频`。 | 写成参考账号、达人主页、素材等非固定值。 |',
+    '| 博主名称/主页链接 | 填真实博主和主页链接。 | 只有昵称、主页缺失、example.com。 |',
+    '| 视频链接 | 优先填真实视频 URL。 | 只填主页链接,或链接不可判断为视频资源。 |',
+    '| 封面链接 | 没有视频 URL 时至少补封面、ASR、帧图或正文证据之一。 | 真实资源行没有任何可审计证据。 |',
+    '| 字幕或ASR文本 | 至少 12 字,能反映表达结构或卖点。 | 只写“无”“待补”“看视频”。 |',
+    '| 帧图链接 | 多张图用 `|` 分隔。 | 多个链接用中文逗号导致解析困难。 |',
+    '| 内容摘要/风格调性标签/画面人设场景信号 | 描述人群、场景、内容结构、画面质感和风险。 | 只写“不错”“高质感”但没有具体信号。 |',
+    '| 是否真实资源 | 有真实 URL 或证据时填 `是`,占位或待补填 `否`。 | 把占位行标成真实资源。 |',
+    '',
+    '## 先跑预审',
+    '',
+    '填完后先跑:',
+    '',
+    '```powershell',
+    'npm run video:resource-readiness -- --input outputs\\video-intake-pack-latest\\video-resource-template.csv --output outputs\\video-resource-readiness-latest --strict',
+    '```',
+    '',
+    '视频资源预审通过只代表 A/B 前置资源齐备,不代表视频分析已经提升命中率。',
+    '',
+    '## 与视频 A/B 验收的关系',
+    '',
+    '真实视频 A/B 通过前,至少需要证明:',
+    '',
+    '- 真实参考资源已加载。',
+    '- 真实视频 URL 已加载。',
+    '- 封面、ASR 或帧图至少一类已加载。',
+    '- 视频分析 provider 状态为 ok。',
+    '- 证据卡已生成,且不是待补占位。',
+    '- Top 10 证据命中提升且分数不下降。',
+    '- 强推荐数量不下降。',
+    '',
+    '## 命令',
+    '',
+    '```powershell',
+    'npm run video:intake-template -- --output outputs\\video-intake-pack-latest',
+    'npm run acceptance:video-ab',
+    'npm run evidence:index -- --output outputs\\evidence-index-latest',
+    '```',
+    '',
+    '## 边界',
+    '',
+    '- 模板生成本身不证明视频分析完成。',
+    '- 只有真实视频 URL、封面、字幕、ASR、帧图或正文证据被加载,并且 `acceptance:video-ab` 通过后,才能声明视频分析链路有效。',
+    '- 没有真实客户复核或客户选择数据时,仍不能声明视频分析提升了客户命中率。'
+  ].join('\n');
+}
+
+function writeCsv(file, rows) {
+  fs.writeFileSync(file, withBom(rows.map(row => row.map(csvCell).join(',')).join('\n')), 'utf8');
+}
+
+function csvCell(value) {
+  const text = String(value ?? '');
+  return /[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
+}
+
+function parseArgs(argv) {
+  const args = {};
+  for (let index = 0; index < argv.length; index += 1) {
+    const raw = argv[index];
+    if (!raw.startsWith('--')) continue;
+    const key = raw.slice(2).replace(/-([a-z])/g, (_, char) => char.toUpperCase());
+    const next = argv[index + 1];
+    if (!next || next.startsWith('--')) args[key] = true;
+    else {
+      args[key] = next;
+      index += 1;
+    }
+  }
+  return args;
+}
+
+function withBom(text) {
+  return `\uFEFF${text}`;
+}
+
+if (require.main === module) main();
+
+module.exports = {
+  VIDEO_RESOURCE_HEADER,
+  renderReadme
+};

+ 96 - 0
claude-code-tihao-sourcing/scripts/customer-effect-audit-smoke.js

@@ -0,0 +1,96 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const { buildMetrics, readCsv } = require('./review-metrics');
+const { buildCustomerEffectSummary } = require('./customer-effect-audit');
+
+function main() {
+  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-customer-effect-'));
+  const reviewCsv = path.join(dir, 'reviewed.csv');
+  fs.writeFileSync(reviewCsv, withBom([
+    'brief编号,策略,排名,平台,博主名称,综合分,brief匹配分,参考风格分,主页证据分,视觉质感分,调性一致分,证据加分,证据风险扣分,推荐理由,风险提示,主页链接,人工复核标签,客户选择,归因类型,本轮人工补号量',
+    'brief-a,baseline-live,1,小红书,基础召回A,80,80,40,60,70,70,0,0,基础召回,需复核,http://example.com/a1,商务复核,客户选中,,3',
+    'brief-a,baseline-live,2,小红书,基础召回B,79,79,40,60,70,70,0,0,基础召回,调性偏泛,http://example.com/a2,跑偏,,召回关键词错,3',
+    'brief-a,baseline-live,3,小红书,基础召回C,78,78,40,60,70,70,0,0,基础召回,需复核,http://example.com/a3,商务复核,客户选中,,3',
+    'brief-a,reference-account,4,小红书,参考召回A,90,90,88,85,85,88,2,0,参考相似,需复核,http://example.com/a4,可直接发客户,客户选中,,3',
+    'brief-a,reference-account,5,小红书,参考召回B,89,89,87,85,85,88,2,0,参考相似,需复核,http://example.com/a5,商务复核,客户选中,,3',
+    'brief-a,homepage-evidence,6,小红书,主页证据A,88,88,82,90,85,88,2,0,主页匹配,需复核,http://example.com/a6,商务复核,客户选中,,3',
+    'brief-a,homepage-evidence,7,小红书,主页证据B,87,87,82,89,85,88,2,0,主页匹配,需复核,http://example.com/a7,商务复核,客户选中,,3',
+    'brief-a,video-enhanced,8,小红书,视频证据A,86,86,84,88,85,88,2,0,视频证据匹配,需复核,http://example.com/a8,商务复核,客户选中,,3',
+    'brief-a,video-enhanced,9,小红书,视频证据B,85,85,84,88,85,88,2,0,视频证据匹配,需复核,http://example.com/a9,商务复核,,,3',
+    'brief-a,result-first,10,小红书,结果优先A,84,84,80,86,84,86,2,0,综合匹配,需复核,http://example.com/a10,商务复核,客户选中,,3'
+  ].join('\n')), 'utf8');
+
+  const rows = readCsv(reviewCsv);
+  const reviewMetrics = buildMetrics({ inputPath: reviewCsv, rows });
+  const passingHistory = {
+    acceptance: { readyForCustomerEffectProof: true },
+    items: [
+      { id: 'brief-a', manualSupplementBaseline: 6 }
+    ]
+  };
+  const passSummary = buildCustomerEffectSummary({
+    reviewPath: reviewCsv,
+    reviewRows: rows,
+    reviewMetrics,
+    history: passingHistory,
+    historyAuditPath: path.join(dir, 'historical-dataset-audit.json')
+  });
+  assert(passSummary.customerSelectedRate === 0.8, 'customer selected rate should be 80%');
+  assert(passSummary.referenceCustomerSelectedRate === 0.8333, 'reference selected rate should be 83%');
+  assert(passSummary.manualSupplementReductionRate === 0.5, 'manual supplement reduction should be 50%');
+  assert(passSummary.acceptance.customerSelectedRate30Pass, '30% selected-rate gate should pass');
+  assert(passSummary.acceptance.customerSelectedRate50Pass, '50% selected-rate gate should pass');
+  assert(passSummary.acceptance.referenceCustomerSelectedRate40Pass, 'reference selected-rate gate should pass');
+  assert(passSummary.acceptance.manualSupplementReduction50Pass, 'manual supplement reduction gate should pass');
+  assert(passSummary.acceptance.overallPass, 'complete customer effect proof should pass');
+
+  const missingCurrentCsv = path.join(dir, 'missing-current.csv');
+  fs.writeFileSync(missingCurrentCsv, withBom([
+    'brief编号,策略,排名,平台,博主名称,综合分,brief匹配分,参考风格分,主页证据分,视觉质感分,调性一致分,证据加分,证据风险扣分,推荐理由,风险提示,主页链接,人工复核标签,客户选择,归因类型',
+    'brief-b,reference-account,1,小红书,参考召回B,90,90,88,85,85,88,2,0,参考相似,需复核,http://example.com/b1,可直接发客户,客户选中,'
+  ].join('\n')), 'utf8');
+  const missingCurrentRows = readCsv(missingCurrentCsv);
+  const missingCurrentSummary = buildCustomerEffectSummary({
+    reviewPath: missingCurrentCsv,
+    reviewRows: missingCurrentRows,
+    reviewMetrics: buildMetrics({ inputPath: missingCurrentCsv, rows: missingCurrentRows }),
+    history: passingHistory,
+    historyAuditPath: path.join(dir, 'historical-dataset-audit.json')
+  });
+  assert(!missingCurrentSummary.acceptance.currentManualSupplementAvailable, 'missing current manual supplement count should be pending');
+  assert(!missingCurrentSummary.acceptance.overallPass, 'missing current manual supplement count should block overall pass');
+
+  const missingBaselineSummary = buildCustomerEffectSummary({
+    reviewPath: reviewCsv,
+    reviewRows: rows,
+    reviewMetrics,
+    history: {
+      acceptance: { readyForCustomerEffectProof: false },
+      items: [{ id: 'brief-a', manualSupplementBaseline: null }]
+    },
+    historyAuditPath: path.join(dir, 'bad-history.json')
+  });
+  assert(!missingBaselineSummary.acceptance.manualSupplementBaselineAvailable, 'missing baseline should be detected');
+  assert(!missingBaselineSummary.acceptance.historyReadyForCustomerEffectProof, 'bad history audit should block customer proof');
+  assert(!missingBaselineSummary.acceptance.overallPass, 'missing baseline should block overall pass');
+
+  console.log(JSON.stringify({
+    ok: true,
+    output: dir,
+    customerSelectedRate: passSummary.customerSelectedRate,
+    referenceCustomerSelectedRate: passSummary.referenceCustomerSelectedRate,
+    manualSupplementReductionRate: passSummary.manualSupplementReductionRate
+  }, null, 2));
+}
+
+function withBom(text) {
+  return `\uFEFF${text}`;
+}
+
+function assert(condition, message) {
+  if (!condition) throw new Error(message);
+}
+
+if (require.main === module) main();

+ 274 - 0
claude-code-tihao-sourcing/scripts/customer-effect-audit.js

@@ -0,0 +1,274 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const path = require('path');
+const { buildMetrics, readCsv } = require('./review-metrics');
+
+function main() {
+  const args = parseArgs(process.argv.slice(2));
+  const reviewCsv = args.reviewCsv || args.input || process.env.TIHAO_REVIEW_CSV || '';
+  const historyAudit = args.historyAudit || process.env.TIHAO_HISTORY_AUDIT || '';
+  if (!reviewCsv) throw new Error('Usage: node scripts/customer-effect-audit.js --review-csv <reviewed.csv> [--history-audit <historical-dataset-audit.json>] [--current-manual-supplement-count <n>] [--output <dir>] [--strict]');
+
+  const reviewPath = path.resolve(reviewCsv);
+  const outputDir = path.resolve(args.output || process.env.TIHAO_CUSTOMER_EFFECT_OUTPUT || path.join(path.dirname(reviewPath), 'customer-effect-audit'));
+  const strict = Boolean(args.strict || process.env.TIHAO_CUSTOMER_EFFECT_STRICT === 'true');
+  const reviewRows = readCsv(reviewPath);
+  const reviewMetrics = buildMetrics({ inputPath: reviewPath, rows: reviewRows });
+  const history = historyAudit ? readJson(path.resolve(historyAudit)) : null;
+  const summary = buildCustomerEffectSummary({
+    reviewPath,
+    reviewRows,
+    reviewMetrics,
+    history,
+    historyAuditPath: historyAudit ? path.resolve(historyAudit) : '',
+    currentManualSupplementCountArg: args.currentManualSupplementCount ?? args.currentManualAddedCount ?? args.manualSupplementCount
+  });
+
+  fs.mkdirSync(outputDir, { recursive: true });
+  const jsonPath = path.join(outputDir, 'customer-effect-summary.json');
+  const reportPath = path.join(outputDir, 'customer-effect-report.md');
+  fs.writeFileSync(jsonPath, JSON.stringify(summary, null, 2), 'utf8');
+  fs.writeFileSync(reportPath, withBom(renderReport(summary)), 'utf8');
+  console.log(JSON.stringify({
+    reviewCsv: reviewPath,
+    historyAudit: summary.historyAuditPath || null,
+    outputDir,
+    json: jsonPath,
+    report: reportPath,
+    customerSelectedRate: summary.customerSelectedRate,
+    referenceCustomerSelectedRate: summary.referenceCustomerSelectedRate,
+    manualSupplementReductionRate: summary.manualSupplementReductionRate,
+    overallPass: summary.acceptance.overallPass
+  }, null, 2));
+  if (strict && !summary.acceptance.overallPass) process.exitCode = 2;
+}
+
+function buildCustomerEffectSummary({ reviewPath, reviewRows, reviewMetrics, history, historyAuditPath, currentManualSupplementCountArg }) {
+  const currentManualSupplement = resolveCurrentManualSupplementCount(reviewRows, currentManualSupplementCountArg);
+  const manualBaseline = resolveManualSupplementBaseline(history);
+  const manualSupplementReductionRate = manualBaseline.available && currentManualSupplement.available && manualBaseline.total > 0
+    ? roundRatio((manualBaseline.total - currentManualSupplement.total) / manualBaseline.total)
+    : null;
+  const hasReferenceSelectionData = reviewMetrics.strategyComparison.reference.total > 0 &&
+    reviewMetrics.strategyComparison.reference.selected > 0;
+  const acceptance = {
+    reviewMetricsOverallUsable: reviewMetrics.acceptance.overallPass,
+    customerSelectedRateMeasured: reviewMetrics.selectedCount > 0,
+    customerSelectedRate30Pass: reviewMetrics.selectedCount > 0 ? reviewMetrics.customerSelectedRate >= 0.3 : null,
+    customerSelectedRate50Pass: reviewMetrics.selectedCount > 0 ? reviewMetrics.customerSelectedRate >= 0.5 : null,
+    referenceCustomerSelectedRateMeasured: hasReferenceSelectionData,
+    referenceCustomerSelectedRate40Pass: hasReferenceSelectionData ? reviewMetrics.strategyComparison.reference.customerSelectedRate >= 0.4 : null,
+    historyReadyForCustomerEffectProof: Boolean(history?.acceptance?.readyForCustomerEffectProof),
+    manualSupplementBaselineAvailable: manualBaseline.available,
+    currentManualSupplementAvailable: currentManualSupplement.available,
+    manualSupplementReduction50Pass: manualSupplementReductionRate === null ? null : manualSupplementReductionRate >= 0.5
+  };
+  acceptance.overallPass = acceptance.reviewMetricsOverallUsable &&
+    acceptance.customerSelectedRate30Pass === true &&
+    acceptance.referenceCustomerSelectedRate40Pass !== false &&
+    acceptance.historyReadyForCustomerEffectProof &&
+    acceptance.manualSupplementBaselineAvailable &&
+    acceptance.currentManualSupplementAvailable &&
+    acceptance.manualSupplementReduction50Pass === true;
+  return {
+    reviewPath,
+    historyAuditPath,
+    generatedAt: new Date().toISOString(),
+    reviewMetrics: {
+      total: reviewMetrics.total,
+      labeled: reviewMetrics.labeled,
+      positiveCount: reviewMetrics.positiveCount,
+      negativeCount: reviewMetrics.negativeCount,
+      selectedCount: reviewMetrics.selectedCount,
+      businessUsableRate: reviewMetrics.businessUsableRate,
+      offTargetHardFailRate: reviewMetrics.offTargetHardFailRate,
+      customerSelectedRate: reviewMetrics.customerSelectedRate,
+      referenceCustomerSelectedRate: reviewMetrics.strategyComparison.reference.customerSelectedRate,
+      referenceSelectedCount: reviewMetrics.strategyComparison.reference.selected,
+      keywordBusinessUsableRate: reviewMetrics.strategyComparison.keyword.businessUsableRate,
+      referenceBusinessUsableRate: reviewMetrics.strategyComparison.reference.businessUsableRate,
+      failureAttributionCoverage: reviewMetrics.failureAttributionCoverage,
+      acceptance: reviewMetrics.acceptance
+    },
+    customerSelectedRate: reviewMetrics.customerSelectedRate,
+    referenceCustomerSelectedRate: reviewMetrics.strategyComparison.reference.customerSelectedRate,
+    manualSupplementBaseline: manualBaseline,
+    currentManualSupplement,
+    manualSupplementReductionRate,
+    acceptance,
+    missingEvidence: missingEvidence(acceptance)
+  };
+}
+
+function resolveCurrentManualSupplementCount(rows, argValue) {
+  const argNumber = toNumberOrNull(argValue);
+  if (argNumber !== null) return { available: true, source: 'cli-arg', total: argNumber, byBrief: [] };
+  const fields = ['本轮人工补号量', '当前人工补号量', '人工补号量', '人工补号数', 'manualSupplementCount', 'currentManualSupplementCount', 'manualAddedCount'];
+  const byBrief = new Map();
+  let total = 0;
+  let found = false;
+  for (const row of rows.body) {
+    const value = firstNumber(row, fields);
+    if (value === null) continue;
+    found = true;
+    const briefId = row['brief编号'] || '未命名brief';
+    if (!byBrief.has(briefId)) byBrief.set(briefId, value);
+  }
+  for (const value of byBrief.values()) total += value;
+  return {
+    available: found,
+    source: found ? 'review-csv' : '',
+    total: found ? total : null,
+    byBrief: [...byBrief.entries()].map(([briefId, count]) => ({ briefId, count }))
+  };
+}
+
+function resolveManualSupplementBaseline(history) {
+  if (!history) return { available: false, source: '', total: null, byBrief: [] };
+  const values = [];
+  for (const item of history.items || []) {
+    if (item.manualSupplementBaseline !== null && item.manualSupplementBaseline !== undefined && item.manualSupplementBaseline !== '' && Number.isFinite(Number(item.manualSupplementBaseline))) {
+      values.push({ briefId: item.id || item.name || path.basename(item.file || ''), count: Number(item.manualSupplementBaseline) });
+    }
+  }
+  if (!values.length) return { available: false, source: 'history-audit', total: null, byBrief: [] };
+  return {
+    available: true,
+    source: 'history-audit',
+    total: values.reduce((sum, item) => sum + item.count, 0),
+    byBrief: values
+  };
+}
+
+function missingEvidence(acceptance) {
+  const missing = [];
+  if (!acceptance.customerSelectedRateMeasured) missing.push('缺少客户最终选择字段,不能证明客户选中率。');
+  if (acceptance.customerSelectedRate30Pass === false) missing.push('客户选中率未达到 30%。');
+  if (acceptance.customerSelectedRate50Pass === false) missing.push('客户选中率未达到长期 50%。');
+  if (!acceptance.referenceCustomerSelectedRateMeasured) missing.push('缺少参考账号/主页/视频策略客户选择数据,不能证明参考链路 40%。');
+  if (acceptance.referenceCustomerSelectedRate40Pass === false) missing.push('参考链路客户选中率未达到 40%。');
+  if (!acceptance.historyReadyForCustomerEffectProof) missing.push('历史数据集未通过客户效果证明准备门禁。');
+  if (!acceptance.manualSupplementBaselineAvailable) missing.push('缺少历史人工补号量基线。');
+  if (!acceptance.currentManualSupplementAvailable) missing.push('缺少本轮人工补号量。');
+  if (acceptance.manualSupplementReduction50Pass === false) missing.push('人工补号量减少未达到 50%。');
+  return missing;
+}
+
+function renderReport(summary) {
+  const lines = [
+    '# 客户效果证明审计报告',
+    '',
+    `- 复核表:${summary.reviewPath}`,
+    `- 历史审计:${summary.historyAuditPath || '未提供'}`,
+    `- 生成时间:${summary.generatedAt}`,
+    `- 总体验收:${summary.acceptance.overallPass ? '通过' : '未通过'}`,
+    '',
+    '## 核心门禁',
+    '',
+    '| 门禁 | 当前值 | 目标 | 状态 |',
+    '| --- | ---: | ---: | --- |',
+    `| 客户选中率 | ${measuredPct(summary.acceptance.customerSelectedRateMeasured, summary.customerSelectedRate)} | >= 30% | ${pendingPassFail(summary.acceptance.customerSelectedRate30Pass)} |`,
+    `| 长期客户选中率 | ${measuredPct(summary.acceptance.customerSelectedRateMeasured, summary.customerSelectedRate)} | >= 50% | ${pendingPassFail(summary.acceptance.customerSelectedRate50Pass)} |`,
+    `| 参考链路客户选中率 | ${measuredPct(summary.acceptance.referenceCustomerSelectedRateMeasured, summary.referenceCustomerSelectedRate)} | >= 40% | ${pendingPassFail(summary.acceptance.referenceCustomerSelectedRate40Pass)} |`,
+    `| 历史数据可用于客户效果证明 | ${summary.acceptance.historyReadyForCustomerEffectProof ? '是' : '否'} | 是 | ${passFail(summary.acceptance.historyReadyForCustomerEffectProof)} |`,
+    `| 历史人工补号基线 | ${summary.manualSupplementBaseline.available ? summary.manualSupplementBaseline.total : '缺失'} | 已提供 | ${passFail(summary.acceptance.manualSupplementBaselineAvailable)} |`,
+    `| 本轮人工补号量 | ${summary.currentManualSupplement.available ? summary.currentManualSupplement.total : '缺失'} | 已提供 | ${passFail(summary.acceptance.currentManualSupplementAvailable)} |`,
+    `| 人工补号减少率 | ${summary.manualSupplementReductionRate === null ? '待补数据' : pct(summary.manualSupplementReductionRate)} | >= 50% | ${pendingPassFail(summary.acceptance.manualSupplementReduction50Pass)} |`,
+    '',
+    '## 复核质量摘要',
+    '',
+    '| 指标 | 当前值 |',
+    '| --- | ---: |',
+    `| 已标注行数 | ${summary.reviewMetrics.labeled} |`,
+    `| 商务可用率 | ${pct(summary.reviewMetrics.businessUsableRate)} |`,
+    `| 负样本率 | ${pct(summary.reviewMetrics.offTargetHardFailRate)} |`,
+    `| 负样本归因覆盖率 | ${pct(summary.reviewMetrics.failureAttributionCoverage)} |`,
+    `| 参考链路商务可用率 | ${pct(summary.reviewMetrics.referenceBusinessUsableRate)} |`,
+    `| 关键词基线商务可用率 | ${pct(summary.reviewMetrics.keywordBusinessUsableRate)} |`,
+    '',
+    '## 待补证据',
+    '',
+    ...(summary.missingEvidence.length ? summary.missingEvidence.map(item => `- ${item}`) : ['- 无']),
+    '',
+    '## 说明',
+    '',
+    '- 本报告只在客户最终选择、历史人工补号基线和本轮人工补号量同时存在时,才判断“人工补号量减少 50%”。',
+    '- 只有客户选中率达到 30%,才能证明短期客户效果;达到 50% 才能证明长期目标。',
+    '- 有参考账号/参考视频的 Brief,需要参考链路客户选中率达到 40%。'
+  ];
+  return lines.join('\n');
+}
+
+function firstNumber(row, fields) {
+  for (const field of fields) {
+    const value = toNumberOrNull(row[field]);
+    if (value !== null) return value;
+  }
+  return null;
+}
+
+function toNumberOrNull(value) {
+  if (value === undefined || value === null || value === '') return null;
+  const numeric = Number(value);
+  return Number.isFinite(numeric) && numeric >= 0 ? numeric : null;
+}
+
+function readJson(file) {
+  return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, ''));
+}
+
+function parseArgs(argv) {
+  const args = {};
+  for (let i = 0; i < argv.length; i += 1) {
+    const raw = argv[i];
+    if (!raw.startsWith('--')) continue;
+    const key = raw.slice(2).replace(/-([a-z])/g, (_, char) => char.toUpperCase());
+    const next = argv[i + 1];
+    if (!next || next.startsWith('--')) args[key] = true;
+    else {
+      args[key] = next;
+      i += 1;
+    }
+  }
+  return args;
+}
+
+function roundRatio(value) {
+  return Math.round(Number(value || 0) * 10000) / 10000;
+}
+
+function pct(value) {
+  return `${Math.round(Number(value || 0) * 100)}%`;
+}
+
+function measuredPct(measured, value) {
+  return measured ? pct(value) : '未标注';
+}
+
+function passFail(value) {
+  return value ? '通过' : '未通过';
+}
+
+function pendingPassFail(value) {
+  if (value === null) return '待补数据';
+  return passFail(value);
+}
+
+function withBom(text) {
+  return `\uFEFF${text}`;
+}
+
+if (require.main === module) {
+  try {
+    main();
+  } catch (error) {
+    console.error(error && error.stack ? error.stack : String(error));
+    process.exit(1);
+  }
+}
+
+module.exports = {
+  buildCustomerEffectSummary,
+  resolveCurrentManualSupplementCount,
+  resolveManualSupplementBaseline
+};

+ 534 - 0
claude-code-tihao-sourcing/scripts/evidence-index-smoke.js

@@ -0,0 +1,534 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const { buildEvidenceIndex } = require('./evidence-index');
+
+function main() {
+  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-evidence-index-root-'));
+  const outputs = path.join(root, 'outputs');
+  fs.mkdirSync(outputs, { recursive: true });
+  writeJson(path.join(outputs, 'overnight-live', 'aggregate-summary.json'), {
+    manifest: { liveEnabled: true },
+    acceptance: { overallPass: true, failedGateCount: 0 },
+    runCount: 12,
+    failureCount: 0,
+    fixtureCount: 3,
+    variantCount: 4
+  });
+  writeJson(path.join(outputs, 'overnight-live-failed', 'aggregate-summary.json'), {
+    manifest: { liveEnabled: true },
+    acceptance: { overallPass: false, failedGateCount: 1 },
+    runCount: 12,
+    failureCount: 0,
+    fixtureCount: 3,
+    variantCount: 4
+  });
+  writeJson(path.join(outputs, 'status', 'optimization-status-summary.json'), {
+    complete: false,
+    counts: { passed: 12, ready_not_proven: 2, blocked_by_external_data: 2 }
+  });
+  writeJson(path.join(outputs, 'live-preflight-ready', 'live-preflight-summary.json'), {
+    readyForLiveAcceptance: true,
+    readyForVideoAb: true,
+    checks: [
+      { name: 'session-token-present', status: 'pass' },
+      { name: 'company-resolved', status: 'pass' },
+      { name: 'video-model-doubao-seed-2-0-pro', status: 'pass' }
+    ]
+  });
+  writeJson(path.join(outputs, 'live-preflight-missing', 'live-preflight-summary.json'), {
+    readyForLiveAcceptance: false,
+    readyForVideoAb: false,
+    checks: [
+      { name: 'session-token-present', status: 'fail' },
+      { name: 'company-resolved', status: 'fail' },
+      { name: 'homepage-provider-configured', status: 'warn' }
+    ]
+  });
+  writeJson(path.join(outputs, 'review', 'review-metrics-summary.json'), {
+    total: 10,
+    selectedCount: 4,
+    businessUsableRate: 0.7,
+    offTargetHardFailRate: 0.1,
+    customerSelectedRate: 0.4,
+    acceptance: { overallPass: true, customerSelectedRatePass: true }
+  });
+  writeJson(path.join(outputs, 'history-real', 'historical-dataset-audit.json'), passingHistoryAudit());
+  writeJson(path.join(outputs, 'history-thin', 'historical-dataset-audit.json'), {
+    briefCount: 5,
+    categoryCount: 3,
+    withCustomerDecision: 5,
+    acceptance: {
+      readyForLongRun: true,
+      readyForCustomerEffectProof: true
+    }
+  });
+  writeJson(path.join(outputs, 'video', 'video-hit-rate-summary.json'), {
+    acceptance: {
+      passed: true,
+      realReferenceResourceLoaded: true,
+      realVideoResourceLoaded: true,
+      coverOrFrameOrAsrLoaded: true,
+      evidenceCardsNotPending: true,
+      evidenceCardsHaveSignals: true,
+      strongNotDegraded: true,
+      top10EvidenceImproved: true
+    },
+    delta: { strong: 1, top10EvidenceHitCandidates: 3 }
+  });
+  writeJson(path.join(outputs, 'video-weak', 'video-hit-rate-summary.json'), {
+    acceptance: {
+      passed: true,
+      realReferenceResourceLoaded: true,
+      realVideoResourceLoaded: false,
+      coverOrFrameOrAsrLoaded: true,
+      evidenceCardsNotPending: true,
+      evidenceCardsHaveSignals: true,
+      strongNotDegraded: true,
+      top10EvidenceImproved: true
+    },
+    delta: { strong: 1, top10EvidenceHitCandidates: 3 }
+  });
+  writeJson(path.join(outputs, 'video-ab-preflight-missing', 'video-hit-rate-preflight-summary.json'), {
+    materialType: 'video_ab_runtime_preflight',
+    directCustomerProof: false,
+    proofLevel: 'not_business_proof',
+    canCloseProofGap: false,
+    readyForVideoAb: false,
+    failureCount: 4,
+    proofContext: {
+      generatedBy: 'acceptance:video-ab-preflight',
+      mode: 'not_run',
+      collectionMode: 'not_run'
+    }
+  });
+  writeJson(path.join(outputs, 'video-resource-ready', 'video-resource-readiness-summary.json'), {
+    acceptance: { readyForVideoAbPreflight: true },
+    counts: { realReferenceRows: 1, realCandidateRows: 1, videoUrlRows: 2 },
+    failureCount: 0
+  });
+  writeJson(path.join(outputs, 'video-resource-missing', 'video-resource-readiness-summary.json'), {
+    acceptance: { readyForVideoAbPreflight: false },
+    counts: { realReferenceRows: 0, realCandidateRows: 0, videoUrlRows: 0 },
+    failureCount: 3
+  });
+  writeJson(path.join(outputs, 'homepage-evidence-ready', 'homepage-evidence-readiness-summary.json'), {
+    ready: true,
+    directCustomerProof: false,
+    proofLevel: 'smoke_or_local',
+    counts: { candidates: 2, providerEvidenceCreators: 2, creatorsWithPosts: 2 },
+    failureCount: 0
+  });
+  writeJson(path.join(outputs, 'homepage-evidence-missing', 'homepage-evidence-readiness-summary.json'), {
+    ready: false,
+    directCustomerProof: false,
+    proofLevel: 'not_business_proof',
+    counts: { candidates: 1, providerEvidenceCreators: 0, creatorsWithPosts: 0 },
+    failureCount: 4
+  });
+  fs.writeFileSync(
+    path.join(outputs, 'homepage-evidence-missing', 'homepage-evidence-repair-actions.csv'),
+    'owner,action,reason\nbusiness-media,补齐最近 10 篇主页内容,缺主页近期内容证据\n',
+    'utf8'
+  );
+  fs.writeFileSync(
+    path.join(outputs, 'video-resource-missing', 'video-resource-repair-actions.csv'),
+    'owner,action,reason\nbusiness-media,补齐真实候选视频 URL,缺候选视频\n',
+    'utf8'
+  );
+  writeJson(path.join(outputs, 'customer-effect', 'customer-effect-summary.json'), {
+    customerSelectedRate: 0.5,
+    referenceCustomerSelectedRate: 0.6,
+    manualSupplementReductionRate: 0.5,
+    acceptance: {
+      overallPass: true,
+      customerSelectedRateMeasured: true,
+      customerSelectedRate30Pass: true,
+      referenceCustomerSelectedRateMeasured: true,
+      referenceCustomerSelectedRate40Pass: true,
+      historyReadyForCustomerEffectProof: true,
+      manualSupplementBaselineAvailable: true,
+      currentManualSupplementAvailable: true,
+      manualSupplementReduction50Pass: true
+    }
+  });
+  writeJson(path.join(outputs, 'customer-effect-missing-current', 'customer-effect-summary.json'), {
+    customerSelectedRate: 0.5,
+    referenceCustomerSelectedRate: 0.6,
+    manualSupplementReductionRate: null,
+    acceptance: {
+      overallPass: false,
+      customerSelectedRateMeasured: true,
+      customerSelectedRate30Pass: true,
+      referenceCustomerSelectedRateMeasured: true,
+      referenceCustomerSelectedRate40Pass: true,
+      historyReadyForCustomerEffectProof: true,
+      manualSupplementBaselineAvailable: true,
+      currentManualSupplementAvailable: false,
+      manualSupplementReduction50Pass: null
+    }
+  });
+  writeJson(path.join(outputs, 'pipeline-pass', 'optimization-pipeline-summary.json'), {
+    dryRun: false,
+    readyForClaim: true,
+    proofGapClosure: { complete: true, closedCount: 5, openCount: 0 },
+    counts: { pass: 5 },
+    results: [
+      { id: 'history-audit', status: 'pass' },
+      { id: 'review-metrics', status: 'pass' },
+      { id: 'customer-effect-audit', status: 'pass' },
+      { id: 'proof-gap-closure', status: 'pass' },
+      { id: 'evidence-index', status: 'pass' }
+    ]
+  });
+  writeJson(path.join(outputs, 'pipeline-claim-with-open-closure', 'optimization-pipeline-summary.json'), {
+    dryRun: false,
+    readyForClaim: true,
+    proofGapClosure: { complete: false, closedCount: 4, openCount: 1 },
+    counts: { pass: 5 },
+    results: [
+      { id: 'history-audit', status: 'pass' },
+      { id: 'review-metrics', status: 'pass' },
+      { id: 'customer-effect-audit', status: 'pass' },
+      { id: 'proof-gap-closure', status: 'pass' },
+      { id: 'evidence-index', status: 'pass' }
+    ]
+  });
+  writeJson(path.join(outputs, 'pipeline-claim-with-open-closure', 'customer-effect-audit', 'customer-effect-summary.json'), {
+    customerSelectedRate: 0.5,
+    referenceCustomerSelectedRate: 0.6,
+    manualSupplementReductionRate: 0.5,
+    acceptance: {
+      overallPass: true,
+      customerSelectedRateMeasured: true,
+      customerSelectedRate30Pass: true,
+      referenceCustomerSelectedRateMeasured: true,
+      referenceCustomerSelectedRate40Pass: true,
+      historyReadyForCustomerEffectProof: true,
+      manualSupplementBaselineAvailable: true,
+      currentManualSupplementAvailable: true,
+      manualSupplementReduction50Pass: true
+    }
+  });
+  writeJson(path.join(outputs, 'pipeline-fail', 'optimization-pipeline-summary.json'), {
+    dryRun: false,
+    readyForClaim: false,
+    counts: { pass: 1, fail: 1 },
+    results: [
+      { id: 'history-audit', status: 'pass' },
+      { id: 'customer-effect-audit', status: 'fail' }
+    ]
+  });
+  writeJson(path.join(outputs, 'completion-open', 'optimization-completion-summary.json'), {
+    complete: false,
+    readyForClaim: false,
+    proofGapOpenCount: 5,
+    proofGapClosedCount: 0,
+    blockingReasons: [
+      { id: 'intake-readiness' },
+      { id: 'customer-effect-proof' }
+    ],
+    canClaim: {
+      tihaoRateImproved: false,
+      customerEffectAchieved: false,
+      manualSupplementReduced: false
+    }
+  });
+  writeJson(path.join(outputs, 'handoff', 'optimization-handoff-summary.json'), {
+    complete: false,
+    readyCapabilities: [{ id: 'software-table' }],
+    readyNotProven: [{ id: 'video-ab-proof' }],
+    externalBlockers: [{ id: 'customer-effect-proof' }]
+  });
+  writeJson(path.join(outputs, 'proof-gap', 'proof-gap-request-summary.json'), {
+    complete: false,
+    unresolvedCount: 4,
+    rows: [{ id: 'video-real-candidate' }, { id: 'customer-effect-proof' }]
+  });
+  writeJson(path.join(outputs, 'proof-gap-closure-open', 'proof-gap-closure-summary.json'), {
+    complete: false,
+    closedCount: 1,
+    openCount: 4,
+    rows: [{ id: 'historical-dataset', status: 'open' }]
+  });
+  writeJson(path.join(outputs, 'proof-gap-closure-closed', 'proof-gap-closure-summary.json'), {
+    complete: true,
+    closedCount: 5,
+    openCount: 0,
+    rows: [{ id: 'historical-dataset', status: 'closed' }]
+  });
+  writeJson(path.join(outputs, 'proof-gap-software-form', 'tihao-proof-gap-software-form-summary.json'), {
+    passed: true,
+    rowCount: 5,
+    duplicateIdCount: 0,
+    headerMatches: true,
+    rowWidthOk: true
+  });
+  writeJson(path.join(outputs, 'round-deposition', 'round-deposition-summary.json'), {
+    passed: true,
+    counts: { pass: 24, warn: 0, fail: 0 }
+  });
+  writeJson(path.join(outputs, 'business-proof-progress', 'business-proof-progress-summary.json'), {
+    complete: false,
+    counts: { pass: 4, fail: 2, pending: 2, blocked_by_external_data: 7 }
+  });
+  writeJson(path.join(outputs, 'business-proof-next-actions', 'business-proof-next-actions-summary.json'), {
+    complete: false,
+    sourceState: { progressComplete: false, proofGapOpenCount: 4 },
+    actions: [{ title: '填真实历史 Brief' }, { title: '填真实视频资源' }]
+  });
+  writeJson(path.join(outputs, 'business-execution-index', 'business-execution-index-summary.json'), {
+    passed: true,
+    directCustomerProof: false,
+    rowCount: 7,
+    actionRowCount: 3,
+    repairRowCount: 2,
+    proofOpenCount: 5
+  });
+  writeJson(path.join(outputs, 'local-seed-material-index', 'local-seed-material-index-summary.json'), {
+    proofLevel: 'not_business_proof',
+    directCustomerProof: false,
+    canCloseProofGap: false,
+    materialCount: 4,
+    existingMaterialCount: 2,
+    promisingLiveAggregateCount: 1
+  });
+  writeJson(path.join(outputs, 'local-seed-to-intake-worklist', 'local-seed-to-intake-worklist-summary.json'), {
+    proofLevel: 'not_business_proof',
+    directCustomerProof: false,
+    canCloseProofGap: false,
+    complete: false,
+    counts: {
+      historyDraftRows: 1,
+      referenceSeedRows: 3,
+      candidateSeedRows: 5,
+      videoWorklistRows: 4
+    }
+  });
+  writeJson(path.join(outputs, 'real-proof-closure-work-order', 'real-proof-closure-work-order-summary.json'), {
+    passed: true,
+    complete: false,
+    proofLevel: 'not_business_proof',
+    materialType: 'real_proof_closure_work_order',
+    directCustomerProof: false,
+    canCloseProofGap: false,
+    requiresHumanRealMaterial: true,
+    workOrderCount: 19,
+    ownerGroupCount: 3,
+    ownerArtifacts: [
+      {
+        owner: 'business',
+        markdown: 'outputs/real-proof-closure-work-order-latest/by-owner/business.md',
+        csv: 'outputs/real-proof-closure-work-order-latest/by-owner/business.csv'
+      },
+      {
+        owner: 'business-media',
+        markdown: 'outputs/real-proof-closure-work-order-latest/by-owner/business-media.md',
+        csv: 'outputs/real-proof-closure-work-order-latest/by-owner/business-media.csv'
+      },
+      {
+        owner: 'tech-ai',
+        markdown: 'outputs/real-proof-closure-work-order-latest/by-owner/tech-ai.md',
+        csv: 'outputs/real-proof-closure-work-order-latest/by-owner/tech-ai.csv'
+      }
+    ],
+    sourceState: {
+      proofGapOpenCount: 5,
+      intakeFailureCount: 9,
+      realCandidateRows: 0
+    }
+  });
+  writeJson(path.join(outputs, 'real-gap-material-audit', 'real-gap-material-audit-summary.json'), {
+    proofLevel: 'not_business_proof',
+    materialType: 'three_real_gap_material_audit',
+    directCustomerProof: false,
+    canCloseProofGap: false,
+    complete: false,
+    gapCount: 3,
+    materialFoundCount: 3,
+    openGapCount: 3
+  });
+  writeJson(path.join(outputs, 'experience-transcript-index', 'experience-transcript-index-summary.json'), {
+    proofLevel: 'not_business_proof',
+    materialType: 'experience_seed',
+    directCustomerProof: false,
+    canCloseProofGap: false,
+    transcriptCount: 2,
+    ruleCount: 8,
+    coveredRuleCount: 8
+  });
+  writeJson(path.join(outputs, 'latest-form-index', 'latest-form-index-summary.json'), {
+    passed: true,
+    primaryForm: { csv: 'outputs/tihao-proof-gap-software-form-latest/tihao-proof-gap-software-form.csv' },
+    clientList: { csv: 'outputs/software-client-latest/software-client-list.final.csv' },
+    nextActions: { actionCount: 5, proofOpenCount: 5 }
+  });
+  writeJson(path.join(outputs, 'intake-readiness-ready', 'intake-readiness-summary.json'), {
+    failureCount: 0,
+    acceptance: {
+      overallReady: true,
+      historyReady: true,
+      videoReady: true,
+      reviewMetricsReady: true,
+      customerEffectReady: true
+    }
+  });
+  writeJson(path.join(outputs, 'intake-readiness-template', 'intake-readiness-summary.json'), {
+    failureCount: 9,
+    acceptance: {
+      overallReady: false,
+      historyReady: false,
+      videoReady: false,
+      reviewMetricsReady: false,
+      customerEffectReady: false
+    }
+  });
+  fs.writeFileSync(
+    path.join(outputs, 'intake-readiness-template', 'intake-readiness-repair-actions.csv'),
+    'owner,action,reason\nbusiness,补齐真实历史 Brief,仍是模板占位\nbusiness,补齐客户选择字段,缺客户选择\n',
+    'utf8'
+  );
+  const intake = path.join(outputs, 'data-intake-pack');
+  fs.mkdirSync(intake, { recursive: true });
+  fs.writeFileSync(path.join(intake, 'history-data-template.csv'), 'brief编号,客户原始Brief,历史人工补号量基线\n', 'utf8');
+  fs.writeFileSync(path.join(intake, 'manual-review-template.csv'), 'brief编号,客户选择,本轮人工补号量\n', 'utf8');
+  fs.writeFileSync(path.join(intake, 'README.md'), '# 数据收集包\n', 'utf8');
+  const videoIntake = path.join(outputs, 'video-intake-pack');
+  fs.mkdirSync(videoIntake, { recursive: true });
+  fs.writeFileSync(path.join(videoIntake, 'video-resource-template.csv'), 'brief编号,视频链接,封面链接,字幕或ASR文本,帧图链接,是否真实资源\n', 'utf8');
+  fs.writeFileSync(path.join(videoIntake, 'README.md'), '# 视频资源收集包\n', 'utf8');
+  const summary = buildEvidenceIndex({ root, outputsDir: outputs });
+  assert(summary.total === 44, 'should classify forty-four evidence entries');
+  assert(summary.counts.real_evidence === 6, 'passing live, selected review metrics, strict video A/B, customer effect, detailed history, and closed proof-gap closure should be real evidence');
+  assert(summary.counts.not_business_proof === 19, 'incomplete status, failed live, thin history, weak video, video A/B runtime preflight failure, incomplete customer effect, pipeline-scoped customer effect with open proof gaps, missing preflight, failed pipeline, missing video resources, missing homepage evidence, open proof-gap closure, template intake readiness, incomplete completion audit, local seed material index, seed-to-intake worklist, real proof closure work order, real gap material audit, and experience transcript index should not be business proof');
+  assert(summary.counts.smoke_or_local === 19, 'ready live preflight, passing pipelines, handoff summary, proof gap request, software proof-gap form, latest form index, round deposition, business proof progress, business proof next actions, business execution index, repair actions, data intake pack, video intake pack, video resource readiness, homepage readiness, and ready intake readiness should be smoke/local startup evidence');
+  assert(summary.byType['overnight-quality'] === 2, 'should classify overnight quality');
+  assert(summary.byType['customer-effect'] === 3, 'should classify customer effect proof');
+  assert(summary.byType['live-preflight'] === 2, 'should classify live preflight readiness');
+  assert(summary.byType['optimization-pipeline'] === 3, 'should classify optimization pipeline orchestration');
+  assert(summary.byType['historical-dataset'] === 2, 'should classify historical dataset audits');
+  assert(summary.byType['optimization-completion'] === 1, 'should classify optimization completion audits');
+  assert(summary.byType['optimization-handoff'] === 1, 'should classify optimization handoff summary');
+  assert(summary.byType['proof-gap-request'] === 1, 'should classify proof gap request summary');
+  assert(summary.byType['proof-gap-closure'] === 2, 'should classify proof gap closure summaries');
+  assert(summary.byType['proof-gap-software-form'] === 1, 'should classify proof gap software form summaries');
+  assert(summary.byType['latest-form-index'] === 1, 'should classify latest form index summaries');
+  assert(summary.byType['round-deposition'] === 1, 'should classify round deposition summary');
+  assert(summary.byType['business-proof-progress'] === 1, 'should classify business proof progress summary');
+  assert(summary.byType['business-proof-next-actions'] === 1, 'should classify business proof next actions summary');
+  assert(summary.byType['business-execution-index'] === 1, 'should classify business execution index summary');
+  assert(summary.byType['local-seed-material-index'] === 1, 'should classify local seed material index summary');
+  assert(summary.byType['local-seed-to-intake-worklist'] === 1, 'should classify seed-to-intake worklist summary');
+  assert(summary.byType['real-proof-closure-work-order'] === 1, 'should classify real proof closure work order summary');
+  assert(summary.byType['real-gap-material-audit'] === 1, 'should classify real gap material audit summary');
+  assert(summary.byType['experience-transcript-index'] === 1, 'should classify experience transcript index summary');
+  assert(summary.byType['intake-readiness'] === 2, 'should classify intake readiness audits');
+  assert(summary.byType['data-intake-pack'] === 1, 'should classify data intake pack readiness');
+  assert(summary.byType['video-intake-pack'] === 1, 'should classify video intake pack readiness');
+  assert(summary.byType['video-resource-readiness'] === 2, 'should classify video resource readiness audits');
+  assert(summary.byType['video-ab-runtime-preflight'] === 1, 'should classify video A/B runtime preflight failures');
+  assert(summary.byType['homepage-evidence-readiness'] === 2, 'should classify homepage evidence readiness audits');
+  assert(summary.byType['intake-repair-actions'] === 1, 'should classify intake repair action CSVs');
+  assert(summary.byType['video-resource-repair-actions'] === 1, 'should classify video repair action CSVs');
+  assert(summary.byType['homepage-evidence-repair-actions'] === 1, 'should classify homepage evidence repair action CSVs');
+  const intakeRepair = summary.entries.find(item => item.type === 'intake-repair-actions');
+  assert(intakeRepair.status === 'actions_open' && intakeRepair.summary.includes('actions=2'), 'intake repair actions should expose action count');
+  const videoRepair = summary.entries.find(item => item.type === 'video-resource-repair-actions');
+  assert(videoRepair.status === 'actions_open' && videoRepair.summary.includes('actions=1'), 'video repair actions should expose action count');
+  const homepageRepair = summary.entries.find(item => item.type === 'homepage-evidence-repair-actions');
+  assert(homepageRepair.status === 'actions_open' && homepageRepair.summary.includes('actions=1'), 'homepage repair actions should expose action count');
+  const businessExecution = summary.entries.find(item => item.type === 'business-execution-index');
+  assert(businessExecution.status === 'actions_open' && businessExecution.summary.includes('rows=7') && businessExecution.summary.includes('repairs=2'), 'business execution index should expose row and repair counts');
+  const localSeed = summary.entries.find(item => item.type === 'local-seed-material-index');
+  assert(localSeed.proofLevel === 'not_business_proof' && localSeed.canCloseProofGap === false, 'local seed material index should preserve proof boundary');
+  assert(localSeed.summary.includes('canCloseProofGap=false'), 'local seed material index should expose non-closure boundary');
+  const seedWorklist = summary.entries.find(item => item.type === 'local-seed-to-intake-worklist');
+  assert(seedWorklist.proofLevel === 'not_business_proof' && seedWorklist.canCloseProofGap === false, 'seed-to-intake worklist should preserve proof boundary');
+  assert(seedWorklist.summary.includes('historyDraft=1') && seedWorklist.next.includes('不能替代客户选择'), 'seed-to-intake worklist should expose draft counts and proof boundary');
+  const realGapAudit = summary.entries.find(item => item.type === 'real-gap-material-audit');
+  assert(realGapAudit.proofLevel === 'not_business_proof' && realGapAudit.canCloseProofGap === false, 'real gap material audit should preserve proof boundary');
+  assert(realGapAudit.status === 'open_gaps_have_materials', 'real gap material audit should not close proof gaps');
+  assert(realGapAudit.summary.includes('materialFound=3') && realGapAudit.summary.includes('open=3'), 'real gap material audit should expose material coverage and open gaps');
+  const realProofClosureWorkOrder = summary.entries.find(item => item.type === 'real-proof-closure-work-order');
+  assert(realProofClosureWorkOrder.proofLevel === 'not_business_proof' && realProofClosureWorkOrder.canCloseProofGap === false, 'real proof closure work order should preserve proof boundary');
+  assert(realProofClosureWorkOrder.directCustomerProof === false && realProofClosureWorkOrder.complete === false, 'real proof closure work order should not be direct proof or complete');
+  assert(realProofClosureWorkOrder.status === 'work_orders_open', 'real proof closure work order should expose open work order status');
+  assert(realProofClosureWorkOrder.summary.includes('workOrders=19') && realProofClosureWorkOrder.summary.includes('owners=3'), 'real proof closure work order should expose work order counts');
+  assert(realProofClosureWorkOrder.next.includes('工单本身不证明客户效果'), 'real proof closure work order should keep customer-effect boundary in next action');
+  const experienceTranscript = summary.entries.find(item => item.type === 'experience-transcript-index');
+  assert(experienceTranscript.proofLevel === 'not_business_proof' && experienceTranscript.canCloseProofGap === false, 'experience transcript index should preserve proof boundary');
+  assert(experienceTranscript.summary.includes('rules=8/8'), 'experience transcript index should expose rule coverage');
+  const openClosurePipeline = summary.entries.find(item => item.type === 'optimization-pipeline' && item.dir.includes('pipeline-claim-with-open-closure'));
+  assert(openClosurePipeline.summary.includes('readyForClaim=false'), 'pipeline evidence should not expose readyForClaim=true when closure is open');
+  assert(openClosurePipeline.summary.includes('closureOpen=1'), 'pipeline evidence should expose closure open count');
+  const standaloneCustomerEffect = summary.entries.find(item => item.type === 'customer-effect' && item.dir.endsWith('/customer-effect'));
+  assert(standaloneCustomerEffect.proofLevel === 'real_evidence', 'standalone passing customer-effect should remain real evidence');
+  const pipelineCustomerEffect = summary.entries.find(item => item.type === 'customer-effect' && item.dir.includes('pipeline-claim-with-open-closure'));
+  assert(pipelineCustomerEffect.proofLevel === 'not_business_proof', 'pipeline-scoped customer-effect should not become real evidence while proof gaps are open');
+  assert(pipelineCustomerEffect.pipelineScoped === true && pipelineCustomerEffect.pipelineClaimable === false, 'pipeline-scoped customer-effect should expose non-claimable pipeline context');
+  assert(pipelineCustomerEffect.summary.includes('pipelineBlocked=') && pipelineCustomerEffect.summary.includes('proofGapClosure.openCount=1'), 'pipeline-scoped customer-effect should expose pipeline blockers');
+  const detailedHistory = summary.entries.find(item => item.type === 'historical-dataset' && item.dir.endsWith('/history-real'));
+  assert(detailedHistory.proofLevel === 'real_evidence' && detailedHistory.status === 'ready_for_long_run', 'detailed history audit should be real evidence');
+  assert(detailedHistory.summary.includes('missing=0'), 'detailed history audit should expose zero missing requirements');
+  const thinHistory = summary.entries.find(item => item.type === 'historical-dataset' && item.dir.endsWith('/history-thin'));
+  assert(thinHistory.proofLevel === 'not_business_proof', 'thin history audit should not be real evidence');
+  assert(thinHistory.status === 'not_ready', 'thin history audit should not be ready for long run');
+  assert(thinHistory.missingProofRequirements.includes('acceptance.allHaveManualFinalList=true'), 'thin history audit should require manual final-list coverage');
+  assert(thinHistory.missingProofRequirements.includes('items.length>=5'), 'thin history audit should require auditable item details');
+  const completion = summary.entries.find(item => item.type === 'optimization-completion');
+  assert(completion.proofLevel === 'not_business_proof' && completion.status === 'incomplete', 'incomplete completion audit should not be business proof');
+  assert(completion.summary.includes('readyForClaim=false') && completion.next.includes('不能宣称提号率提升'), 'completion evidence should expose claim boundary');
+  console.log(JSON.stringify({ ok: true, total: summary.total, counts: summary.counts }, null, 2));
+}
+
+function writeJson(file, value) {
+  fs.mkdirSync(path.dirname(file), { recursive: true });
+  fs.writeFileSync(file, JSON.stringify(value, null, 2), 'utf8');
+}
+
+function passingHistoryAudit() {
+  return {
+    generatedAt: '2026-06-11T02:48:48.000Z',
+    minBriefs: 5,
+    briefCount: 5,
+    categoryCount: 3,
+    categories: ['skincare', 'mom-baby', 'home'],
+    withReference: 5,
+    withManualFinalList: 5,
+    withCustomerDecision: 5,
+    withRejectionReason: 2,
+    withManualSupplementBaseline: 5,
+    missingCriticalCount: 0,
+    acceptance: {
+      parseOk: true,
+      minBriefsMet: true,
+      allHaveBriefText: true,
+      allHaveManualFinalList: true,
+      allHaveCustomerDecision: true,
+      allHaveFeedbackReason: true,
+      allHaveManualSupplementBaseline: true,
+      categoryCoverageMet: true,
+      referenceCoverageMeasured: true,
+      readyForLongRun: true,
+      readyForCustomerEffectProof: true
+    },
+    items: Array.from({ length: 5 }, (_, index) => ({
+      id: `brief-${index + 1}`,
+      parseOk: true,
+      hasBriefText: true,
+      hasManualFinalList: true,
+      hasCustomerDecision: true,
+      hasRejectionReason: index < 2,
+      hasManualSupplementBaseline: true,
+      manualSupplementBaseline: 2,
+      missingCritical: []
+    }))
+  };
+}
+
+function assert(condition, message) {
+  if (!condition) throw new Error(message);
+}
+
+if (require.main === module) main();

+ 869 - 0
claude-code-tihao-sourcing/scripts/evidence-index.js

@@ -0,0 +1,869 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const path = require('path');
+
+const ROOT = path.resolve(__dirname, '..');
+const OUTPUTS = path.join(ROOT, 'outputs');
+
+function main() {
+  const args = parseArgs(process.argv.slice(2));
+  const outputDir = path.resolve(args.output || process.env.TIHAO_EVIDENCE_INDEX_OUTPUT || path.join(OUTPUTS, `evidence-index-${Date.now()}`));
+  const summary = buildEvidenceIndex({ root: ROOT, outputsDir: path.resolve(args.outputs || OUTPUTS) });
+  fs.mkdirSync(outputDir, { recursive: true });
+  const jsonPath = path.join(outputDir, 'evidence-index-summary.json');
+  const reportPath = path.join(outputDir, 'evidence-index-report.md');
+  fs.writeFileSync(jsonPath, JSON.stringify(summary, null, 2), 'utf8');
+  fs.writeFileSync(reportPath, withBom(renderReport(summary)), 'utf8');
+  console.log(JSON.stringify({
+    outputDir,
+    json: jsonPath,
+    report: reportPath,
+    total: summary.total,
+    realEvidence: summary.counts.real_evidence || 0,
+    smokeOrLocal: summary.counts.smoke_or_local || 0,
+    notProof: summary.counts.not_business_proof || 0
+  }, null, 2));
+}
+
+function buildEvidenceIndex({ root, outputsDir }) {
+  const dirs = fs.existsSync(outputsDir)
+    ? fs.readdirSync(outputsDir, { withFileTypes: true }).filter(item => item.isDirectory()).map(item => path.join(outputsDir, item.name))
+    : [];
+  const entries = dirs.flatMap(dir => classifyDir(root, dir)).sort((a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt)));
+  return {
+    generatedAt: new Date().toISOString(),
+    root,
+    outputsDir,
+    total: entries.length,
+    counts: countBy(entries, 'proofLevel'),
+    byType: countBy(entries, 'type'),
+    entries
+  };
+}
+
+function classifyDir(root, dir) {
+  const entries = [];
+  const aggregate = readJson(path.join(dir, 'aggregate-summary.json'));
+  if (aggregate) entries.push(classifyOvernight(root, dir, aggregate));
+
+  const status = readJson(path.join(dir, 'optimization-status-summary.json'));
+  if (status) entries.push(classifyStatus(root, dir, status));
+
+  const readiness = readJson(path.join(dir, 'long-run-readiness-summary.json'));
+  if (readiness) entries.push(classifyReadiness(root, dir, readiness));
+
+  const pipeline = readJson(path.join(dir, 'optimization-pipeline-summary.json'));
+  if (pipeline) entries.push(classifyPipeline(root, dir, pipeline));
+
+  const completion = readJson(path.join(dir, 'optimization-completion-summary.json'));
+  if (completion) entries.push(classifyOptimizationCompletion(root, dir, completion));
+
+  const handoff = readJson(path.join(dir, 'optimization-handoff-summary.json'));
+  if (handoff) entries.push(classifyHandoff(root, dir, handoff));
+
+  const proofGap = readJson(path.join(dir, 'proof-gap-request-summary.json'));
+  if (proofGap) entries.push(classifyProofGap(root, dir, proofGap));
+
+  const proofGapClosure = readJson(path.join(dir, 'proof-gap-closure-summary.json'));
+  if (proofGapClosure) entries.push(classifyProofGapClosure(root, dir, proofGapClosure));
+
+  const proofGapOperatorPack = readJson(path.join(dir, 'proof-gap-operator-pack-summary.json'));
+  if (proofGapOperatorPack) entries.push(classifyProofGapOperatorPack(root, dir, proofGapOperatorPack));
+
+  const realProofIntakeBundle = readJson(path.join(dir, 'real-proof-intake-bundle-summary.json'));
+  if (realProofIntakeBundle) entries.push(classifyRealProofIntakeBundle(root, dir, realProofIntakeBundle));
+
+  const realProofClosureWorkOrder = readJson(path.join(dir, 'real-proof-closure-work-order-summary.json'));
+  if (realProofClosureWorkOrder) entries.push(classifyRealProofClosureWorkOrder(root, dir, realProofClosureWorkOrder));
+
+  const realGapMaterialAudit = readJson(path.join(dir, 'real-gap-material-audit-summary.json'));
+  if (realGapMaterialAudit) entries.push(classifyRealGapMaterialAudit(root, dir, realGapMaterialAudit));
+
+  const localSeedMaterialIndex = readJson(path.join(dir, 'local-seed-material-index-summary.json'));
+  if (localSeedMaterialIndex) entries.push(classifyLocalSeedMaterialIndex(root, dir, localSeedMaterialIndex));
+
+  const localSeedToIntakeWorklist = readJson(path.join(dir, 'local-seed-to-intake-worklist-summary.json'));
+  if (localSeedToIntakeWorklist) entries.push(classifyLocalSeedToIntakeWorklist(root, dir, localSeedToIntakeWorklist));
+
+  const experienceTranscriptIndex = readJson(path.join(dir, 'experience-transcript-index-summary.json'));
+  if (experienceTranscriptIndex) entries.push(classifyExperienceTranscriptIndex(root, dir, experienceTranscriptIndex));
+
+  const feedbackLoopClosure = readJson(path.join(dir, 'feedback-loop-closure-summary.json'));
+  if (feedbackLoopClosure) entries.push(classifyFeedbackLoopClosure(root, dir, feedbackLoopClosure));
+
+  const homepageEvidenceReadiness = readJson(path.join(dir, 'homepage-evidence-readiness-summary.json'));
+  if (homepageEvidenceReadiness) entries.push(classifyHomepageEvidenceReadiness(root, dir, homepageEvidenceReadiness));
+
+  const proofGapSoftwareForm = readJson(path.join(dir, 'tihao-proof-gap-software-form-summary.json'));
+  if (proofGapSoftwareForm) entries.push(classifyProofGapSoftwareForm(root, dir, proofGapSoftwareForm));
+
+  const latestFormIndex = readJson(path.join(dir, 'latest-form-index-summary.json'));
+  if (latestFormIndex) entries.push(classifyLatestFormIndex(root, dir, latestFormIndex));
+
+  const roundDeposition = readJson(path.join(dir, 'round-deposition-summary.json'));
+  if (roundDeposition) entries.push(classifyRoundDeposition(root, dir, roundDeposition));
+
+  const businessProofProgress = readJson(path.join(dir, 'business-proof-progress-summary.json'));
+  if (businessProofProgress) entries.push(classifyBusinessProofProgress(root, dir, businessProofProgress));
+
+  const businessProofNextActions = readJson(path.join(dir, 'business-proof-next-actions-summary.json'));
+  if (businessProofNextActions) entries.push(classifyBusinessProofNextActions(root, dir, businessProofNextActions));
+
+  const businessExecutionIndex = readJson(path.join(dir, 'business-execution-index-summary.json'));
+  if (businessExecutionIndex) entries.push(classifyBusinessExecutionIndex(root, dir, businessExecutionIndex));
+
+  const intakeReadiness = readJson(path.join(dir, 'intake-readiness-summary.json'));
+  if (intakeReadiness) entries.push(classifyIntakeReadiness(root, dir, intakeReadiness));
+
+  const intakeFieldChecklist = readJson(path.join(dir, 'intake-field-checklist-summary.json'));
+  if (intakeFieldChecklist) entries.push(classifyIntakeFieldChecklist(root, dir, intakeFieldChecklist));
+
+  if (isDataIntakePack(dir)) entries.push(classifyDataIntakePack(root, dir));
+  if (isVideoIntakePack(dir)) entries.push(classifyVideoIntakePack(root, dir));
+
+  const preflight = readJson(path.join(dir, 'live-preflight-summary.json'));
+  if (preflight) entries.push(classifyPreflight(root, dir, preflight));
+
+  const review = readJson(path.join(dir, 'review-metrics-summary.json'));
+  if (review) entries.push(classifyReview(root, dir, review));
+
+  const customerEffect = readJson(path.join(dir, 'customer-effect-summary.json')) ||
+    readJson(path.join(dir, 'customer-effect-audit', 'customer-effect-summary.json'));
+  if (customerEffect) entries.push(classifyCustomerEffect(root, dir, customerEffect, { pipeline }));
+
+  const history = readJson(path.join(dir, 'historical-dataset-audit.json'));
+  if (history) entries.push(classifyHistory(root, dir, history));
+
+  const video = readJson(path.join(dir, 'video-hit-rate-summary.json'));
+  if (video) entries.push(classifyVideo(root, dir, video));
+
+  const videoRuntimePreflight = readJson(path.join(dir, 'video-hit-rate-preflight-summary.json'));
+  if (videoRuntimePreflight) entries.push(classifyVideoRuntimePreflight(root, dir, videoRuntimePreflight));
+
+  const videoResourceReadiness = readJson(path.join(dir, 'video-resource-readiness-summary.json'));
+  if (videoResourceReadiness) entries.push(classifyVideoResourceReadiness(root, dir, videoResourceReadiness));
+
+  if (fs.existsSync(path.join(dir, 'intake-readiness-repair-actions.csv'))) {
+    entries.push(classifyRepairActions(root, dir, 'intake-repair-actions', 'intake-readiness-repair-actions.csv'));
+  }
+  if (fs.existsSync(path.join(dir, 'video-resource-repair-actions.csv'))) {
+    entries.push(classifyRepairActions(root, dir, 'video-resource-repair-actions', 'video-resource-repair-actions.csv'));
+  }
+  if (fs.existsSync(path.join(dir, 'homepage-evidence-repair-actions.csv'))) {
+    entries.push(classifyRepairActions(root, dir, 'homepage-evidence-repair-actions', 'homepage-evidence-repair-actions.csv'));
+  }
+
+  return entries;
+}
+
+function classifyOvernight(root, dir, aggregate) {
+  const liveProof = Boolean(aggregate.manifest?.liveEnabled) &&
+    aggregate.acceptance?.overallPass === true &&
+    Number(aggregate.failureCount || 0) === 0 &&
+    Number(aggregate.acceptance?.failedGateCount ?? aggregate.failedGateCount ?? 0) === 0;
+  return {
+    type: 'overnight-quality',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: liveProof ? 'real_evidence' : aggregate.manifest?.liveEnabled ? 'not_business_proof' : 'smoke_or_local',
+    status: aggregate.acceptance?.overallPass ? 'pass' : 'not_passed',
+    liveEnabled: Boolean(aggregate.manifest?.liveEnabled),
+    runCount: aggregate.runCount || 0,
+    failureCount: aggregate.failureCount || 0,
+    failedGateCount: aggregate.acceptance?.failedGateCount ?? aggregate.failedGateCount ?? 0,
+    summary: `fixtures=${aggregate.fixtureCount || 0}, variants=${aggregate.variantCount || 0}, runs=${aggregate.runCount || 0}`,
+    next: liveProof
+      ? '可作为 live 自动化证据;仍需人工复核和客户选择数据证明真实业务效果。'
+      : aggregate.manifest?.liveEnabled
+        ? 'live 产物未通过全部门槛,不能作为业务证明。'
+        : 'sample/smoke 只能证明结构和本地门槛。'
+  };
+}
+
+function classifyStatus(root, dir, status) {
+  return {
+    type: 'optimization-status',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: status.complete ? 'real_evidence' : 'not_business_proof',
+    status: status.complete ? 'complete' : 'incomplete',
+    summary: `passed=${status.counts?.passed || 0}, ready_not_proven=${status.counts?.ready_not_proven || 0}, blocked_by_external_data=${status.counts?.blocked_by_external_data || 0}`,
+    next: status.complete ? '状态审计已完成。' : '仍有 ready_not_proven 或 blocked_by_external_data,不能宣布长期目标完成。'
+  };
+}
+
+function classifyReadiness(root, dir, readiness) {
+  return {
+    type: 'long-run-readiness',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: readiness.ready ? 'smoke_or_local' : 'not_business_proof',
+    status: readiness.ready ? 'ready' : 'not_ready',
+    mode: readiness.mode,
+    summary: `mode=${readiness.mode}, fail=${readiness.counts?.fail || 0}, warn=${readiness.counts?.warn || 0}`,
+    next: readiness.ready ? '可作为启动前置检查;不等同于 live 或客户效果证明。' : '先补齐失败项,再启动对应长跑。'
+  };
+}
+
+function classifyPipeline(root, dir, pipeline) {
+  const allStepsPass = Array.isArray(pipeline.results) && pipeline.results.length > 0 &&
+    pipeline.results.every(item => item.status === 'pass' || item.status === 'planned');
+  const closure = pipeline.proofGapClosure || {};
+  const closureComplete = Boolean(closure.complete);
+  const readyForClaim = Boolean(pipeline.readyForClaim) && closureComplete;
+  return {
+    type: 'optimization-pipeline',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: allStepsPass ? 'smoke_or_local' : 'not_business_proof',
+    status: allStepsPass ? (pipeline.dryRun ? 'planned' : 'pass') : 'not_passed',
+    summary: `steps=${pipeline.results?.length || 0}, pass=${pipeline.counts?.pass || 0}, planned=${pipeline.counts?.planned || 0}, fail=${pipeline.counts?.fail || 0}, nextActions=${pipeline.nextActions?.length || 0}, readyForClaim=${readyForClaim}, closureComplete=${closureComplete}, closureOpen=${closure.openCount ?? 'unknown'}`,
+    next: readyForClaim
+      ? 'pipeline 已跑通并且 proof-gap:closure complete=true;仍需回看 customer-effect、video A/B 和 live/provider 证据细项。'
+      : 'pipeline 只能证明编排链路或局部审计完成;proof-gap:closure 未关闭前不能宣称业务效果。'
+  };
+}
+
+function classifyOptimizationCompletion(root, dir, completion) {
+  const readyForClaim = Boolean(completion.complete && completion.readyForClaim && Number(completion.proofGapOpenCount || 0) === 0);
+  return {
+    type: 'optimization-completion',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: readyForClaim ? 'real_evidence' : 'not_business_proof',
+    status: readyForClaim ? 'ready_for_claim' : 'incomplete',
+    summary: `complete=${Boolean(completion.complete)}, readyForClaim=${Boolean(completion.readyForClaim)}, proofGapOpen=${completion.proofGapOpenCount ?? 'unknown'}, blockers=${completion.blockingReasons?.length ?? 'unknown'}`,
+    next: readyForClaim
+      ? '完成度审计允许进入业务效果声明;仍需回看具体客户效果、视频 A/B 和 live/provider 证据。'
+      : '完成度审计显示仍有阻塞项,不能宣称提号率提升、客户效果达标或人工补号量下降。'
+  };
+}
+
+function classifyDataIntakePack(root, dir) {
+  return {
+    type: 'data-intake-pack',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: 'smoke_or_local',
+    status: 'ready',
+    summary: 'history template, manual review template, and README are present',
+    next: '交给商务填写真实历史 Brief、客户选择、历史人工补号基线和本轮人工补号量;模板本身不证明业务效果。'
+  };
+}
+
+function classifyVideoIntakePack(root, dir) {
+  return {
+    type: 'video-intake-pack',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: 'smoke_or_local',
+    status: 'ready',
+    summary: 'video resource template and README are present',
+    next: '交给商务补齐真实参考视频和候选视频的 URL、封面、ASR、帧图或正文证据;模板本身不证明视频 A/B 通过。'
+  };
+}
+
+function classifyHandoff(root, dir, handoff) {
+  return {
+    type: 'optimization-handoff',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: 'smoke_or_local',
+    status: handoff.complete ? 'complete_reported' : 'incomplete_reported',
+    summary: `ready=${handoff.readyCapabilities?.length || 0}, readyNotProven=${handoff.readyNotProven?.length || 0}, blockers=${handoff.externalBlockers?.length || 0}`,
+    next: handoff.complete
+      ? '交接摘要声称完成时必须回看 optimization:status 和客户效果证据。'
+      : '可用于下一轮 AI 或商务交接;不证明客户效果。'
+  };
+}
+
+function classifyProofGap(root, dir, proofGap) {
+  return {
+    type: 'proof-gap-request',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: 'smoke_or_local',
+    status: proofGap.unresolvedCount ? 'gaps_requested' : 'no_gaps_reported',
+    summary: `gaps=${proofGap.rows?.length || 0}, unresolved=${proofGap.unresolvedCount || 0}`,
+    next: '可作为商务/下一轮 AI 补真实证明数据的请求清单;不证明业务效果。'
+  };
+}
+
+function classifyProofGapClosure(root, dir, closure) {
+  return {
+    type: 'proof-gap-closure',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: closure.complete ? 'real_evidence' : 'not_business_proof',
+    status: closure.complete ? 'all_closed' : 'open_gaps',
+    summary: `closed=${closure.closedCount || 0}, open=${closure.openCount || 0}`,
+    next: closure.complete
+      ? '所有真实证明缺口均已关闭;仍需回看 optimization:status 是否 complete。'
+      : '仍有 proof gap 未关闭,不能宣布长期优化目标完成。'
+  };
+}
+
+function classifyProofGapOperatorPack(root, dir, pack) {
+  return {
+    type: 'proof-gap-operator-pack',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: 'not_business_proof',
+    complete: Boolean(pack.complete),
+    directCustomerProof: false,
+    summary: `openCount=${Number(pack.openCount || 0)}, rowCount=${Number(pack.rowCount || 0)}`,
+    next: '按负责人、填写文件、复验命令和边界补齐真实证明;不证明客户效果。'
+  };
+}
+
+function classifyRealProofIntakeBundle(root, dir, bundle) {
+  return {
+    type: 'real-proof-intake-bundle',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: 'not_business_proof',
+    status: bundle.complete ? 'all_real_proof_closed' : 'materials_requested',
+    complete: Boolean(bundle.complete),
+    directCustomerProof: false,
+    summary: `proofOpen=${Number(bundle.proofOpenCount || 0)}, fillRows=${Number(bundle.fillRowCount || 0)}, proofGaps=${Number(bundle.proofGapRowCount || 0)}, commands=${Number(bundle.recheckCommandCount || 0)}`,
+    next: bundle.complete
+      ? '真实材料总包显示 proofOpenCount=0;仍需回看 customer-effect、video A/B 和 live/provider 证据细项。'
+      : '按总包中的模板、字段缺口、修复清单和复验命令补齐真实材料;不证明客户效果。'
+  };
+}
+
+function classifyRealProofClosureWorkOrder(root, dir, summary) {
+  return {
+    type: 'real-proof-closure-work-order',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: 'not_business_proof',
+    status: summary.complete ? 'all_work_orders_closed' : 'work_orders_open',
+    complete: Boolean(summary.complete),
+    directCustomerProof: false,
+    canCloseProofGap: false,
+    summary: `workOrders=${Number(summary.workOrderCount || 0)}, owners=${Number(summary.ownerGroupCount || 0)}, proofOpen=${summary.sourceState?.proofGapOpenCount ?? 'unknown'}, intakeFailures=${summary.sourceState?.intakeFailureCount ?? 'unknown'}, realCandidateRows=${summary.sourceState?.realCandidateRows ?? 'unknown'}`,
+    next: summary.complete
+      ? '闭环工单显示无待补项;仍需回看 proof-gap:closure 和 optimization:completion 是否允许完成声明。'
+      : '按负责人附件逐字段补真实历史 Brief、真实候选视频、客户选择和人工补号量;工单本身不证明客户效果。'
+  };
+}
+
+function classifyRealGapMaterialAudit(root, dir, summary) {
+  return {
+    type: 'real-gap-material-audit',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: 'not_business_proof',
+    status: summary.canCloseProofGap ? 'claimable' : 'open_gaps_have_materials',
+    complete: Boolean(summary.complete),
+    directCustomerProof: false,
+    canCloseProofGap: false,
+    summary: `gaps=${Number(summary.gapCount || 0)}, materialFound=${Number(summary.materialFoundCount || 0)}, open=${Number(summary.openGapCount || 0)}`,
+    next: 'Use this audit to prove project materials were found for the three open gaps; do not use it as customer-effect, video A/B, or manual-supplement business proof.'
+  };
+}
+
+function classifyProofGapSoftwareForm(root, dir, form) {
+  return {
+    type: 'proof-gap-software-form',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: form.passed ? 'smoke_or_local' : 'not_business_proof',
+    status: form.passed ? 'pass' : 'not_passed',
+    summary: `rows=${form.rowCount || 0}, duplicateId=${form.duplicateIdCount || 0}, header=${Boolean(form.headerMatches)}, rowWidth=${Boolean(form.rowWidthOk)}`,
+    next: '用于软件端/商务补证任务表;不证明客户效果或命中率提升。'
+  };
+}
+
+function classifyLatestFormIndex(root, dir, index) {
+  return {
+    type: 'latest-form-index',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: index.passed ? 'smoke_or_local' : 'not_business_proof',
+    status: index.passed ? 'pass' : 'not_passed',
+    summary: `actions=${index.nextActions?.actionCount || 0}, proofOpen=${index.nextActions?.proofOpenCount ?? 'unknown'}, primaryForm=${Boolean(index.primaryForm?.csv)}, clientList=${Boolean(index.clientList?.csv)}`,
+    next: '用于快速定位最新补证表单、候选名单和负责人行动文件;不证明客户效果或命中率提升。'
+  };
+}
+
+function classifyRoundDeposition(root, dir, roundDeposition) {
+  return {
+    type: 'round-deposition',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: roundDeposition.passed ? 'smoke_or_local' : 'not_business_proof',
+    status: roundDeposition.passed ? 'pass' : 'not_passed',
+    summary: `pass=${roundDeposition.counts?.pass || 0}, warn=${roundDeposition.counts?.warn || 0}, fail=${roundDeposition.counts?.fail || 0}`,
+    next: roundDeposition.passed
+      ? '可证明本轮交接沉淀完整;仍不证明客户效果或命中率提升。'
+      : '先补齐实施日志、证据台账、状态审计或交接摘要,再进入下一轮。'
+  };
+}
+
+function classifyBusinessProofProgress(root, dir, progress) {
+  return {
+    type: 'business-proof-progress',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: 'smoke_or_local',
+    status: progress.complete ? 'all_steps_pass' : 'in_progress',
+    summary: `pass=${progress.counts?.pass || 0}, fail=${progress.counts?.fail || 0}, pending=${progress.counts?.pending || 0}, blocked=${progress.counts?.blocked_by_external_data || 0}`,
+    next: progress.complete
+      ? '15 步补证进度均为 pass;仍需回看 proof-gap:closure 和 customer-effect:audit 是否真实通过。'
+      : '用于实时同步补证步骤进度;不证明客户效果或命中率提升。'
+  };
+}
+
+function classifyBusinessProofNextActions(root, dir, nextActions) {
+  return {
+    type: 'business-proof-next-actions',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: 'smoke_or_local',
+    status: nextActions.complete ? 'no_actions_needed' : 'actions_open',
+    summary: `actions=${nextActions.actions?.length || 0}, progressComplete=${Boolean(nextActions.sourceState?.progressComplete)}, proofGapOpen=${nextActions.sourceState?.proofGapOpenCount ?? 'unknown'}`,
+    next: nextActions.complete
+      ? '行动队列显示无待办;仍需回看 proof-gap:closure 和 customer-effect:audit 是否真实通过。'
+      : '用于把 blocked/fail/pending 转成可分派任务;不证明客户效果或命中率提升。'
+  };
+}
+
+function classifyBusinessExecutionIndex(root, dir, index) {
+  return {
+    type: 'business-execution-index',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: 'smoke_or_local',
+    status: index.passed ? (index.proofOpenCount > 0 ? 'actions_open' : 'ready_for_claim_audit') : 'not_passed',
+    summary: `rows=${index.rowCount || 0}, actions=${index.actionRowCount || 0}, repairs=${index.repairRowCount || 0}, proofOpen=${index.proofOpenCount ?? 'unknown'}`,
+    next: '用于商务集中定位补证表单、负责人文件和修复清单;不证明客户效果或命中率提升。'
+  };
+}
+
+function classifyLocalSeedMaterialIndex(root, dir, summary) {
+  return {
+    type: 'local-seed-material-index',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: 'not_business_proof',
+    status: summary.existingMaterialCount > 0 ? 'seed_materials_available' : 'seed_materials_not_found',
+    complete: false,
+    directCustomerProof: false,
+    canCloseProofGap: false,
+    summary: `materials=${summary.existingMaterialCount || 0}/${summary.materialCount || 0}, liveCandidates=${summary.promisingLiveAggregateCount || 0}, canCloseProofGap=false`,
+    next: '可用于补真实 Brief、候选池和视频资源模板;不能替代客户选择、人工补号量、真实候选视频或 proof-gap:closure。'
+  };
+}
+
+function classifyLocalSeedToIntakeWorklist(root, dir, summary) {
+  return {
+    type: 'local-seed-to-intake-worklist',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: 'not_business_proof',
+    status: Number(summary.counts?.historyDraftRows || 0) > 0 ? 'seed_worklist_ready' : 'seed_worklist_empty',
+    complete: false,
+    directCustomerProof: false,
+    canCloseProofGap: false,
+    summary: `historyDraft=${summary.counts?.historyDraftRows || 0}, references=${summary.counts?.referenceSeedRows || 0}, candidates=${summary.counts?.candidateSeedRows || 0}, videoWorklist=${summary.counts?.videoWorklistRows || 0}, canCloseProofGap=false`,
+    next: '可作为商务/投放把本地 DHA 种子转写进真实 intake 模板的补表清单;不能替代客户选择、人工补号量、真实候选视频或 proof-gap:closure。'
+  };
+}
+
+function classifyExperienceTranscriptIndex(root, dir, summary) {
+  return {
+    type: 'experience-transcript-index',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: 'not_business_proof',
+    status: Number(summary.transcriptCount || 0) > 0 ? 'experience_seed_available' : 'experience_seed_missing',
+    complete: false,
+    directCustomerProof: false,
+    canCloseProofGap: false,
+    summary: `transcripts=${summary.transcriptCount || 0}, rules=${summary.coveredRuleCount || 0}/${summary.ruleCount || 0}, canCloseProofGap=false`,
+    next: '可用于追踪提号经验来源和校准规则实现;不能替代真实历史 Brief、客户选择、人工补号量或 customer-effect:audit。'
+  };
+}
+
+function classifyFeedbackLoopClosure(root, dir, closure) {
+  return {
+    type: 'feedback-loop-closure',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: closure.directCustomerProof ? 'real_evidence' : closure.passed ? 'smoke_or_local' : 'not_business_proof',
+    status: closure.directCustomerProof ? 'direct_customer_proof' : closure.passed ? 'local_loop_passed' : 'missing_or_open',
+    summary: `negative=${closure.negativeFeedbackCount || 0}, blocked=${closure.blockedCreatorCount || 0}, leaked=${closure.leakedCount || 0}, directCustomerProof=${Boolean(closure.directCustomerProof)}`,
+    next: closure.directCustomerProof
+      ? '反馈二轮闭环已有真实客户反馈和真实二轮结果;仍需 customer-effect:audit 证明客户效果。'
+      : '用于证明反馈剔除/降权闭环是否执行;缺真实客户反馈或真实二轮结果时不能证明客户效果。'
+  };
+}
+
+function classifyHomepageEvidenceReadiness(root, dir, readiness) {
+  return {
+    type: 'homepage-evidence-readiness',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: readiness.ready ? 'smoke_or_local' : 'not_business_proof',
+    status: readiness.ready ? 'ready' : 'not_ready',
+    summary: `candidates=${readiness.counts?.candidates || 0}, providerEvidence=${readiness.counts?.providerEvidenceCreators || 0}, posts=${readiness.counts?.creatorsWithPosts || 0}, failureCount=${readiness.failureCount || 0}`,
+    next: readiness.ready
+      ? '可作为主页近期内容证据进入强推荐复核;仍需真实人工复核和客户选择证明命中率。'
+      : '先补齐最近 10/20 篇内容、封面、标题、互动、发布时间和风险信号,不能把 fallback 当成强推荐证据。'
+  };
+}
+
+function classifyIntakeReadiness(root, dir, intakeReadiness) {
+  const ready = intakeReadiness.acceptance?.overallReady === true &&
+    Number(intakeReadiness.failureCount || 0) === 0;
+  return {
+    type: 'intake-readiness',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: ready ? 'smoke_or_local' : 'not_business_proof',
+    status: ready ? 'ready' : 'not_ready',
+    summary: `history=${intakeReadiness.acceptance?.historyReady ? 'ready' : 'not_ready'}, video=${intakeReadiness.acceptance?.videoReady ? 'ready' : 'not_ready'}, review=${intakeReadiness.acceptance?.reviewMetricsReady ? 'ready' : 'not_ready'}, customerEffect=${intakeReadiness.acceptance?.customerEffectReady ? 'ready' : 'not_ready'}, failureCount=${intakeReadiness.failureCount || 0}`,
+    next: ready
+      ? '可进入 history:audit、video:resource-readiness、review:metrics 和 customer-effect:audit;仍不等同于业务效果证明。'
+      : '先把模板占位替换为真实历史 Brief、真实视频资源、客户选择和本轮人工补号量。'
+  };
+}
+
+function classifyIntakeFieldChecklist(root, dir, checklist) {
+  return {
+    type: 'intake-field-checklist',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: 'smoke_or_local',
+    status: Number(checklist.itemCount || 0) > 0 ? 'actions_open' : 'empty',
+    summary: `items=${checklist.itemCount || 0}, missingRequired=${checklist.missingRequiredCount || 0}, placeholders=${checklist.placeholderCount || 0}`,
+    next: '用于指导商务/投放逐字段补齐真实历史 Brief、人工复核、客户选择和视频资源;不证明客户效果。'
+  };
+}
+
+function classifyPreflight(root, dir, preflight) {
+  const failureCount = Array.isArray(preflight.checks) ? preflight.checks.filter(item => item.status === 'fail').length : 0;
+  const warnCount = Array.isArray(preflight.checks) ? preflight.checks.filter(item => item.status === 'warn').length : 0;
+  return {
+    type: 'live-preflight',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: preflight.readyForLiveAcceptance ? 'smoke_or_local' : 'not_business_proof',
+    status: preflight.readyForLiveAcceptance ? 'ready' : 'not_ready',
+    readyForVideoAb: Boolean(preflight.readyForVideoAb),
+    summary: `live=${preflight.readyForLiveAcceptance ? 'ready' : 'not_ready'}, videoAb=${preflight.readyForVideoAb ? 'ready' : 'not_ready'}, fail=${failureCount}, warn=${warnCount}`,
+    next: preflight.readyForLiveAcceptance
+      ? preflight.readyForVideoAb
+        ? '可作为 live/video A/B 启动前置证据;仍不等同于业务效果证明。'
+        : '可启动 live 子集;视频 A/B 还需补齐视频分析配置。'
+      : '缺 sessionToken、company 或 provider 关键配置时,不能启动 live 长跑。'
+  };
+}
+
+function classifyReview(root, dir, review) {
+  return {
+    type: 'review-metrics',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: review.acceptance?.customerSelectedRatePass === true ? 'real_evidence' : 'not_business_proof',
+    status: review.acceptance?.overallPass ? 'pass' : 'not_passed',
+    summary: `rows=${review.total || 0}, usable=${pct(review.businessUsableRate)}, negative=${pct(review.offTargetHardFailRate)}, selected=${review.selectedCount ? pct(review.customerSelectedRate) : 'unmeasured'}`,
+    next: review.selectedCount ? '可用于客户选择效果复盘。' : '缺客户选择字段时不能证明客户选中率。'
+  };
+}
+
+function classifyCustomerEffect(root, dir, customerEffect, context = {}) {
+  const effectProof = customerEffect.acceptance?.overallPass === true &&
+    customerEffect.acceptance?.customerSelectedRate30Pass === true &&
+    customerEffect.acceptance?.referenceCustomerSelectedRate40Pass !== false &&
+    customerEffect.acceptance?.historyReadyForCustomerEffectProof === true &&
+    customerEffect.acceptance?.manualSupplementBaselineAvailable === true &&
+    customerEffect.acceptance?.currentManualSupplementAvailable === true &&
+    customerEffect.acceptance?.manualSupplementReduction50Pass === true;
+  const pipelineBlocker = getPipelineScopedCustomerEffectBlocker(context.pipeline);
+  const acceptedProof = effectProof && !pipelineBlocker;
+  return {
+    type: 'customer-effect',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: acceptedProof ? 'real_evidence' : 'not_business_proof',
+    status: customerEffect.acceptance?.overallPass ? 'pass' : 'not_passed',
+    pipelineScoped: Boolean(context.pipeline),
+    pipelineClaimable: context.pipeline ? !pipelineBlocker : null,
+    summary: `selected=${customerEffect.acceptance?.customerSelectedRateMeasured ? pct(customerEffect.customerSelectedRate) : 'unmeasured'}, reference=${customerEffect.acceptance?.referenceCustomerSelectedRateMeasured ? pct(customerEffect.referenceCustomerSelectedRate) : 'unmeasured'}, supplementReduction=${customerEffect.manualSupplementReductionRate === null || customerEffect.manualSupplementReductionRate === undefined ? 'unmeasured' : pct(customerEffect.manualSupplementReductionRate)}${pipelineBlocker ? `, pipelineBlocked=${pipelineBlocker.reasons.join('+')}` : ''}`,
+    next: acceptedProof
+      ? '可作为客户效果证明:客户选中率、参考链路和人工补号减少门槛已通过。'
+      : pipelineBlocker
+        ? '该 customer-effect 位于未完成或不可声明的 optimization-pipeline 中;必须先刷新当前 proof-gap closure 和 completion,不能用旧 pipeline 内嵌产物证明客户效果完成。'
+        : '缺客户选择、历史补号基线、本轮补号量或减少率未达标时,不能证明客户效果完成。'
+  };
+}
+
+function getPipelineScopedCustomerEffectBlocker(pipeline) {
+  if (!pipeline) return null;
+  const openCount = Number(pipeline.proofGapClosure?.openCount || 0);
+  const closureComplete = pipeline.proofGapClosure?.complete === true && openCount === 0;
+  if (pipeline.readyForClaim === true && closureComplete) return null;
+  const reasons = [];
+  if (pipeline.readyForClaim !== true) reasons.push('readyForClaim=false');
+  if (pipeline.proofGapClosure?.complete !== true) reasons.push('proofGapClosure.complete=false');
+  if (openCount !== 0) reasons.push(`proofGapClosure.openCount=${openCount}`);
+  return { reasons };
+}
+
+function classifyHistory(root, dir, history) {
+  const missingCustomerEffectProof = getHistoryCustomerEffectProofMissingRequirements(history);
+  const readyForCustomerEffectProof = missingCustomerEffectProof.length === 0;
+  const readyForLongRun = readyForCustomerEffectProof || isHistoryReadyForLongRun(history);
+  return {
+    type: 'historical-dataset',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: readyForCustomerEffectProof ? 'real_evidence' : readyForLongRun ? 'smoke_or_local' : 'not_business_proof',
+    status: readyForLongRun ? 'ready_for_long_run' : 'not_ready',
+    summary: `briefs=${history.briefCount || 0}, categories=${history.categoryCount || 0}, customerDecision=${history.withCustomerDecision || 0}, missing=${missingCustomerEffectProof.length}`,
+    missingProofRequirements: missingCustomerEffectProof,
+    next: readyForCustomerEffectProof ? '可用于客户效果证明前置数据。' : '缺客户选择、拒绝原因、人工补号量基线或可审计明细时不能证明客户效果。'
+  };
+}
+
+function isHistoryReadyForLongRun(history) {
+  if (!history) return false;
+  const acceptance = history.acceptance || {};
+  return Boolean(
+    acceptance.readyForLongRun === true &&
+    acceptance.parseOk === true &&
+    acceptance.minBriefsMet === true &&
+    acceptance.allHaveBriefText === true &&
+    acceptance.allHaveManualFinalList === true &&
+    acceptance.allHaveCustomerDecision === true &&
+    acceptance.allHaveFeedbackReason === true &&
+    acceptance.categoryCoverageMet === true &&
+    Number(history.briefCount || 0) >= 5 &&
+    Array.isArray(history.items) &&
+    history.items.length >= 5 &&
+    Number(history.missingCriticalCount || 0) === 0
+  );
+}
+
+function getHistoryCustomerEffectProofMissingRequirements(history) {
+  if (!history) return ['missing historical-dataset-audit.json'];
+  const missing = [];
+  const acceptance = history.acceptance || {};
+  if (acceptance.readyForCustomerEffectProof !== true) missing.push('acceptance.readyForCustomerEffectProof=true');
+  if (Number(history.briefCount || 0) < 5) missing.push('briefCount>=5');
+  if (acceptance.parseOk !== true) missing.push('acceptance.parseOk=true');
+  if (acceptance.minBriefsMet !== true) missing.push('acceptance.minBriefsMet=true');
+  if (acceptance.allHaveBriefText !== true) missing.push('acceptance.allHaveBriefText=true');
+  if (acceptance.allHaveManualFinalList !== true) missing.push('acceptance.allHaveManualFinalList=true');
+  if (acceptance.allHaveCustomerDecision !== true) missing.push('acceptance.allHaveCustomerDecision=true');
+  if (acceptance.allHaveFeedbackReason !== true) missing.push('acceptance.allHaveFeedbackReason=true');
+  if (acceptance.allHaveManualSupplementBaseline !== true) missing.push('acceptance.allHaveManualSupplementBaseline=true');
+  if (acceptance.categoryCoverageMet !== true) missing.push('acceptance.categoryCoverageMet=true');
+  if (Number(history.missingCriticalCount || 0) !== 0) missing.push('missingCriticalCount=0');
+  if (!Array.isArray(history.items) || history.items.length < 5) missing.push('items.length>=5');
+  if (Number(history.withManualFinalList || 0) < 5) missing.push('withManualFinalList>=5');
+  if (Number(history.withCustomerDecision || 0) < 5) missing.push('withCustomerDecision>=5');
+  if (Number(history.withRejectionReason || 0) < 1) missing.push('withRejectionReason>=1');
+  if (Number(history.withManualSupplementBaseline || 0) < 5) missing.push('withManualSupplementBaseline>=5');
+  return uniqueStrings(missing);
+}
+
+function classifyVideo(root, dir, video) {
+  const videoProof = video.acceptance?.passed === true &&
+    video.acceptance?.realReferenceResourceLoaded === true &&
+    video.acceptance?.realVideoResourceLoaded === true &&
+    video.acceptance?.coverOrFrameOrAsrLoaded === true &&
+    video.acceptance?.evidenceCardsNotPending === true &&
+    video.acceptance?.evidenceCardsHaveSignals === true &&
+    video.acceptance?.strongNotDegraded === true &&
+    video.acceptance?.top10EvidenceImproved === true;
+  return {
+    type: 'video-ab',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: videoProof ? 'real_evidence' : 'not_business_proof',
+    status: video.acceptance?.passed ? 'pass' : 'not_passed',
+    summary: `strongDelta=${video.delta?.strong ?? 'n/a'}, evidenceDelta=${video.delta?.top10EvidenceHitCandidates ?? 'n/a'}`,
+    next: videoProof ? '可作为视频证据提升的 A/B 证明。' : '不能宣称视频分析提升命中率。'
+  };
+}
+
+function classifyVideoRuntimePreflight(root, dir, preflight) {
+  return {
+    type: 'video-ab-runtime-preflight',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: 'not_business_proof',
+    status: preflight.readyForVideoAb ? 'ready_to_run' : 'not_ready',
+    summary: `readyForVideoAb=${Boolean(preflight.readyForVideoAb)}, failureCount=${preflight.failureCount || 0}, generatedBy=${preflight.proofContext?.generatedBy || 'unknown'}`,
+    next: preflight.readyForVideoAb
+      ? '只表示运行环境可启动 acceptance:video-ab;必须生成 video-hit-rate-summary.json 才能进入 proof-gap closure。'
+      : '补齐运行时凭证、company、VOC social provider 和视频分析 provider 后再运行 acceptance:video-ab,并生成 live video-hit-rate-summary.json;preflight 失败不证明视频提升。'
+  };
+}
+
+function renderReport(summary) {
+  const latest = pickLatestUseful(summary.entries);
+  const lines = [
+    '# 提号优化证据台账',
+    '',
+    `- 生成时间:${summary.generatedAt}`,
+    `- 扫描目录:${summary.outputsDir}`,
+    `- 证据条目:${summary.total}`,
+    '',
+    '## 汇总',
+    '',
+    ...Object.entries(summary.counts).map(([key, value]) => `- ${key}: ${value}`),
+    '',
+    '## 最新可用证据',
+    '',
+    '| 类型 | 证明等级 | 状态 | 目录 | 摘要 | 下一步 |',
+    '| --- | --- | --- | --- | --- | --- |',
+    ...latest.map(item => row(item)),
+    '',
+    '## 全部条目',
+    '',
+    '| 类型 | 证明等级 | 状态 | 更新时间 | 目录 | 摘要 | 下一步 |',
+    '| --- | --- | --- | --- | --- | --- | --- |',
+    ...summary.entries.map(item => row(item, true)),
+    '',
+    '## 说明',
+    '',
+    '- `real_evidence` 表示该产物包含真实 live、客户选择、历史数据或视频 A/B 证据,且关键门槛通过。',
+    '- `smoke_or_local` 表示可证明结构、门槛或启动前置条件,但不能证明真实客户效果。',
+    '- `not_business_proof` 表示该产物明确显示仍缺真实数据或状态未完成。'
+  ];
+  return lines.join('\n');
+}
+
+function classifyVideoResourceReadiness(root, dir, readiness) {
+  return {
+    type: 'video-resource-readiness',
+    dir: rel(root, dir),
+    updatedAt: mtime(dir),
+    proofLevel: readiness.acceptance?.readyForVideoAbPreflight ? 'smoke_or_local' : 'not_business_proof',
+    status: readiness.acceptance?.readyForVideoAbPreflight ? 'ready' : 'not_ready',
+    summary: `realReference=${readiness.counts?.realReferenceRows || 0}, realCandidate=${readiness.counts?.realCandidateRows || 0}, videoUrl=${readiness.counts?.videoUrlRows || 0}, failureCount=${readiness.failureCount || 0}`,
+    next: readiness.acceptance?.readyForVideoAbPreflight
+      ? '可作为视频 A/B 启动前资源证明;仍需运行 acceptance:video-ab 证明 provider、证据卡和 Top10 非退化。'
+      : '先补齐真实参考视频、候选视频、视频 URL、封面/ASR/帧图或正文证据。'
+  };
+}
+
+function classifyRepairActions(root, dir, type, fileName) {
+  const csvPath = path.join(dir, fileName);
+  const actionCount = countCsvRows(csvPath);
+  const isVideo = type === 'video-resource-repair-actions';
+  const isHomepage = type === 'homepage-evidence-repair-actions';
+  return {
+    type,
+    dir: rel(root, dir),
+    updatedAt: fs.statSync(csvPath).mtime.toISOString(),
+    proofLevel: 'smoke_or_local',
+    status: actionCount > 0 ? 'actions_open' : 'empty',
+    summary: `csv=${fileName}, actions=${actionCount}`,
+    next: isHomepage
+      ? '用于指导商务/投放补齐主页近期内容、封面、发布时间、互动和风险信号;不证明客户效果。'
+      : isVideo
+        ? '用于指导商务/投放补齐真实视频 URL、封面、ASR、帧图或正文证据;不证明视频 A/B 或命中率提升。'
+        : '用于指导商务补齐真实历史 Brief、客户选择、人工补号基线和复核字段;不证明客户效果。'
+  };
+}
+
+function pickLatestUseful(entries) {
+  const byType = new Map();
+  for (const entry of entries) {
+    if (!byType.has(entry.type)) byType.set(entry.type, entry);
+  }
+  return [...byType.values()];
+}
+
+function row(item, includeTime = false) {
+  const cells = includeTime
+    ? [item.type, item.proofLevel, item.status, item.updatedAt, item.dir, item.summary, item.next]
+    : [item.type, item.proofLevel, item.status, item.dir, item.summary, item.next];
+  return `| ${cells.map(escapeCell).join(' | ')} |`;
+}
+
+function readJson(file) {
+  if (!fs.existsSync(file)) return null;
+  try {
+    return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, ''));
+  } catch {
+    return null;
+  }
+}
+
+function isDataIntakePack(dir) {
+  return fs.existsSync(path.join(dir, 'history-data-template.csv')) &&
+    fs.existsSync(path.join(dir, 'manual-review-template.csv')) &&
+    fs.existsSync(path.join(dir, 'README.md'));
+}
+
+function isVideoIntakePack(dir) {
+  return fs.existsSync(path.join(dir, 'video-resource-template.csv')) &&
+    fs.existsSync(path.join(dir, 'README.md'));
+}
+
+function countBy(entries, key) {
+  return entries.reduce((acc, item) => {
+    acc[item[key]] = (acc[item[key]] || 0) + 1;
+    return acc;
+  }, {});
+}
+
+function mtime(dir) {
+  return fs.statSync(dir).mtime.toISOString();
+}
+
+function rel(root, file) {
+  return path.relative(root, file).replace(/\\/g, '/');
+}
+
+function pct(value) {
+  return `${Math.round(Number(value || 0) * 100)}%`;
+}
+
+function countCsvRows(file) {
+  const text = fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, '').trim();
+  if (!text) return 0;
+  return Math.max(0, text.split(/\r?\n/).length - 1);
+}
+
+function uniqueStrings(values) {
+  return [...new Set((values || []).filter(Boolean).map(value => String(value)))];
+}
+
+function parseArgs(argv) {
+  const args = {};
+  for (let index = 0; index < argv.length; index += 1) {
+    const raw = argv[index];
+    if (!raw.startsWith('--')) continue;
+    const key = raw.slice(2).replace(/-([a-z])/g, (_, char) => char.toUpperCase());
+    const next = argv[index + 1];
+    if (!next || next.startsWith('--')) args[key] = true;
+    else {
+      args[key] = next;
+      index += 1;
+    }
+  }
+  return args;
+}
+
+function escapeCell(value) {
+  return String(value ?? '').replace(/\|/g, '/').replace(/\r?\n/g, ' ');
+}
+
+function withBom(text) {
+  return `\uFEFF${text}`;
+}
+
+if (require.main === module) main();
+
+module.exports = {
+  buildEvidenceIndex,
+  classifyDir
+};

+ 86 - 0
claude-code-tihao-sourcing/scripts/experience-transcript-index-smoke.js

@@ -0,0 +1,86 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const { spawnSync } = require('child_process');
+
+const ROOT = path.resolve(__dirname, '..');
+
+function main() {
+  const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-experience-transcript-index-'));
+  const transcriptsDir = path.join(temp, 'transcripts');
+  const output = path.join(temp, 'index');
+  fs.mkdirSync(transcriptsDir, { recursive: true });
+  fs.writeFileSync(path.join(transcriptsDir, '黄圆-AI提号经验逐字稿.md'), [
+    '# 黄圆',
+    '参考账号要拆类型和调性。',
+    '小红书和蒲公英可以通过相似推荐链路继续找。',
+    '抖音星图标签不准时,用招募和关键词兜底。',
+    '看主页近10篇、20篇封面首图判断风格。',
+    '先用预算、CPE、CP1、CPM、粉丝、女粉和城市做第一轮过滤。',
+    'Brief 之外还要看产品、参考账号和客户聊天记录,客户说的隐藏要求要并入。'
+  ].join('\n'), 'utf8');
+  fs.writeFileSync(path.join(transcriptsDir, '邓发-AI提号经验逐字稿.md'), [
+    '# 邓发',
+    '美容仪和脱毛仪要理解女性受众,不能推荐男性账号。',
+    '内容质感看画面清晰、像素、排版,不要下沉。',
+    '报价 300 元以上要看封面统一、背景干净和画面精致。',
+    '近一个月停更、平均点赞百赞以下、重复评论和同一个人评论都要复核数据真实性。',
+    '时尚穿搭要拆港风、成熟风,美妆护肤要拆妆教、测评和产品合集。',
+    '封面不确定再点进去看视频内容,可以继续解析每一帧。',
+    '最终要看客户选中率、降本增效和补号变化。',
+    ['Authorization', 'Bearer abcdefghijklmnopqrstuvwxyz'].join(': ')
+  ].join('\n'), 'utf8');
+
+  const result = spawnSync(process.execPath, [
+    'scripts/experience-transcript-index.js',
+    '--transcripts-dir',
+    transcriptsDir,
+    '--output',
+    output,
+    '--strict'
+  ], {
+    cwd: ROOT,
+    encoding: 'utf8',
+    shell: false
+  });
+  if (result.stdout) process.stdout.write(result.stdout);
+  if (result.stderr) process.stderr.write(result.stderr);
+  assert(result.status === 0, 'experience transcript index should pass');
+
+  const summaryPath = path.join(output, 'experience-transcript-index-summary.json');
+  const reportPath = path.join(output, 'experience-transcript-index.md');
+  const csvPath = path.join(output, 'experience-transcript-index.csv');
+  const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8'));
+  const report = fs.readFileSync(reportPath, 'utf8');
+  const csv = fs.readFileSync(csvPath, 'utf8');
+
+  assert(summary.proofLevel === 'not_business_proof', 'summary should not be business proof');
+  assert(summary.materialType === 'experience_seed', 'summary should classify as experience seed');
+  assert(summary.directCustomerProof === false, 'summary should not be direct customer proof');
+  assert(summary.canCloseProofGap === false, 'summary should not close proof gap');
+  assert(summary.complete === false, 'summary should not claim completion');
+  assert(summary.transcriptCount === 2, 'summary should index two transcripts');
+  assert(summary.coveredRuleCount === summary.ruleCount, 'fixture should cover all rules');
+  assert(summary.ruleCoverage.every(item => item.directCustomerProof === false), 'rules should keep proof boundary');
+  assert(summary.ruleCoverage.some(item => item.id === 'reference-type-tone' && item.status === 'covered'), 'reference rule should be covered');
+  assert(summary.ruleCoverage.some(item => item.id === 'customer-effect-goal' && item.status === 'covered'), 'customer effect boundary rule should be covered');
+  assert(report.includes('提号经验逐字稿索引'), 'report should render title');
+  assert(report.includes('canCloseProofGap:false'), 'report should render proof boundary');
+  assert(csv.includes('experience-rule'), 'csv should include rule rows');
+  assert(!JSON.stringify(summary).includes('Authorization'), 'summary should not expose secret-like transcript text');
+  assert(!report.includes('Authorization'), 'report should not expose secret-like transcript text');
+  assert(!csv.includes('Authorization'), 'csv should not expose secret-like transcript text');
+
+  console.log(JSON.stringify({
+    ok: true,
+    transcriptCount: summary.transcriptCount,
+    coveredRuleCount: summary.coveredRuleCount
+  }, null, 2));
+}
+
+function assert(condition, message) {
+  if (!condition) throw new Error(message);
+}
+
+main();

+ 392 - 0
claude-code-tihao-sourcing/scripts/experience-transcript-index.js

@@ -0,0 +1,392 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const path = require('path');
+
+const ROOT = path.resolve(__dirname, '..');
+const DEFAULT_TRANSCRIPTS_DIR = path.resolve(ROOT, '..', 'docs', '提号经验');
+const DEFAULT_OUTPUT = path.join(ROOT, 'outputs', 'experience-transcript-index-latest');
+
+const RULES = [
+  {
+    id: 'reference-type-tone',
+    title: '参考账号拆类型和调性',
+    terms: ['参考账号', '类型', '调性'],
+    practicalRule: '参考账号只能拆成账号类型、内容调性、场景和表达方式等锚点;多参考账号冲突时不能强行合并成单一标准。',
+    usableFor: '校准 referenceUsability、referenceStyleFingerprints 和强推荐解释。'
+  },
+  {
+    id: 'xhs-pgy-similar-chain',
+    title: '小红书/蒲公英相似账号链路',
+    terms: ['小红书', '蒲公英', '相似', '推荐'],
+    practicalRule: '小红书和蒲公英优先用参考账号相似推荐链路,再按类型和调性二次筛选。',
+    usableFor: '校准小红书召回路径和平台策略。'
+  },
+  {
+    id: 'douyin-xingtu-fallback',
+    title: '抖音/星图只作入口或兜底',
+    terms: ['抖音', '星图', '招募', '关键词'],
+    practicalRule: '抖音相似推荐和星图标签不能直接当适配证明;找不到时用招募、APP 关键词或人工补找兜底。',
+    usableFor: '校准抖音召回路径和低置信复核边界。'
+  },
+  {
+    id: 'homepage-recent-posts',
+    title: '主页近 10/20 篇优先判断',
+    terms: ['主页', '前十', '10', '20', '封面', '首图'],
+    practicalRule: '人工通常先扫主页近 10 篇左右封面/首图,必要时扩到 20 篇或点进图文/视频深看。',
+    usableFor: '校准 homepage evidence readiness、recentContentFitScore、visualQualityScore 和 toneConsistencyScore。'
+  },
+  {
+    id: 'quant-metric-first-filter',
+    title: '先用量化指标做第一轮过滤',
+    terms: ['CPE', 'CP1', 'CPM', '粉丝', '预算', '城市', '女粉'],
+    practicalRule: '先用关键词、预算、CPE/CP1/CPM、粉丝量、男女粉占比和城市等硬指标缩小候选,再做主页风格复核。',
+    usableFor: '校准 parseBrief.performanceRequirements、requirementLayers.hardConstraints 和 live 小规模召回参数。'
+  },
+  {
+    id: 'visual-tone-quality',
+    title: '视觉质感和调性是核心难点',
+    terms: ['质感', '画面清晰', '像素', '排版', '下沉', '风格'],
+    practicalRule: '提号难点不只是数据维度,而是账号风格、画面质感、内容垂类和品牌调性是否匹配。',
+    usableFor: '校准强推荐门槛、风险提示和人工复核标签。'
+  },
+  {
+    id: 'price-visual-threshold',
+    title: '报价 300 元以上提高视觉质感要求',
+    terms: ['300', '三百', '画面清晰', '背景', '封面统一', '精致'],
+    practicalRule: '低价账号可相对放宽,但报价 300 元以上应优先选择封面清晰、背景统一、画面精致、风格稳定的账号。',
+    usableFor: '校准 visualQualityScore、homepageQualityRisks、manualReviewFields 和强推荐门槛。'
+  },
+  {
+    id: 'recent-data-authenticity',
+    title: '近期数据和评论真实性复核',
+    terms: ['近一个月', '近30天', '平均点赞', '百赞', '重复评论', '同一个人评论', '数据真实性', '停更'],
+    practicalRule: '复核近一个月是否更新、近作平均点赞是否过低,以及多篇笔记是否出现同一批评论者;疑似刷评或停更不要强推。',
+    usableFor: '校准 homepageEvidence.dataRiskSignals、dataQualityScore、riskNote 和人工复核标签。'
+  },
+  {
+    id: 'implicit-product-audience',
+    title: '产品隐性人群规则',
+    terms: ['脱毛仪', '美容仪', '女性', '男性账号', '受众'],
+    practicalRule: '美容仪、脱毛仪等产品即使 Brief 未写明,也要默认复核女性使用场景和女性博主语境。',
+    usableFor: '校准品类隐性规则和硬性降级。'
+  },
+  {
+    id: 'multi-input-brief-product-chat',
+    title: 'Brief + 产品 + 聊天记录多输入解析',
+    terms: ['Brief', '产品', '聊天记录', '客户聊天', '参考账号', '客户说'],
+    practicalRule: '最佳输入不只是 Brief,还应合并产品介绍、参考账号和客户聊天记录;聊天里常有隐藏偏好、禁区和子类方向。',
+    usableFor: '校准 parseBrief.contextSources、productIntro/chatRecords schema 和下一轮校准问题。'
+  },
+  {
+    id: 'category-subtype-refinement',
+    title: '宽类目必须拆到可执行子类型',
+    terms: ['时尚穿搭', '港风', '成熟风', '美妆', '护肤', '妆教', '测评', '产品合集'],
+    practicalRule: '精致生活、时尚穿搭、美妆护肤等宽类目不能只按平台标签筛,必须结合产品和参考账号拆到港风/成熟风、护肤/彩妆/妆教/测评等子方向。',
+    usableFor: '校准 inferCategory、关键词扩展、categoryRules 和召回关键词。'
+  },
+  {
+    id: 'video-deep-check',
+    title: '封面不确定再深看视频内容',
+    terms: ['视频', '内容', '点进去', '每一帧', '解析'],
+    practicalRule: '封面或主页判断不确定时,再进入图文正文、视频内容、ASR 或帧图分析;没有真实资源时只能标待补证。',
+    usableFor: '校准视频资源收集包和视频 A/B 前置门禁。'
+  },
+  {
+    id: 'customer-effect-goal',
+    title: '最终目标是客户选中率和效率',
+    terms: ['客户', '选中率', '降本增效', '补号', '效果'],
+    practicalRule: '提号优化最终要用客户选中、拒绝归因和人工补号量变化证明;经验判断本身不能替代客户效果审计。',
+    usableFor: '校准 proof-gap、customer-effect:audit 和禁止误宣称边界。'
+  }
+];
+
+function main() {
+  const args = parseArgs(process.argv.slice(2));
+  const transcriptsDir = path.resolve(args.transcriptsDir || DEFAULT_TRANSCRIPTS_DIR);
+  const outputDir = path.resolve(args.output || DEFAULT_OUTPUT);
+  const summary = buildExperienceTranscriptIndex({ root: ROOT, transcriptsDir });
+
+  fs.mkdirSync(outputDir, { recursive: true });
+  const summaryPath = path.join(outputDir, 'experience-transcript-index-summary.json');
+  const reportPath = path.join(outputDir, 'experience-transcript-index.md');
+  const csvPath = path.join(outputDir, 'experience-transcript-index.csv');
+  fs.writeFileSync(summaryPath, JSON.stringify(summary, null, 2), 'utf8');
+  fs.writeFileSync(reportPath, withBom(renderReport(summary)), 'utf8');
+  fs.writeFileSync(csvPath, withBom(renderCsv(summary)), 'utf8');
+
+  console.log(JSON.stringify({
+    outputDir,
+    summary: summaryPath,
+    report: reportPath,
+    csv: csvPath,
+    transcriptCount: summary.transcriptCount,
+    coveredRuleCount: summary.coveredRuleCount,
+    canCloseProofGap: summary.canCloseProofGap
+  }, null, 2));
+
+  if (args.strict && summary.directCustomerProof !== false) process.exitCode = 1;
+}
+
+function buildExperienceTranscriptIndex({ root, transcriptsDir }) {
+  const transcripts = inspectTranscripts({ root, transcriptsDir });
+  const coverage = RULES.map(rule => inspectRuleCoverage(rule, transcripts));
+  const coveredRuleCount = coverage.filter(item => item.status === 'covered').length;
+  const transcriptCount = transcripts.filter(item => item.exists).length;
+  return {
+    generatedAt: new Date().toISOString(),
+    root,
+    transcriptsDir,
+    proofLevel: 'not_business_proof',
+    materialType: 'experience_seed',
+    directCustomerProof: false,
+    canCloseProofGap: false,
+    complete: false,
+    transcriptCount,
+    missingTranscriptCount: transcripts.filter(item => !item.exists).length,
+    ruleCount: RULES.length,
+    coveredRuleCount,
+    transcripts,
+    ruleCoverage: coverage,
+    guardrails: [
+      '逐字稿只证明团队经验来源可追踪,不能证明提号率提升、客户效果达标或人工补号量下降。',
+      '逐字稿里的经验规则必须进入真实候选、真实复核和客户效果审计后,才可能参与业务证明。',
+      '不要把经验判断、样例项目、PoC 推荐名单或本索引本身当作 customer-effect:audit 证据。',
+      '本索引只输出规则覆盖、文件路径和行号,不输出原文长段内容,避免带出敏感信息或噪声。'
+    ],
+    nextActions: [
+      {
+        owner: '技术/AI',
+        title: '对照规则实现',
+        action: '把 ruleCoverage 与 experience-rules、ranker、homepage readiness、video readiness 和 skill references 对齐。',
+        acceptance: '规则实现继续覆盖参考账号、主页近作、品类隐性规则、抖音兜底和视频待补证边界。'
+      },
+      {
+        owner: '商务/投放',
+        title: '把经验种子转成真实复核数据',
+        action: '用逐字稿经验指导真实历史 Brief、候选视频、人工复核标签、客户选择和拒绝归因填写。',
+        acceptance: 'intake:readiness failureCount=0,history:audit 与 customer-effect:audit 通过。'
+      }
+    ]
+  };
+}
+
+function inspectTranscripts({ root, transcriptsDir }) {
+  if (!fs.existsSync(transcriptsDir)) {
+    return [{
+      id: 'transcripts-dir',
+      title: '提号经验逐字稿目录',
+      path: rel(root, transcriptsDir),
+      exists: false,
+      proofLevel: 'not_business_proof',
+      directCustomerProof: false,
+      canCloseProofGap: false,
+      lineCount: 0,
+      matchedRuleIds: [],
+      status: 'missing',
+      summary: '未发现逐字稿目录;不影响真实业务证明缺口判断。'
+    }];
+  }
+  return fs.readdirSync(transcriptsDir, { withFileTypes: true })
+    .filter(entry => entry.isFile() && entry.name.endsWith('.md'))
+    .sort((a, b) => a.name.localeCompare(b.name, 'zh-CN'))
+    .map(entry => inspectTranscript({ root, file: path.join(transcriptsDir, entry.name) }));
+}
+
+function inspectTranscript({ root, file }) {
+  const text = fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, '');
+  const lines = text.split(/\r?\n/);
+  const matchedRuleIds = RULES
+    .filter(rule => findRuleMatches(lines, rule).length > 0)
+    .map(rule => rule.id);
+  return {
+    id: slug(path.basename(file, '.md')),
+    title: path.basename(file, '.md'),
+    path: rel(root, file),
+    exists: true,
+    proofLevel: 'not_business_proof',
+    materialType: 'experience_seed',
+    directCustomerProof: false,
+    canCloseProofGap: false,
+    lineCount: lines.length,
+    matchedRuleIds,
+    status: 'available_experience_seed',
+    summary: `覆盖经验规则 ${matchedRuleIds.length}/${RULES.length} 条;只能作为规则来源和填表提示,不是业务证明。`
+  };
+}
+
+function inspectRuleCoverage(rule, transcripts) {
+  const sources = transcripts
+    .filter(item => item.exists)
+    .map(item => {
+      const file = path.resolve(ROOT, item.path);
+      const text = fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, '');
+      const lines = text.split(/\r?\n/);
+      const matches = findRuleMatches(lines, rule);
+      return {
+        transcriptId: item.id,
+        path: item.path,
+        hitCount: matches.length,
+        lineNumbers: matches.slice(0, 12).map(match => match.lineNumber)
+      };
+    })
+    .filter(item => item.hitCount > 0);
+  const hitCount = sources.reduce((sum, item) => sum + item.hitCount, 0);
+  return {
+    id: rule.id,
+    title: rule.title,
+    status: hitCount > 0 ? 'covered' : 'not_found',
+    proofLevel: 'not_business_proof',
+    directCustomerProof: false,
+    canCloseProofGap: false,
+    hitCount,
+    practicalRule: rule.practicalRule,
+    usableFor: rule.usableFor,
+    sources,
+    missingForProof: [
+      '真实历史 Brief 数据集',
+      '真实候选视频资源',
+      '商务复核标签和客户选择',
+      'customer-effect:audit 通过'
+    ]
+  };
+}
+
+function findRuleMatches(lines, rule) {
+  return lines.flatMap((line, index) => {
+    const text = String(line || '');
+    const hitTerms = rule.terms.filter(term => text.includes(term));
+    return hitTerms.length ? [{ lineNumber: index + 1, hitTerms }] : [];
+  });
+}
+
+function renderReport(summary) {
+  return [
+    '# 提号经验逐字稿索引',
+    '',
+    `- 生成时间:${summary.generatedAt}`,
+    `- proofLevel:${summary.proofLevel}`,
+    `- materialType:${summary.materialType}`,
+    `- directCustomerProof:${summary.directCustomerProof}`,
+    `- canCloseProofGap:${summary.canCloseProofGap}`,
+    `- 逐字稿:${summary.transcriptCount}`,
+    `- 覆盖经验规则:${summary.coveredRuleCount}/${summary.ruleCount}`,
+    '',
+    '## 边界',
+    '',
+    ...summary.guardrails.map(item => `- ${item}`),
+    '',
+    '## 逐字稿',
+    '',
+    '| ID | 状态 | 路径 | 行数 | 覆盖规则 | 摘要 |',
+    '| --- | --- | --- | ---: | --- | --- |',
+    ...summary.transcripts.map(item => tableRow([
+      item.id,
+      item.status,
+      item.path,
+      item.lineCount,
+      item.matchedRuleIds.join(';'),
+      item.summary
+    ])),
+    '',
+    '## 经验规则覆盖',
+    '',
+    '| 规则 | 状态 | 命中数 | 可用于 | 来源行号 | 缺失证明 |',
+    '| --- | --- | ---: | --- | --- | --- |',
+    ...summary.ruleCoverage.map(item => tableRow([
+      item.title,
+      item.status,
+      item.hitCount,
+      item.usableFor,
+      item.sources.map(source => `${source.path}:${source.lineNumbers.join('/')}`).join(';'),
+      item.missingForProof.join(';')
+    ])),
+    '',
+    '## 下一步',
+    '',
+    '| 负责人 | 动作 | 验收 |',
+    '| --- | --- | --- |',
+    ...summary.nextActions.map(item => tableRow([item.owner, `${item.title}:${item.action}`, item.acceptance])),
+    ''
+  ].join('\n');
+}
+
+function renderCsv(summary) {
+  const rows = [
+    ['类型', 'ID', '状态', 'proofLevel', 'directCustomerProof', 'canCloseProofGap', '路径/来源', '摘要/规则', '缺失证明'],
+    ...summary.transcripts.map(item => [
+      'experience-transcript',
+      item.id,
+      item.status,
+      item.proofLevel,
+      item.directCustomerProof,
+      item.canCloseProofGap,
+      item.path,
+      item.summary,
+      '客户选择;人工补号量;customer-effect:audit'
+    ]),
+    ...summary.ruleCoverage.map(item => [
+      'experience-rule',
+      item.id,
+      item.status,
+      item.proofLevel,
+      item.directCustomerProof,
+      item.canCloseProofGap,
+      item.sources.map(source => `${source.path}:${source.lineNumbers.join('/')}`).join(';'),
+      item.practicalRule,
+      item.missingForProof.join(';')
+    ])
+  ];
+  return rows.map(row => row.map(csvCell).join(',')).join('\n');
+}
+
+function parseArgs(argv) {
+  const result = {};
+  for (let index = 0; index < argv.length; index += 1) {
+    const arg = argv[index];
+    if (!arg.startsWith('--')) continue;
+    const key = arg.slice(2).replace(/-([a-z])/g, (_, char) => char.toUpperCase());
+    const next = argv[index + 1];
+    if (!next || next.startsWith('--')) result[key] = true;
+    else {
+      result[key] = next;
+      index += 1;
+    }
+  }
+  return result;
+}
+
+function slug(value) {
+  return String(value || '')
+    .toLowerCase()
+    .replace(/[^\p{L}\p{N}]+/gu, '-')
+    .replace(/^-+|-+$/g, '') || 'transcript';
+}
+
+function rel(root, file) {
+  const relative = path.relative(root, file).replace(/\\/g, '/');
+  return relative || '.';
+}
+
+function withBom(text) {
+  return `\uFEFF${text}`;
+}
+
+function tableRow(values) {
+  return `| ${values.map(value => escapeCell(value)).join(' | ')} |`;
+}
+
+function escapeCell(value) {
+  return String(value ?? '').replace(/\|/g, '\\|').replace(/\r?\n/g, '<br>');
+}
+
+function csvCell(value) {
+  return `"${String(value ?? '').replace(/"/g, '""')}"`;
+}
+
+if (require.main === module) main();
+
+module.exports = {
+  buildExperienceTranscriptIndex,
+  inspectRuleCoverage,
+  RULES
+};

+ 64 - 0
claude-code-tihao-sourcing/scripts/export-proof-gap-software-form-smoke.js

@@ -0,0 +1,64 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const { spawnSync } = require('child_process');
+const { SOFTWARE_HEADER } = require('./export-proof-gap-software-form');
+
+const root = path.resolve(__dirname, '..');
+
+function main() {
+  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-proof-gap-form-'));
+  const outputDir = path.join(tmp, 'form-latest');
+  const result = spawnSync(process.execPath, [
+    path.join(root, 'scripts', 'export-proof-gap-software-form.js'),
+    '--output',
+    outputDir,
+    '--strict'
+  ], { cwd: root, encoding: 'utf8' });
+  if (result.status !== 0) {
+    process.stderr.write(result.stderr || result.stdout || '');
+    throw new Error('proof-gap software form export should pass');
+  }
+
+  const csvPath = path.join(outputDir, 'tihao-proof-gap-software-form.csv');
+  const mdPath = path.join(outputDir, 'tihao-proof-gap-software-form.md');
+  const summaryPath = path.join(outputDir, 'tihao-proof-gap-software-form-summary.json');
+  assert(fs.existsSync(csvPath), 'csv should exist');
+  assert(fs.existsSync(mdPath), 'markdown should exist');
+  assert(fs.existsSync(summaryPath), 'summary should exist');
+
+  const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8'));
+  const csv = fs.readFileSync(csvPath, 'utf8');
+  const md = fs.readFileSync(mdPath, 'utf8').replace(/^\uFEFF/, '');
+  const lines = csv.replace(/^\uFEFF/, '').split(/\r?\n/).filter(Boolean);
+  assert(csv.charCodeAt(0) === 0xFEFF, 'csv should have UTF-8 BOM');
+  assert(summary.passed === true, 'summary should pass');
+  assert(summary.headerMatches === true, 'header should match software header');
+  assert(summary.rowWidthOk === true, 'all rows should have software header width');
+  assert(summary.rowCount === 5, 'row count should be 5');
+  assert(summary.duplicateIdCount === 0, 'duplicate ids should be 0');
+  assert(lines[0] === SOFTWARE_HEADER.join(','), 'csv header should match software header');
+  assert(lines.filter(line => /^GAP-\d{3},/.test(line)).length === 5, 'csv should include 5 GAP rows');
+  for (const title of summary.expectedTitles) {
+    assert(csv.includes(title), `csv should include ${title}`);
+    assert(md.includes(title), `markdown should include ${title}`);
+  }
+  assert(md.includes('最新提号补证表单'), 'markdown should be Chinese');
+  assert(md.includes('不能把 sample、smoke'), 'markdown should keep proof boundary');
+  assert(!/sk-[A-Za-z0-9_-]{10,}/.test(csv + md), 'form should not contain model token patterns');
+  assert(!/r:[A-Za-z0-9]{20,}/.test(csv + md), 'form should not contain Parse sessionToken patterns');
+
+  console.log(JSON.stringify({
+    ok: true,
+    outputDir,
+    rowCount: summary.rowCount,
+    duplicateIdCount: summary.duplicateIdCount
+  }, null, 2));
+}
+
+function assert(condition, message) {
+  if (!condition) throw new Error(message);
+}
+
+main();

+ 163 - 0
claude-code-tihao-sourcing/scripts/export-proof-gap-software-form.js

@@ -0,0 +1,163 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const path = require('path');
+
+const ROOT = path.resolve(__dirname, '..');
+const DEFAULT_INPUT = path.join(ROOT, 'docs', 'ai-optimization-proof-gap-software-table.md');
+const DEFAULT_OUTPUT = path.join(ROOT, 'outputs', 'tihao-proof-gap-software-form-latest');
+const SOFTWARE_HEADER = [
+  'brief编号',
+  '策略',
+  '排名',
+  '平台',
+  '博主名称',
+  '综合分',
+  'brief匹配分',
+  '参考风格分',
+  '主页证据分',
+  '视觉质感分',
+  '调性一致分',
+  '证据加分',
+  '证据风险扣分',
+  '推荐理由',
+  '风险提示',
+  '主页链接',
+  '人工复核标签'
+];
+
+function main() {
+  const args = parseArgs(process.argv.slice(2));
+  const input = path.resolve(args.input || DEFAULT_INPUT);
+  const outputDir = path.resolve(args.output || DEFAULT_OUTPUT);
+  const result = buildProofGapSoftwareForm({ input, outputDir });
+
+  fs.mkdirSync(outputDir, { recursive: true });
+  fs.writeFileSync(result.files.csv, withBom(renderCsv([SOFTWARE_HEADER, ...result.rows])), 'utf8');
+  fs.writeFileSync(result.files.markdown, withBom(renderMarkdown(result)), 'utf8');
+  fs.writeFileSync(result.files.summary, JSON.stringify(result.summary, null, 2), 'utf8');
+
+  console.log(JSON.stringify({
+    outputDir,
+    csv: result.files.csv,
+    markdown: result.files.markdown,
+    summary: result.files.summary,
+    rowCount: result.summary.rowCount,
+    duplicateIdCount: result.summary.duplicateIdCount,
+    headerMatches: result.summary.headerMatches
+  }, null, 2));
+
+  if (args.strict && !result.summary.passed) process.exitCode = 1;
+}
+
+function buildProofGapSoftwareForm({ input, outputDir }) {
+  const text = fs.readFileSync(input, 'utf8').replace(/^\uFEFF/, '');
+  const rows = text.split(/\r?\n/).map(splitMarkdownRow).filter(cells => cells.length);
+  const header = rows.find(cells => cells[0] === 'brief编号') || [];
+  const dataRows = rows.filter(cells => /^GAP-\d{3}$/.test(cells[0] || ''));
+  const ids = dataRows.map(cells => cells[0]);
+  const duplicateIds = ids.filter((id, index) => ids.indexOf(id) !== index);
+  const headerMatches = JSON.stringify(header) === JSON.stringify(SOFTWARE_HEADER);
+  const expectedTitles = [
+    '真实历史 Brief 数据集',
+    '真实视频资源表',
+    '真实视频证据 A/B',
+    '真实 live/provider 长跑证明',
+    '商务复核和客户效果'
+  ];
+  const actualTitles = dataRows.map(cells => cells[4]);
+  const missingTitles = expectedTitles.filter(title => !actualTitles.includes(title));
+  const rowWidthOk = dataRows.every(cells => cells.length === SOFTWARE_HEADER.length);
+  const summary = {
+    generatedAt: new Date().toISOString(),
+    input: path.relative(ROOT, input).replace(/\\/g, '/'),
+    outputDir: path.relative(ROOT, outputDir).replace(/\\/g, '/'),
+    headerMatches,
+    rowWidthOk,
+    rowCount: dataRows.length,
+    duplicateIdCount: duplicateIds.length,
+    duplicateIds: Array.from(new Set(duplicateIds)),
+    expectedTitles,
+    actualTitles,
+    missingTitles,
+    passed: headerMatches && rowWidthOk && dataRows.length === 5 && duplicateIds.length === 0 && missingTitles.length === 0,
+    guardrails: [
+      '本表单用于安排补证任务,不证明客户效果完成。',
+      '不能把 sample、smoke、模板、provider fallback 或接口 200 当成业务证明。',
+      '表单不得包含 sessionToken、Authorization、模型 token 或 npm token。'
+    ]
+  };
+  return {
+    rows: dataRows,
+    summary,
+    files: {
+      csv: path.join(outputDir, 'tihao-proof-gap-software-form.csv'),
+      markdown: path.join(outputDir, 'tihao-proof-gap-software-form.md'),
+      summary: path.join(outputDir, 'tihao-proof-gap-software-form-summary.json')
+    }
+  };
+}
+
+function renderMarkdown(result) {
+  const lines = [
+    '# 最新提号补证表单(软件端格式)',
+    '',
+    `- 生成时间:${result.summary.generatedAt}`,
+    `- 行数:${result.summary.rowCount}`,
+    `- 表头匹配:${result.summary.headerMatches ? '是' : '否'}`,
+    `- 是否通过:${result.summary.passed ? '是' : '否'}`,
+    '',
+    '| ' + SOFTWARE_HEADER.join(' | ') + ' |',
+    '| ' + SOFTWARE_HEADER.map(() => '---').join(' | ') + ' |',
+    ...result.rows.map(row => '| ' + row.map(escapeMarkdownCell).join(' | ') + ' |'),
+    '',
+    '## 边界',
+    '',
+    ...result.summary.guardrails.map(item => `- ${item}`)
+  ];
+  return lines.join('\n');
+}
+
+function renderCsv(rows) {
+  return rows.map(row => row.map(csvCell).join(',')).join('\n');
+}
+
+function splitMarkdownRow(line) {
+  if (!line || !line.trim().startsWith('|')) return [];
+  return line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map(cell => cell.trim());
+}
+
+function parseArgs(argv) {
+  const args = {};
+  for (let index = 0; index < argv.length; index += 1) {
+    const raw = argv[index];
+    if (!raw.startsWith('--')) continue;
+    const key = raw.slice(2).replace(/-([a-z])/g, (_, char) => char.toUpperCase());
+    const next = argv[index + 1];
+    if (!next || next.startsWith('--')) args[key] = true;
+    else {
+      args[key] = next;
+      index += 1;
+    }
+  }
+  return args;
+}
+
+function csvCell(value) {
+  const text = String(value ?? '');
+  return /[",\r\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
+}
+
+function escapeMarkdownCell(value) {
+  return String(value ?? '').replace(/\|/g, '/').replace(/\r?\n/g, ' ');
+}
+
+function withBom(text) {
+  return `\uFEFF${text}`;
+}
+
+if (require.main === module) main();
+
+module.exports = {
+  SOFTWARE_HEADER,
+  buildProofGapSoftwareForm
+};

+ 243 - 0
claude-code-tihao-sourcing/scripts/export-software-dedup-table.py

@@ -0,0 +1,243 @@
+import csv
+from collections import Counter, defaultdict
+from pathlib import Path
+
+from openpyxl import Workbook
+from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
+
+
+ROOT = Path(__file__).resolve().parents[1]
+OUTPUT_ROOT = ROOT / "outputs" / "overnight-quality-1780682025651"
+SOURCE = OUTPUT_ROOT / "manual-review-sample.csv"
+CSV_OUT = OUTPUT_ROOT / "software-client-list-dedup.csv"
+XLSX_OUT = OUTPUT_ROOT / "software-client-list-dedup.xlsx"
+SUMMARY_OUT = OUTPUT_ROOT / "software-client-list-dedup-summary.md"
+
+HEADERS = [
+    "brief编号",
+    "策略",
+    "排名",
+    "平台",
+    "博主名称",
+    "综合分",
+    "brief匹配分",
+    "参考风格分",
+    "证据加分",
+    "证据风险扣分",
+    "推荐理由",
+    "风险提示",
+    "主页链接",
+    "人工复核标签",
+]
+
+STRATEGY_PRIORITY = {
+    "result-first-broad": 50,
+    "result-first": 40,
+    "video-enhanced": 30,
+    "result-first-risk": 20,
+    "baseline-live": 10,
+}
+
+
+def to_num(row, key):
+    try:
+        return float(str(row.get(key, "")).strip() or 0)
+    except ValueError:
+        return 0.0
+
+
+def norm(text):
+    return " ".join(str(text or "").strip().lower().split())
+
+
+def dedupe_key(row):
+    link = norm(row.get("主页链接"))
+    if link:
+        return (norm(row.get("brief编号")), norm(row.get("平台")), link)
+    return (norm(row.get("brief编号")), norm(row.get("平台")), norm(row.get("博主名称")))
+
+
+def quality_key(row):
+    return (
+        STRATEGY_PRIORITY.get(row.get("策略"), 0),
+        to_num(row, "综合分"),
+        to_num(row, "brief匹配分"),
+        to_num(row, "参考风格分"),
+        to_num(row, "证据加分"),
+        -to_num(row, "证据风险扣分"),
+        -to_num(row, "排名"),
+    )
+
+
+def load_rows():
+    with SOURCE.open("r", encoding="utf-8-sig", newline="") as handle:
+        rows = list(csv.DictReader(handle))
+    for row in rows:
+        for header in HEADERS:
+            row.setdefault(header, "")
+    return rows
+
+
+def dedupe_rows(rows):
+    kept = {}
+    all_by_key = defaultdict(list)
+    for row in rows:
+        key = dedupe_key(row)
+        all_by_key[key].append(row)
+        if key not in kept or quality_key(row) > quality_key(kept[key]):
+            kept[key] = dict(row)
+
+    unique_rows = list(kept.values())
+    unique_rows.sort(
+        key=lambda row: (
+            row.get("brief编号", ""),
+            -to_num(row, "综合分"),
+            -to_num(row, "brief匹配分"),
+            -to_num(row, "参考风格分"),
+            -to_num(row, "证据加分"),
+            to_num(row, "证据风险扣分"),
+            row.get("平台", ""),
+            row.get("博主名称", ""),
+        )
+    )
+
+    rank_by_brief = defaultdict(int)
+    for row in unique_rows:
+        brief = row.get("brief编号", "")
+        rank_by_brief[brief] += 1
+        row["排名"] = str(rank_by_brief[brief])
+        row["人工复核标签"] = ""
+    return unique_rows, all_by_key
+
+
+def write_csv(rows):
+    with CSV_OUT.open("w", encoding="utf-8-sig", newline="") as handle:
+        writer = csv.DictWriter(handle, fieldnames=HEADERS, extrasaction="ignore")
+        writer.writeheader()
+        writer.writerows(rows)
+
+
+def write_xlsx(rows, original_count, duplicate_key_count):
+    wb = Workbook()
+    ws = wb.active
+    ws.title = "去重名单"
+    ws.append(HEADERS)
+    for row in rows:
+        ws.append([row.get(header, "") for header in HEADERS])
+
+    header_fill = PatternFill("solid", fgColor="1F4E78")
+    header_font = Font(color="FFFFFF", bold=True)
+    thin = Side(style="thin", color="D9E2F3")
+    for cell in ws[1]:
+        cell.fill = header_fill
+        cell.font = header_font
+        cell.alignment = Alignment(horizontal="center", vertical="center")
+        cell.border = Border(bottom=thin)
+
+    widths = {
+        "A": 20,
+        "B": 18,
+        "C": 8,
+        "D": 14,
+        "E": 24,
+        "F": 10,
+        "G": 12,
+        "H": 12,
+        "I": 10,
+        "J": 12,
+        "K": 110,
+        "L": 85,
+        "M": 56,
+        "N": 16,
+    }
+    for col, width in widths.items():
+        ws.column_dimensions[col].width = width
+
+    for row in ws.iter_rows(min_row=2):
+        for cell in row:
+            cell.alignment = Alignment(vertical="top", wrap_text=True)
+        for idx in [3, 6, 7, 8, 9, 10]:
+            row[idx - 1].alignment = Alignment(horizontal="center", vertical="top", wrap_text=True)
+
+    ws.freeze_panes = "A2"
+    ws.auto_filter.ref = ws.dimensions
+
+    summary = wb.create_sheet("去重说明")
+    summary_rows = [
+        ["项目", "值"],
+        ["来源文件", str(SOURCE)],
+        ["原始行数", original_count],
+        ["去重后行数", len(rows)],
+        ["删除重复行数", original_count - len(rows)],
+        ["存在重复的博主键数量", duplicate_key_count],
+        ["去重口径", "同一 brief编号 + 平台 + 主页链接;没有主页链接时使用 brief编号 + 平台 + 博主名称"],
+        ["保留规则", "优先 result-first-broad,其次 result-first、video-enhanced、result-first-risk、baseline-live;同优先级下保留综合分/brief匹配/参考风格/证据加分更高且风险扣分更低的一条"],
+        ["排名规则", "去重后按每个 brief 重新从 1 排名"],
+    ]
+    for item in summary_rows:
+        summary.append(item)
+    summary.column_dimensions["A"].width = 22
+    summary.column_dimensions["B"].width = 130
+    for cell in summary[1]:
+        cell.fill = header_fill
+        cell.font = header_font
+    for row in summary.iter_rows():
+        for cell in row:
+            cell.alignment = Alignment(vertical="top", wrap_text=True)
+
+    wb.save(XLSX_OUT)
+
+
+def write_summary(rows, original_count, duplicate_key_count):
+    brief_counts = Counter(row["brief编号"] for row in rows)
+    strategy_counts = Counter(row["策略"] for row in rows)
+    lines = [
+        "# 软件端去重名单导出说明",
+        "",
+        f"- 来源:`{SOURCE}`",
+        f"- 原始行数:{original_count}",
+        f"- 去重后行数:{len(rows)}",
+        f"- 删除重复行数:{original_count - len(rows)}",
+        f"- 存在重复的博主键数量:{duplicate_key_count}",
+        f"- CSV:`{CSV_OUT}`",
+        f"- XLSX:`{XLSX_OUT}`",
+        "",
+        "## 各 brief 行数",
+        "",
+    ]
+    for brief, count in sorted(brief_counts.items()):
+        lines.append(f"- {brief}: {count}")
+    lines.extend(["", "## 保留策略分布", ""])
+    for strategy, count in sorted(strategy_counts.items()):
+        lines.append(f"- {strategy}: {count}")
+    lines.extend(
+        [
+            "",
+            "## 去重与排序口径",
+            "",
+            "- 去重口径:同一 `brief编号 + 平台 + 主页链接` 视为同一博主;没有主页链接时用 `brief编号 + 平台 + 博主名称`。",
+            "- 保留规则:优先发布策略 `result-first-broad`、`result-first`,再保留诊断策略;同优先级下看综合分、brief 匹配、参考风格、证据加分和风险扣分。",
+            "- 排名规则:去重后按每个 brief 重新排序。",
+        ]
+    )
+    SUMMARY_OUT.write_text("\n".join(lines), encoding="utf-8-sig")
+
+
+def main():
+    rows = load_rows()
+    unique_rows, all_by_key = dedupe_rows(rows)
+    duplicate_key_count = sum(1 for values in all_by_key.values() if len(values) > 1)
+    write_csv(unique_rows)
+    write_xlsx(unique_rows, len(rows), duplicate_key_count)
+    write_summary(unique_rows, len(rows), duplicate_key_count)
+    print(f"rows_in={len(rows)}")
+    print(f"rows_out={len(unique_rows)}")
+    print(f"dropped={len(rows) - len(unique_rows)}")
+    print(f"duplicate_keys={duplicate_key_count}")
+    print(f"csv={CSV_OUT}")
+    print(f"xlsx={XLSX_OUT}")
+    print(f"summary={SUMMARY_OUT}")
+
+
+if __name__ == "__main__":
+    main()

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.