autocrew 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,24 +1,41 @@
1
1
  ---
2
2
  name: teardown
3
3
  description: |
4
- 拆解对标内容 — 用户粘贴一段文案,输出结构化拆解报告。分析钩子、HKRR、时钟节奏、评论触发、微操技巧。结果存入 intel 系统供创作参考。
5
- trigger: 当用户说"拆解"、"teardown"、"分析这条"、"对标分析"时触发
4
+ 拆解对标内容 — 支持文本和视频两种模式。文本:用户粘贴文案,输出结构化拆解。
5
+ 视频:用户发视频链接或文件,通过 Omni 全模态分析输出四学科视角深度拆解。
6
+ 结果存入 intel 系统供创作参考。
7
+ trigger: 当用户说"拆解"、"teardown"、"分析这条"、"对标分析"、"拆解这个视频"时触发
6
8
  ---
7
9
 
8
10
  # Competitive Teardown(对标拆解)
9
11
 
10
- > 免费版:分析用户粘贴的文案。Pro 版(待实现):通过 Chrome Relay 抓取视频/账号数据。
12
+ > 支持文本和视频两种模态。文本拆解分析文案技巧;视频拆解通过 MiMo-V2-Omni 全模态
13
+ > 分析画面+文案+节奏+结构,输出传播学/心理学/内容结构/视听语言四个学科维度的深度报告。
11
14
 
12
15
  ## 触发方式
13
16
 
14
- 用户说 "拆解这条内容" / "teardown" / "帮我分析对标" 并粘贴一段文案。
17
+ - 用户说 "拆解这条" / "teardown" / "帮我分析对标" 并粘贴文案 → **文本模式**
18
+ - 用户发视频链接(douyin/xiaohongshu/bilibili/youtube/weixin 等)→ **视频模式**
19
+ - 用户发本地视频文件路径(.mp4/.mov/.avi)→ **视频模式**
15
20
 
16
21
  ## 前置加载
17
22
 
18
23
  - `HAMLETDEER.md` — HKRR 框架、Clock Theory、Micro-Retention Techniques、Bang Moment Types
19
- - `~/.autocrew/creator-profile.json` — 用于对比自己的定位
24
+ - `~/.autocrew/creator-profile.json` — 用于对比自己的定位 + 读取 videoCrawler/omniConfig 配置
20
25
 
21
- ## 拆解流程
26
+ ## Step 0: 输入类型检测
27
+
28
+ 判断用户输入属于哪种模式:
29
+
30
+ | 输入特征 | 判定 | 路径 |
31
+ |----------|------|------|
32
+ | 纯文本,没有 URL 也没有文件路径 | 文本模式 | → Step 1(文本拆解,下方) |
33
+ | URL 包含 douyin/xiaohongshu/bilibili/youtube/weixin 域名 | 视频模式 | → Step V1(视频拆解) |
34
+ | 路径以 .mp4 / .mov / .avi / .mkv / .webm 结尾 | 视频模式 | → Step V1(视频拆解) |
35
+
36
+ ---
37
+
38
+ # 文本拆解(原有流程,不变)
22
39
 
23
40
  ### Step 1: 基本信息提取
24
41
 
@@ -77,68 +94,261 @@ trigger: 当用户说"拆解"、"teardown"、"分析这条"、"对标分析"时
77
94
 
78
95
  列出 2-3 个可以应用到自己内容的具体技巧。
79
96
 
80
- ## 保存拆解结果
97
+ ---
98
+
99
+ # 视频拆解(Omni 全模态分析)
100
+
101
+ ### Step V1: 视频获取
102
+
103
+ **如果输入是本地文件路径:** 直接使用,跳到 Step V3。
104
+
105
+ **如果输入是视频链接:** 读取 `creator-profile.json` → `videoCrawler` 配置:
106
+
107
+ ```
108
+ if videoCrawler.type == "mediacrawl":
109
+ → 用 Bash 执行: {videoCrawler.command} --url {链接}
110
+ → 等待下载完成,获取本地文件路径
111
+
112
+ elif videoCrawler.type == "playwright":
113
+ → 用 Playwright 浏览器打开链接
114
+ → 通过浏览器下载视频到临时目录
115
+ → 获取本地文件路径
116
+
117
+ elif videoCrawler.type == "manual" (或未配置):
118
+ → 提示用户:
119
+ "视频拆解需要先获取视频文件。请:
120
+ 1. 手动下载视频到本地
121
+ 2. 告诉我文件路径(例如 ~/Downloads/video.mp4)"
122
+ → 等待用户提供路径
123
+ ```
124
+
125
+ 如果 `videoCrawler` 未配置,默认走 manual 模式。
126
+
127
+ ### Step V2: 视频元信息提取(可选)
128
+
129
+ 如果视频来自链接,尝试提取:
130
+ - 视频标题
131
+ - 发布账号
132
+ - 点赞/评论/收藏数
133
+ - 发布时间
134
+
135
+ 获取不到不阻塞,标记为"未获取"继续。
136
+
137
+ ### Step V3: Omni API 视频分析
138
+
139
+ **前置检查:** 读取 `creator-profile.json` → `omniConfig`。如果未配置:
140
+ > "视频拆解需要配置 Omni API。请在 creator-profile.json 中添加 omniConfig:
141
+ > ```json
142
+ > "omniConfig": {
143
+ > "baseUrl": "https://api.xiaomimimo.com/v1",
144
+ > "model": "mimo-v2-omni",
145
+ > "apiKey": "your-api-key"
146
+ > }
147
+ > ```
148
+ > 获取 API Key:https://platform.xiaomimimo.com"
149
+
150
+ **限制检查:**
151
+ - 视频时长 ≤ 30 分钟。超过提示用户裁剪关键片段。
152
+ - 文件大小 ≤ 200MB。超过提示压缩或裁剪。
153
+
154
+ **调用方式(OpenAI 兼容):**
155
+
156
+ 使用 Bash 或 SDK 调用 Omni API。视频文件 → base64 编码 → 通过 `image_url` content type
157
+ 以 `data:video/mp4;base64,{encoded}` 格式发送。
158
+
159
+ Prompt 使用下方**视频拆解模板 v2**的完整内容。
160
+
161
+ ### Step V4: 视频拆解模板 v2(Omni Prompt)
162
+
163
+ ⚠️ 以下是发送给 Omni 模型的完整 prompt。**不可删减任何维度,每个子维度必须有具体分析。**
164
+
165
+ ```
166
+ 请对这段视频进行深度拆解分析。你是一位精通传播学、心理学、内容创作和视听语言的分析师。
167
+ 按以下四个学科视角逐一分析,每个子维度都必须给出具体分析,不可跳过或泛泛而谈。
168
+
169
+ ---
170
+
171
+ ## 1. 传播学视角 — 这条内容为什么会被传播?
172
+
173
+ ### 信息不对称设计
174
+ - 创作者掌握了什么观众不知道的信息?这个信息差是如何被制造和维持的?
175
+ - 信息揭示的节奏:一次性全揭示 vs 逐层剥开?哪种策略更有效?
176
+
177
+ ### 社会货币
178
+ - 观众转发这条视频时,他在向朋友表达什么身份信号?
179
+ ("我很懂" / "我有品味" / "我关心你" / "看这个离谱的事")
180
+ - Berger 的 STEPPS 模型命中了哪些?逐一判断:
181
+ Social Currency / Triggers / Emotion / Public / Practical Value / Stories
182
+
183
+ ### 框架效应
184
+ - 这个创作者选择了什么叙事框架来组织信息?
185
+ - 如果换一个框架(比如从"机会"换成"危机",或反过来),效果会怎样?
186
+ - 框架选择背后的受众洞察是什么?
187
+
188
+ ---
189
+
190
+ ## 2. 心理学视角 — 观众的大脑在经历什么?
191
+
192
+ ### 认知负荷管理
193
+ - 复杂概念是如何被拆分成可消化的信息单元的?每个单元有多"重"?
194
+ - 有没有刻意设计的"认知休息区"(笑点、停顿、重复、举例)?在什么位置?
195
+
196
+ ### 情绪弧线
197
+ - 画出这条视频的情绪曲线:开场情绪状态→中间的起伏节点→结尾情绪状态
198
+ - Peak-End Rule:观众会记住哪个高峰时刻?结尾留下了什么情绪?
199
+ - 情绪转折点在视频的什么时间位置?
200
+
201
+ ### 身份投射
202
+ - 观众看完这条视频后,会觉得自己是什么样的人?
203
+ ("我是聪明人" / "我是行动派" / "我不孤单" / "我能做到")
204
+ - 这条视频满足了马斯洛需求层次的哪一层?(归属感/尊重/自我实现)
205
+
206
+ ### 开放循环与蔡格尼克效应
207
+ - 视频中有多少个未关闭的信息缺口?分别在什么时间点打开、什么时间点关闭?
208
+ - 观众是在"满足中离开"还是在"好奇中留下"?
209
+ - 哪个开放循环设计得最巧妙?
210
+
211
+ ---
212
+
213
+ ## 3. 内容结构视角 — 论证架构是否有效?
214
+
215
+ ### 论证结构
216
+ - 这条视频的核心论证链是什么?按这个格式分析:
217
+ 观点 → 证据类型(数据/案例/权威背书/类比) → 反驳预处理 → 行动号召
218
+ - 论证链中最薄弱的环节在哪里?观众可能在哪个环节开始质疑?
219
+
220
+ ### 承诺-兑现分析
221
+ - 前 3 秒(显性或隐性地)承诺了什么?
222
+ - 这个承诺最终兑现了吗?是超额兑现、刚好兑现、还是打折兑现?
223
+ - 如果打折兑现:观众是否会感到被标题党了?
224
+
225
+ ### 信息密度曲线
226
+ - 哪些时间段信息密度高(新概念/新数据密集出现)?
227
+ - 哪些时间段是"呼吸空间"(重复、举例、情绪渲染、视觉留白)?
228
+ - 高密度和低密度的交替节奏是否合理?有没有连续高密度导致"听不进去"的段落?
229
+
230
+ ### 观点光谱定位
231
+ - 这个观点在行业共识光谱上的位置:纯共识 ←────→ 极度反共识
232
+ - 反共识的程度是否恰到好处?(太顺=没传播价值,太反=失去信任)
233
+ - 创作者如何在"反共识"和"可信度"之间取得平衡?
234
+
235
+ ---
236
+
237
+ ## 4. 视听语言视角 — 形式如何服务内容?
238
+
239
+ ### 视觉叙事
240
+ - 画面在传递什么文案/口播没有说的信息?(隐性信息层)
241
+ - 在关键论证节点,为什么选择这个画面?是强化、反差、还是补充论证?
242
+ - 有没有纯靠画面推进叙事(没有口播/文字)的段落?效果如何?
81
243
 
82
- 将拆解报告保存到 intel 系统:
244
+ ### 节奏与注意力(Clock Theory)
245
+ - 按时钟理论标注四个关键节点(12:00/3:00/6:00/9:00)各有什么 bang moment:
246
+ | 时钟位 | 时间点 | 内容 | Bang moment 类型 | 强度 |
247
+ |--------|--------|------|-----------------|------|
248
+ | 12:00 | | | | 强/弱/缺失 |
249
+ | 3:00 | | | | 强/弱/缺失 |
250
+ | 6:00 | | | | 强/弱/缺失 |
251
+ | 9:00 | | | | 强/弱/缺失 |
252
+ - 剪辑切镜节奏是否和论证节奏同步?论点密集时切镜是否加快?
83
253
 
254
+ ### HKRR 诊断
255
+ - 主导维度是哪个(H快乐 / K知识 / R共鸣 / R节奏)?纯粹度如何?
256
+ - 是否存在维度冲突?(想教知识又想搞笑 = 哪个都不到位)
257
+ - 短视频(<60s)是否只选了一个维度?长视频是否有效组合了多个维度?
258
+
259
+ ---
260
+
261
+ ## 5. 可借鉴清单(最重要的输出)
262
+
263
+ ### ✅ 方法论级借鉴(可迁移到任何主题的"公式")
264
+ - 列出 2-3 个这个创作者用的可复用方法
265
+
266
+ ### ⚠️ 风格级借鉴(好但需要适配自身风格)
267
+ - 列出 1-2 个需要调整后才能用的点
268
+
269
+ ### ❌ 不适合借鉴
270
+ - 列出 1 个这个账号做但不适合我们做的,说明为什么
271
+
272
+ ### 💡 创作启发
273
+ - 看完这个视频后产生的新选题想法、新切入角度、或新的表达方式
84
274
  ```
85
- ~/.autocrew/data/pipeline/intel/_teardowns/{slug}-{date}.md
275
+
276
+ ### Step V5: 格式化拆解报告
277
+
278
+ 将 Omni 返回的分析整理为结构化 markdown 报告。
279
+
280
+ ### Step V6: 保存拆解报告
281
+
282
+ **写入 `_teardowns/` 目录:**
283
+
284
+ ```
285
+ ~/.autocrew/data/pipeline/intel/_teardowns/{date}-{slug}.md
86
286
  ```
87
287
 
88
288
  YAML frontmatter:
89
289
  ```yaml
90
290
  ---
91
- title: "拆解: {原内容标题或前20字}"
291
+ title: "拆解: {视频标题或前20字}"
92
292
  type: teardown
93
- platform: "{预估平台}"
94
- hookType: "{钩子类型}"
95
- dominantHKRR: "{最强HKRR维度}"
96
- hookScore: 0
293
+ mode: video
294
+ platform: "{平台}"
295
+ source_url: "{视频链接,如有}"
296
+ source_account: "{账号名,如有}"
297
+ video_duration: "{时长}"
298
+ analysis_model: "mimo-v2-omni"
299
+ dominantHKRR: "{最强维度}"
97
300
  overallScore: 0
98
301
  createdAt: "{ISO timestamp}"
99
- tags: [teardown]
302
+ tags: [teardown, video]
100
303
  ---
101
304
  ```
102
305
 
103
- ## 输出格式
306
+ **写入 intel 库触发 wiki sync:**
104
307
 
308
+ 调用 `autocrew_intel` with `action: "ingest"`:
309
+ ```json
310
+ {
311
+ "action": "ingest",
312
+ "text": "{报告摘要:基础信息 + 四个视角各一句核心结论 + 可借鉴清单}",
313
+ "domain": "content-strategy",
314
+ "tags": ["teardown", "video", "{相关标签}"]
315
+ }
105
316
  ```
106
- ## 🔍 对标拆解报告
107
317
 
108
- **内容摘要:** {前50字}...
109
- **预估平台:** {platform}
110
- **字数:** {count}
318
+ 自动触发 knowledge-sync → wiki 页面更新。
111
319
 
112
- ### 钩子分析
113
- - 类型:{hook type}
114
- - 公式:{title formula or 无匹配}
115
- - 强度:{score}/10
116
- - 评价:{一句话评价}
320
+ ### Step V7: 输出给用户
117
321
 
118
- ### HKRR 评分
119
- | H | K | R | R |
120
- |---|---|---|---|
121
- | {n}/10 | {n}/10 | {n}/10 | {n}/10 |
122
- **最强维度:** {element}
322
+ 展示完整拆解报告,然后:
123
323
 
124
- ### Clock 映射
125
- {table}
324
+ > 拆解报告已保存到 `_teardowns/{filename}`,知识库已同步。
325
+ >
326
+ > 你可以:
327
+ > 1. 基于这个拆解写一篇内容 — 我会自动引用拆解结论
328
+ > 2. 继续拆解另一个视频
329
+ > 3. 对比多个拆解报告,找共性规律
126
330
 
127
- ### 评论触发点
128
- {list}
331
+ ---
129
332
 
130
- ### 微操技巧
131
- {list}
333
+ ## 与 write-script 联动
132
334
 
133
- ### 💡 总结
134
- {一句话总结}
335
+ write-script 在创作时会自动搜索 `pipeline/intel/_teardowns/` 目录,查找与当前主题相关的
336
+ 拆解报告作为参考。视频拆解和文本拆解的报告都在同一目录,统一被检索。无需手动操作。
135
337
 
136
- ### 🎯 可借鉴
137
- {2-3 bullet points}
138
- ```
338
+ ## Error Handling
139
339
 
140
- 向用户确认后保存。
340
+ | 失败场景 | 处理 |
341
+ |---------|------|
342
+ | videoCrawler 未配置 | 降级到 manual 模式,提示用户手动下载 |
343
+ | omniConfig 未配置 | 提示用户配置 API Key,给出具体步骤 |
344
+ | 视频文件过大(>200MB)| 提示压缩或裁剪 |
345
+ | 视频时长过长(>30min)| 提示裁剪关键片段 |
346
+ | 采集器下载失败 | 降级到 manual 模式 |
347
+ | Omni API 调用失败 | 显示错误信息,建议检查 API Key 和网络 |
348
+ | 拆解报告保存失败 | 在聊天中展示完整报告供用户复制 |
141
349
 
142
- ## 与 write-script 联动
350
+ ## Changelog
143
351
 
144
- write-script 在创作时会自动搜索 `pipeline/intel/_teardowns/` 目录,查找与当前主题相关的拆解报告作为参考。无需手动操作。
352
+ - 2026-04-06: v2 — 新增视频拆解路径。Omni 全模态分析,四学科视角模板 v2,
353
+ 可插拔视频采集器,intel ingest + wiki sync 集成。
354
+ - 原版: v1 — 文本拆解。HKRR + Clock Theory + 微操技巧分析。
@@ -47,6 +47,104 @@ For video scripts: apply the **Clock Theory** from HAMLETDEER.md. Map the script
47
47
 
48
48
  Search `~/.autocrew/data/pipeline/intel/_teardowns/` for teardown reports related to this topic. If found, use them as reference for what works in this space.
49
49
 
50
+ 5.5. **⚠️ MANDATORY — Topic-specific research → populate `references/` BEFORE writing:**
51
+
52
+ **Why this exists:** Writing from LLM memory alone produces generic, unverifiable content
53
+ indistinguishable from a random chat response. Every claim, case study, name, and data
54
+ point in a draft must trace back to a real source. This step is non-negotiable.
55
+
56
+ **Step-by-step:**
57
+
58
+ a.0. **Query wiki knowledge base (if wiki exists):**
59
+ 1. Check if `~/.autocrew/data/pipeline/wiki/index.md` exists. If not, skip to step a.
60
+ 2. Read `index.md` and find wiki pages whose title, aliases, or summary
61
+ match the current topic's keywords or angle (fuzzy match, not exact).
62
+ 3. Read matched pages (max 5, prioritize by number of sources — more sources
63
+ = more synthesized = more valuable).
64
+ 4. For each matched wiki page, write it as a reference file into the project's
65
+ `references/` folder:
66
+ - Filename: `wiki-{page-slug}.md`
67
+ - Format: same as other reference files, but with `source: wiki/{page-slug}.md`
68
+ - Set `relevance: 8` (synthesized knowledge is higher value than raw intel)
69
+ 5. Wiki-sourced references count toward the 6-reference minimum and can satisfy
70
+ multiple angle-coverage categories (wiki pages are cross-source syntheses).
71
+ 6. If wiki already provides 4+ solid references, the subsequent intel queries
72
+ (steps b-d) can be lighter — focus on filling angle gaps rather than full research.
73
+
74
+ a. Compute the project slug early: `projectSlug = slugify(topic_title)`.
75
+ References will live at `~/.autocrew/data/pipeline/drafting/{projectSlug}/references/`.
76
+ mkdir it if needed (the save step will reuse the same dir).
77
+
78
+ b. **Query the existing intel library first** (fast, free):
79
+ ```json
80
+ { "action": "list" }
81
+ ```
82
+ via `autocrew_intel`. Scan the returned items for any whose title/summary matches
83
+ this topic's keywords or angle. If 3+ strong matches exist, you may skip step (c).
84
+
85
+ c. **Pull fresh intel targeted at this topic** (only if the library is thin):
86
+ ```json
87
+ { "action": "pull", "keywords": ["<keyword 1>", "<keyword 2>", "<keyword 3>"] }
88
+ ```
89
+ Derive 3-5 keywords from the topic title/angle — specific phrases, not generic.
90
+ Example: topic "vibe-coding 重新定义能力边界" → keywords like
91
+ `["vibe-coding 案例", "非程序员 AI 工具 产品", "Cursor 文科生", "GitHub 趋势 独立开发者"]`.
92
+ Avoid over-broad terms like "AI" alone.
93
+
94
+ d. If `web_search` is available, run 2-4 additional targeted queries to find primary
95
+ sources the intel collectors missed: news articles, product pages, creator interviews,
96
+ concrete numbers. Prefer named examples over anonymous anecdotes.
97
+
98
+ e. **Write each source as a reference file** into the project's `references/` folder.
99
+ Use the `Write` tool. Filename: `{short-slug}.md`. Format (strict):
100
+ ```markdown
101
+ ---
102
+ source: <url or intel ref>
103
+ title: <original title>
104
+ collected: <ISO timestamp>
105
+ relevance: <1-10, how directly this informs the draft>
106
+ ---
107
+
108
+ ## 要点
109
+ - <3-6 bullet points of the most useful facts/quotes/data>
110
+
111
+ ## 可引用
112
+ - <specific sentences, names, numbers you might cite verbatim>
113
+
114
+ ## 角度
115
+ - <what angle of the topic this source unlocks>
116
+ ```
117
+
118
+ f. **Minimum bar to proceed — both quantity AND angle coverage:**
119
+
120
+ **Quantity:** at least **6 reference files** with `relevance ≥ 6`. Fewer than 6
121
+ means you'll run out of fresh material by the middle of the draft and fall back to
122
+ LLM-memory filler.
123
+
124
+ **Angle coverage (all 4 categories must have ≥1 reference):**
125
+ 1. **具体案例/人物** — named individual or company with a traceable story
126
+ (e.g., "腾讯文科生用 Cursor 3 个月做了约饭小程序")
127
+ 2. **数据/数字** — concrete numbers from a report, dashboard, or study
128
+ (not "很多人", but "2000+ 员工在用" / "3000 万投资" / "涨薪 30%")
129
+ 3. **反面/争议观点** — a dissenting view, failure case, or counter-evidence
130
+ (prevents the draft from becoming one-sided cheerleading)
131
+ 4. **趋势/背景** — market context, timeline, or "why now" signal
132
+ (answers why this topic is worth writing about today, not last year)
133
+
134
+ Think of it as 2+2+1+1 = 6 minimum. More is better. Duplicates in the same
135
+ category do NOT count toward the other categories.
136
+
137
+ If you cannot hit BOTH bars (6+ total AND all 4 angles covered), stop and tell
138
+ the user honestly:
139
+ > 我目前找到 N 条相关信息源,覆盖角度:{已覆盖的角度}。缺少:{缺失的角度}。
140
+ > 不够支撑一篇有分量的文案。要不要我再调研一轮、换个切入角度、还是你直接补充几个你知道的案例?
141
+
142
+ Do NOT proceed to writing on thin research. "写出来再说" is the failure mode
143
+ this step exists to prevent.
144
+
145
+ g. Record the reference filenames in working memory — Step 6 will cite them, and
146
+ Step 8 will record them in `meta.yaml` via the save params.
147
+
50
148
  6. **Write the script:**
51
149
 
52
150
  a. **Hook** — pick the ONE strongest type for this topic:
@@ -67,6 +165,10 @@ For video scripts: apply the **Clock Theory** from HAMLETDEER.md. Map the script
67
165
  - Write like you're talking to ONE person sitting across from you, not lecturing to a crowd.
68
166
  - Vary sentence length deliberately: 3-4 short sentences, then one longer one. Then a one-word sentence. "真的。"
69
167
  - Each claim needs: why it's true + concrete example (named, specific, verifiable when possible).
168
+ - **Ground every factual claim in a reference file** from Step 5.5. If you cite a company,
169
+ number, name, or case study, it MUST come from one of the `references/*.md` files you
170
+ collected — not from LLM memory. If a claim has no reference backing, either find one
171
+ now or cut the claim. "我编的例子" is a bug, not a feature.
70
172
  - Total body: 800-1500 characters (text) or platform-specific limit.
71
173
 
72
174
  **Anti-patterns (NEVER do these):**
@@ -138,6 +240,12 @@ For video scripts: apply the **Clock Theory** from HAMLETDEER.md. Map the script
138
240
  7. **Self-review before saving** (fix any failure, don't just check):
139
241
  - [ ] 800+ characters total?
140
242
  - [ ] Contains at least 2 concrete examples or scenarios (not vague claims)?
243
+ - [ ] **Every named entity/number/case in the draft traces back to a file in `references/`?**
244
+ - [ ] **Reference angle coverage used in the draft** — the draft actually cites at least
245
+ one reference from EACH of the 4 angle categories collected in Step 5.5.f
246
+ (具体案例/人物, 数据/数字, 反面/争议观点, 趋势/背景). Collecting 6 references but
247
+ only drawing from the "案例" bucket produces a single-sided draft — fix by
248
+ weaving in at least one sentence grounded in each missing bucket.
141
249
  - [ ] Has a non-obvious insight or twist?
142
250
  - [ ] Tone matches STYLE.md profile (if available)?
143
251
  - [ ] No generic greetings, no essay-style paragraphs?
@@ -196,7 +304,52 @@ For video scripts: apply the **Clock Theory** from HAMLETDEER.md. Map the script
196
304
  Then:
197
305
  > 要修改的话直接说,或者确认后我帮你标记为待发布。
198
306
 
199
- 11. **If adaptation is needed:**
307
+ **⚠️ IMPORTANT Remember the `content_id` from the save result.** You will need it
308
+ for any subsequent revision in this conversation.
309
+
310
+ 11. **Handling revisions (MANDATORY — 不可只在聊天框里改):**
311
+
312
+ When the user requests modifications to a saved draft ("改一改", "换个角度", "这里调整一下",
313
+ "第二段重写", etc.), you MUST persist the revised content back to the pipeline. Never show
314
+ a rewritten version in chat only — the draft file must stay in sync.
315
+
316
+ Steps for every revision:
317
+
318
+ a. Rewrite the full body (or title) incorporating the user's feedback. Re-check against
319
+ the Step 7 self-review list — a revision is still a draft, same standards apply.
320
+
321
+ b. Call `autocrew_content` with `action: "update"`:
322
+ ```json
323
+ {
324
+ "action": "update",
325
+ "id": "<content-id from the original save>",
326
+ "title": "<updated title if changed, else original>",
327
+ "body": "<full revised body — not a diff, the complete new text>",
328
+ "diff_note": "<one-line summary of what changed and why, e.g. '换成悬念式开头,加 vibe-coding 具体案例'>"
329
+ }
330
+ ```
331
+ The storage layer will archive the previous `draft.md` into an immutable
332
+ `draft-v{N}.md` snapshot, then replace `draft.md` with your new body, and append
333
+ the snapshot entry to `meta.yaml` → `versions`. After this:
334
+ - `draft.md` always holds the newest revision
335
+ - `draft-v1.md`, `draft-v2.md`, ... are frozen historical states (never modified)
336
+
337
+ b.5. **If the user's revision changes the angle or adds new claims** (new companies,
338
+ new data, new cases), go back to Step 5.5 and add more reference files BEFORE
339
+ rewriting. New claims still need backing. Don't smuggle new unverified material in
340
+ under the label "minor tweak".
341
+
342
+ c. Confirm to the user that the new version is saved, and show the updated content.
343
+ Example:
344
+ > 已更新到 v2(`~/.autocrew/data/pipeline/drafting/{project}/draft.md`)。改动:{diff_note}
345
+
346
+ d. If the revision is substantial (new angle, new hook, new structure), re-run
347
+ `full_review` from Step 9 on the updated content before confirming.
348
+
349
+ **Never do:** paste the revised content in chat without calling `update`. If you catch
350
+ yourself about to show "这是修改后的版本..." without a tool call, stop and save first.
351
+
352
+ 12. **If adaptation is needed:**
200
353
  - Do not just trim one draft for another platform.
201
354
  - Use `platform-rewrite` / `autocrew_rewrite` to create the first platform-native version.
202
355
 
@@ -112,12 +112,13 @@ describe("Pipeline Integration — full flow", () => {
112
112
 
113
113
  const files = await fs.readdir(projectDir);
114
114
  expect(files).toContain("meta.yaml");
115
- expect(files).toContain("draft-v1.md");
116
115
  expect(files).toContain("draft.md");
116
+ // No draft-v*.md exists on initial create — only after revisions replace draft.md
117
+ expect(files.filter((f) => f.startsWith("draft-v"))).toHaveLength(0);
117
118
 
118
119
  const projectName = slugify("高分选题");
119
120
 
120
- // 5. Add draft version → meta.yaml updated with v2
121
+ // 5. Add draft version → original content archived to draft-v1.md, draft.md updated
121
122
  await addDraftVersion(
122
123
  projectName,
123
124
  "# 高分选题\n\n第二版内容",
@@ -127,9 +128,10 @@ describe("Pipeline Integration — full flow", () => {
127
128
 
128
129
  let meta = await getProjectMeta(projectName, testDir);
129
130
  expect(meta).not.toBeNull();
130
- expect(meta!.versions.length).toBe(2);
131
- expect(meta!.current).toBe("draft-v2.md");
132
- expect(meta!.versions[1].note).toBe("improved draft");
131
+ expect(meta!.versions.length).toBe(1);
132
+ expect(meta!.versions[0].file).toBe("draft-v1.md");
133
+ expect(meta!.current).toBe("draft.md");
134
+ expect(meta!.versions[0].note).toBe("improved draft");
133
135
 
134
136
  // 6. Advance: drafting → production → published
135
137
  await advanceProject(projectName, testDir);
@@ -183,8 +185,8 @@ describe("Pipeline Integration — trash and restore", () => {
183
185
  // Verify state is preserved
184
186
  const meta = await getProjectMeta(projectName, testDir);
185
187
  expect(meta).not.toBeNull();
186
- expect(meta!.versions.length).toBe(2);
187
- expect(meta!.current).toBe("draft-v2.md");
188
+ expect(meta!.versions.length).toBe(1);
189
+ expect(meta!.current).toBe("draft.md");
188
190
  expect(meta!.title).toBe("回收还原测试");
189
191
 
190
192
  // History should show: drafting → trash → drafting
@@ -142,6 +142,57 @@ describe("addCompetitor", () => {
142
142
  });
143
143
  });
144
144
 
145
+ describe("videoCrawler and omniConfig", () => {
146
+ it("saves and loads videoCrawler and omniConfig", async () => {
147
+ const profile = await initProfile(testDir);
148
+ const full: CreatorProfile = {
149
+ ...profile,
150
+ videoCrawler: { type: "mediacrawl", command: "python3 /opt/mediacrawl/main.py" },
151
+ omniConfig: {
152
+ baseUrl: "https://api.xiaomimimo.com/v1",
153
+ model: "mimo-v2-omni",
154
+ apiKey: "sk-test-key-123",
155
+ },
156
+ };
157
+ await saveProfile(full, testDir);
158
+ const loaded = await loadProfile(testDir);
159
+ expect(loaded).not.toBeNull();
160
+ expect(loaded!.videoCrawler).toEqual({ type: "mediacrawl", command: "python3 /opt/mediacrawl/main.py" });
161
+ expect(loaded!.omniConfig).toEqual({
162
+ baseUrl: "https://api.xiaomimimo.com/v1",
163
+ model: "mimo-v2-omni",
164
+ apiKey: "sk-test-key-123",
165
+ });
166
+ });
167
+
168
+ it("loads profile without video config (backward compatible)", async () => {
169
+ // Write a minimal JSON file without the new fields
170
+ const filePath = path.join(testDir, "creator-profile.json");
171
+ const legacy = {
172
+ industry: "科技",
173
+ platforms: ["xhs"],
174
+ audiencePersona: null,
175
+ creatorPersona: null,
176
+ writingRules: [],
177
+ styleBoundaries: { never: [], always: [] },
178
+ competitorAccounts: [],
179
+ performanceHistory: [],
180
+ expressionPersona: "",
181
+ secondaryPersonas: [],
182
+ styleCalibrated: false,
183
+ createdAt: "2025-01-01T00:00:00.000Z",
184
+ updatedAt: "2025-01-01T00:00:00.000Z",
185
+ };
186
+ await fs.writeFile(filePath, JSON.stringify(legacy, null, 2), "utf-8");
187
+
188
+ const loaded = await loadProfile(testDir);
189
+ expect(loaded).not.toBeNull();
190
+ expect(loaded!.industry).toBe("科技");
191
+ expect(loaded!.videoCrawler).toBeUndefined();
192
+ expect(loaded!.omniConfig).toBeUndefined();
193
+ });
194
+ });
195
+
145
196
  describe("detectMissingInfo", () => {
146
197
  it("reports all missing fields on empty profile", async () => {
147
198
  const profile = await initProfile(testDir);