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.
- package/package.json +1 -1
- package/skills/configure/SKILL.md +228 -0
- package/skills/spawn-writer/SKILL.md +2 -1
- package/skills/teardown/SKILL.md +9 -6
- package/skills/write-script/SKILL.md +330 -114
- package/src/modules/config/migrate.test.ts +111 -0
- package/src/modules/config/migrate.ts +83 -0
- package/src/modules/config/service-config.test.ts +140 -0
- package/src/modules/config/service-config.ts +139 -0
- package/src/modules/intel/integration.test.ts +2 -1
- package/src/storage/pipeline-store.test.ts +3 -0
- package/src/storage/pipeline-store.ts +129 -20
- package/src/tools/content-save.ts +53 -0
|
@@ -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.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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%) | Hook — 3 秒决生死 | {具体写什么} | {数据炸弹/反转/故事/共鸣} | {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
|
+
});
|