autocrew 0.2.0 → 0.3.1

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.
@@ -8,6 +8,60 @@ description: |
8
8
 
9
9
  > Executor skill. Single responsibility: generate one complete original script and save it.
10
10
 
11
+ ## THE OPERATING SYSTEM — Read This Before Anything Else
12
+
13
+ These 5 principles override every other instruction in this file. When a specific rule
14
+ conflicts with a principle, the principle wins. When you're unsure what to do, return
15
+ to these principles. They are not suggestions — they are the mental model you run on
16
+ while writing.
17
+
18
+ ```
19
+ 1. EMPATHY FIRST
20
+ You are not writing. You are sitting across from ONE person — the audiencePersona —
21
+ having a conversation. They are scrolling on their phone, half-distracted, ready to
22
+ swipe away in 2 seconds. Everything you write must earn the next 3 seconds of their
23
+ attention. Before committing any sentence, simulate their reaction: do they feel
24
+ curiosity, recognition, surprise, or relief? If a sentence triggers none of these —
25
+ if the reader's inner voice says "so what?" or "I know this already" — delete it.
26
+ The reader's emotional state is the only metric that matters mid-writing.
27
+
28
+ 2. THEIR WORDS, NOT YOURS
29
+ Every word must pass one test: would the reader say this to a friend over coffee?
30
+ If no, replace it. You are matching THEIR vocabulary, not showcasing yours. The
31
+ reader never thinks in abstractions — they think in scenes, faces, and feelings.
32
+ When you catch yourself reaching for a "smart" word, stop. The smarter move is the
33
+ simpler word. A concept the reader can't picture is a concept that doesn't exist.
34
+ This applies to every language: Chinese, English, technical terms, metaphors. If
35
+ the audiencePersona wouldn't use it, you can't use it.
36
+
37
+ 3. SHOW THE MOVIE
38
+ Abstractions are invisible. Stories are visible. "AI improves productivity" is
39
+ invisible — the reader's brain generates no image. "She built an entire product
40
+ in 3 weeks, alone, without writing a single line of code" — the reader sees a
41
+ person, a timeline, a result. Every claim in your draft needs a SCENE: a face,
42
+ a number, a moment in time. If you can't attach a scene to a claim, the claim
43
+ is too abstract to include. Concreteness is not decoration — it IS the content.
44
+
45
+ 4. TENSION IS OXYGEN
46
+ Content without tension is content nobody finishes. The reader stays because they
47
+ need to know what happens next. Every paragraph must either OPEN a question or
48
+ CLOSE one. If a paragraph does neither — if it's just "information sitting there"
49
+ — the reader's thumb is already moving to the next post. Tension can be explicit
50
+ ("But why don't big companies do this?") or structural (a gap between what the
51
+ reader assumed and what you're about to reveal). When you feel the energy dropping,
52
+ you've lost tension. Inject a question, a contradiction, or a surprise immediately.
53
+
54
+ 5. THE CREATOR IS THE PROOF
55
+ The most persuasive evidence is not data or expert quotes — it's the creator saying
56
+ "I did this, and here's what happened." The creator's lived experience is
57
+ irreplaceable social proof. Third-party cases support the argument; the creator's
58
+ own story IS the argument. Always lead with the creator's experience when available.
59
+ Vulnerability beats authority: "I failed at this three times before it worked" is
60
+ more compelling than "Studies show a 30% improvement." If the creator has relevant
61
+ experience for this topic, it must appear in the draft — not as a footnote, but as
62
+ the backbone.
63
+ ```
64
+
11
65
  ## Prerequisites
12
66
 
13
67
  Before writing, load these reference documents:
@@ -25,6 +79,28 @@ For video scripts: apply the **Clock Theory** from HAMLETDEER.md. Map the script
25
79
  c. Read `~/.autocrew/creator-profile.json` — check `styleCalibrated`, `platforms`, `writingRules`, `contentPillars`.
26
80
  d. If none exist, proceed with sensible defaults and note that style calibration is recommended.
27
81
 
82
+ 1.5. **⚠️ 提取创作者自己的故事和经历(MANDATORY):**
83
+
84
+ 创作者自己的亲身经历是最高级别的真实性素材 — 比任何第三方案例都有说服力。
85
+
86
+ a. 从 MEMORY.md、creator-profile.json、对话上下文中,提取与当前主题相关的
87
+ **创作者本人的经历、数据、案例**。例如:
88
+ - 创作者自己做过什么项目?结果如何?
89
+ - 创作者踩过什么坑?学到了什么?
90
+ - 创作者有什么独特视角是别人没有的?
91
+
92
+ b. 如果对话中创作者提到了自己的经历("我做了两个独立站"、"我花了 3 个月…"),
93
+ 这些是**必须使用的一级素材**,不可以被第三方案例替代。
94
+
95
+ c. 素材优先级(高→低):
96
+ 1. 创作者自己的亲身经历(最高说服力,最强共鸣)
97
+ 2. 创作者认识的真人案例(有名有姓,可追溯)
98
+ 3. references/ 里的公开案例和数据
99
+ 4. LLM 知识库中的案例(最低优先级,尽量不用)
100
+
101
+ d. 在 Phase A 骨架中标注哪些素材来自创作者本人。
102
+ 如果骨架里没有一条创作者自己的素材 → 重新审视,一定有可以用的。
103
+
28
104
  2. **Content positioning check:**
29
105
 
30
106
  a. Identify which **content pillar** this content belongs to (from `creator-profile.json` → `contentPillars`).
@@ -145,120 +221,245 @@ For video scripts: apply the **Clock Theory** from HAMLETDEER.md. Map the script
145
221
  g. Record the reference filenames in working memory — Step 6 will cite them, and
146
222
  Step 8 will record them in `meta.yaml` via the save params.
147
223
 
148
- 6. **Write the script:**
149
-
150
- a. **Hook** — pick the ONE strongest type for this topic:
151
-
152
- | Type | When to use | Example pattern |
153
- |------|-------------|-----------------|
154
- | Pain point | Audience has an obvious unresolved frustration | "XX最大的问题不是YY,而是ZZ" |
155
- | Suspense | Topic has a counterintuitive truth or surprising data | "我花了X万测试,结果发现…" |
156
- | Ideal state | Topic sells a desirable outcome | "X个月后,我再也不用YY了" |
157
- | Emotional resonance | Topic touches identity, belonging, or aspiration | "每个做XX的人都经历过这一刻" |
158
- | Contrast | Clear gap between common belief and reality | "别人XX,你却在YY" |
159
-
160
- Write 1-3 sentences. NEVER open with "哈喽大家好", "你有没有想过", or any generic greeting.
161
-
162
- b. **Body** — the core content. NOT a list of points. A conversation.
163
-
164
- **Writing rules (non-negotiable):**
165
- - Write like you're talking to ONE person sitting across from you, not lecturing to a crowd.
166
- - Vary sentence length deliberately: 3-4 short sentences, then one longer one. Then a one-word sentence. "真的。"
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.
172
- - Total body: 800-1500 characters (text) or platform-specific limit.
173
-
174
- **Anti-patterns (NEVER do these):**
175
- - ❌ "总而言之" / "综上所述" / "值得一提的是" — essay transitions that kill conversational tone
176
- - ❌ "首先…其次…最后…" — numbered structure that screams AI/lecture
177
- - ❌ Balanced "一方面…另一方面…" hedging — pick a side
178
- - ❌ Lists of exactly 5 items — AI signature pattern
179
- - ❌ Every paragraph same length — real people write unevenly
180
- - ❌ Generic examples ("某个企业", "一家公司") — name it or don't use it
181
-
182
- **Must-include elements:**
183
- - 1-2 expectation-breaking twists (contrarian flip, data bomb)
184
- - 1-2 interaction hooks (questions, "你猜怎么着", comment prompts)
185
- - HKRR annotation: for each section, note which HKRR element is active (in your planning, not in output)
186
-
187
- **Micro-retention techniques (use at least 2):**
188
- - **Open Loop**: raise a question early, resolve it later — "后面告诉你为什么"
189
- - **Curiosity Gap**: end paragraphs with forward momentum — "但这还不是最离谱的"
190
- - **Visual Anchor**: standalone one-liner between paragraphs — a quotable insight
191
- - **Rhythm Break**: sudden short sentence after buildup — "错。" / "但这不重要。"
192
-
193
- c. **Comment triggers (MANDATORY — annotate 1-2):**
194
-
195
- After writing the body, identify and annotate comment trigger points:
196
-
197
- | Type | What it does | Example |
198
- |------|-------------|---------|
199
- | Controversy plant | Leave a debatable opinion | "我知道很多人不同意,但我觉得XX根本没用" |
200
- | Unanswered question | Raise but don't fully answer | "至于为什么大厂不这么做?评论区聊" |
201
- | Quote hook | One sentence worth screenshotting | "AI不是来替你干活的,是来替你做决定的" |
202
-
203
- Record as: `commentTriggers: [{ type: "controversy", position: "paragraph 3" }]`
204
-
205
- d. **CTA** — 1-2 sentences guiding a specific action (save/comment/follow).
206
- Must connect to the content's value "收藏这条,下次用得上" beats "觉得有用就点赞".
207
-
208
- e. **Titlegenerate 3-5 candidates using title formulas:**
209
-
210
- | Formula | Pattern | Example |
211
- |---------|---------|---------|
212
- | Number + Result | 数字+具体结果 | "用了3个月AI,我把团队从12人砍到3人" |
213
- | Contrarian | 反直觉声明 | "AI写的代码比人快10倍,但我劝你别用" |
214
- | Identity + Pain | 身份标签+痛点 | "传统老板看过来:这3种AI项目100%是坑" |
215
- | Curiosity Gap | 悬念缺口 | "花了20万做AI系统,结果…" |
216
- | Contrast | 对比结构 | "别人用AI赚钱,你用AI亏钱,差在哪?" |
217
- | Resonance Question | 共鸣提问 | "为什么你学了那么多AI课,还是不会用?" |
218
-
219
- Rules:
220
- - Generate 3-5 candidates, annotate which formula each uses.
221
- - Also call `generateForPlatform(baseTopic, platform)` from title-hashtag.ts for additional variants.
222
- - Pick the best as primary. 15-25 characters. Can include emoji if it adds value.
223
- - If `web_search` is available, search 2-3 trending keywords and embed 1 naturally.
224
-
225
- f. **Hashtags** — generate platform-specific hashtags:
226
- - Call `generateHashtags(topic, platform, tags)` from `title-hashtag.ts`.
227
- - Append hashtags to the body (for platforms that use inline hashtags like XHS/Douyin).
228
- - Save hashtags separately in the `hashtags` field.
229
-
230
- g. **Clock mapping (video content):**
231
- Before finalizing, map the script to clock positions:
232
-
233
- | Clock | Content section | Bang moment type | HKRR element |
234
- |-------|----------------|-----------------|-------------|
235
- | 12:00 | [your hook] | [e.g., pattern break] | [e.g., Resonance] |
236
- | 3:00 | [paragraph X] | [e.g., data bomb] | [e.g., Knowledge] |
237
- | 6:00 | [paragraph Y] | [e.g., framework reveal] | [e.g., Knowledge] |
238
- | 9:00 | [CTA section] | [e.g., audience mirror] | [e.g., Resonance] |
239
-
240
- 7. **Self-review before saving** (fix any failure, don't just check):
241
- - [ ] 800+ characters total?
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.
249
- - [ ] Has a non-obvious insight or twist?
250
- - [ ] Tone matches STYLE.md profile (if available)?
251
- - [ ] No generic greetings, no essay-style paragraphs?
252
- - [ ] No anti-pattern violations (总而言之, 首先其次最后, balanced hedging)?
253
- - [ ] Body is plain text with blank-line separators (no markdown headers)?
254
- - [ ] Title within platform character limit? Uses a title formula?
255
- - [ ] At least 1 comment trigger annotated?
256
- - [ ] At least 2 micro-retention techniques used?
257
- - [ ] Hypothesis stated?
258
- - [ ] Content pillar specified (if pillars configured)?
259
- - [ ] Hashtags generated and relevant?
260
-
261
- 8. **Save via tool:**
224
+ 6. **⚠️ 创作分两个阶段执行。不可跳过 Phase A 直接写正文。**
225
+
226
+ **为什么分两阶段**:一口气从头写到尾会产出逻辑混乱、节奏平淡的流水账。
227
+ 先搭骨架(结构)再填肉(正文),确保每一段都有存在的理由。
228
+
229
+ ---
230
+
231
+ ## Phase A 搭结构骨架(先想清楚再动笔)
232
+
233
+ 在写任何正文之前,必须先完成以下 5 个决策。输出为结构化的骨架文档,
234
+ 展示给用户确认后才进入 Phase B。
235
+
236
+ **A1. 核心观点(一句话)**
237
+
238
+ 用一句话说清楚这篇内容的核心主张。不是主题,是观点。
239
+
240
+ | 不是这样(主题) | ✅ 而是这样(观点) |
241
+ |---|---|
242
+ | "聊聊 vibe-coding" | "vibe-coding 重新定义的不是会不会写代码,是会不会想清楚问题" |
243
+ | "AI 工具推荐" | "90% 的人用 AI 工具亏钱,因为他们把 AI 当员工而不是当合伙人" |
244
+ | "创业经验分享" | "创业最大的坑不是没钱,是你以为自己想清楚了其实没有" |
245
+
246
+ 这个观点必须满足:
247
+ - 有立场(不是两边讨好的"一方面...另一方面..."
248
+ - 有反直觉成分(读者看到会想"真的吗?")
249
+ - 能用 references 里的具体案例支撑
250
+
251
+ **A2. 论证结构(观点的骨架)**
252
+
253
+ 确定论证链条:核心观点要通过什么逻辑路径让读者信服?
254
+
255
+ ```
256
+ 核心观点: {一句话}
257
+
258
+ ├── 误区/共识切入: 大多数人以为___(读者的当前认知)
259
+
260
+ ├── 真相揭示: 但实际上___(反转,用数据或案例支撑)
261
+ │ └── 证据: {来自 references/ 的具体案例/数据}
262
+
263
+ ├── 深入解释: 为什么会这样?因为___(给出底层逻辑)
264
+ │ └── 证据: {来自 references/ 的第二个案例}
265
+
266
+ └── 行动指引: 所以你应该___(给读者一个可执行的 takeaway)
267
+ ```
268
+
269
+ 论证结构不是唯一形式。以下几种都可以,选最适合这个观点的:
270
+ - **误区→真相→行动**(适合反直觉观点)
271
+ - **故事→提炼→框架**(适合经验分享)
272
+ - **现象→归因→预判**(适合趋势分析)
273
+ - **问题→拆解→方案**(适合教程类)
274
+
275
+ **A3. Clock Theory 节点规划(4 bang moment)**
276
+
277
+ ⚠️ **Clock Theory 是创作骨架,不是事后审计。** 先定 4 bang moment,
278
+ 然后围绕这 4 个点展开写。
279
+
280
+ | 时钟位 | 内容功能 | 你的 bang moment | Bang 类型 | 来自哪个 reference |
281
+ |--------|---------|-----------------|-----------|-------------------|
282
+ | 12:00 (0%) | Hook3 秒决生死 | {具体写什么} | {数据炸弹/反转/故事/共鸣} | {ref} |
283
+ | 3:00 (25%) | Escalation — 兑现 hook 的承诺 | {具体写什么} | {类型} | {ref} |
284
+ | 6:00 (50%) | Payload 核心价值交付 | {具体写什么} | {类型} | {ref} |
285
+ | 9:00 (75%) | Climax — 最强一击 | {具体写什么} | {类型} | {ref} |
286
+
287
+ 每个 bang moment 必须具体到"写什么内容",不能是"在这里放一个反转"这种空话。
288
+ 如果想不出 bang moment,说明这个论点不够强 A2 调整论证结构。
289
+
290
+ **A4. HKRR 选择**
291
+
292
+ 短视频(<60s):只选 1 个维度,全力打透。
293
+ 长内容(图文/长视频):选 1 个主导 + 1 个辅助。
294
+
295
+ ```
296
+ 主导维度: {H/K/R/R}
297
+ 理由: {为什么这个主题用这个维度最有效}
298
+ 辅助维度: {H/K/R/R 无}
299
+ ```
300
+
301
+ **A5. 微操技巧预埋**
302
+
303
+ 4 种微操技巧中选 2-3 种,标注在骨架的具体位置:
304
+
305
+ | 技巧 | 埋在哪里 | 具体怎么用 |
306
+ |------|---------|-----------|
307
+ | Open Loop | {位置,如"hook 之后"} | {具体的悬念句} |
308
+ | Curiosity Gap | {位置} | {具体的过渡句} |
309
+ | Visual Anchor | {位置} | {具体的金句} |
310
+ | Rhythm Break | {位置} | {具体的短句} |
311
+
312
+ ---
313
+
314
+ **输出骨架给用户确认:**
315
+
316
+ ```
317
+ 📐 内容骨架:
318
+
319
+ 核心观点:{A1}
320
+ 论证结构:{A2 的链条}
321
+ HKRR:主打 {A4}
322
+
323
+ Clock 节点:
324
+ 🕛 12:00 Hook: {内容}
325
+ 🕐 3:00 Escalation: {内容}
326
+ 🕕 6:00 Payload: {内容}
327
+ 🕘 9:00 Climax: {内容}
328
+
329
+ 微操预埋:{A5 的技巧和位置}
330
+
331
+ 这个结构 OK 吗?确认后开始写正文。
332
+ ```
333
+
334
+ **等待用户确认。** 用户说 OK 进入 Phase B。
335
+ 用户说要调整 修改骨架后再次确认。
336
+
337
+ ---
338
+
339
+ ## Phase B — 基于骨架填充正文
340
+
341
+ ⚠️ **每一段正文必须服务于 Phase A 确定的骨架。** 如果写着写着偏离了
342
+ 论证结构或错过了 clock 节点,停下来对照骨架修正,不要硬写下去。
343
+
344
+ **B1. Hook(12:00 位 bang moment)**
345
+
346
+ 写 1-3 句。从 A3 已经确定的 hook bang moment 展开。
347
+
348
+ | Hook 类型 | 模式 |
349
+ |-----------|------|
350
+ | Pain point | "XX最大的问题不是YY,而是ZZ" |
351
+ | Suspense | "我花了X万测试,结果发现…" |
352
+ | Contrast | "别人XX,你却在YY" |
353
+ | Identity | "每个做XX的人都经历过这一刻" |
354
+
355
+ ❌ 绝对禁止:"哈喽大家好" / "你有没有想过" / 任何通用问候
356
+
357
+ **坏 hook vs 好 hook 对比:**
358
+ ```
359
+ ❌ 坏:"今天聊聊 vibe-coding,这是一个很火的概念。"
360
+ → 没有观点、没有冲突、没有理由继续看
361
+
362
+ ✅ 好:"我不会写代码。但我用 3 周时间,做了一个智能分类平台——
363
+ 自动识别图片、分类、智能推荐。不是外包给程序员做的,
364
+ 是她自己用 AI 工具一个人搞定的。"
365
+ → 有具体案例、有反直觉(不会代码但做出了产品)、有悬念(怎么做到的)
366
+ ```
367
+
368
+ **B2. Body(3:00 → 6:00 → 9:00 的论证展开)**
369
+
370
+ Follow the argument chain from A2. Each paragraph maps to a clock node from A3.
371
+
372
+ Apply the Operating System principles throughout — they replace specific writing rules:
373
+ - EMPATHY FIRST → earn the reader's next 3 seconds with every sentence
374
+ - THEIR WORDS NOT YOURS → if the reader wouldn't say it, you can't write it
375
+ - SHOW THE MOVIE → every claim needs a scene (face, number, moment)
376
+ - TENSION IS OXYGEN → every paragraph opens or closes a question
377
+ - THE CREATOR IS THE PROOF → lead with creator's own experience
378
+
379
+ **Length:** No cap. Fear short, not long. If a case study deserves 200 words of vivid
380
+ detail, give it 200 words. Readers leave because of boredom, not length. The only
381
+ constraint: every sentence must have a reason to exist (advance argument, build emotion,
382
+ provide evidence). No padding.
383
+
384
+ **After each clock section, verify against skeleton:**
385
+ - Does this paragraph serve the argument structure from A2?
386
+ - Is the planned bang moment present and strong?
387
+ - Are the pre-placed micro-retention techniques firing?
388
+ - Apply EMPATHY FIRST: would the reader keep reading at this point? If not, rewrite
389
+ before continuing.
390
+
391
+ **B3. Comment triggers(评论触发)**
392
+
393
+ 在正文中自然嵌入 1-2 个评论触发点(不是单独加在最后):
394
+
395
+ | 类型 | 做法 | 例子 |
396
+ |------|------|------|
397
+ | 争议埋点 | 故意留一个可辩论的观点 | "AI 不会让你变成超人,它只是放大你已经有的东西。" |
398
+ | 未答问题 | 提出但不完全回答 | "至于为什么大厂不这么做?评论区聊" |
399
+ | 金句钩子 | 一句值得截图的话 | "想到了,就能做出来。这才是真正的能力边界变化。" |
400
+
401
+ 标注:`commentTriggers: [{ type: "controversy", position: "paragraph N" }]`
402
+
403
+ **B4. CTA**
404
+
405
+ 1-2 句收尾。连接内容价值:"收藏这条,下次用得上" > "觉得有用就点赞"。
406
+
407
+ **B5. Title(3-5 个候选)**
408
+
409
+ | 公式 | 模式 | 例子 |
410
+ |------|------|------|
411
+ | Number + Result | 数字+结果 | "用了3个月AI,我把团队从12人砍到3人" |
412
+ | Contrarian | 反直觉 | "AI写的代码比人快10倍,但我劝你别用" |
413
+ | Identity + Pain | 身份+痛点 | "传统老板看过来:这3种AI项目100%是坑" |
414
+ | Curiosity Gap | 悬念 | "花了20万做AI系统,结果…" |
415
+ | Contrast | 对比 | "别人用AI赚钱,你用AI亏钱,差在哪?" |
416
+
417
+ 15-25 字。标注每个候选用了什么公式。
418
+
419
+ **B6. Hashtags**
420
+
421
+ 调用 `generateHashtags(topic, platform, tags)` 生成平台专用标签。
422
+
423
+ ---
424
+
425
+ 7. **Self-review — run the Operating System check**
426
+
427
+ Re-read the entire draft once through. For each paragraph, verify the 5 principles:
428
+
429
+ **EMPATHY FIRST check:** Read the draft as if you are the audiencePersona, scrolling
430
+ on your phone. At every paragraph boundary ask: would I keep reading? If any point
431
+ makes you think "so what?" or "I'd swipe away here" — rewrite that section.
432
+
433
+ **THEIR WORDS NOT YOURS check:** Scan for any word the reader wouldn't use in casual
434
+ conversation. Every term must pass the coffee-chat test.
435
+
436
+ **SHOW THE MOVIE check:** Is every claim attached to a scene (face, number, moment)?
437
+ Any paragraph that reads like a summary instead of a story needs a concrete example
438
+ injected.
439
+
440
+ **TENSION IS OXYGEN check:** Does every paragraph either open or close a question?
441
+ Any paragraph that just "sits there" providing information without tension is a
442
+ dropout point — add a question, contradiction, or surprise.
443
+
444
+ **THE CREATOR IS THE PROOF check:** If the creator has relevant personal experience
445
+ for this topic, is it in the draft? Not as a footnote — as the backbone?
446
+
447
+ **Structure verification:**
448
+ - [ ] 4 clock bang moments present and strong?
449
+ - [ ] Argument chain from A2 complete — no missing steps?
450
+ - [ ] HKRR dominant dimension consistent throughout?
451
+ - [ ] ≥2 micro-retention techniques fired at planned positions?
452
+ - [ ] ≥1 comment trigger embedded naturally?
453
+ - [ ] Hypothesis, title, hashtags ready?
454
+
455
+ 8. **Save via `autocrew_content` tool — THE ONLY ALLOWED WAY TO SAVE:**
456
+
457
+ ⚠️ **CRITICAL — 绝对禁止用 Write 工具直接写 draft.md 文件。**
458
+ `autocrew_content` save action 会同时创建 `draft.md`、`meta.yaml`、pipeline 项目结构、
459
+ 运行去AI味处理。用 Write 工具直接写文件会导致 meta.yaml 缺失、版本记录丢失、pipeline
460
+ 状态机断裂。如果你脑子里有一个"我先用 Write 写到文件,然后再..."的想法 — 停下来,
461
+ 那个想法是错的。唯一正确的路径是调用 `autocrew_content`。
462
+
262
463
  ```json
263
464
  {
264
465
  "action": "save",
@@ -278,6 +479,21 @@ For video scripts: apply the **Clock Theory** from HAMLETDEER.md. Map the script
278
479
 
279
480
  Note: Auto-humanize runs automatically inside the save tool. You do NOT need to call humanize separately.
280
481
 
482
+ 8.5. **⚠️ Post-save verification (MANDATORY):**
483
+
484
+ After `autocrew_content` returns, verify these fields exist in the response:
485
+ - `ok: true` — save succeeded
486
+ - `filePath` — draft.md 路径
487
+ - `projectDir` — pipeline 项目目录
488
+
489
+ If ANY of these are missing or `ok: false`:
490
+ - Do NOT proceed to output.
491
+ - Do NOT fall back to Write tool.
492
+ - Show the error to the user and ask them to retry.
493
+
494
+ If save succeeded, do a quick sanity check — the response should contain `pipelinePath`.
495
+ If it does, the pipeline project is properly initialized with meta.yaml and draft.md.
496
+
281
497
  9. **Auto-review (MANDATORY — runs silently):**
282
498
  - After saving, ALWAYS run content review:
283
499
  ```json
@@ -0,0 +1,111 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import fs from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { migrateProfileToServices } from "./migrate.js";
6
+
7
+ let testDir: string;
8
+
9
+ beforeEach(async () => {
10
+ testDir = await fs.mkdtemp(path.join(os.tmpdir(), "autocrew-migrate-test-"));
11
+ });
12
+
13
+ afterEach(async () => {
14
+ await fs.rm(testDir, { recursive: true, force: true });
15
+ });
16
+
17
+ describe("migrateProfileToServices", () => {
18
+ it("moves omniConfig and videoCrawler from profile to services", async () => {
19
+ const profile = {
20
+ industry: "tech",
21
+ platforms: ["xhs"],
22
+ contentTypes: ["video"],
23
+ tone: "casual",
24
+ omniConfig: {
25
+ baseUrl: "https://api.xiaomimimo.com/v1",
26
+ model: "mimo-v2-omni",
27
+ apiKey: "sk-omni-test-123",
28
+ },
29
+ videoCrawler: {
30
+ type: "mediacrawl",
31
+ command: "python3 /opt/mediacrawl/main.py",
32
+ },
33
+ createdAt: "2026-01-01T00:00:00.000Z",
34
+ updatedAt: "2026-01-01T00:00:00.000Z",
35
+ };
36
+ await fs.writeFile(
37
+ path.join(testDir, "creator-profile.json"),
38
+ JSON.stringify(profile, null, 2),
39
+ "utf-8",
40
+ );
41
+
42
+ const result = await migrateProfileToServices(testDir);
43
+ expect(result.migrated).toBe(true);
44
+
45
+ // Verify services.json was created with correct data
46
+ const svcRaw = await fs.readFile(path.join(testDir, "services.json"), "utf-8");
47
+ const svc = JSON.parse(svcRaw);
48
+ expect(svc.omni.provider).toBe("xiaomi");
49
+ expect(svc.omni.apiKey).toBe("sk-omni-test-123");
50
+ expect(svc.omni.baseUrl).toBe("https://api.xiaomimimo.com/v1");
51
+ expect(svc.omni.model).toBe("mimo-v2-omni");
52
+ expect(svc.videoCrawler.type).toBe("mediacrawl");
53
+ expect(svc.videoCrawler.command).toBe("python3 /opt/mediacrawl/main.py");
54
+
55
+ // Verify profile was cleaned up
56
+ const profileRaw = await fs.readFile(path.join(testDir, "creator-profile.json"), "utf-8");
57
+ const updatedProfile = JSON.parse(profileRaw);
58
+ expect(updatedProfile.omniConfig).toBeUndefined();
59
+ expect(updatedProfile.videoCrawler).toBeUndefined();
60
+ expect(updatedProfile.industry).toBe("tech");
61
+ expect(updatedProfile.updatedAt).not.toBe("2026-01-01T00:00:00.000Z");
62
+ });
63
+
64
+ it("skips migration when services.json already exists", async () => {
65
+ const profile = {
66
+ industry: "tech",
67
+ platforms: ["xhs"],
68
+ omniConfig: {
69
+ baseUrl: "https://api.xiaomimimo.com/v1",
70
+ model: "mimo-v2-omni",
71
+ apiKey: "sk-omni-test-123",
72
+ },
73
+ createdAt: "2026-01-01T00:00:00.000Z",
74
+ updatedAt: "2026-01-01T00:00:00.000Z",
75
+ };
76
+ await fs.writeFile(
77
+ path.join(testDir, "creator-profile.json"),
78
+ JSON.stringify(profile, null, 2),
79
+ "utf-8",
80
+ );
81
+ await fs.writeFile(
82
+ path.join(testDir, "services.json"),
83
+ JSON.stringify({ configuredAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-01T00:00:00.000Z" }),
84
+ "utf-8",
85
+ );
86
+
87
+ const result = await migrateProfileToServices(testDir);
88
+ expect(result.migrated).toBe(false);
89
+ expect(result.reason).toContain("already exists");
90
+ });
91
+
92
+ it("handles profile without old fields gracefully", async () => {
93
+ const profile = {
94
+ industry: "tech",
95
+ platforms: ["xhs"],
96
+ contentTypes: ["video"],
97
+ tone: "casual",
98
+ createdAt: "2026-01-01T00:00:00.000Z",
99
+ updatedAt: "2026-01-01T00:00:00.000Z",
100
+ };
101
+ await fs.writeFile(
102
+ path.join(testDir, "creator-profile.json"),
103
+ JSON.stringify(profile, null, 2),
104
+ "utf-8",
105
+ );
106
+
107
+ const result = await migrateProfileToServices(testDir);
108
+ expect(result.migrated).toBe(false);
109
+ expect(result.reason).toContain("nothing to migrate");
110
+ });
111
+ });