diff --git a/.gitignore b/.gitignore index 48b285d..56d7206 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ Icon? # Claude Code per-user config (含本地路径 / 个人 permission grants) # 整个 .claude/ 目录在源码 repo 里都是本地状态——hooks/settings 的项目级配置走用户自己的 content project 不走这里 .claude/ +.codex/ +.gemini/ +.playwright-mcp/ # Python (whisper adapter / score-curve.py 跑过留下的) __pycache__/ @@ -25,12 +28,16 @@ venv/ .cheat-secrets.json predictions/ videos/ +articles/ +xhs/ scripts/ samples/ candidates.md rubric_notes.md +rubric-memo.md script_patterns.md benchmark.md +audience.md STATUS.md WORKFLOW.md content.db diff --git a/SKILL.md b/SKILL.md index 47a7693..90aabed 100644 --- a/SKILL.md +++ b/SKILL.md @@ -11,9 +11,10 @@ allowed-tools: Bash(*), Read, Write, Edit, Grep, Glob, Skill, mcp__llm-chat__cha > > **方法论**(5 阶段闭环):任何能被量化的内容形态都适用——视频 / 文章 / 播客 / Newsletter / 短文 thread。 > -> **当前内置 rubric**:观点类视频(评论 / 时评 / 论说 / 议题讨论 / 个人观点表达),7 个维度由参考博主 25+ 已发样本拟合而来。如果你做其他形态,需要: -> - 自己写一份 rubric(参照 [starter-rubrics/opinion-video-zero.md](starter-rubrics/opinion-video-zero.md) 的格式) -> - 或等内置扩展(长文 / 短文 / 播客 starter 在批次 3 路线图) +> **当前内置 rubric**:观点类视频(评论 / 时评 / 论说 / 议题讨论 / 个人观点表达),7 个维度由参考博主 25+ 已发样本拟合而来;另提供小红书图文 starter,供 `content_form=short-text` 且平台为 xhs 的用户起步。 +> 如果你做其他形态,需要: +> - 自己写一份 rubric(参照 [starter-rubrics/opinion-video-zero.md](starter-rubrics/opinion-video-zero.md) 或 [starter-rubrics/xhs-post-zero.md](starter-rubrics/xhs-post-zero.md) 的格式) +> - 或等内置扩展(长文 / 播客 starter 在后续路线图) > > 默认假设:**用户是从零开始的新人**(一条视频都没发过)——cold-start 期的预测会**简化**,只要 7 维打分 + 一句话 bet,不强求 bucket 数字(避免 false precision)。已有 5+ 篇数据的老手走 calibration 模式解锁完整 7 组件预测。 @@ -55,7 +56,7 @@ Codex 没有 Claude Code 的 slash-command harness。安装到 Codex 后,按 | "找选题" / "我不知道拍什么" / "seed" / "找前 5 个选题" | `/cheat-seed` | 已 init(cold-start 用户专用一次性种子动作) | | "打分这篇 [path]" / "score this [path]" | `/cheat-score` | rubric_notes.md 存在 | | "启动预测" / "start prediction" / "给这稿子打分并预测" | `/cheat-predict` | 已 init + 有最终稿 | -| "拍了 X" / "shot it" / "录完了" | `/cheat-shoot` | 对应预测已写(buffer +1) | +| "拍了 X" / "shot it" / "录完了" | `/cheat-shoot` | `content_form=opinion-video` + 对应预测已写(buffer +1) | | "已发布" / "I shipped it" / "发布链接是 X" | `/cheat-publish` | 对应预测文件存在(buffer -1) | | "复盘" / "retro this" / "T+3d 数据来了" | `/cheat-retro` | 对应预测文件存在 + 已过 RETRO_WINDOW_DAYS | | "构造受众画像" / "更新 persona" / "我的观众是谁" / "build persona" | `/cheat-persona` | 已 init;有复盘评论数据(或 benchmark seed) | @@ -66,6 +67,7 @@ Codex 没有 Claude Code 的 slash-command harness。安装到 Codex 后,按 | "迁移" / "升级 state" / "schema 版本不对" / "migrate" | `/cheat-migrate` | 已 init;用户 git pull 拉了新版后;SessionStart hook 提示 schema mismatch 后 | > 拍 vs 发分两个动作:buffer 警戒系统需要明确知道"拍了但没发"vs"已发"两种状态。详见 [shared-references/cadence-protocol.md](shared-references/cadence-protocol.md)。 +> `cheat-shoot` 仅适用于视频。公众号、小红书等非视频内容直接走 `/cheat-publish`,不要先做拍摄登记。 **Mode detection**(首次接到非 init 触发词时执行): 1. 检查用户当前目录是否有 `.cheat-state.json` → 没有 → 强制路由到 `/cheat-init` @@ -98,6 +100,7 @@ skill 期望用户的项目布局如下。`/cheat-init` 会创建缺失项;** ``` / ├── rubric_notes.md # 评分规则的真实来源 +├── rubric-memo.md # bump / retro 观察证据,blind 硬禁读 ├── WORKFLOW.md # 5 阶段流程文档(cheat-init 创建) ├── STATUS.md # 看板(cheat-status 维护) ├── .cheat-state.json # 状态文件,子 skill 共享上下文 @@ -116,6 +119,15 @@ skill 期望用户的项目布局如下。`/cheat-init` 会创建缺失项;** │ └── YYYY-MM-DD__/ │ ├── script.md # 用户提供的最终拍摄稿(cheat-shoot 时询问"和 scripts/ 一致吗") │ └── report.md # T+3d 抓的数据 + 评论(cheat-retro 写) +├── articles/ # 文章产物(非视频内容可选) +│ └── YYYY-MM-DD__/ +│ ├── draft.md +│ └── report.md +├── xhs/ # 小红书图文产物(可选) +│ └── YYYY-MM-DD__/ +│ ├── post.md +│ ├── images/ +│ └── report.md ├── samples/ # 对标账号视频 / 转录(cheat-learn-from 创建) │ └── <账号名>//{source.mp4 (可选), transcript.md, meta.md} ├── candidates.md # 选题池(可选) @@ -163,6 +175,8 @@ cheat-on-content/ ├── starter-rubrics/ # 各内容形态的先验 rubric │ ├── opinion-video.md # ✅ 观点视频(中文,已校准 25+ 样本) │ ├── opinion-video-zero.md # ✅ v0 等权占位(cold-start) +│ ├── xhs-post.md # ✅ 小红书图文 starter(收藏/标题/首图驱动) +│ ├── xhs-post-zero.md # ✅ 小红书 cold-start 等权占位 │ ├── long-form-essay.md # ⬜ 公众号 / Substack │ └── short-form-text.md # ⬜ X thread / 微博长文 ├── templates/ # skill 写进用户 repo 的文件骨架 @@ -174,6 +188,7 @@ cheat-on-content/ │ ├── script_patterns.template.md # ✅ 写作 pattern 沉淀(含 benchmark 借鉴段说明) │ ├── benchmark.template.md # ✅ 对标账号 reference │ ├── audience.template.md # ✅ 受众画像骨架 +│ ├── xhs-post.template.md # ✅ 小红书标题 / 正文 / 排版骨架 │ ├── workflow.template.md # ✅ │ ├── status.template.md # ✅ │ └── content.db.schema.sql # ✅ @@ -186,6 +201,9 @@ cheat-on-content/ │ └── log-event.sh # ✅ meta-logging 脚本 ├── tools/ # 独立 CLI 脚本 │ ├── score-curve.py # ⬜ 预测精度收敛曲线 +│ ├── diff_pct.py # ✅ cheat-shoot 改稿幅度计算 +│ ├── diff_pct_test.sh # ✅ diff_pct smoke test +│ ├── verify-package.sh # ✅ 包结构 / 语法 / 安装列表一键验证 │ ├── md-to-sqlite.py # ⬜ markdown → content.db 升级(批次 3) │ └── validate-bump.py # ⬜ 校准池全量重打(批次 3) ├── adapters/ # 数据源适配 diff --git a/skills/cheat-init/SKILL.md b/skills/cheat-init/SKILL.md index 575d3b2..a3741d3 100644 --- a/skills/cheat-init/SKILL.md +++ b/skills/cheat-init/SKILL.md @@ -111,6 +111,15 @@ allowed-tools: Bash(*), Read, Write, Edit, Glob, WebFetch, Skill - 选 b/c/d/e/f/g → `true`,cheat-status 持续提示"你的形态可能需要 bump 调权重" - **不再有"严重不匹配"档**——所有形态都能跑工作流,只是有的 rubric 需要更激进的 bump +**Q1.2: 短文平台**(仅 Q1=c 时问) + +> "你的短文主要在哪个平台发? +> a) **小红书** — 推荐 xhs 专用 starter rubric +> b) **微博 / X** — 即 thread +> c) **其他 / 不确定" + +记录到 `short_text_platform`(`xhs` / `weibo-x` / `other`)。Q1 不是 c 时写 `null`。 + **Q1.5: 典型时长**(仅 Q1=a/d/f 时问) > "你的视频典型时长? @@ -275,6 +284,7 @@ c) 不找 → state 标 `benchmark_status: none`,用通用 v0 起步 "skill_version": "1.0.0", "rubric_version": "v0", "content_form": "<查 Q1 映射表,写 enum 字符串如 \"opinion-video\">", + "short_text_platform": , "typical_duration_seconds": , "target_publish_cadence_days": , "rubric_form_mismatch": , @@ -319,7 +329,10 @@ c) 不找 → state 标 `benchmark_status: none`,用通用 v0 起步 只能含通用语言(公式 / 维度定义 / bucket 边界),不能含真实视频名 / 实绩。 每次 bump 升级时的 Memo(含证据数据 + 派生证据)写到 rubric-memo.md(下一步创建)。" ``` - - 复制 `cheat-on-content/starter-rubrics/
-zero.md`(cold-start)或 `.md`(已有数据时仍可参考) + - starter 选择规则: + - `content_form=opinion-video` → 复制 `starter-rubrics/opinion-video-zero.md`(cold-start)或参考 `opinion-video.md` + - `content_form=short-text` 且(`enabled_perf_adapters` 含 `xhs-explore` 或 `short_text_platform=xhs`)→ 复制 `starter-rubrics/xhs-post-zero.md`(cold-start)或参考 `xhs-post.md` + - 其他形态暂没有内置 starter → 询问用户是否先复制 `opinion-video-zero.md` 作为占位并在顶部标 `rubric_form_mismatch: true`,或让用户提供自定义 rubric 2.5. **`rubric-memo.md`**(**新**——配合 cheat-score-blind 隔离协议) ``` @@ -495,10 +508,11 @@ cheat-learn-from 完成后回到 init 的 Phase 5。 | 字段 | 写入时机 | 来源 | |---|---|---| -| `schema_version` | Phase 3 | 硬编码 "1.1" | +| `schema_version` | Phase 3 | 硬编码 "1.4" | | `skill_version` | Phase 3 | 硬编码 "1.0.0" | | `rubric_version` | Phase 3 | "v0" | | `content_form` | Phase 3 | Q1 → 查映射表换 enum 值(**不是字母**) | +| `short_text_platform` | Phase 3 | Q1.2 派生(Q1≠c 时 null) | | `typical_duration_seconds` | Phase 3 | Q1.5 派生 | | `target_publish_cadence_days` | Phase 3 | Q1.6 派生 | | `rubric_form_mismatch` | Phase 3 | Q1≠a → true | diff --git a/skills/cheat-publish/SKILL.md b/skills/cheat-publish/SKILL.md index 56090a7..1144ec7 100644 --- a/skills/cheat-publish/SKILL.md +++ b/skills/cheat-publish/SKILL.md @@ -35,6 +35,8 @@ allowed-tools: Bash(*), Read, Edit, Glob | `` 或 URL | 用户参数;缺失则用 `.cheat-state.json` 的 `in_progress_session.file` | | `.cheat-state.json` | 用户项目根 | +内容形态判断:读取 `.cheat-state.json.content_form`。`opinion-video` 走视频 folder + buffer;`long-essay` / `short-text` / `other` 等非视频形态不要求 `/cheat-shoot`。 + ## Workflow ### Phase 0: 找到对应的预测文件 @@ -89,11 +91,16 @@ allowed-tools: Bash(*), Read, Edit, Glob 如果用户给的是分享短链(无法立刻 resolve)→ 标 `Aweme ID: pending`,下次 `/cheat-retro` 时由 adapter 解析。 -**video folder 处理**:到 cheat-publish 这一步,对应的 `videos//` 目录**应该已经由 cheat-shoot 创建**(含 script.md)。 +**artifact folder 处理**: -- 如 video folder 不存在 → 警告"你跳过了 cheat-shoot?建议先跑 cheat-shoot 把拍摄稿登记进 video folder 再发",**询问用户是否跳过登记直接发**: - - 是 → 自动建一个 video folder(fallback),但不询问稿子一致性,标 `ad_hoc_publish: true` - - 否 → 让用户先跑 cheat-shoot 再回来 publish +- `content_form=opinion-video`:对应的 `videos//` 目录**应该已经由 cheat-shoot 创建**(含 script.md)。 + - 如 video folder 不存在 → 警告"你跳过了 cheat-shoot?建议先跑 cheat-shoot 把拍摄稿登记进 video folder 再发",**询问用户是否跳过登记直接发**: + - 是 → 自动建一个 video folder(fallback),但不询问稿子一致性,标 `ad_hoc_publish: true` + - 否 → 让用户先跑 cheat-shoot 再回来 publish +- 非视频:**不要要求 cheat-shoot**。 + - `long-essay` → 如 `articles//` 不存在,直接创建;建议把终稿放到 `draft.md`。 + - `short-text` 且平台为 xhs → 如 `xhs//` 不存在,直接创建;建议把终稿放到 `post.md`,图片放到 `images/`。 + - 其他形态 → 不强制 artifact folder;有需要时用 `//` 或让用户确认。 用 Edit 工具(不是 Write 重写整个文件)。 @@ -106,7 +113,8 @@ allowed-tools: Bash(*), Read, Edit, Glob "in_progress_session": null, "last_published_at": "", "last_published_file": "predictions/", - "last_published_video_folder": "videos/<...>/", + "last_published_video_folder": "videos/<...>/ 或 null", + "last_published_artifact_folder": "videos|articles|xhs/<...>/", "last_published_platform_id": "", "pending_retros": [ "predictions/" @@ -117,10 +125,11 @@ allowed-tools: Bash(*), Read, Edit, Glob } ``` -**`shoots` 队列处理**(buffer 跟踪关键): -1. 读 state.shoots[] +**`shoots` 队列处理**(仅视频,buffer 跟踪关键): +1. `content_form=opinion-video` 时读 state.shoots[] 2. 找 `video_folder == 本次发布的 video_folder` 的项 → 移除 3. 如果没找到 → 警告"buffer 队列里没有这条视频。是直接发布没经过 /cheat-shoot 吗?"——不阻塞,但提示用户下次走 /cheat-shoot 让 buffer 跟踪准确 +4. 非视频内容不读写 shoots,也不输出 buffer 警告 `last_published_platform_id` 是 cheat-retro 调 adapter 时的输入——如 douyin-session 需要 aweme_id 直接抓数据。 @@ -134,7 +143,7 @@ allowed-tools: Bash(*), Read, Edit, Glob - Platform: douyin - URL: https://v.douyin.com/abc123 -📦 Buffer:N 篇(颜色 + 含义) +📦 Buffer:N 篇(颜色 + 含义;仅视频显示) 按你的 cadence(X)= N×X 天 buffer [如颜色变了,提示"现在该去拍/暂停拍"] @@ -146,7 +155,7 @@ allowed-tools: Bash(*), Read, Edit, Glob 到时间说:"复盘 predictions/2026-05-04_..." ``` -Buffer 颜色由 [shared-references/cadence-protocol.md](../../shared-references/cadence-protocol.md) 派生。如本次发布让 buffer 跌入红色(断更风险)→ 高亮警告"今天必须再拍 ≥1 条"。 +Buffer 颜色由 [shared-references/cadence-protocol.md](../../shared-references/cadence-protocol.md) 派生,仅视频形态显示。如本次发布让 buffer 跌入红色(断更风险)→ 高亮警告"今天必须再拍 ≥1 条"。非视频内容输出"已加入待复盘",不显示拍摄 buffer。 ## Key Rules diff --git a/skills/cheat-seed/SKILL.md b/skills/cheat-seed/SKILL.md index cf75c58..5362b79 100644 --- a/skills/cheat-seed/SKILL.md +++ b/skills/cheat-seed/SKILL.md @@ -58,14 +58,15 @@ Batch Mode — 用户显式要批量(`/cheat-seed --batch 5`): - **MODE_B_MAX_REPROBE_TURNS = 2** — Mode B "为什么" 反问最多 2 轮;超过则转 Mode C - **MAX_DEEP_DIVE_TURNS = 4** — Mode A 收敛阶段最多 4 轮反问,避免 AI 过度盘问 - **WITH_DRAFT = yes** — 默认确认角度后立刻写 draft;用户可说 "等下,我自己写" 跳过 -- **DRAFT_LENGTH** — 派生自 `state.typical_duration_seconds`:30s→100-200字 / 90s→250-500字 / 240s→600-1000字 / 450s→1100-2000字 / 900s→2200+字 +- **DRAFT_LENGTH** — 视频稿派生自 `state.typical_duration_seconds`:30s→100-200字 / 90s→250-500字 / 240s→600-1000字 / 450s→1100-2000字 / 900s→2200+字 +- **XHS_DRAFT_LENGTH = 600-900 字** — 当 `content_form=short-text` 且用户明确要小红书 / state 启用了 `xhs-explore` 时使用 - **HUMANIZE_DRAFT = on**(默认)/ off —— 写完 draft 后用 `humanizer` skill 过一遍,去掉 AI 写作 tells(em-dash 滥用 / rule of three / inflated 词汇 / 空泛归因等)。off 时直接出原始 AI draft。**只 humanize 正文,不动 header 的"必须改写"警告** ## Inputs | 必填 | 来源 | |---|---| -| `.cheat-state.json` | 读 calibration_samples / typical_duration / cadence | +| `.cheat-state.json` | 读 calibration_samples / typical_duration / cadence / content_form / enabled_perf_adapters | | `rubric_notes.md` | 读当前 rubric(粗打分用) | | `script_patterns.md` | 读已有 pattern(写 draft 时按 cheat sheet 选结构) | | `predictions/*.md`(如有) | 已发历史,brainstorm 时作为 context | @@ -279,7 +280,23 @@ Mode A 默认深挖用户经历。但如果**用户讲的本身是时事话题** **写 draft 前必读** `script_patterns.md` —— 按"结构选型 cheat sheet"对应用户的 topic 选合适结构。如果文件还在抽象骨架阶段(用户没填几个 pattern),就用 starter rubric 对应的通用框架。 -**字数**:按 `DRAFT_LENGTH` 派生(基于 `typical_duration_seconds`)。 +**字数与格式分流**: +- `content_form=opinion-video` 或未明确非视频 → 按 `DRAFT_LENGTH` 派生(基于 `typical_duration_seconds`)。 +- `content_form=short-text` 且用户明确要小红书 / `enabled_perf_adapters` 含 `xhs-explore` → 走下面的小红书图文规则。 + +#### 小红书图文规则 + +当当前稿件是小红书图文时,不走下面的视频段落稿规则,改用小红书图文规则: + +1. 先读 `starter-rubrics/xhs-post.md` 的“小红书特有注意事项”。 +2. 写 3 个候选标题,每个 ≤20 个中文字符,并标注策略:数字 / 痛点 / 悬念 / 直接价值。 +3. 正文目标 600-900 字,前 2 行必须直接给痛点、反直觉、结果或具体场景。 +4. 结构必须从 5 类里选一种:清单体 / 教程体 / 故事体 / 对比体 / 测评体。 +5. 手机排版:每段 2-4 句,段间空行,用 emoji 做段落锚点;不要用 markdown H2/H3 标题层级。 +6. 结尾 1-2 句给明确动作:收藏理由 / 评论问题 / 下次想看的方向。 +7. 写入 `scripts/__.md` 时使用 `templates/xhs-post.template.md` 的结构。 + +完成后提示用户下一步是 `/cheat-predict scripts/<...>.md`,发布后直接走 `/cheat-publish`;小红书图文不需要 `/cheat-shoot`。 #### ⚠️ 正文必须是段落版,不是字幕格式(**最常见的生成跑偏**) diff --git a/skills/cheat-shoot/SKILL.md b/skills/cheat-shoot/SKILL.md index 1a51efb..ca8c87d 100644 --- a/skills/cheat-shoot/SKILL.md +++ b/skills/cheat-shoot/SKILL.md @@ -20,6 +20,18 @@ cheat-shoot 自己**不**写预测内容——所有预测落盘逻辑在 cheat- - "实际拍摄稿" ≠ "pre-shoot 草稿"是常态。这一步是把 diff 显式化、触发 v2 重判、采集"用户改稿 pattern"信号的入口 - v2 预测 vs v1 预测的差异本身就是 rubric 升级证据——比如 v1 给 ER=4,v2 给 ER=5(用户改稿改高了 hook 强度),就告诉 rubric "这个用户的 ER 阈值跟我现在公式不一致" +## 格式守卫 + +cheat-shoot 仅适用于视频内容。 + +1. 读 `.cheat-state.json` 的 `content_form` +2. 如果用户指定了 `--format`,用用户指定的格式判断 +3. 如果格式不是 `opinion-video` / `video` → **拒绝**,提示: + > "cheat-shoot 仅用于视频格式。文章、小红书图文、播客等非视频内容不需要拍摄登记。 + > 如果你要登记发布,请用 '已发布 [URL]'。 + > 如果你要复盘数据,请发布后到窗口期再说 '复盘 [prediction]'。" +4. 老用户 state 缺 `content_form` 时,按 legacy video 项目处理,不阻塞。 + ## Overview ``` diff --git a/starter-rubrics/xhs-post-zero.md b/starter-rubrics/xhs-post-zero.md new file mode 100644 index 0000000..96ed8d6 --- /dev/null +++ b/starter-rubrics/xhs-post-zero.md @@ -0,0 +1,82 @@ +# Starter Rubric:小红书图文 — v0 cold-start 占位 + +**这是给完全没数据的新小红书账号用的占位 rubric。**前 5 篇预测精度大概 ±50%。 + +--- + +## v0 综合分公式(等权占位) + +``` +composite = (SC + ER + TA + IQ + ID + IG) / 6 × 2.0 +``` + +每个维度 0-5 整数分。综合分范围 0-10。 + +**为什么等权**:你没有数据支持任何特定权重。等权让你在数据出来之前对每个维度都给"中立的关注"。 + +--- + +## 6 个维度 + +(与 xhs-post.md 相同的维度定义,详见该文件) + +### SC — 收藏价值 +### ER — 情感共鸣 +### TA — 标题吸引力 +### IQ — 首图质量 +### ID — 信息密度 +### IG — 互动引导 + +--- + +## 小红书写作硬约束(cold-start 也必须遵守) + +- 标题 ≤20 个中文字符;写稿时给 3 个候选标题,至少覆盖数字 / 痛点 / 悬念 / 直接价值中的 2 类。 +- 正文目标 600-900 字。超过 900 字先删铺垫、重复解释和泛泛总结。 +- 前 2 行必须抓注意力:直接给痛点、反直觉、结果或具体场景,不要寒暄。 +- 结构优先选一种:清单体 / 教程体 / 故事体 / 对比体 / 测评体。 +- 手机排版:每段 2-4 句,段间空行,emoji 做段落锚点,不用 markdown 标题。 +- 结尾给自然互动:收藏理由、评论问题或“下一篇想看什么”。 + +--- + +## Cold-start 战略 + +前 5 篇你不是在做决策,你是在收集数据。 + +### 取样策略:主动选差异最大的帖子 + +- 1 篇 SC 主导(收藏价值拉满,干货攻略型) +- 1 篇 ER 主导(情感共鸣极强) +- 1 篇 TA 主导(标题党,内容一般) +- 1 篇 IQ 主导(首图惊艳) +- 1 篇综合中等 + +### 何时开始相信预测 + +| 校准样本 | 你能相信什么 | +|---|---| +| N=0-2 | 啥都别信 | +| N=3-5 | 相信方向,不信数字 | +| N=5-10 | 相信 bucket 排序 | +| N=10-20 | 中枢可信 ±30% | +| N≥20 | rubric 真正成为"作弊器" | + +--- + +## Cold-start 期的复盘纪律 + +前 5 篇每篇必走完整循环。 + +每次复盘必须填的最少信息: +- 实际曝光数 +- 实际点赞数 +- 实际收藏数 +- 实际评论数 +- 我的 v0 预测 vs 实际:哪个维度被验证 / 推翻 + +--- + +## 最重要的一句话 + +**前 5 篇你不是在做决策,你是在收集数据。** diff --git a/starter-rubrics/xhs-post.md b/starter-rubrics/xhs-post.md new file mode 100644 index 0000000..b5d6fcf --- /dev/null +++ b/starter-rubrics/xhs-post.md @@ -0,0 +1,177 @@ +# Starter Rubric:小红书图文 — v0 未校准 + +适用于小红书图文帖子(正文建议 600-900 字 + 多图),标题 ≤20 个中文字符。 + +> **重要:这份是 v0 未校准版本** +> 小红书的核心传播逻辑是**收藏 > 点赞 > 评论**。收藏率是算法推荐的第一权重信号——一篇被大量收藏的帖子会被持续推流。本 rubric 的维度选择围绕"值得收藏"展开。 +> +> 跑完 5 篇有 T+3d 复盘数据后,通过 `/cheat-bump` 走校准流程。 + +--- + +## v0 综合分公式(**未校准**) + +``` +composite = (SC×1.5 + ER×1.3 + TA×1.3 + IQ + ID + IG) / 7.1 × 2.0 +``` + +每个维度 0-5 整数分。综合分范围 0-10。 + +权重设定理由: +- **SC×1.5**(收藏价值):小红书的算法核心指标是收藏率。被收藏 = 被持续推流。这是小红书和抖音最大的区别——抖音看完播率,小红书看收藏率。 +- **ER×1.3**(情感共鸣):读者必须在 3 秒内产生"这说的就是我"的感觉,否则直接划走。 +- **TA×1.3**(标题吸引力):小红书是双列信息流,标题 + 封面图决定 80% 的点击率。 +- IQ / ID / IG 各 ×1.0:内容质量维度,重要但不是传播主驱动力。 + +> ⚠️ 这些权重是**直觉设定**,不是数据拟合。你的真实权重需要校准后才知道。 + +--- + +## 6 个维度 + +### SC — Save & Collect Value(收藏价值,权重 ×1.5) +*读者会不会收藏这篇,以后还会翻出来看?* + +- **0** — 一次性阅读,看完即忘,没有任何值得保存的内容 +- **3** — 有一定信息量,但和小红书上其他同类帖子差不多 +- **5** — 独特的攻略、方法论、清单、或稀缺信息,读者觉得"这个必须收藏" + +5/5 anchor:实用攻略(如"XX城市3天2晚全攻略")、方法论清单(如"5个让你显瘦的穿搭技巧")、稀缺资源整合(如"XX行业最全入门指南")。 +0 分对照:纯情绪抒发、日常碎碎念、没有新信息的转述。 + +**为什么×1.5**:小红书的算法逻辑是——收藏率高的帖子被推到更大的流量池,更大的流量池带来更多收藏,形成正循环。一篇收藏率 10% 的帖子和收藏率 2% 的帖子,最终曝光量可能差 10 倍。**收藏是小红书的货币**。 + +**关键区分**:SC 不等于"信息量大"。一篇信息量很大的帖子如果排版混乱、找不到重点,读者也不会收藏。SC 是"信息量 × 可检索性"——读者收藏后能快速找到想要的内容。 + +### ER — Emotional Resonance(情感共鸣,权重 ×1.3) +*读者在前 3 秒能否产生"这说的就是我"的感觉?* + +- **0** — 纯信息传递,没有任何情感连接 +- **3** — 有共鸣点但不够锐利 +- **5** — 精准命中一个具体的、能命名的情感或处境,读者想"被说中了" + +5/5 anchor:写容貌焦虑、职场PUA、恋爱中的自我怀疑、消费主义陷阱——任何让读者认出自己同时微微感到不适的内容。 +0 分对照:纯产品评测、景点打卡、无观点的信息罗列。 + +**为什么×1.3**:小红书用户刷到你的帖子只有 1-2 秒决定是否停留。ER 是留人的第一道关——标题和首图必须在 3 秒内触发情感共鸣,否则直接划走。 + +### TA — Title Appeal(标题吸引力,权重 ×1.3) +*标题(≤20 个中文字符)的钩子力如何?* + +- **0** — 无表情,纯陈述("分享一下我的经验") +- **3** — 有表情但标题平庸("穿搭分享") +- **5** — 表情 + 标题组合让人非点不可,精准命中痛点或制造好奇缺口 + +小红书标题的核心公式:**具体数字 / 痛点 / 反直觉 / 情绪词**,emoji 可用但不能挤占信息密度。 +- ❌ "穿搭分享" → 太泛 +- ✅ "微胖女生别再穿错了!这3件显瘦10斤" → 具体 + 痛点 + 数字 +- ❌ "护肤心得" → 没有钩子 +- ✅ "花了2万块护肤才知道:这一步比精华重要100倍" → 反直觉 + 具体 + +写作时必须给 3 个候选标题,并标注策略:数字型 / 痛点型 / 悬念型 / 直接价值型。最终发布标题必须 ≤20 个中文字符。 + +### IQ — Image Quality(首图质量,权重 ×1.0) +*第一张图决定点击率。是否有视觉冲击力?* + +- **0** — 随手拍/截图,没有构图意识 +- **3** — 有设计感但不够突出 +- **5** — 在双列信息流中一眼就能抓住注意力,让人想点进去 + +小红书是视觉优先平台。首图的作用等同于视频的前 3 秒——决定生死。 + +### ID — Information Density(信息密度,权重 ×1.0) +*短篇幅内塞了多少有用信息?* + +- **0** — 水文,废话多,读完觉得浪费时间 +- **3** — 有干货但不够紧凑,有些段落可以删 +- **5** — 每句话都有信息量,无废字,读者觉得"浓缩的都是精华" + +小红书用户期望的是**可执行的干货**,不是长篇大论。600-900 字是安全区:足够展开,又给 emoji、多图说明和平台计数字符留余量。900 字内每句都有用,比 1500 字的水文更有可能被收藏。 + +### IG — Interaction Guide(互动引导,权重 ×1.0) +*是否有引导收藏/点赞/评论的钩子?* + +- **0** — 无互动引导,文章自然结束 +- **3** — 结尾有"点赞收藏"但很生硬 +- **5** — 自然的互动引导,读者觉得"确实该收藏" + +**关键**:引导收藏 > 引导点赞 > 引导评论。小红书的算法权重是收藏 > 点赞 > 评论。最好的引导是让读者自然地想收藏,而不是说"记得收藏哦"。 + +比如: +- ❌ "觉得有用就点赞收藏吧!" → 太直白 +- ✅ "建议先收藏,下次穿搭前翻出来看。" → 给了收藏的理由 +- ✅ "你最想试哪一个?评论区告诉我。" → 给了评论的理由 + +--- + +## Bucket 预测 + +小红书的指标是"曝光 → 点赞/收藏/评论"。小红书的分发是双列信息流 + 推荐池机制。 + +### 第 1 篇:平台通用默认 + +| Bucket | 曝光范围 | 含义 | 先验概率 | +|---|---|---|---| +| 底部 | < 200 | 被算法埋了 | 30% | +| 基础盘 | 200 - 1,000 | 自然流量 | 40% | +| 命中 | 1,000 - 5,000 | 进入推荐池 | 20% | +| 小爆 | 5,000 - 50,000 | 被大量推荐 | 8% | +| 大爆 | > 50,000 | 现象级 | 2% | + +### 第 2 篇起:比率桶 + +| Bucket | 倍数范围 | 含义 | +|---|---|---| +| 退步 | < 0.3 × baseline | 明显差 | +| 持平 | 0.3 - 1 × baseline | 同档 | +| 命中 | 1 - 3 × baseline | 中度突破 | +| 小爆 | 3 - 10 × baseline | 显著破圈 | +| 大爆 | > 10 × baseline | 量级跃迁 | + +--- + +## 已知局限性 + +1. **没有"首图"独立维度**:IQ 评估首图质量,但小红书的封面设计(拼图、文字叠加、滤镜风格)对点击率的影响可能被低估。校准时可能需要拆分为 IQ(视觉冲击力)和 CD(封面设计)两个维度。 +2. **SC 和 ID 高度相关**:信息密度高的帖子天然更值得收藏。校准时需要验证两者是否有足够区分度。 +3. **没有"账号权重"维度**:小红书对新账号有流量扶持期,同一内容在新号和老号的曝光可能差 3-5 倍。本 rubric 不捕捉这个变量。 +4. **没有"发布时间"维度**:小红书的流量有明显的时间波峰(晚 8-11 点)。本 rubric 不区分发布时间。 +5. **ER 和 TA 部分重叠**:标题的钩子力(TA)很多时候就是靠情感共鸣(ER)实现的。打分时注意区分:TA 是"标题本身的吸引力",ER 是"内容是否触动了情感"。 + +--- + +## 打分速度 cheat sheet + +- **每个维度 30 秒**。超过这个时间你在合理化,不是在打分 +- **SC 和 ER 先打**。这两个是主驱动力,先定调再看其他维度 +- **TA 看标题就够了**。标题是否让人想点,3 秒判断 +- **IQ 看首图就够了**。首图是否在信息流中突出,3 秒判断 + +--- + +## 小红书特有注意事项 + +- **标题限制**:≤20 个中文字符;不是越短越好,而是要在短标题里保留数字、痛点、反直觉或情绪词 +- **正文长度**:目标 600-900 字;超过 900 字必须删重复解释、铺垫和泛泛结论 +- **前 2 行 hook**:折叠前可见区域必须说清“为什么现在要看”,不要用寒暄、背景介绍或“今天给大家分享” +- **更受喜欢的结构**:优先从 5 类里选一种,不要混成散文流水账 + - 清单体:适合资源、工具、避坑、步骤总结 + - 教程体:适合“手把手教你”“3 步完成” + - 故事体:适合个人经历、踩坑复盘、情绪共鸣 + - 对比体:适合 A vs B、前后变化、误区纠正 + - 测评体:适合产品、方法、工具、路线实测 +- **手机排版**:短段落,每段 2-4 句;段落之间空行;用 emoji 做段落锚点,但不要每句都堆 emoji +- **首图**:决定 80% 的点击率,必须有视觉冲击力 +- **表情分段**:小红书用户习惯用 emoji 分段,不是 markdown 标题 +- **互动**:收藏 > 点赞 > 评论,引导收藏比引导点赞更重要 +- **结尾互动**:最后 1-2 句给明确动作:收藏理由 / 评论问题 / 下次想看的方向 +- **排版**:短段落 + emoji 前缀,不要用 markdown 标题层级 +- **封面设计**:拼图式封面(多图拼接 + 文字标注)比单图封面点击率高 + +--- + +## 与 `cheat-on-content` 子 skill 的对应 + +- `/cheat-init` → 默认复制本文件到 `rubric_notes.md`(如选择 xhs 格式) +- `/cheat-score` 和 `/cheat-predict` → 读 `rubric_notes.md` 拿当前公式 +- `/cheat-bump` → 这份 starter 是 bump 的"先验起点",校准后被替换为用户自己拟合的版本 diff --git a/templates/xhs-post.template.md b/templates/xhs-post.template.md new file mode 100644 index 0000000..57ddcf9 --- /dev/null +++ b/templates/xhs-post.template.md @@ -0,0 +1,44 @@ +# [小红书标题候选] + +> **格式**: 小红书图文 +> **选题 ID**: [DATE]_[ID]_[SHORT] +> **状态**: draft | final +> **标题限制**: 最终标题 ≤20 个中文字符 +> **正文字数**: 600-900 字 +> **结构选型**: 清单体 | 教程体 | 故事体 | 对比体 | 测评体 + +--- + +## 标题候选 + +1. [标题 A] — [数字 / 痛点 / 悬念 / 直接价值] +2. [标题 B] — [数字 / 痛点 / 悬念 / 直接价值] +3. [标题 C] — [数字 / 痛点 / 悬念 / 直接价值] + +**推荐标题**: [≤20 字] + +--- + +[正文 600-900 字] + +[前 2 行必须抓注意力:痛点 / 反直觉 / 具体结果 / 具体场景] + +📌 [段落 1:2-4 句,信息密度高] + +✨ [段落 2:2-4 句,展开方法 / 经验 / 对比] + +🧩 [段落 3:2-4 句,补充清单 / 步骤 / 关键注意] + +💬 [结尾 1-2 句:给收藏理由或评论问题] + +--- + +## 发布前自检 + +- [ ] 推荐标题 ≤20 个中文字符 +- [ ] 正文 600-900 字 +- [ ] 前 2 行有 hook,不是寒暄 +- [ ] 结构属于清单体 / 教程体 / 故事体 / 对比体 / 测评体之一 +- [ ] 每段 2-4 句,段间空行 +- [ ] emoji 是段落锚点,不是噪音 +- [ ] 结尾有自然互动或收藏理由 diff --git a/tools/verify-package.sh b/tools/verify-package.sh new file mode 100755 index 0000000..123a88b --- /dev/null +++ b/tools/verify-package.sh @@ -0,0 +1,198 @@ +#!/usr/bin/env bash +# +# One-shot package verification for cheat-on-content maintainers. +# It is intentionally local-only: no network calls and no user project writes. + +set -uo pipefail + +ROOT="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )/.." &> /dev/null && pwd )" +FAILURES=0 + +fail() { + echo "❌ $*" + FAILURES=$((FAILURES + 1)) +} + +pass() { + echo "✓ $*" +} + +check_cmd() { + local label="$1" + shift + if "$@" >/tmp/cheat-verify.$$.out 2>/tmp/cheat-verify.$$.err; then + pass "$label" + else + fail "$label" + sed 's/^/ /' /tmp/cheat-verify.$$.err + fi + rm -f /tmp/cheat-verify.$$.out /tmp/cheat-verify.$$.err +} + +echo "== cheat-on-content package verification ==" +echo "root: $ROOT" +echo + +echo "== install list ==" +skills=() +while IFS= read -r skill_name; do + skills+=("$skill_name") +done < <( + awk ' + /^SUB_SKILLS=\(/ { inside=1; next } + inside && /^\)/ { exit } + inside { + sub(/#.*/, "") + gsub(/[[:space:]]/, "") + if (length($0) > 0) print $0 + } + ' "$ROOT/install.sh" +) + +if [[ "${#skills[@]}" -eq 15 ]]; then + pass "install.sh declares 15 sub-skills" +else + fail "install.sh declares ${#skills[@]} sub-skills, expected 15" +fi + +for skill in "${skills[@]}"; do + if [[ -f "$ROOT/skills/$skill/SKILL.md" ]]; then + pass "skill exists: $skill" + else + fail "missing skill: skills/$skill/SKILL.md" + fi +done + +echo +echo "== package structure ==" +if find "$ROOT/skills" -type l | grep -q .; then + fail "skills/ contains symlinks:" + find "$ROOT/skills" -type l -print | sed 's/^/ /' +else + pass "skills/ contains no symlinks" +fi + +GIT_TOP=$(git -C "$ROOT" rev-parse --show-toplevel 2>/dev/null || true) +if [[ -n "$GIT_TOP" && "$ROOT" == "$GIT_TOP"* ]]; then + rel_root="${ROOT#"$GIT_TOP"/}" + if [[ "$rel_root" == "$ROOT" ]]; then + rel_root="." + fi + gitlinks=$(git -C "$GIT_TOP" ls-files --stage -- "$rel_root/skills" 2>/dev/null | awk '$1 == "160000" { print $4 }') + active_gitlinks="" + if [[ -n "$gitlinks" ]]; then + while IFS= read -r gitlink_path; do + [[ -z "$gitlink_path" ]] && continue + # During a conversion, the index may still remember a deleted gitlink until + # the maintainer stages the delete/add. Treat deleted gitlinks as pending + # conversion, but fail if the gitlink still exists in the working tree. + if [[ -e "$GIT_TOP/$gitlink_path" || -L "$GIT_TOP/$gitlink_path" ]]; then + active_gitlinks="${active_gitlinks}${gitlink_path}"$'\n' + fi + done <<< "$gitlinks" + fi + if [[ -n "$active_gitlinks" ]]; then + fail "skills/ contains active gitlinks:" + echo "$active_gitlinks" | sed '/^$/d; s/^/ /' + else + pass "skills/ contains no active gitlinks" + fi + + if [[ "$ROOT" != "$GIT_TOP" ]]; then + flat_found=0 + for dirname in adapters skills hooks migrations shared-references templates tools; do + if [[ -e "$GIT_TOP/$dirname" ]]; then + echo " $GIT_TOP/$dirname" + flat_found=1 + fi + done + if [[ "$flat_found" -eq 0 ]]; then + pass "no upstream-flattened package dirs at repository root" + else + fail "found package dirs at repository root; keep them under cheat-on-content/" + fi + fi +fi + +echo +echo "== shell syntax ==" +while IFS= read -r sh_file; do + check_cmd "bash -n ${sh_file#$ROOT/}" bash -n "$sh_file" +done < <( + find "$ROOT" \ + \( -path "$ROOT/install.sh" -o -path "$ROOT/uninstall.sh" -o -path "$ROOT/hooks/*.sh" -o -path "$ROOT/adapters/perf-data/*/run.sh" -o -path "$ROOT/adapters/script-extraction/*/run.sh" -o -path "$ROOT/tools/*.sh" \) \ + -type f | sort +) + +echo +echo "== python syntax ==" +while IFS= read -r py_file; do + check_cmd "py_compile ${py_file#$ROOT/}" python3 -m py_compile "$py_file" +done < <( + find "$ROOT/tools" "$ROOT/adapters/perf-data" "$ROOT/skills" -name '*.py' -type f | sort +) + +echo +echo "== focused tests ==" +if [[ -x "$ROOT/tools/diff_pct_test.sh" || -f "$ROOT/tools/diff_pct_test.sh" ]]; then + check_cmd "diff_pct_test.sh" bash "$ROOT/tools/diff_pct_test.sh" +else + fail "missing tools/diff_pct_test.sh" +fi + +echo +echo "== xhs writing rules ==" +python3 - "$ROOT" <<'PY' +import sys +from pathlib import Path + +root = Path(sys.argv[1]) +files = { + "starter-rubrics/xhs-post.md": root / "starter-rubrics" / "xhs-post.md", + "starter-rubrics/xhs-post-zero.md": root / "starter-rubrics" / "xhs-post-zero.md", + "skills/cheat-seed/SKILL.md": root / "skills" / "cheat-seed" / "SKILL.md", + "templates/xhs-post.template.md": root / "templates" / "xhs-post.template.md", +} +combined = "\n".join(path.read_text() for path in files.values()) +required = [ + "≤20", + "600-900", + "前 2 行", + "清单体", + "教程体", + "故事体", + "对比体", + "测评体", + "每段 2-4 句", +] +missing = [item for item in required if item not in combined] +missing_files = [name for name, path in files.items() if not path.exists()] +if missing_files or missing: + if missing_files: + print("missing files:", ", ".join(missing_files), file=sys.stderr) + if missing: + print("missing rules:", ", ".join(missing), file=sys.stderr) + raise SystemExit(1) +PY +if [[ "$?" -eq 0 ]]; then + pass "xhs title/body/layout rules present" +else + fail "xhs title/body/layout rules missing" +fi + +echo +echo "== install help ==" +if bash "$ROOT/install.sh" --help | grep -q "15 sub-skills"; then + pass "install.sh --help reports 15 sub-skills" +else + fail "install.sh --help does not report 15 sub-skills" +fi + +echo +if [[ "$FAILURES" -eq 0 ]]; then + echo "✅ verify-package passed" + exit 0 +else + echo "❌ verify-package failed: $FAILURES issue(s)" + exit 1 +fi