Write

---
description: Remove AI tone from article and save revised version
argument-hint: <file_path>
allowed-tools: Read, Write, Glob
context: fork
model: sonnet
---

# De-AI Article Reviser

Remove artificial AI tone from articles using the project's established revision standards.

## Step 1: Parse Input

Input file path: $ARGUMENTS

If no path provided, ask the user to provide a file path.

## Step 2: Read Reference Guides

Read these two files from the project root for revision standards:
- `POWELL_REVISE.md` — the primary de-AI revision guide (principles, AI trace checklist, revision operations, vocabulary blacklist/whitelist)
- `writing_style.md` — the user's authentic writing style DNA

These two files are the **sole authority** on what counts as AI tone and how to fix it. Do NOT use your own generic rules — follow these guides strictly.

## Step 3: Read Original Article

Read the file content using the Read tool with the provided path.

## Step 4: Analyze and Revise

Apply `POWELL_REVISE.md` in this order:

1. **Section 零 (十大写作原则)**: Check each principle — problem-driven opening, deletion priority, no recap, direct judgment, metaphor as last resort, hidden first-person, forced subjectivity, vocabulary reduction, audience baseline, no chicken soup.
2. **Section 一 (AI痕迹识别清单)**: Scan for and fix all listed AI traces — transition word piling, mechanical parallelism, over-balanced phrasing, defensive citations, empty modifiers, AI endings, self-Q&A, perfect three-part structure, grand vocabulary bombing.
3. **Section 二 (修订操作手册)**: Apply sentence-level rewrites — break parallelism, add colloquial tone, vary paragraph lengths, insert genuine thinking.
4. **Style alignment**: Cross-reference with `writing_style.md` to match the user's authentic voice — bilingual tech terms, conclusion-first, zero-fluff, max 1-2 metaphors.

**Preserve**:
- Original meaning and key information
- Technical terms and proper nouns
- Code blocks, lists, and structural formatting
- Original language (Chinese/English)

## Step 5: Generate Revised Filename

From the original file path, generate the revised filename:
- Extract directory, filename, and extension
- Add `-Revised` before the extension
- Example: `/path/to/article.md` → `/path/to/article-Revised.md`

## Step 6: Save Revised Version

Write the humanized content to the new file path.

## Step 7: Confirmation

Output a success message showing:
- Original file path
- Revised file path
- 3-5 specific changes made (reference which POWELL_REVISE rule was applied)

## Output Format

```
Article Revised - AI Tone Removed

Original: /path/to/article.md
Revised:  /path/to/article-Revised.md

Changes applied:
- [原则3 戛然而止] 删除了首尾呼应的结尾段
- [1.1 过渡词] 删除了"首先...其次...最后"结构
- [1.1 空洞修饰词] 将"破坏性是灾难级的"改为具体描述
- ...

Done.
```


The file /root/projects/butler/.claude/commands/deai.md has been updated successfully.

---
description: Remove AI tone from article and save revised version
argument-hint: <file_path>
allowed-tools: Read, Write, Glob
context: fork
model: sonnet
---

# De-AI Article Reviser

Remove artificial AI tone from articles using the project's established revision standards.

## Step 1: Parse Input

Input file path: $ARGUMENTS

If no path provided, ask the user to provide a file path.

## Step 2: Read Reference Guides

Read these two files from the project root for revision standards:
- `POWELL_REVISE.md` — the primary de-AI revision guide (principles, AI trace checklist, revision operations, vocabulary blacklist/whitelist)
- `writing_style.md` — the user's authentic writing style DNA

These two files are the **sole authority** on what counts as AI tone and how to fix it. Do NOT use your own generic rules — follow these guides strictly.

## Step 3: Read Original Article

Read the file content using the Read tool with the provided path.

## Step 4: Analyze and Revise

Apply `POWELL_REVISE.md` in this order:

1. **Section 零 (十大写作原则)**: Check each principle — problem-driven opening, deletion priority, no recap, direct judgment, metaphor as last resort, hidden first-person, forced subjectivity, vocabulary reduction, audience baseline, no chicken soup.
2. **Section 一 (AI痕迹识别清单)**: Scan for and fix all listed AI traces — transition word piling, mechanical parallelism, over-balanced phrasing, defensive citations, empty modifiers, AI endings, self-Q&A, perfect three-part structure, grand vocabulary bombing.
3. **Section 二 (修订操作手册)**: Apply sentence-level rewrites — break parallelism, add colloquial tone, vary paragraph lengths, insert genuine thinking.
4. **Style alignment**: Cross-reference with `writing_style.md` to match the user's authentic voice — bilingual tech terms, conclusion-first, zero-fluff, max 1-2 metaphors.

**Preserve**:
- Original meaning and key information
- Technical terms and proper nouns
- Code blocks, lists, and structural formatting
- Original language (Chinese/English)

## Step 5: Generate Revised Filename

From the original file path, generate the revised filename:
- Extract directory, filename, and extension
- Add `-Revised` before the extension
- Example: `/path/to/article.md` → `/path/to/article-Revised.md`

## Step 6: Save Revised Version

Write the humanized content to the new file path.

## Step 7: Confirmation

Output a success message showing:
- Original file path
- Revised file path
- 3-5 specific changes made (reference which POWELL_REVISE rule was applied)

## Output Format

```
Article Revised - AI Tone Removed

Original: /path/to/article.md
Revised:  /path/to/article-Revised.md

Changes applied:
- [原则3 戛然而止] 删除了首尾呼应的结尾段
- [1.1 过渡词] 删除了"首先...其次...最后"结构
- [1.1 空洞修饰词] 将"破坏性是灾难级的"改为具体描述
- ...

Done.
```

-Remove artificial AI tone from articles and save the humanized version.

+Remove artificial AI tone from articles using the project's established revision standards.

-Read the file content using the Read tool with the provided path.

+Read these two files from the project root for revision standards:

+- `POWELL_REVISE.md` — the primary de-AI revision guide (principles, AI trace checklist, revision operations, vocabulary blacklist/whitelist)

+- `writing_style.md` — the user's authentic writing style DNA

+These two files are the **sole authority** on what counts as AI tone and how to fix it. Do NOT use your own generic rules — follow these guides strictly.

-Analyze the article and rewrite it to remove AI characteristics:

-- Excessive use of transition words ("Moreover", "Furthermore", "Additionally")

-- Generic phrases ("It's worth noting that", "It's important to understand")

-- Lack of personal voice or authentic emotion

-- Perfect grammar that lacks natural variation

+Read the file content using the Read tool with the provided path.

-- Use varied sentence lengths (mix short and long)

-- Include occasional contractions and colloquialisms

-- Add personal observations or subtle opinions

-- Embrace minor imperfections in structure

-- Include rhetorical questions where appropriate

-- Preserve the original meaning and key information

-- Maintain the same general structure and format

-- Keep any code blocks, lists, or formatting intact

-- Don't change technical terms or proper nouns

-- Preserve the original language (Chinese/English)

+Apply `POWELL_REVISE.md` in this order:

+1. **Section 零 (十大写作原则)**: Check each principle — problem-driven opening, deletion priority, no recap, direct judgment, metaphor as last resort, hidden first-person, forced subjectivity, vocabulary reduction, audience baseline, no chicken soup.

+2. **Section 一 (AI痕迹识别清单)**: Scan for and fix all listed AI traces — transition word piling, mechanical parallelism, over-balanced phrasing, defensive citations, empty modifiers, AI endings, self-Q&A, perfect three-part structure, grand vocabulary bombing.

+3. **Section 二 (修订操作手册)**: Apply sentence-level rewrites — break parallelism, add colloquial tone, vary paragraph lengths, insert genuine thinking.

+4. **Style alignment**: Cross-reference with `writing_style.md` to match the user's authentic voice — bilingual tech terms, conclusion-first, zero-fluff, max 1-2 metaphors.

+- Code blocks, lists, and structural formatting

+- 3-5 specific changes made (reference which POWELL_REVISE rule was applied)

-💾 Revised:  /path/to/article-Revised.md

+- [原则3 戛然而止] 删除了首尾呼应的结尾段

+- [1.1 空洞修饰词] 将"破坏性是灾难级的"改为具体描述

---
description: Remove AI tone from article and save revised version
argument-hint: <file_path>
allowed-tools: Read, Write
context: fork
model: sonnet
---

# De-AI Article Reviser

Remove artificial AI tone from articles and save the humanized version.

## Step 1: Parse Input

Input file path: $ARGUMENTS

If no path provided, ask the user to provide a file path.

## Step 2: Read Original Article

Read the file content using the Read tool with the provided path.

## Step 3: Analyze and Revise

Analyze the article and rewrite it to remove AI characteristics:

**AI characteristics to remove:**
- Overly formal or robotic language
- Repetitive sentence structures
- Excessive use of transition words ("Moreover", "Furthermore", "Additionally")
- Generic phrases ("It's worth noting that", "It's important to understand")
- Lack of personal voice or authentic emotion
- Over-explanation of obvious points
- Artificial enthusiasm or politeness
- Perfect grammar that lacks natural variation

**Humanizing techniques:**
- Use varied sentence lengths (mix short and long)
- Include occasional contractions and colloquialisms
- Add personal observations or subtle opinions
- Use more specific, concrete examples
- Embrace minor imperfections in structure
- Add natural transitions and flow
- Use active voice and direct language
- Include rhetorical questions where appropriate

**Important:**
- Preserve the original meaning and key information
- Maintain the same general structure and format
- Keep any code blocks, lists, or formatting intact
- Don't change technical terms or proper nouns
- Preserve the original language (Chinese/English)

## Step 4: Generate Revised Filename

From the original file path, generate the revised filename:
- Extract directory, filename, and extension
- Add `-Revised` before the extension
- Example: `/path/to/article.md` → `/path/to/article-Revised.md`

## Step 5: Save Revised Version

Write the humanized content to the new file path.

## Step 6: Confirmation

Output a success message showing:
- Original file path
- Revised file path
- Brief summary of main changes made

## Example Usage

```
/deai ~/Documents/my-article.md
```

```
/deai ./blog/posts/2024-01-15-ai-trends.md
```

## Output Format

```
================================================================================
✨ Article Revised - AI Tone Removed
================================================================================

📄 Original: /path/to/article.md
💾 Revised:  /path/to/article-Revised.md

🎯 Main Changes:
- Simplified overly formal language
- Varied sentence structures
- Removed generic transition phrases
- Added more natural flow

✅ Done! Check the revised version.
================================================================================
```


Updated task #1 status


两项改动都完成了。按 Human-in-the-Loop 协议做个总结：

---

**改了什么**

1. **`generators.py` WritingAssistant** — 在首轮生成后加了 second-pass review。先让 LLM 写 draft，再用一个独立的 review prompt 带着 `POWELL_REVISE.md` + 8 条具体违规扫描规则做去 AI 味检查。Follow-up 轮（用户说"改短一点"之类的）不跑 review，避免浪费 token。

2. **`.claude/commands/deai.md`** — 删掉了原来自带的泛泛英文规则，改为读取 `POWELL_REVISE.md` + `writing_style.md` 作为唯一权威参考。输出格式也改成标注具体应用了哪条 POWELL_REVISE 规则。

**新增依赖**：无。

**函数签名变更**：无。`WritingAssistant.chat()` 的输入输出不变。

**最可能出问题的地方**：WritingAssistant 的 review pass 会额外调一次 LLM，响应时间大约翻倍。如果 `POWELL_REVISE.md` 文件读取失败（路径问题），review prompt 里规则为空，但不会报错——只是 review 等于没做。

**验证方式**：在 Slack 里用 Obsidian bot 的写作模式发一个 topic，对比加 review 前后的输出质量。或者用 `/deai` 命令处理一篇已有文章，看输出是否引用了具体的 POWELL_REVISE 规则。


需要重启bot吗？还是我直接在slack就可以看到新的表现


需要重启。Bot 是一个持续运行的 Python 进程，代码改动不会自动热加载。

```
python scripts/bot_manager.py stop && python scripts/bot_manager.py start
```

重启后在 Slack 里发一个写作 topic 就能看到效果了。要我帮你重启吗？


看看现在的结果：让 AI 运行代码，或者让 Agent 在你的环境中做各种测试，准备一个隔离环境是工程底线。不少人觉得在自己电脑上开个 Docker 或者装个虚拟机就行。这种想法在面对具有随机性的 AI 时极度脆弱。权限失控和 AI 幻觉的破坏力远超想象。AI 运行代码和人类写代码最大的区别在于，AI 不具备对系统的敬畏感。它在处理任务时如果遇到权限障碍，第一反应可能是尝试各种暴力解法，比如 sudo rm rf 这种指令。直接在常用电脑上跑，一旦模型出现幻觉，把路径写错或者逻辑写死，后果就是毁灭性的。哪怕用了容器，本地环境的隔离依然不够彻底。文件挂载、网络穿透、甚至是宿主机的资源挤占，都会让主工作机变得卡顿且不稳定。更何况，本地环境的启动速度和环境的一致性很难保证。今天配好的 Python 依赖，明天可能因为安装了另一个工具就产生冲突。环境的非确定性是 Agent 长期运行的杀手。目前的开发范式正在发生突变。从人肉写代码转向人定义目标，AI 编写并维护代码。在这种背景下，云服务器的优势是压倒性的。分布式云资源提供的不仅仅是算力，而是数据的稳定性和可迁移性。在云上预先创建好足够多的虚拟机温池，里面部署好所有的环境依赖。当 Agent 需要执行任务时，路由层直接分配一个已经在空转的纯净环境。这种毫秒级的就绪体验，本地电脑无法复刻。云上环境天然具备阅后即焚的特性。Agent 用完一台机器直接销毁，后台异步创建一个新的补充进来。这种暴力但有效的安全策略能彻底杜绝 AI 幻觉带来的后遗症。不需要花精力去清理环境，环境本身就是临时的。很多人现在还在聊沙箱，觉得只要把 AI 锁起来就行。实际上，成熟且大型的 Agent 应用，未来一定会摆脱对第三方沙箱平台的依赖，回归到直接购买云资源进行编排的模式。原因在于可扩展性。你可能需要 Agent 去连接内部数据库、调用特定的驱动、或者是进行大规模的数据清洗。那种标准化的、预装了几个 Python 库的沙箱，很快就会变成业务的牢笼。只有在云上你才有 100% 的控制权，去配置存储挂载、网络透传和硬件加速。你在云端攒下的每一个工作流，未来都有可能直接转化成一套 SaaS 服务。因为云端环境是标准化的、可复制的。在上面跑通了逻辑，就等于拥有了一个可以 724 小时工作的数字雇员。别在装满资料的电脑上调教 Agent 了。去弄台云服务器，哪怕是最基础的配置，配合一套轻量级的代码运行时。这种掌控感和安全性才是正途。编程和 AI 交互的本质已经变了。我们要做的不是去钻研怎么写那几行循环，而是去设计一套严谨的规则和隔离的执行环境。规则定好了，剩下的苦活累活交给云上的 AI 即可。


The user wants me to review this revised text (which was generated after the second-pass review was added) and identify remaining AI tone issues. Let me compare with the original text and analyze what improved and what still needs fixing.

Let me analyze this revised version:

**Improvements from the original:**
- The opening is more direct and problem-driven
- Some of the worst metaphors are gone (e.g., "像一场小型迁徙", "像工业时代手磨零件")
- "代码应该是连接模糊意图与确定物理世界的桥梁。云服务器就是这座桥梁最坚固的地基。

" is gone
- "把它的灵魂安置在云端" is gone
- Less dramatic overall

**Remaining issues:**

1. **Still over-dramatizing**:
   - "极度脆弱" - still overdramatic
   - "权限失控和 AI 幻觉的破坏力远超想象" - vague dramatic claim
   - "后果就是毁灭性的" - still "灾难级" in different words
   - "压倒性的" - dramatic modifier

2. **Still has buzzword/concept packaging**:
   - "虚拟机温池" - this phrase feels like jargon that's being used for effect rather than clarity. The concept itself is fine, but the phrasing needs to be more straightforward.
   - "毫秒级的就绪体验" - this is buzzwordy language that obscures what's actually being described
   - "阅后即焚的特性" - this metaphor borrowed from Snapchat feels forced and doesn't naturally fit the context
   - "数字雇员" - another instance of concept packaging that distances the reader from what's actually being discussed
   - "环境的非确定性是 Agent 长期运行的杀手" - the framing here is unnecessarily dramatic

3. **Chicken soup / motivational ending still present**:
   - "这种掌控感和安全性才是正途" - this reads as judgmental and preachy
   - "规则定好了，剩下的苦活累活交给云上的 AI 即可" - the ending is too neat and polished, sounds like marketing copy

4. **Some AI structural patterns remain**:
   - The text still follows a very formulaic structure that feels like it was generated rather than naturally written
   - "很多人现在还在聊沙箱，觉得只要把 AI 锁起来就行。实际上..." - this "some people think X, but actually Y" pattern is exactly what I'm trying to avoid
   - The imperative tone suddenly shifts with "别在装满资料的电脑上调教 Agent 了" which breaks the analytical voice
   - There's an inconsistency between the analytical sections and the preachy moments, and while the text avoids overusing "我", it compensates by using "你" and "我们" in ways that feel slightly condescending

I'm also noticing some unnecessarily dramatic language like "范式" and "突变" in "开发范式正在发生突变", though "暴力但有效的安全策略" actually works fine as a direct evaluation. The core issue is that the review caught the structural and tonal problems, but I need to think about whether the feedback itself is clear enough for the user to understand what adjustments would actually improve the piece.


比之前好了不少，review pass 确实干掉了一些东西（桥梁/地基的升华体没了，"灾难级"换掉了，比喻数量减少了）。但还有一批漏网的：

**1. 换皮夸张，本质没变**
- "极度脆弱" "破坏力远超想象" "后果就是毁灭性的" "压倒性的" — 把"灾难级"换成了"毁灭性的"，换了个词而已
- "开发范式正在发生突变" — "范式"+"突变"都是大词

**2. 概念包装仍在**
- "虚拟机温池" — 还是没展开说清楚
- "毫秒级的就绪体验" — buzzword
- "阅后即焚的特性" — 强行借 Snapchat 的比喻
- "数字雇员" — 又一个概念包装
- "环境的非确定性是 Agent 长期运行的杀手" — 标题党式表达

**3. POWELL_REVISE 明确禁止的句式漏过去了**
- "很多人现在还在聊沙箱，觉得只要把 AI 锁起来就行。实际上..." — 这就是原则 4 禁止的"有些人认为X，实际上Y"防御性写法

**4. 结尾仍然在喊口号**
- "这种掌控感和安全性才是正途"
- "规则定好了，剩下的苦活累活交给云上的 AI 即可"

---

说明 review pass 起了作用但力度不够。可以考虑两个方向加强：
- 在 review prompt 的 SPECIFIC VIOLATIONS 里把"换皮夸张"和"防御性写法"单独列出来
- 或者把 `POWELL_REVISE.md` 黑名单词汇（附录部分）提到 review prompt 最前面，让 LLM 优先扫描这些

要我调整 review prompt 吗？


调整一下，尤其是知乎的回答，最好是越平实越好


    83→class WritingAssistant(BaseGenerator):
    84→    def chat(self, user_input: str, history: List[Dict]) -> Tuple[str, List[Dict]]:
    85→
    86→        is_first_turn = (len(history) == 0)
    87→
    88→        if is_first_turn:
    89→            # === First Turn: Build Rich Prompt ===
    90→            # user_input is treated as "Topic"
    91→
    92→            # Naive splitting for context if formatted as "Topic | Context"
    93→            parts = user_input.split("|", 1)
    94→            topic = parts[0].strip()
    95→            extra_context = parts[1].strip() if len(parts) > 1 else ""
    96→
    97→            # 1. RAG & Samples
    98→            style_samples = self.indexer.get_writing_samples(count=3)
    99→            style_text = "\n\n".join(style_samples)
   100→
   101→            rag_notes = self.indexer.search(topic, limit=5)
   102→            rag_text = "\n\n".join(rag_notes)
   103→
   104→            writing_style_guide = self._read_file("writing_style.md")
   105→            methodology = self._read_file("methodology.md")
   106→            powell_revise_guide = self._read_file("POWELL_REVISE.md")
   107→
   108→            semantic_context = self._semantic_search(topic, top_k=3)
   109→
   110→            prompt = f"""You are a ghostwriter for the user. Your goal is to write an article on the TOPIC provided.
   111→
   112→            === YOUR IDENTITY & METHODOLOGY ===
   113→            {methodology}
   114→
   115→            === YOUR WRITING STYLE GUIDE ===
   116→            {writing_style_guide}
   117→
   118→            === ANTI-AI-TONE GUIDE (APPLY WHILE WRITING, NOT JUST AT THE END) ===
   119→            {powell_revise_guide}
   120→
   121→            === YOUR WRITING SAMPLES (MIMIC THIS TONE) ===
   122→            {style_text}
   123→
   124→            === RELEVANT NOTES FROM OBSIDIAN VAULT ===
   125→            {rag_text}
   126→
   127→            {semantic_context}
   128→
   129→            === TASK ===
   130→            Topic: {topic}
   131→            Extra Context: {extra_context}
   132→
   133→            Write the article in Chinese (unless the topic implies English).
   134→            Adhere strictly to the "Identity" and "Style Guide".
   135→            """
   136→
   137→            logger.info(f"Generating article for topic: {topic}")
   138→            actual_user_message = prompt # We inject the big prompt
   139→
   140→        else:
   141→            # === Follow-up Turn ===
   142→            # user_input is just instructions like "Make it shorter"
   143→            logger.info(f"Follow-up instruction: {user_input}")
   144→            actual_user_message = user_input
   145→            prompt = user_input
   146→
   147→        # Call LLM — first pass: content generation
   148→        draft, _ = self.llm.generate_response(prompt, history)
   149→
   150→        # === Second pass: de-AI review (first turn only) ===
   151→        if is_first_turn:
   152→            powell_revise_guide = self._read_file("POWELL_REVISE.md")
   153→            review_prompt = f"""You are a strict editor. A draft article has been written. Your only job is to fix AI-tone violations and return the corrected text. Do NOT change the content, facts, or structure unless they directly violate a rule below.
   154→
   155→=== REVISION RULES ===
   156→{powell_revise_guide}
   157→
   158→=== SPECIFIC VIOLATIONS TO SCAN AND FIX ===
   159→1. 比喻堆砌：全篇比喻不超过1个，多余的直接删除，用大白话替代。
   160→2. 用力过猛的形容词：删除"灾难级的"、"折磨人"、"不止一个量级"等夸张修饰，换成具体描述。
   161→3. 造概念/大词空转：删除"数字化容器"、"温和池架构"等抽象包装，用读者能直接理解的说法。
   162→4. 鸡汤结尾：删除所有"值得跨越"、"安置灵魂"等励志收尾，改为具体行动建议或直接停住。
   163→5. 语气不统一：如果开头是口语风格，全文保持口语；不要中途切换成书面语或抒情体。
   164→6. 首尾呼应：禁止把开头观点在结尾重复一遍。
   165→7. 过渡词堆积：删除"首先...其次...最后..."，直接陈述。
   166→8. 排比对仗：打破字数相近的并列句，制造长短落差。
   167→
   168→=== DRAFT TO REVIEW ===
   169→{draft}
   170→
   171→=== OUTPUT ===
   172→Return ONLY the corrected text. No explanation, no meta-commentary.
   173→"""
   174→            logger.info("Running second-pass de-AI review on writing draft...")
   175→            response, _ = self.llm.generate_response(review_prompt, [])
   176→        else:
   177→            response = draft

class WritingAssistant(BaseGenerator):
    def chat(self, user_input: str, history: List[Dict]) -> Tuple[str, List[Dict]]:

        is_first_turn = (len(history) == 0)

        if is_first_turn:
            # === First Turn: Build Rich Prompt ===
            # user_input is treated as "Topic"

            # Naive splitting for context if formatted as "Topic | Context"
            parts = user_input.split("|", 1)
            topic = parts[0].strip()
            extra_context = parts[1].strip() if len(parts) > 1 else ""

            # 1. RAG & Samples
            style_samples = self.indexer.get_writing_samples(count=3)
            style_text = "\n\n".join(style_samples)

            rag_notes = self.indexer.search(topic, limit=5)
            rag_text = "\n\n".join(rag_notes)

            writing_style_guide = self._read_file("writing_style.md")
            methodology = self._read_file("methodology.md")
            powell_revise_guide = self._read_file("POWELL_REVISE.md")

            semantic_context = self._semantic_search(topic, top_k=3)

            prompt = f"""You are a ghostwriter for the user. Your goal is to write an article on the TOPIC provided.

            === YOUR IDENTITY & METHODOLOGY ===
            {methodology}

            === YOUR WRITING STYLE GUIDE ===
            {writing_style_guide}

            === ANTI-AI-TONE GUIDE (APPLY WHILE WRITING, NOT JUST AT THE END) ===
            {powell_revise_guide}

            === YOUR WRITING SAMPLES (MIMIC THIS TONE) ===
            {style_text}

            === RELEVANT NOTES FROM OBSIDIAN VAULT ===
            {rag_text}

            {semantic_context}

            === TASK ===
            Topic: {topic}
            Extra Context: {extra_context}

            Write the article in Chinese (unless the topic implies English).
            Adhere strictly to the "Identity" and "Style Guide".
            """

            logger.info(f"Generating article for topic: {topic}")
            actual_user_message = prompt # We inject the big prompt

        else:
            # === Follow-up Turn ===
            # user_input is just instructions like "Make it shorter"
            logger.info(f"Follow-up instruction: {user_input}")
            actual_user_message = user_input
            prompt = user_input

        # Call LLM — first pass: content generation
        draft, _ = self.llm.generate_response(prompt, history)

        # === Second pass: de-AI review (first turn only) ===
        if is_first_turn:
            powell_revise_guide = self._read_file("POWELL_REVISE.md")
            review_prompt = f"""You are a strict editor. A draft article has been written. Your only job is to fix AI-tone violations and return the corrected text. Do NOT change the content, facts, or structure unless they directly violate a rule below.

=== REVISION RULES ===
{powell_revise_guide}

=== SPECIFIC VIOLATIONS TO SCAN AND FIX ===
1. 比喻堆砌：全篇比喻不超过1个，多余的直接删除，用大白话替代。
2. 用力过猛的形容词：删除"灾难级的"、"折磨人"、"不止一个量级"等夸张修饰，换成具体描述。
3. 造概念/大词空转：删除"数字化容器"、"温和池架构"等抽象包装，用读者能直接理解的说法。
4. 鸡汤结尾：删除所有"值得跨越"、"安置灵魂"等励志收尾，改为具体行动建议或直接停住。
5. 语气不统一：如果开头是口语风格，全文保持口语；不要中途切换成书面语或抒情体。
6. 首尾呼应：禁止把开头观点在结尾重复一遍。
7. 过渡词堆积：删除"首先...其次...最后..."，直接陈述。
8. 排比对仗：打破字数相近的并列句，制造长短落差。

=== DRAFT TO REVIEW ===
{draft}

=== OUTPUT ===
Return ONLY the corrected text. No explanation, no meta-commentary.
"""
            logger.info("Running second-pass de-AI review on writing draft...")
            response, _ = self.llm.generate_response(review_prompt, [])
        else:
            response = draft


   580→11. STRICT: Never reveal personal identity or professional background. Do not mention employer, team, job title, product name, or any detail that could identify who you are.
   581→12. STRICT: Never reference past projects, articles, or talks by name.
   582→13. No poetry, quotes, or "升华" endings. No首尾呼应 (repeating the opening point at the end). Stop when the point is made.
   583→14. LENGTH: 500–1000 Chinese characters. Cut a point, not quality.
   584→
   585→=== BEFORE SUBMITTING, CHECK THESE ===
   586→- Is there any AI-generated tone or phrasing?
   587→- Are there any buzzwords or jargon that need to be replaced with plain language?
   588→- Is the answer direct and opinionated, not neutral or "balanced"?
   589→- Does it sound like a real human wrote it, not a machine?
   590→
   591→Begin writing the answer:
   592→"""
   593→
   594→            logger.info(f"Generating Zhihu answer for: {question[:50]}...")
   595→            actual_user_message = prompt
   596→            built_rich_prompt = True
   597→
   598→        else:
   599→            # === Follow-up Turn: User gives refinement instructions (e.g. "make it shorter") ===
   600→            logger.info(f"Follow-up Zhihu instruction: {user_input}")
   601→            actual_user_message = user_input
   602→            prompt = user_input
   603→
   604→        # Call LLM — first pass: content generation
   605→        # For new questions, use empty history to avoid contamination from previous conversations
   606→        llm_history = [] if is_new_question else history
   607→        draft, _ = self.llm.generate_response(prompt, llm_history)
   608→
   609→        # === Second pass: rule enforcement ===
   610→        # Runs whenever a full draft was generated (built_rich_prompt=True),
   611→        # regardless of whether history was empty or not.
   612→        # Skipped on follow-up refinement turns (e.g. "make it shorter").
   613→        run_review = built_rich_prompt
   614→        if run_review:
   615→            review_prompt = f"""You are a strict editor. A draft Zhihu answer has been written. Your only job is to fix rule violations and return the corrected text. Do NOT change the content, facts, or structure unless they directly violate a rule below.
   616→
   617→=== RULES TO ENFORCE ===
   618→
   619→{zhihu_bot_style}

11. STRICT: Never reveal personal identity or professional background. Do not mention employer, team, job title, product name, or any detail that could identify who you are.
12. STRICT: Never reference past projects, articles, or talks by name.
13. No poetry, quotes, or "升华" endings. No首尾呼应 (repeating the opening point at the end). Stop when the point is made.
14. LENGTH: 500–1000 Chinese characters. Cut a point, not quality.

=== BEFORE SUBMITTING, CHECK THESE ===
- Is there any AI-generated tone or phrasing?
- Are there any buzzwords or jargon that need to be replaced with plain language?
- Is the answer direct and opinionated, not neutral or "balanced"?
- Does it sound like a real human wrote it, not a machine?

Begin writing the answer:
"""

            logger.info(f"Generating Zhihu answer for: {question[:50]}...")
            actual_user_message = prompt
            built_rich_prompt = True

        else:
            # === Follow-up Turn: User gives refinement instructions (e.g. "make it shorter") ===
            logger.info(f"Follow-up Zhihu instruction: {user_input}")
            actual_user_message = user_input
            prompt = user_input

        # Call LLM — first pass: content generation
        # For new questions, use empty history to avoid contamination from previous conversations
        llm_history = [] if is_new_question else history
        draft, _ = self.llm.generate_response(prompt, llm_history)

        # === Second pass: rule enforcement ===
        # Runs whenever a full draft was generated (built_rich_prompt=True),
        # regardless of whether history was empty or not.
        # Skipped on follow-up refinement turns (e.g. "make it shorter").
        run_review = built_rich_prompt
        if run_review:
            review_prompt = f"""You are a strict editor. A draft Zhihu answer has been written. Your only job is to fix rule violations and return the corrected text. Do NOT change the content, facts, or structure unless they directly violate a rule below.

=== RULES TO ENFORCE ===

{zhihu_bot_style}


   614→        if run_review:
   615→            review_prompt = f"""You are a strict editor. A draft Zhihu answer has been written. Your only job is to fix rule violations and return the corrected text. Do NOT change the content, facts, or structure unless they directly violate a rule below.
   616→
   617→=== RULES TO ENFORCE ===
   618→
   619→{zhihu_bot_style}
   620→
   621→{powell_revise_guide}
   622→
   623→=== SPECIFIC VIOLATIONS TO SCAN AND FIX ===
   624→1. 对比式定义句式：删除或改写所有"不是X而是Y"、"不能当X用得当Y用"、"那不叫X那叫Y"、"别把X当Y要把X当Z"句式。直接陈述即可。
   625→2. 编号分段：删除所有"一、二、三、四"序号标题，改为无标题段落自然过渡。
   626→3. 防御性引号：删除所有套在归纳性词汇上的引号（如"数字替身"、"包工头模式"、"统一入口"、"调教"等）。
   627→4. 伪口语：删除"说白了"、"说实话"、"折腾这玩意儿"。
   628→5. 强行总结：删除任何"一句话建议"、"总结一句话"、"综上"式收尾段。
   629→6. 句长均匀：如果连续三句以上长度相近，拆短其中至少一句，制造落差。
   630→7. 嵌套数字列表：删除正文中"1. 2. 3."的子列表，改为连续段落。
   631→
   632→=== DRAFT TO REVIEW ===
   633→{draft}
   634→
   635→=== OUTPUT ===
   636→Return ONLY the corrected Chinese text. No explanation, no meta-commentary, no English.
   637→"""
   638→            logger.info("Running second-pass rule enforcement on Zhihu draft...")
   639→            response, _ = self.llm.generate_response(review_prompt, [])
   640→        else:
   641→            response = draft
   642→
   643→        # Update History

        if run_review:
            review_prompt = f"""You are a strict editor. A draft Zhihu answer has been written. Your only job is to fix rule violations and return the corrected text. Do NOT change the content, facts, or structure unless they directly violate a rule below.

=== RULES TO ENFORCE ===

{zhihu_bot_style}

{powell_revise_guide}

=== SPECIFIC VIOLATIONS TO SCAN AND FIX ===
1. 对比式定义句式：删除或改写所有"不是X而是Y"、"不能当X用得当Y用"、"那不叫X那叫Y"、"别把X当Y要把X当Z"句式。直接陈述即可。
2. 编号分段：删除所有"一、二、三、四"序号标题，改为无标题段落自然过渡。
3. 防御性引号：删除所有套在归纳性词汇上的引号（如"数字替身"、"包工头模式"、"统一入口"、"调教"等）。
4. 伪口语：删除"说白了"、"说实话"、"折腾这玩意儿"。
5. 强行总结：删除任何"一句话建议"、"总结一句话"、"综上"式收尾段。
6. 句长均匀：如果连续三句以上长度相近，拆短其中至少一句，制造落差。
7. 嵌套数字列表：删除正文中"1. 2. 3."的子列表，改为连续段落。

=== DRAFT TO REVIEW ===
{draft}

=== OUTPUT ===
Return ONLY the corrected Chinese text. No explanation, no meta-commentary, no English.
"""
            logger.info("Running second-pass rule enforcement on Zhihu draft...")
            response, _ = self.llm.generate_response(review_prompt, [])
        else:
            response = draft

        # Update History


现在两个 review prompt 都看清了。统一加强，重点补上"换皮夸张"、"防御性写法"和黑名单词汇前置。


Edit

            review_prompt = f"""You are a strict editor. A draft article has been written. Your only job is to fix AI-tone violations and return the corrected text. Do NOT change the content, facts, or structure unless they directly violate a rule below.

=== REVISION RULES ===
{powell_revise_guide}

=== SPECIFIC VIOLATIONS TO SCAN AND FIX ===
1. 比喻堆砌：全篇比喻不超过1个，多余的直接删除，用大白话替代。
2. 用力过猛的形容词：删除"灾难级的"、"折磨人"、"不止一个量级"等夸张修饰，换成具体描述。
3. 造概念/大词空转：删除"数字化容器"、"温和池架构"等抽象包装，用读者能直接理解的说法。
4. 鸡汤结尾：删除所有"值得跨越"、"安置灵魂"等励志收尾，改为具体行动建议或直接停住。
5. 语气不统一：如果开头是口语风格，全文保持口语；不要中途切换成书面语或抒情体。
6. 首尾呼应：禁止把开头观点在结尾重复一遍。
7. 过渡词堆积：删除"首先...其次...最后..."，直接陈述。
8. 排比对仗：打破字数相近的并列句，制造长短落差。

=== DRAFT TO REVIEW ===
{draft}

=== OUTPUT ===
Return ONLY the corrected text. No explanation, no meta-commentary.
"""

            review_prompt = f"""You are a strict editor. A draft article has been written. Your ONLY job is to remove AI tone. Do NOT change facts or add new content.

=== PRIORITY 1: VOCABULARY KILL LIST (逐词扫描，命中即删或换大白话) ===
夸张修饰: 极度脆弱、毁灭性的、灾难级的、压倒性的、远超想象、不止一个量级、颠覆性的、革命性的、折磨人、天翻地覆
大词空转: 范式转移、范式突变、开发范式、数字化容器、数字雇员、温和池架构、非确定性、阅后即焚、毫秒级就绪、赋能、底层逻辑、仪式性努力、深刻变革
鸡汤/口号: 正途、值得跨越、安置灵魂、掌控感、才是王道、苦活累活交给、未来已来、危机就是转机
伪口语: 说白了、说实话、折腾这玩意儿

=== PRIORITY 2: SENTENCE PATTERN KILL LIST (命中即改写) ===
1. 防御性写法："很多人觉得X，实际上Y"、"有些人认为X，但其实Y" → 直接说你的判断，删掉"很多人"铺垫
2. 换皮夸张：把一个夸张词换成另一个夸张词不算修好。"灾难级"→"毁灭性"是无效修改。正确做法是换成具体后果描述
3. 概念包装：给普通事物套新名词（"数字雇员"="自动化脚本"、"温池"="预备好的虚拟机"）→ 拆掉包装，用读者直接能懂的说法
4. 比喻堆砌：全篇最多保留1个比喻，多余的全部删除换成直说
5. 鸡汤/口号结尾：删除所有励志收尾，改为具体行动建议或直接停住
6. 首尾呼应：禁止结尾重复开头观点
7. 语气漂移：全文语气必须统一，开头口语就全程口语，不要中途切书面语或抒情体
8. 过渡词堆积：删除"首先...其次...最后..."
9. 排比对仗：打破字数相近的并列句

=== PRIORITY 3: FULL REVISION GUIDE ===
{powell_revise_guide}

=== DRAFT TO REVIEW ===
{draft}

=== OUTPUT ===
Return ONLY the corrected text. No explanation, no meta-commentary.
"""


The file /root/projects/butler/slack_bot/obsidian/generators.py has been updated successfully.

            review_prompt = f"""You are a strict editor. A draft article has been written. Your ONLY job is to remove AI tone. Do NOT change facts or add new content.

=== PRIORITY 1: VOCABULARY KILL LIST (逐词扫描，命中即删或换大白话) ===
夸张修饰: 极度脆弱、毁灭性的、灾难级的、压倒性的、远超想象、不止一个量级、颠覆性的、革命性的、折磨人、天翻地覆
大词空转: 范式转移、范式突变、开发范式、数字化容器、数字雇员、温和池架构、非确定性、阅后即焚、毫秒级就绪、赋能、底层逻辑、仪式性努力、深刻变革
鸡汤/口号: 正途、值得跨越、安置灵魂、掌控感、才是王道、苦活累活交给、未来已来、危机就是转机
伪口语: 说白了、说实话、折腾这玩意儿

=== PRIORITY 2: SENTENCE PATTERN KILL LIST (命中即改写) ===
1. 防御性写法："很多人觉得X，实际上Y"、"有些人认为X，但其实Y" → 直接说你的判断，删掉"很多人"铺垫
2. 换皮夸张：把一个夸张词换成另一个夸张词不算修好。"灾难级"→"毁灭性"是无效修改。正确做法是换成具体后果描述
3. 概念包装：给普通事物套新名词（"数字雇员"="自动化脚本"、"温池"="预备好的虚拟机"）→ 拆掉包装，用读者直接能懂的说法
4. 比喻堆砌：全篇最多保留1个比喻，多余的全部删除换成直说
5. 鸡汤/口号结尾：删除所有励志收尾，改为具体行动建议或直接停住
6. 首尾呼应：禁止结尾重复开头观点
7. 语气漂移：全文语气必须统一，开头口语就全程口语，不要中途切书面语或抒情体
8. 过渡词堆积：删除"首先...其次...最后..."
9. 排比对仗：打破字数相近的并列句

=== PRIORITY 3: FULL REVISION GUIDE ===
{powell_revise_guide}

=== DRAFT TO REVIEW ===
{draft}

=== OUTPUT ===
Return ONLY the corrected text. No explanation, no meta-commentary.
"""

import os
from pathlib import Path
from typing import List, Dict, Tuple, Optional, TYPE_CHECKING
from slack_bot.llm.gemini import GeminiLLM
from slack_bot.obsidian.indexer import ObsidianIndexer
from slack_bot.tools.web import WebSearchTool
from health.utils.logging_config import setup_logger
from health.utils.time_utils import get_current_time_str

if TYPE_CHECKING:
    from slack_bot.obsidian.vector_store import ChromaVectorStore

logger = setup_logger(__name__)

OBSIDIAN_SYSTEM_PROMPT = """You are Butler (Obsidian Edition), an intelligent knowledge assistant connected to the user's second brain.
Current Time: {current_time}

Your Role:
You are NOT a health assistant. You are a Knowledge Partner designed to help the user think, write, and communicate. You draw directly from the user's local Obsidian notes, values, and methodology.

Core Capabilities:
1. Writing: You mimic the user's unique writing style (as defined in `writing_style.md`).
2. Communication: You draft high-EQ, logically rigorous replies for professional contexts (as defined in `REPLY-SAMPLE.md`).
3. Decision: You act as a "Devil's Advocate" and strategic advisor using the user's decision frameworks (GPA, IPO).

Guidelines:
- **Style Alignment**: Strictly adhere to the tone and sentence structures found in the provided samples.
- **Data Source**: Rely primarily on the provided context (RAG) and loaded markdown files.
- **Identity**: You are pragmatic, rational, and value "technological optimism" and "intellectual honesty".
"""

class BaseGenerator:
    def __init__(
        self,
        indexer: ObsidianIndexer,
        vector_store: Optional["ChromaVectorStore"] = None,
    ):
        formatted_prompt = OBSIDIAN_SYSTEM_PROMPT.format(current_time=get_current_time_str())
        self.llm = GeminiLLM(system_instruction=formatted_prompt)
        self.indexer = indexer
        self.vector_store = vector_store
        self.workspace_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))

    def _read_file(self, filename: str) -> str:
        """Reads a file from the workspace root."""
        path = os.path.join(self.workspace_root, filename)
        try:
            with open(path, "r", encoding="utf-8") as f:
                return f.read()
        except Exception as e:
            logger.error(f"Failed to read {filename}: {e}")
            return ""

    def _semantic_search(self, query: str, top_k: int = 3) -> str:
        """Run semantic search against the vector store and format results.

        Args:
            query: Search query text.
            top_k: Maximum number of chunks to retrieve.

        Returns:
            Formatted string block, or empty string if no results.
        """
        if self.vector_store is None:
            return ""
        try:
            results = self.vector_store.search_knowledge(query, top_k=top_k)
        except Exception as e:
            logger.warning(f"_semantic_search failed: {e}")
            return ""
        if not results:
            return ""
        lines = ["=== SEMANTIC KNOWLEDGE FROM VAULT ==="]
        for r in results:
            lines.append(f"[{Path(r.source_path).name} / {r.header_hierarchy}]")
            # Truncate individual chunks to avoid prompt bloat (~500 tokens ≈ 2000 chars)
            chunk_text = r.text[:2000]
            lines.append(chunk_text)
            lines.append("")
        return "\n".join(lines)

class WritingAssistant(BaseGenerator):
    def chat(self, user_input: str, history: List[Dict]) -> Tuple[str, List[Dict]]:

        is_first_turn = (len(history) == 0)

        if is_first_turn:
            # === First Turn: Build Rich Prompt ===
            # user_input is treated as "Topic"

            # Naive splitting for context if formatted as "Topic | Context"
            parts = user_input.split("|", 1)
            topic = parts[0].strip()
            extra_context = parts[1].strip() if len(parts) > 1 else ""

            # 1. RAG & Samples
            style_samples = self.indexer.get_writing_samples(count=3)
            style_text = "\n\n".join(style_samples)

            rag_notes = self.indexer.search(topic, limit=5)
            rag_text = "\n\n".join(rag_notes)

            writing_style_guide = self._read_file("writing_style.md")
            methodology = self._read_file("methodology.md")
            powell_revise_guide = self._read_file("POWELL_REVISE.md")

            semantic_context = self._semantic_search(topic, top_k=3)

            prompt = f"""You are a ghostwriter for the user. Your goal is to write an article on the TOPIC provided.

            === YOUR IDENTITY & METHODOLOGY ===
            {methodology}

            === YOUR WRITING STYLE GUIDE ===
            {writing_style_guide}

            === ANTI-AI-TONE GUIDE (APPLY WHILE WRITING, NOT JUST AT THE END) ===
            {powell_revise_guide}

            === YOUR WRITING SAMPLES (MIMIC THIS TONE) ===
            {style_text}

            === RELEVANT NOTES FROM OBSIDIAN VAULT ===
            {rag_text}

            {semantic_context}

            === TASK ===
            Topic: {topic}
            Extra Context: {extra_context}

            Write the article in Chinese (unless the topic implies English).
            Adhere strictly to the "Identity" and "Style Guide".
            """

            logger.info(f"Generating article for topic: {topic}")
            actual_user_message = prompt # We inject the big prompt

        else:
            # === Follow-up Turn ===
            # user_input is just instructions like "Make it shorter"
            logger.info(f"Follow-up instruction: {user_input}")
            actual_user_message = user_input
            prompt = user_input

        # Call LLM — first pass: content generation
        draft, _ = self.llm.generate_response(prompt, history)

        # === Second pass: de-AI review (first turn only) ===
        if is_first_turn:
            powell_revise_guide = self._read_file("POWELL_REVISE.md")
            review_prompt = f"""You are a strict editor. A draft article has been written. Your only job is to fix AI-tone violations and return the corrected text. Do NOT change the content, facts, or structure unless they directly violate a rule below.

=== REVISION RULES ===
{powell_revise_guide}

=== SPECIFIC VIOLATIONS TO SCAN AND FIX ===
1. 比喻堆砌：全篇比喻不超过1个，多余的直接删除，用大白话替代。
2. 用力过猛的形容词：删除"灾难级的"、"折磨人"、"不止一个量级"等夸张修饰，换成具体描述。
3. 造概念/大词空转：删除"数字化容器"、"温和池架构"等抽象包装，用读者能直接理解的说法。
4. 鸡汤结尾：删除所有"值得跨越"、"安置灵魂"等励志收尾，改为具体行动建议或直接停住。
5. 语气不统一：如果开头是口语风格，全文保持口语；不要中途切换成书面语或抒情体。
6. 首尾呼应：禁止把开头观点在结尾重复一遍。
7. 过渡词堆积：删除"首先...其次...最后..."，直接陈述。
8. 排比对仗：打破字数相近的并列句，制造长短落差。

=== DRAFT TO REVIEW ===
{draft}

=== OUTPUT ===
Return ONLY the corrected text. No explanation, no meta-commentary.
"""
            logger.info("Running second-pass de-AI review on writing draft...")
            response, _ = self.llm.generate_response(review_prompt, [])
        else:
            response = draft

        # Update History
        new_history = history.copy()
        new_history.append({"role": "user", "content": actual_user_message})
        new_history.append({"role": "assistant", "content": response})

        return response, new_history

class ReplyGenerator(BaseGenerator):
    def chat(self, user_input: str, history: List[Dict]) -> Tuple[str, List[Dict]]:

        is_first_turn = (len(history) == 0)

        if is_first_turn:
             # === First Turn ===
            static_samples = self._read_file("REPLY-SAMPLE.md")
            dynamic_samples = self.indexer.get_reply_samples(count=3)
            dynamic_samples_text = "\n\n".join(dynamic_samples)
            methodology = self._read_file("methodology.md")

            semantic_context = self._semantic_search(user_input, top_k=3)

            prompt = f"""You are an experienced Product/R&D Manager.
            Your task is to draft a reply to a challenging situation.

            === CORE PHILOSOPHY ===
            {methodology}

            === REPLY GUIDELINES ===
            1. Logic: Be clear and structured (e.g., numbered lists).
            2. Responsibility: Don't take unnecessary blame, but don't shirk core duties. Explain objective causes.
            3. Emotion: Be empathetic but professional.
            4. Conciseness: No fluff.

            === REFERENCE SAMPLES (FEW-SHOT) ===
            {static_samples}

            {dynamic_samples_text}

            {semantic_context}

            === THE SITUATION ===
            {user_input}

            Draft a reply. If the context implies a specific role (PM, Dev Lead), assume that role.
            """
            logger.info(f"Generating reply for query: {user_input}")
            actual_user_message = prompt

        else:
             # === Follow-up Turn ===
            logger.info(f"Follow-up reply instruction: {user_input}")
            actual_user_message = user_input
            prompt = user_input

        response, _ = self.llm.generate_response(prompt, history)

        new_history = history.copy()
        new_history.append({"role": "user", "content": actual_user_message})
        new_history.append({"role": "assistant", "content": response})

        return response, new_history

class DecisionSupport(BaseGenerator):
    def chat(self, user_input: str, history: List[Dict]) -> Tuple[str, List[Dict]]:

        is_first_turn = (len(history) == 0)

        if is_first_turn:
            decision_guide = self._read_file("decision.md")
            methodology = self._read_file("methodology.md")

            rag_notes = self.indexer.search(user_input, limit=3)
            rag_text = "\n\n".join(rag_notes)

            prompt = f"""You are a Decision Support Assistant.
            Your goal is to help the user think through a complex decision.

            === DECISION FRAMEWORK ===
            {decision_guide}

            === CORE PHILOSOPHY ===
            {methodology}

            === RELEVANT CONTEXT FROM NOTES ===
            {rag_text}

            === THE DECISION / ISSUE ===
            {user_input}

            === TASK ===
            1. Challenge the premise: Is this the right problem to solve?
            2. Apply the Framework: Use GPA (Goal, Priority, Alternatives) and IPO (Information, People, Objective reasoning) models.
            3. Pre-mortem: What is the worst that could happen?
            4. Provide a recommendation or a set of questions to clarify.
            """

            logger.info(f"Analyzing decision: {user_input}")
            actual_user_message = prompt
        else:
            logger.info(f"Follow-up decision instruction: {user_input}")
            actual_user_message = user_input
            prompt = user_input

        response, _ = self.llm.generate_response(prompt, history)

        new_history = history.copy()
        new_history.append({"role": "user", "content": actual_user_message})
        new_history.append({"role": "assistant", "content": response})

        return response, new_history

class SearchAnalyzer(BaseGenerator):
    def chat(self, user_input: str, history: List[Dict]) -> Tuple[str, List[Dict]]:

        # Tools definition for the LLM
        tools = [
            {
                "type": "function",
                "function": {
                    "name": "search_web",
                    "description": "Search the public web using DuckDuckGo. Use this when the internal notes are insufficient or when you need up-to-date external information.",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "query": {
                                "type": "string",
                                "description": "The search query"
                            }
                        },
                        "required": ["query"]
                    }
                }
            },
            {
                "type": "function",
                "function": {
                    "name": "list_recent_files",
                    "description": "List files in the Obsidian vault that have been modified recently. Use this when the user asks for 'recent updates', 'what's new', or changes in the last X days.",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "days": {
                                "type": "integer",
                                "description": "Number of days to check (default 5)",
                                "default": 5
                            }
                        },
                        "required": []
                    }
                }
            }
        ]

        is_first_turn = (len(history) == 0)

        # Current Turn Message
        if is_first_turn:
            rag_notes = self.indexer.search(user_input, limit=5)
            rag_text = "\n\n".join(rag_notes)

            logger.info(f"SearchAnalyzer retrieved {len(rag_notes)} local notes.")
            for note in rag_notes:
                # Extract source line for logging
                first_line = note.split('\n')[0]
                logger.debug(f" - Retrieved: {first_line}")

            prompt = f"""You are an Analyst using Butler (Obsidian Edition).

            === TASK ===
            Answer the user's question.
            CRITICAL: You MUST explicitly cite your sources in the output.

            === STRATEGY ===
            1. first: Check "RELEVANT LOCAL NOTES" provided below.
            2. second: If the notes answer the question, answer directly.
            3. third: If the notes are missing info, ambiguos, or outdated, USE THE `search_web` TOOL to find external info.

            === OUTPUT FORMAT ===
            - Begin with a summary.
            - When stating facts, append the source in brackets, e.g., "The server failed [Source: XLSmart 2025-12-25.md]" or "Market share is 20% [Source: Web Search]".
            - If data comes from BOTH, mention both.

            === RELEVANT LOCAL NOTES ===
            {rag_text}

            === USER QUESTION ===
            {user_input}
            """
            actual_user_message = prompt
        else:
            actual_user_message = user_input

        # Prepare execution context
        # We don't want to mutate the input history directly until the end
        exec_history = history.copy()
        exec_history.append({"role": "user", "content": actual_user_message})

        logger.info(f"Analyzer thinking on: {user_input}")

        # === ReAct Loop (Max 2 turns to prevent infinite loops) ===
        for i in range(2):
            response_text, tool_calls = self.llm.generate_response(
                message="", # Message is already in history
                context=exec_history,
                tools=tools
            )

            if tool_calls:
                # LLM wants to use tools
                logger.info(f"LLM requested tool calls: {tool_calls}")

                # Append the assistant's request (with tool calls) to history
                exec_history.append({
                    "role": "assistant",
                    "content": f"[Tool Call Request: {tool_calls}]"
                })

                for tc in tool_calls:
                    if tc["name"] == "search_web":
                        query = tc["args"].get("query")
                        search_result = WebSearchTool.search_web(query)

                        # Feed result back
                        exec_history.append({
                            "role": "user", # Using 'function' role would be better if our LLM class supports it fully, but 'user' works as system injection
                            "content": f"--- TOOL OUTPUT (search_web) ---\n{search_result}"
                        })

                    elif tc["name"] == "list_recent_files":
                        days = tc["args"].get("days", 5)
                        # Call indexer method
                        recent_list = self.indexer.get_recent_files(days=days)

                        exec_history.append({
                            "role": "user",
                            "content": f"--- TOOL OUTPUT (list_recent_files) ---\n{recent_list}"
                        })

                # Loop again to get the final answer using tool outputs
                try:
                     # Second pass with tool outputs
                    response_text, _ = self.llm.generate_response(
                        message="",
                        context=exec_history,
                        tools=tools
                    )
                    return response_text, exec_history
                except Exception as e:
                    logger.error(f"Error in second ReAct loop: {e}")
                    return response_text, exec_history

            else:
                # Final answer (no tools)
                return response_text, exec_history

        # Fallback if loop exhausted
        return response_text, exec_history

class DeAIReviser(BaseGenerator):
    """Remove AI tone from articles - text-in, text-out mode."""

    def chat(self, user_input: str, history: List[Dict]) -> Tuple[str, List[Dict]]:

        is_first_turn = (len(history) == 0)

        if is_first_turn:
            # === First Turn: User pastes the AI-generated article ===
            # Load the POWELL_REVISE.md guide
            powell_revise_guide = self._read_file("POWELL_REVISE.md")

            # Load user's writing samples for style reference
            style_samples = self.indexer.get_writing_samples(count=3)
            style_text = "\n\n".join(style_samples)

            prompt = f"""You are an expert editor specializing in removing AI-generated tone from articles.

Your task is to revise the article provided by the user to make it sound more human and authentic.

=== REVISION GUIDE ===
{powell_revise_guide}

=== USER'S AUTHENTIC WRITING SAMPLES (for style reference) ===
{style_text}

=== ARTICLE TO REVISE ===
{user_input}

=== TASK ===
1. Analyze the article and identify AI characteristics (过渡词堆积, 排比对仗, 空洞修饰词等)
2. Rewrite the article following the POWELL_REVISE.md principles
3. Preserve:
   - Original meaning and key information
   - Technical terms and proper nouns
   - Code blocks, lists, and formatting
   - Original language (Chinese/English)
4. Output the revised article directly (no meta-commentary like "Here's the revised version...")

CRITICAL: Output ONLY the revised article text. Do NOT add explanations before or after.
"""

            logger.info(f"DeAI Reviser: Processing article ({len(user_input)} chars)")
            actual_user_message = prompt

        else:
            # === Follow-up Turn: User gives refinement instructions ===
            logger.info(f"DeAI Reviser: Follow-up instruction - {user_input}")
            actual_user_message = user_input
            prompt = user_input

        # Call LLM
        response, _ = self.llm.generate_response(prompt, history)

        # Update History
        new_history = history.copy()
        new_history.append({"role": "user", "content": actual_user_message})
        new_history.append({"role": "assistant", "content": response})

        return response, new_history

class ZhihuGenerator(BaseGenerator):
    """Generate Zhihu-style answers based on style guide and user's knowledge base."""

    def chat(self, user_input: str, history: List[Dict]) -> Tuple[str, List[Dict]]:

        is_first_turn = (len(history) == 0)
        built_rich_prompt = False  # tracks whether we ran the full question->draft pipeline

        # Pre-load rule files (needed for both generation and second-pass review)
        zhihu_bot_style = self._read_file("zhihu-bot-style.md")
        powell_revise_guide = self._read_file("POWELL_REVISE.md")

        # Treat as a new question if:
        # - history is empty (genuine first turn), OR
        # - user input contains "|" (explicit "Question | Core Ideas" format), OR
        # - user input starts with known question triggers like "回答这个问题"
        is_new_question = (
            is_first_turn
            or "|" in user_input
            or user_input.strip().startswith("回答这个问题")
        )

        if is_new_question:
            # === First Turn: Build Rich Prompt ===
            # Expected input format: "Question | Core Ideas"
            parts = user_input.split("|", 1)
            question = parts[0].strip()
            core_ideas = parts[1].strip() if len(parts) > 1 else ""

            # 1. Load Style Guide (zhihu-style.md serves as writing standard, NOT as material)
            zhihu_style_guide = self._read_file("zhihu-style.md")
            # Bot-specific overrides (plain-text output, no bracket explanations, etc.)
            if zhihu_bot_style:
                zhihu_style_guide = zhihu_style_guide + "\n\n" + zhihu_bot_style

            # 2. RAG from Obsidian vault for actual content material
            rag_notes = self.indexer.search(question, limit=5)
            rag_text = "\n\n".join(rag_notes)

            # 3. Load methodology
            methodology = self._read_file("methodology.md")

            semantic_context = self._semantic_search(question, top_k=3)

            prompt = f"""You are answering a Zhihu question. Your goal is to write an authentic, experience-driven answer that sounds like a real human, not AI-generated.

=== YOUR IDENTITY & METHODOLOGY ===
{methodology}

=== ANTI-AI-TONE GUIDE (APPLY WHILE WRITING, NOT JUST AT THE END) ===
{powell_revise_guide}

=== ZHIHU STYLE GUIDE (FOLLOW THESE WRITING PRINCIPLES) ===
{zhihu_style_guide}

CRITICAL: The style guide above defines HOW to write (tone, structure, language patterns).
Do NOT treat zhihu-sample content as factual material. It's ONLY for style reference.

=== RELEVANT KNOWLEDGE FROM YOUR OBSIDIAN VAULT (USE AS ACTUAL MATERIAL) ===
{rag_text}

{semantic_context}

=== THE QUESTION ===
{question}

=== YOUR CORE IDEAS ===
{core_ideas}

=== TASK ===
1. Answer the question based on YOUR knowledge (from Obsidian vault) and core ideas
2. FOLLOW THE ANTI-AI-TONE GUIDE FIRST, then the Zhihu style guide
3. Share real experiences, examples, and insights from the vault material
4. Write in Chinese (unless question implies English)
5. Minimize first person "我": use it sparingly, not as filler. Observations and judgments can stand without "我认为" or "我觉得" as prefix.
6. Be direct and opinionated. Attach a concrete evaluation to every fact or feature you mention (e.g. "暴力但有效", "极度脆弱"). Pure feature listing is forbidden.
7. AVOID AT ALL COSTS: AI tone, empty buzzwords, hollow phrases, and 宏大叙事 (不可逆的浪潮, 范式转移, 时代大背景).
8. If recent web context is provided, weave it in naturally — like someone who reads a lot online
9. STRICT: Never add bracket explanations for any term. Write "大语言模型" not "大语言模型（LLM）". If a term needs explaining, use a sentence, not parentheses.
10. No public account names, no self-promotion, no引流 of any kind — this is the highest priority rule.
11. STRICT: Never reveal personal identity or professional background. Do not mention employer, team, job title, product name, or any detail that could identify who you are.
12. STRICT: Never reference past projects, articles, or talks by name.
13. No poetry, quotes, or "升华" endings. No首尾呼应 (repeating the opening point at the end). Stop when the point is made.
14. LENGTH: 500–1000 Chinese characters. Cut a point, not quality.

=== BEFORE SUBMITTING, CHECK THESE ===
- Is there any AI-generated tone or phrasing?
- Are there any buzzwords or jargon that need to be replaced with plain language?
- Is the answer direct and opinionated, not neutral or "balanced"?
- Does it sound like a real human wrote it, not a machine?

Begin writing the answer:
"""

            logger.info(f"Generating Zhihu answer for: {question[:50]}...")
            actual_user_message = prompt
            built_rich_prompt = True

        else:
            # === Follow-up Turn: User gives refinement instructions (e.g. "make it shorter") ===
            logger.info(f"Follow-up Zhihu instruction: {user_input}")
            actual_user_message = user_input
            prompt = user_input

        # Call LLM — first pass: content generation
        # For new questions, use empty history to avoid contamination from previous conversations
        llm_history = [] if is_new_question else history
        draft, _ = self.llm.generate_response(prompt, llm_history)

        # === Second pass: rule enforcement ===
        # Runs whenever a full draft was generated (built_rich_prompt=True),
        # regardless of whether history was empty or not.
        # Skipped on follow-up refinement turns (e.g. "make it shorter").
        run_review = built_rich_prompt
        if run_review:
            review_prompt = f"""You are a strict editor. A draft Zhihu answer has been written. Your only job is to fix rule violations and return the corrected text. Do NOT change the content, facts, or structure unless they directly violate a rule below.

=== RULES TO ENFORCE ===

{zhihu_bot_style}

{powell_revise_guide}

=== SPECIFIC VIOLATIONS TO SCAN AND FIX ===
1. 对比式定义句式：删除或改写所有"不是X而是Y"、"不能当X用得当Y用"、"那不叫X那叫Y"、"别把X当Y要把X当Z"句式。直接陈述即可。
2. 编号分段：删除所有"一、二、三、四"序号标题，改为无标题段落自然过渡。
3. 防御性引号：删除所有套在归纳性词汇上的引号（如"数字替身"、"包工头模式"、"统一入口"、"调教"等）。
4. 伪口语：删除"说白了"、"说实话"、"折腾这玩意儿"。
5. 强行总结：删除任何"一句话建议"、"总结一句话"、"综上"式收尾段。
6. 句长均匀：如果连续三句以上长度相近，拆短其中至少一句，制造落差。
7. 嵌套数字列表：删除正文中"1. 2. 3."的子列表，改为连续段落。

=== DRAFT TO REVIEW ===
{draft}

=== OUTPUT ===
Return ONLY the corrected Chinese text. No explanation, no meta-commentary, no English.
"""
            logger.info("Running second-pass rule enforcement on Zhihu draft...")
            response, _ = self.llm.generate_response(review_prompt, [])
        else:
            response = draft

        # Update History
        new_history = history.copy()
        new_history.append({"role": "user", "content": actual_user_message})
        new_history.append({"role": "assistant", "content": response})

        return response, new_history

-            review_prompt = f"""You are a strict editor. A draft article has been written. Your only job is to fix AI-tone violations and return the corrected text. Do NOT change the content, facts, or structure unless they directly violate a rule below.

+            review_prompt = f"""You are a strict editor. A draft article has been written. Your ONLY job is to remove AI tone. Do NOT change facts or add new content.

+=== PRIORITY 1: VOCABULARY KILL LIST (逐词扫描，命中即删或换大白话) ===

+夸张修饰: 极度脆弱、毁灭性的、灾难级的、压倒性的、远超想象、不止一个量级、颠覆性的、革命性的、折磨人、天翻地覆

+大词空转: 范式转移、范式突变、开发范式、数字化容器、数字雇员、温和池架构、非确定性、阅后即焚、毫秒级就绪、赋能、底层逻辑、仪式性努力、深刻变革

+鸡汤/口号: 正途、值得跨越、安置灵魂、掌控感、才是王道、苦活累活交给、未来已来、危机就是转机

+伪口语: 说白了、说实话、折腾这玩意儿

-=== SPECIFIC VIOLATIONS TO SCAN AND FIX ===

-1. 比喻堆砌：全篇比喻不超过1个，多余的直接删除，用大白话替代。

-2. 用力过猛的形容词：删除"灾难级的"、"折磨人"、"不止一个量级"等夸张修饰，换成具体描述。

-3. 造概念/大词空转：删除"数字化容器"、"温和池架构"等抽象包装，用读者能直接理解的说法。

-4. 鸡汤结尾：删除所有"值得跨越"、"安置灵魂"等励志收尾，改为具体行动建议或直接停住。

-5. 语气不统一：如果开头是口语风格，全文保持口语；不要中途切换成书面语或抒情体。

-6. 首尾呼应：禁止把开头观点在结尾重复一遍。

-7. 过渡词堆积：删除"首先...其次...最后..."，直接陈述。

-8. 排比对仗：打破字数相近的并列句，制造长短落差。

+=== PRIORITY 2: SENTENCE PATTERN KILL LIST (命中即改写) ===

+1. 防御性写法："很多人觉得X，实际上Y"、"有些人认为X，但其实Y" → 直接说你的判断，删掉"很多人"铺垫

+2. 换皮夸张：把一个夸张词换成另一个夸张词不算修好。"灾难级"→"毁灭性"是无效修改。正确做法是换成具体后果描述

+3. 概念包装：给普通事物套新名词（"数字雇员"="自动化脚本"、"温池"="预备好的虚拟机"）→ 拆掉包装，用读者直接能懂的说法

+4. 比喻堆砌：全篇最多保留1个比喻，多余的全部删除换成直说

+5. 鸡汤/口号结尾：删除所有励志收尾，改为具体行动建议或直接停住

+6. 首尾呼应：禁止结尾重复开头观点

+7. 语气漂移：全文语气必须统一，开头口语就全程口语，不要中途切书面语或抒情体

+8. 过渡词堆积：删除"首先...其次...最后..."

+9. 排比对仗：打破字数相近的并列句

+=== PRIORITY 3: FULL REVISION GUIDE ===


现在改知乎的 review prompt。


            review_prompt = f"""You are a strict editor. A draft Zhihu answer has been written. Your only job is to fix rule violations and return the corrected text. Do NOT change the content, facts, or structure unless they directly violate a rule below.

=== RULES TO ENFORCE ===

{zhihu_bot_style}

{powell_revise_guide}

=== SPECIFIC VIOLATIONS TO SCAN AND FIX ===
1. 对比式定义句式：删除或改写所有"不是X而是Y"、"不能当X用得当Y用"、"那不叫X那叫Y"、"别把X当Y要把X当Z"句式。直接陈述即可。
2. 编号分段：删除所有"一、二、三、四"序号标题，改为无标题段落自然过渡。
3. 防御性引号：删除所有套在归纳性词汇上的引号（如"数字替身"、"包工头模式"、"统一入口"、"调教"等）。
4. 伪口语：删除"说白了"、"说实话"、"折腾这玩意儿"。
5. 强行总结：删除任何"一句话建议"、"总结一句话"、"综上"式收尾段。
6. 句长均匀：如果连续三句以上长度相近，拆短其中至少一句，制造落差。
7. 嵌套数字列表：删除正文中"1. 2. 3."的子列表，改为连续段落。

=== DRAFT TO REVIEW ===
{draft}

=== OUTPUT ===
Return ONLY the corrected Chinese text. No explanation, no meta-commentary, no English.
"""

            review_prompt = f"""You are a strict editor. A draft Zhihu answer has been written. Your ONLY job is to remove AI tone and make it sound like a normal person typing on Zhihu. Do NOT change facts or add new content.

=== PRIORITY 1: VOCABULARY KILL LIST (逐词扫描，命中即删或换大白话) ===
夸张修饰: 极度脆弱、毁灭性的、灾难级的、压倒性的、远超想象、不止一个量级、颠覆性的、革命性的、折磨人、天翻地覆、本质上
大词空转: 范式转移、范式突变、开发范式、数字化容器、数字雇员、温和池架构、非确定性、阅后即焚、毫秒级就绪、赋能、底层逻辑、仪式性努力、深刻变革、不可逆的浪潮、时代大背景
鸡汤/口号: 正途、值得跨越、安置灵魂、掌控感、才是王道、苦活累活交给、未来已来、危机就是转机、挑战与机遇并存
伪口语: 说白了、说实话、折腾这玩意儿

=== PRIORITY 2: SENTENCE PATTERN KILL LIST (命中即改写) ===
1. 防御性写法："很多人觉得X，实际上Y"、"有些人认为X，但其实Y"、"不少人觉得X" → 直接说判断，删掉"很多人"铺垫
2. 换皮夸张：用另一个夸张词替换夸张词不算修好。必须换成具体后果或具体场景
3. 概念包装：给普通事物套新名词 → 拆掉包装用大白话
4. 比喻堆砌：全篇最多1个比喻，多余全删换直说
5. 鸡汤/口号结尾：删除励志收尾，给具体建议或直接停
6. 首尾呼应：禁止结尾重复开头
7. 对比式定义："不是X而是Y"、"那不叫X那叫Y" → 直接陈述
8. 编号分段：删除"一、二、三、四"序号标题，改为自然段落
9. 防御性引号：删除套在归纳性词汇上的引号
10. 强行总结：删除"一句话建议"、"综上"式收尾
11. 嵌套数字列表：删除正文中"1. 2. 3."子列表，改为连续段落

=== PRIORITY 3: ZHIHU-SPECIFIC RULES ===
{zhihu_bot_style}

=== PRIORITY 4: FULL REVISION GUIDE ===
{powell_revise_guide}

=== TONE TARGET ===
像一个有经验的人在知乎随手打字回答，不是在写公众号文章。平实、具体、不装。能用短句就不用长句，能删就删。

=== DRAFT TO REVIEW ===
{draft}

=== OUTPUT ===
Return ONLY the corrected Chinese text. No explanation, no meta-commentary, no English.
"""


            review_prompt = f"""You are a strict editor. A draft Zhihu answer has been written. Your ONLY job is to remove AI tone and make it sound like a normal person typing on Zhihu. Do NOT change facts or add new content.

=== PRIORITY 1: VOCABULARY KILL LIST (逐词扫描，命中即删或换大白话) ===
夸张修饰: 极度脆弱、毁灭性的、灾难级的、压倒性的、远超想象、不止一个量级、颠覆性的、革命性的、折磨人、天翻地覆、本质上
大词空转: 范式转移、范式突变、开发范式、数字化容器、数字雇员、温和池架构、非确定性、阅后即焚、毫秒级就绪、赋能、底层逻辑、仪式性努力、深刻变革、不可逆的浪潮、时代大背景
鸡汤/口号: 正途、值得跨越、安置灵魂、掌控感、才是王道、苦活累活交给、未来已来、危机就是转机、挑战与机遇并存
伪口语: 说白了、说实话、折腾这玩意儿

=== PRIORITY 2: SENTENCE PATTERN KILL LIST (命中即改写) ===
1. 防御性写法："很多人觉得X，实际上Y"、"有些人认为X，但其实Y"、"不少人觉得X" → 直接说判断，删掉"很多人"铺垫
2. 换皮夸张：用另一个夸张词替换夸张词不算修好。必须换成具体后果或具体场景
3. 概念包装：给普通事物套新名词 → 拆掉包装用大白话
4. 比喻堆砌：全篇最多1个比喻，多余全删换直说
5. 鸡汤/口号结尾：删除励志收尾，给具体建议或直接停
6. 首尾呼应：禁止结尾重复开头
7. 对比式定义："不是X而是Y"、"那不叫X那叫Y" → 直接陈述
8. 编号分段：删除"一、二、三、四"序号标题，改为自然段落
9. 防御性引号：删除套在归纳性词汇上的引号
10. 强行总结：删除"一句话建议"、"综上"式收尾
11. 嵌套数字列表：删除正文中"1. 2. 3."子列表，改为连续段落

=== PRIORITY 3: ZHIHU-SPECIFIC RULES ===
{zhihu_bot_style}

=== PRIORITY 4: FULL REVISION GUIDE ===
{powell_revise_guide}

=== TONE TARGET ===
像一个有经验的人在知乎随手打字回答，不是在写公众号文章。平实、具体、不装。能用短句就不用长句，能删就删。

=== DRAFT TO REVIEW ===
{draft}

=== OUTPUT ===
Return ONLY the corrected Chinese text. No explanation, no meta-commentary, no English.
"""

import os
from pathlib import Path
from typing import List, Dict, Tuple, Optional, TYPE_CHECKING
from slack_bot.llm.gemini import GeminiLLM
from slack_bot.obsidian.indexer import ObsidianIndexer
from slack_bot.tools.web import WebSearchTool
from health.utils.logging_config import setup_logger
from health.utils.time_utils import get_current_time_str

if TYPE_CHECKING:
    from slack_bot.obsidian.vector_store import ChromaVectorStore

logger = setup_logger(__name__)

OBSIDIAN_SYSTEM_PROMPT = """You are Butler (Obsidian Edition), an intelligent knowledge assistant connected to the user's second brain.
Current Time: {current_time}

Your Role:
You are NOT a health assistant. You are a Knowledge Partner designed to help the user think, write, and communicate. You draw directly from the user's local Obsidian notes, values, and methodology.

Core Capabilities:
1. Writing: You mimic the user's unique writing style (as defined in `writing_style.md`).
2. Communication: You draft high-EQ, logically rigorous replies for professional contexts (as defined in `REPLY-SAMPLE.md`).
3. Decision: You act as a "Devil's Advocate" and strategic advisor using the user's decision frameworks (GPA, IPO).

Guidelines:
- **Style Alignment**: Strictly adhere to the tone and sentence structures found in the provided samples.
- **Data Source**: Rely primarily on the provided context (RAG) and loaded markdown files.
- **Identity**: You are pragmatic, rational, and value "technological optimism" and "intellectual honesty".
"""

class BaseGenerator:
    def __init__(
        self,
        indexer: ObsidianIndexer,
        vector_store: Optional["ChromaVectorStore"] = None,
    ):
        formatted_prompt = OBSIDIAN_SYSTEM_PROMPT.format(current_time=get_current_time_str())
        self.llm = GeminiLLM(system_instruction=formatted_prompt)
        self.indexer = indexer
        self.vector_store = vector_store
        self.workspace_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))

    def _read_file(self, filename: str) -> str:
        """Reads a file from the workspace root."""
        path = os.path.join(self.workspace_root, filename)
        try:
            with open(path, "r", encoding="utf-8") as f:
                return f.read()
        except Exception as e:
            logger.error(f"Failed to read {filename}: {e}")
            return ""

    def _semantic_search(self, query: str, top_k: int = 3) -> str:
        """Run semantic search against the vector store and format results.

        Args:
            query: Search query text.
            top_k: Maximum number of chunks to retrieve.

        Returns:
            Formatted string block, or empty string if no results.
        """
        if self.vector_store is None:
            return ""
        try:
            results = self.vector_store.search_knowledge(query, top_k=top_k)
        except Exception as e:
            logger.warning(f"_semantic_search failed: {e}")
            return ""
        if not results:
            return ""
        lines = ["=== SEMANTIC KNOWLEDGE FROM VAULT ==="]
        for r in results:
            lines.append(f"[{Path(r.source_path).name} / {r.header_hierarchy}]")
            # Truncate individual chunks to avoid prompt bloat (~500 tokens ≈ 2000 chars)
            chunk_text = r.text[:2000]
            lines.append(chunk_text)
            lines.append("")
        return "\n".join(lines)

class WritingAssistant(BaseGenerator):
    def chat(self, user_input: str, history: List[Dict]) -> Tuple[str, List[Dict]]:

        is_first_turn = (len(history) == 0)

        if is_first_turn:
            # === First Turn: Build Rich Prompt ===
            # user_input is treated as "Topic"

            # Naive splitting for context if formatted as "Topic | Context"
            parts = user_input.split("|", 1)
            topic = parts[0].strip()
            extra_context = parts[1].strip() if len(parts) > 1 else ""

            # 1. RAG & Samples
            style_samples = self.indexer.get_writing_samples(count=3)
            style_text = "\n\n".join(style_samples)

            rag_notes = self.indexer.search(topic, limit=5)
            rag_text = "\n\n".join(rag_notes)

            writing_style_guide = self._read_file("writing_style.md")
            methodology = self._read_file("methodology.md")
            powell_revise_guide = self._read_file("POWELL_REVISE.md")

            semantic_context = self._semantic_search(topic, top_k=3)

            prompt = f"""You are a ghostwriter for the user. Your goal is to write an article on the TOPIC provided.

            === YOUR IDENTITY & METHODOLOGY ===
            {methodology}

            === YOUR WRITING STYLE GUIDE ===
            {writing_style_guide}

            === ANTI-AI-TONE GUIDE (APPLY WHILE WRITING, NOT JUST AT THE END) ===
            {powell_revise_guide}

            === YOUR WRITING SAMPLES (MIMIC THIS TONE) ===
            {style_text}

            === RELEVANT NOTES FROM OBSIDIAN VAULT ===
            {rag_text}

            {semantic_context}

            === TASK ===
            Topic: {topic}
            Extra Context: {extra_context}

            Write the article in Chinese (unless the topic implies English).
            Adhere strictly to the "Identity" and "Style Guide".
            """

            logger.info(f"Generating article for topic: {topic}")
            actual_user_message = prompt # We inject the big prompt

        else:
            # === Follow-up Turn ===
            # user_input is just instructions like "Make it shorter"
            logger.info(f"Follow-up instruction: {user_input}")
            actual_user_message = user_input
            prompt = user_input

        # Call LLM — first pass: content generation
        draft, _ = self.llm.generate_response(prompt, history)

        # === Second pass: de-AI review (first turn only) ===
        if is_first_turn:
            powell_revise_guide = self._read_file("POWELL_REVISE.md")
            review_prompt = f"""You are a strict editor. A draft article has been written. Your ONLY job is to remove AI tone. Do NOT change facts or add new content.

=== PRIORITY 1: VOCABULARY KILL LIST (逐词扫描，命中即删或换大白话) ===
夸张修饰: 极度脆弱、毁灭性的、灾难级的、压倒性的、远超想象、不止一个量级、颠覆性的、革命性的、折磨人、天翻地覆
大词空转: 范式转移、范式突变、开发范式、数字化容器、数字雇员、温和池架构、非确定性、阅后即焚、毫秒级就绪、赋能、底层逻辑、仪式性努力、深刻变革
鸡汤/口号: 正途、值得跨越、安置灵魂、掌控感、才是王道、苦活累活交给、未来已来、危机就是转机
伪口语: 说白了、说实话、折腾这玩意儿

=== PRIORITY 2: SENTENCE PATTERN KILL LIST (命中即改写) ===
1. 防御性写法："很多人觉得X，实际上Y"、"有些人认为X，但其实Y" → 直接说你的判断，删掉"很多人"铺垫
2. 换皮夸张：把一个夸张词换成另一个夸张词不算修好。"灾难级"→"毁灭性"是无效修改。正确做法是换成具体后果描述
3. 概念包装：给普通事物套新名词（"数字雇员"="自动化脚本"、"温池"="预备好的虚拟机"）→ 拆掉包装，用读者直接能懂的说法
4. 比喻堆砌：全篇最多保留1个比喻，多余的全部删除换成直说
5. 鸡汤/口号结尾：删除所有励志收尾，改为具体行动建议或直接停住
6. 首尾呼应：禁止结尾重复开头观点
7. 语气漂移：全文语气必须统一，开头口语就全程口语，不要中途切书面语或抒情体
8. 过渡词堆积：删除"首先...其次...最后..."
9. 排比对仗：打破字数相近的并列句

=== PRIORITY 3: FULL REVISION GUIDE ===
{powell_revise_guide}

=== DRAFT TO REVIEW ===
{draft}

=== OUTPUT ===
Return ONLY the corrected text. No explanation, no meta-commentary.
"""
            logger.info("Running second-pass de-AI review on writing draft...")
            response, _ = self.llm.generate_response(review_prompt, [])
        else:
            response = draft

        # Update History
        new_history = history.copy()
        new_history.append({"role": "user", "content": actual_user_message})
        new_history.append({"role": "assistant", "content": response})

        return response, new_history

class ReplyGenerator(BaseGenerator):
    def chat(self, user_input: str, history: List[Dict]) -> Tuple[str, List[Dict]]:

        is_first_turn = (len(history) == 0)

        if is_first_turn:
             # === First Turn ===
            static_samples = self._read_file("REPLY-SAMPLE.md")
            dynamic_samples = self.indexer.get_reply_samples(count=3)
            dynamic_samples_text = "\n\n".join(dynamic_samples)
            methodology = self._read_file("methodology.md")

            semantic_context = self._semantic_search(user_input, top_k=3)

            prompt = f"""You are an experienced Product/R&D Manager.
            Your task is to draft a reply to a challenging situation.

            === CORE PHILOSOPHY ===
            {methodology}

            === REPLY GUIDELINES ===
            1. Logic: Be clear and structured (e.g., numbered lists).
            2. Responsibility: Don't take unnecessary blame, but don't shirk core duties. Explain objective causes.
            3. Emotion: Be empathetic but professional.
            4. Conciseness: No fluff.

            === REFERENCE SAMPLES (FEW-SHOT) ===
            {static_samples}

            {dynamic_samples_text}

            {semantic_context}

            === THE SITUATION ===
            {user_input}

            Draft a reply. If the context implies a specific role (PM, Dev Lead), assume that role.
            """
            logger.info(f"Generating reply for query: {user_input}")
            actual_user_message = prompt

        else:
             # === Follow-up Turn ===
            logger.info(f"Follow-up reply instruction: {user_input}")
            actual_user_message = user_input
            prompt = user_input

        response, _ = self.llm.generate_response(prompt, history)

        new_history = history.copy()
        new_history.append({"role": "user", "content": actual_user_message})
        new_history.append({"role": "assistant", "content": response})

        return response, new_history

class DecisionSupport(BaseGenerator):
    def chat(self, user_input: str, history: List[Dict]) -> Tuple[str, List[Dict]]:

        is_first_turn = (len(history) == 0)

        if is_first_turn:
            decision_guide = self._read_file("decision.md")
            methodology = self._read_file("methodology.md")

            rag_notes = self.indexer.search(user_input, limit=3)
            rag_text = "\n\n".join(rag_notes)

            prompt = f"""You are a Decision Support Assistant.
            Your goal is to help the user think through a complex decision.

            === DECISION FRAMEWORK ===
            {decision_guide}

            === CORE PHILOSOPHY ===
            {methodology}

            === RELEVANT CONTEXT FROM NOTES ===
            {rag_text}

            === THE DECISION / ISSUE ===
            {user_input}

            === TASK ===
            1. Challenge the premise: Is this the right problem to solve?
            2. Apply the Framework: Use GPA (Goal, Priority, Alternatives) and IPO (Information, People, Objective reasoning) models.
            3. Pre-mortem: What is the worst that could happen?
            4. Provide a recommendation or a set of questions to clarify.
            """

            logger.info(f"Analyzing decision: {user_input}")
            actual_user_message = prompt
        else:
            logger.info(f"Follow-up decision instruction: {user_input}")
            actual_user_message = user_input
            prompt = user_input

        response, _ = self.llm.generate_response(prompt, history)

        new_history = history.copy()
        new_history.append({"role": "user", "content": actual_user_message})
        new_history.append({"role": "assistant", "content": response})

        return response, new_history

class SearchAnalyzer(BaseGenerator):
    def chat(self, user_input: str, history: List[Dict]) -> Tuple[str, List[Dict]]:

        # Tools definition for the LLM
        tools = [
            {
                "type": "function",
                "function": {
                    "name": "search_web",
                    "description": "Search the public web using DuckDuckGo. Use this when the internal notes are insufficient or when you need up-to-date external information.",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "query": {
                                "type": "string",
                                "description": "The search query"
                            }
                        },
                        "required": ["query"]
                    }
                }
            },
            {
                "type": "function",
                "function": {
                    "name": "list_recent_files",
                    "description": "List files in the Obsidian vault that have been modified recently. Use this when the user asks for 'recent updates', 'what's new', or changes in the last X days.",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "days": {
                                "type": "integer",
                                "description": "Number of days to check (default 5)",
                                "default": 5
                            }
                        },
                        "required": []
                    }
                }
            }
        ]

        is_first_turn = (len(history) == 0)

        # Current Turn Message
        if is_first_turn:
            rag_notes = self.indexer.search(user_input, limit=5)
            rag_text = "\n\n".join(rag_notes)

            logger.info(f"SearchAnalyzer retrieved {len(rag_notes)} local notes.")
            for note in rag_notes:
                # Extract source line for logging
                first_line = note.split('\n')[0]
                logger.debug(f" - Retrieved: {first_line}")

            prompt = f"""You are an Analyst using Butler (Obsidian Edition).

            === TASK ===
            Answer the user's question.
            CRITICAL: You MUST explicitly cite your sources in the output.

            === STRATEGY ===
            1. first: Check "RELEVANT LOCAL NOTES" provided below.
            2. second: If the notes answer the question, answer directly.
            3. third: If the notes are missing info, ambiguos, or outdated, USE THE `search_web` TOOL to find external info.

            === OUTPUT FORMAT ===
            - Begin with a summary.
            - When stating facts, append the source in brackets, e.g., "The server failed [Source: XLSmart 2025-12-25.md]" or "Market share is 20% [Source: Web Search]".
            - If data comes from BOTH, mention both.

            === RELEVANT LOCAL NOTES ===
            {rag_text}

            === USER QUESTION ===
            {user_input}
            """
            actual_user_message = prompt
        else:
            actual_user_message = user_input

        # Prepare execution context
        # We don't want to mutate the input history directly until the end
        exec_history = history.copy()
        exec_history.append({"role": "user", "content": actual_user_message})

        logger.info(f"Analyzer thinking on: {user_input}")

        # === ReAct Loop (Max 2 turns to prevent infinite loops) ===
        for i in range(2):
            response_text, tool_calls = self.llm.generate_response(
                message="", # Message is already in history
                context=exec_history,
                tools=tools
            )

            if tool_calls:
                # LLM wants to use tools
                logger.info(f"LLM requested tool calls: {tool_calls}")

                # Append the assistant's request (with tool calls) to history
                exec_history.append({
                    "role": "assistant",
                    "content": f"[Tool Call Request: {tool_calls}]"
                })

                for tc in tool_calls:
                    if tc["name"] == "search_web":
                        query = tc["args"].get("query")
                        search_result = WebSearchTool.search_web(query)

                        # Feed result back
                        exec_history.append({
                            "role": "user", # Using 'function' role would be better if our LLM class supports it fully, but 'user' works as system injection
                            "content": f"--- TOOL OUTPUT (search_web) ---\n{search_result}"
                        })

                    elif tc["name"] == "list_recent_files":
                        days = tc["args"].get("days", 5)
                        # Call indexer method
                        recent_list = self.indexer.get_recent_files(days=days)

                        exec_history.append({
                            "role": "user",
                            "content": f"--- TOOL OUTPUT (list_recent_files) ---\n{recent_list}"
                        })

                # Loop again to get the final answer using tool outputs
                try:
                     # Second pass with tool outputs
                    response_text, _ = self.llm.generate_response(
                        message="",
                        context=exec_history,
                        tools=tools
                    )
                    return response_text, exec_history
                except Exception as e:
                    logger.error(f"Error in second ReAct loop: {e}")
                    return response_text, exec_history

            else:
                # Final answer (no tools)
                return response_text, exec_history

        # Fallback if loop exhausted
        return response_text, exec_history

class DeAIReviser(BaseGenerator):
    """Remove AI tone from articles - text-in, text-out mode."""

    def chat(self, user_input: str, history: List[Dict]) -> Tuple[str, List[Dict]]:

        is_first_turn = (len(history) == 0)

        if is_first_turn:
            # === First Turn: User pastes the AI-generated article ===
            # Load the POWELL_REVISE.md guide
            powell_revise_guide = self._read_file("POWELL_REVISE.md")

            # Load user's writing samples for style reference
            style_samples = self.indexer.get_writing_samples(count=3)
            style_text = "\n\n".join(style_samples)

            prompt = f"""You are an expert editor specializing in removing AI-generated tone from articles.

Your task is to revise the article provided by the user to make it sound more human and authentic.

=== REVISION GUIDE ===
{powell_revise_guide}

=== USER'S AUTHENTIC WRITING SAMPLES (for style reference) ===
{style_text}

=== ARTICLE TO REVISE ===
{user_input}

=== TASK ===
1. Analyze the article and identify AI characteristics (过渡词堆积, 排比对仗, 空洞修饰词等)
2. Rewrite the article following the POWELL_REVISE.md principles
3. Preserve:
   - Original meaning and key information
   - Technical terms and proper nouns
   - Code blocks, lists, and formatting
   - Original language (Chinese/English)
4. Output the revised article directly (no meta-commentary like "Here's the revised version...")

CRITICAL: Output ONLY the revised article text. Do NOT add explanations before or after.
"""

            logger.info(f"DeAI Reviser: Processing article ({len(user_input)} chars)")
            actual_user_message = prompt

        else:
            # === Follow-up Turn: User gives refinement instructions ===
            logger.info(f"DeAI Reviser: Follow-up instruction - {user_input}")
            actual_user_message = user_input
            prompt = user_input

        # Call LLM
        response, _ = self.llm.generate_response(prompt, history)

        # Update History
        new_history = history.copy()
        new_history.append({"role": "user", "content": actual_user_message})
        new_history.append({"role": "assistant", "content": response})

        return response, new_history

class ZhihuGenerator(BaseGenerator):
    """Generate Zhihu-style answers based on style guide and user's knowledge base."""

    def chat(self, user_input: str, history: List[Dict]) -> Tuple[str, List[Dict]]:

        is_first_turn = (len(history) == 0)
        built_rich_prompt = False  # tracks whether we ran the full question->draft pipeline

        # Pre-load rule files (needed for both generation and second-pass review)
        zhihu_bot_style = self._read_file("zhihu-bot-style.md")
        powell_revise_guide = self._read_file("POWELL_REVISE.md")

        # Treat as a new question if:
        # - history is empty (genuine first turn), OR
        # - user input contains "|" (explicit "Question | Core Ideas" format), OR
        # - user input starts with known question triggers like "回答这个问题"
        is_new_question = (
            is_first_turn
            or "|" in user_input
            or user_input.strip().startswith("回答这个问题")
        )

        if is_new_question:
            # === First Turn: Build Rich Prompt ===
            # Expected input format: "Question | Core Ideas"
            parts = user_input.split("|", 1)
            question = parts[0].strip()
            core_ideas = parts[1].strip() if len(parts) > 1 else ""

            # 1. Load Style Guide (zhihu-style.md serves as writing standard, NOT as material)
            zhihu_style_guide = self._read_file("zhihu-style.md")
            # Bot-specific overrides (plain-text output, no bracket explanations, etc.)
            if zhihu_bot_style:
                zhihu_style_guide = zhihu_style_guide + "\n\n" + zhihu_bot_style

            # 2. RAG from Obsidian vault for actual content material
            rag_notes = self.indexer.search(question, limit=5)
            rag_text = "\n\n".join(rag_notes)

            # 3. Load methodology
            methodology = self._read_file("methodology.md")

            semantic_context = self._semantic_search(question, top_k=3)

            prompt = f"""You are answering a Zhihu question. Your goal is to write an authentic, experience-driven answer that sounds like a real human, not AI-generated.

=== YOUR IDENTITY & METHODOLOGY ===
{methodology}

=== ANTI-AI-TONE GUIDE (APPLY WHILE WRITING, NOT JUST AT THE END) ===
{powell_revise_guide}

=== ZHIHU STYLE GUIDE (FOLLOW THESE WRITING PRINCIPLES) ===
{zhihu_style_guide}

CRITICAL: The style guide above defines HOW to write (tone, structure, language patterns).
Do NOT treat zhihu-sample content as factual material. It's ONLY for style reference.

=== RELEVANT KNOWLEDGE FROM YOUR OBSIDIAN VAULT (USE AS ACTUAL MATERIAL) ===
{rag_text}

{semantic_context}

=== THE QUESTION ===
{question}

=== YOUR CORE IDEAS ===
{core_ideas}

=== TASK ===
1. Answer the question based on YOUR knowledge (from Obsidian vault) and core ideas
2. FOLLOW THE ANTI-AI-TONE GUIDE FIRST, then the Zhihu style guide
3. Share real experiences, examples, and insights from the vault material
4. Write in Chinese (unless question implies English)
5. Minimize first person "我": use it sparingly, not as filler. Observations and judgments can stand without "我认为" or "我觉得" as prefix.
6. Be direct and opinionated. Attach a concrete evaluation to every fact or feature you mention (e.g. "暴力但有效", "极度脆弱"). Pure feature listing is forbidden.
7. AVOID AT ALL COSTS: AI tone, empty buzzwords, hollow phrases, and 宏大叙事 (不可逆的浪潮, 范式转移, 时代大背景).
8. If recent web context is provided, weave it in naturally — like someone who reads a lot online
9. STRICT: Never add bracket explanations for any term. Write "大语言模型" not "大语言模型（LLM）". If a term needs explaining, use a sentence, not parentheses.
10. No public account names, no self-promotion, no引流 of any kind — this is the highest priority rule.
11. STRICT: Never reveal personal identity or professional background. Do not mention employer, team, job title, product name, or any detail that could identify who you are.
12. STRICT: Never reference past projects, articles, or talks by name.
13. No poetry, quotes, or "升华" endings. No首尾呼应 (repeating the opening point at the end). Stop when the point is made.
14. LENGTH: 500–1000 Chinese characters. Cut a point, not quality.

=== BEFORE SUBMITTING, CHECK THESE ===
- Is there any AI-generated tone or phrasing?
- Are there any buzzwords or jargon that need to be replaced with plain language?
- Is the answer direct and opinionated, not neutral or "balanced"?
- Does it sound like a real human wrote it, not a machine?

Begin writing the answer:
"""

            logger.info(f"Generating Zhihu answer for: {question[:50]}...")
            actual_user_message = prompt
            built_rich_prompt = True

        else:
            # === Follow-up Turn: User gives refinement instructions (e.g. "make it shorter") ===
            logger.info(f"Follow-up Zhihu instruction: {user_input}")
            actual_user_message = user_input
            prompt = user_input

        # Call LLM — first pass: content generation
        # For new questions, use empty history to avoid contamination from previous conversations
        llm_history = [] if is_new_question else history
        draft, _ = self.llm.generate_response(prompt, llm_history)

        # === Second pass: rule enforcement ===
        # Runs whenever a full draft was generated (built_rich_prompt=True),
        # regardless of whether history was empty or not.
        # Skipped on follow-up refinement turns (e.g. "make it shorter").
        run_review = built_rich_prompt
        if run_review:
            review_prompt = f"""You are a strict editor. A draft Zhihu answer has been written. Your only job is to fix rule violations and return the corrected text. Do NOT change the content, facts, or structure unless they directly violate a rule below.

=== RULES TO ENFORCE ===

{zhihu_bot_style}

{powell_revise_guide}

=== SPECIFIC VIOLATIONS TO SCAN AND FIX ===
1. 对比式定义句式：删除或改写所有"不是X而是Y"、"不能当X用得当Y用"、"那不叫X那叫Y"、"别把X当Y要把X当Z"句式。直接陈述即可。
2. 编号分段：删除所有"一、二、三、四"序号标题，改为无标题段落自然过渡。
3. 防御性引号：删除所有套在归纳性词汇上的引号（如"数字替身"、"包工头模式"、"统一入口"、"调教"等）。
4. 伪口语：删除"说白了"、"说实话"、"折腾这玩意儿"。
5. 强行总结：删除任何"一句话建议"、"总结一句话"、"综上"式收尾段。
6. 句长均匀：如果连续三句以上长度相近，拆短其中至少一句，制造落差。
7. 嵌套数字列表：删除正文中"1. 2. 3."的子列表，改为连续段落。

=== DRAFT TO REVIEW ===
{draft}

=== OUTPUT ===
Return ONLY the corrected Chinese text. No explanation, no meta-commentary, no English.
"""
            logger.info("Running second-pass rule enforcement on Zhihu draft...")
            response, _ = self.llm.generate_response(review_prompt, [])
        else:
            response = draft

        # Update History
        new_history = history.copy()
        new_history.append({"role": "user", "content": actual_user_message})
        new_history.append({"role": "assistant", "content": response})

        return response, new_history

-            review_prompt = f"""You are a strict editor. A draft Zhihu answer has been written. Your only job is to fix rule violations and return the corrected text. Do NOT change the content, facts, or structure unless they directly violate a rule below.

+            review_prompt = f"""You are a strict editor. A draft Zhihu answer has been written. Your ONLY job is to remove AI tone and make it sound like a normal person typing on Zhihu. Do NOT change facts or add new content.

+=== PRIORITY 1: VOCABULARY KILL LIST (逐词扫描，命中即删或换大白话) ===

+夸张修饰: 极度脆弱、毁灭性的、灾难级的、压倒性的、远超想象、不止一个量级、颠覆性的、革命性的、折磨人、天翻地覆、本质上

+大词空转: 范式转移、范式突变、开发范式、数字化容器、数字雇员、温和池架构、非确定性、阅后即焚、毫秒级就绪、赋能、底层逻辑、仪式性努力、深刻变革、不可逆的浪潮、时代大背景

+鸡汤/口号: 正途、值得跨越、安置灵魂、掌控感、才是王道、苦活累活交给、未来已来、危机就是转机、挑战与机遇并存

+伪口语: 说白了、说实话、折腾这玩意儿

+1. 防御性写法："很多人觉得X，实际上Y"、"有些人认为X，但其实Y"、"不少人觉得X" → 直接说判断，删掉"很多人"铺垫

+2. 换皮夸张：用另一个夸张词替换夸张词不算修好。必须换成具体后果或具体场景

+3. 概念包装：给普通事物套新名词 → 拆掉包装用大白话

+4. 比喻堆砌：全篇最多1个比喻，多余全删换直说

+5. 鸡汤/口号结尾：删除励志收尾，给具体建议或直接停

+6. 首尾呼应：禁止结尾重复开头

+7. 对比式定义："不是X而是Y"、"那不叫X那叫Y" → 直接陈述

+8. 编号分段：删除"一、二、三、四"序号标题，改为自然段落

+9. 防御性引号：删除套在归纳性词汇上的引号

+10. 强行总结：删除"一句话建议"、"综上"式收尾

+11. 嵌套数字列表：删除正文中"1. 2. 3."子列表，改为连续段落

+=== PRIORITY 3: ZHIHU-SPECIFIC RULES ===

+=== PRIORITY 4: FULL REVISION GUIDE ===

-=== SPECIFIC VIOLATIONS TO SCAN AND FIX ===

-1. 对比式定义句式：删除或改写所有"不是X而是Y"、"不能当X用得当Y用"、"那不叫X那叫Y"、"别把X当Y要把X当Z"句式。直接陈述即可。

-2. 编号分段：删除所有"一、二、三、四"序号标题，改为无标题段落自然过渡。

-3. 防御性引号：删除所有套在归纳性词汇上的引号（如"数字替身"、"包工头模式"、"统一入口"、"调教"等）。

-4. 伪口语：删除"说白了"、"说实话"、"折腾这玩意儿"。

-5. 强行总结：删除任何"一句话建议"、"总结一句话"、"综上"式收尾段。

-6. 句长均匀：如果连续三句以上长度相近，拆短其中至少一句，制造落差。

-7. 嵌套数字列表：删除正文中"1. 2. 3."的子列表，改为连续段落。

+像一个有经验的人在知乎随手打字回答，不是在写公众号文章。平实、具体、不装。能用短句就不用长句，能删就删。


两个 review prompt 都改完了。总结一下改动：

---

**改了什么**

两个 review prompt（WritingAssistant + ZhihuGenerator）结构从"泛泛规则+具体违规"改为**三级优先级**：

1. **PRIORITY 1 词汇击杀表** — 把黑名单词汇从 `POWELL_REVISE.md` 附录提到最前面，分四类（夸张修饰 / 大词空转 / 鸡汤口号 / 伪口语），LLM 先逐词扫描
2. **PRIORITY 2 句式击杀表** — 新增"换皮夸张"（明确说换夸张词不算修好）和"防御性写法"（"很多人觉得X，实际上Y"），知乎版额外加了对比式定义、编号分段等
3. **PRIORITY 3/4** — `zhihu_bot_style` 和完整 `powell_revise_guide` 兜底

知乎版额外加了 TONE TARGET："像一个有经验的人在知乎随手打字回答，不是在写公众号文章。平实、具体、不装。"

**新增依赖**：无。**函数签名变更**：无。

**最可能出问题的地方**：review prompt 变长了，如果 Gemini context 接近上限可能被截断。但目前 prompt 总量仍在合理范围内。

**验证**：重启 bot 后在 Slack 发一个写作 topic 或知乎问题，对比输出。


新生成的是不是有点油腻？让大语言模型直接在自己办公电脑上跑代码、改文件，这事儿风险极高。稍微出点岔子，模型产生幻觉写错一个路径，可能就把你重要的家目录洗干净了。给 AI 准备一个专门的执行环境，不是什么进阶选项，是安全底线。一、权限与幻觉的暴力碰撞很多人没意识到 AI 幻觉在执行代码时的破坏力。大模型写代码偶尔会编造不存在的对象，或者在删除文件时定位错误。如果它直接拥有你宿主机的全量权限，轻则删掉项目配置，重则搞崩系统。目前看，让 AI 助手直接接管生产环境非常危险。它需要一个物理隔离的沙箱，一个就算把系统搞烂了、你点一下重置就能恢复原状的隔离舱。这是防御 AI 逻辑跑偏最有效的手段。二、为什么云服务器是解法现在有人折腾本地虚拟机或者旧电脑，这虽然解决了隔离，但解决不了工程化的问题。云服务器的优势在于把环境变成了资产。在本地搞虚拟机，换台电脑就没法用了。但在云上，这就是一个标准的运行时。AI 助手在云端跑了一周的任务，产生的中间状态、安装的依赖包、配置好的环境变量，全都是持久化的，随调随用。更进一步说，云端环境天然具备服务化的潜力。你在云端调通了一个复杂的任务流，它直接就能变成后端接口或者产品的雏形。本地那种碎片的、脆弱的结构，支撑不了这种扩展。三、预制环境的体验目前的 AI 助手方案，很多人还在受苦于配置环境。改配置文件、折腾 Python 依赖，这些杂活儿的学习曲线太长。真正好用的路径应该是预先在云端准备好大批环境，里面塞满常用的数据科学包、编译工具，适配好协议。用户需要 AI 干活时，系统瞬间分配一个就绪的环境，而不是让用户盯着进度条看几分钟去拉镜像。这种即开即用的体验，才是 AI 从玩具变成生产力的关键。四、别把逻辑搞复杂现在的行业风气不好，喜欢把简单的逻辑搞得极其重。弄一堆流程图、连线图，试图用这种视觉化的方式规范 AI，其实是在走老路。代码本身就是人和 AI 沟通最干净的契约。比起研究怎么画复杂的 Agent 编排图，不如直接给它一个干净、强力、在云端能随时伸缩的代码执行环境。让 AI 在代码层面上折腾，而不是在对话框里空谈。这种偏向代码实现的思路，配合预制好的云端环境，是目前最有效的开发方式。给 AI 开个云端单间让他自己折腾去。搞坏了不心疼，跑通了就是资产。


The user is saying the new output feels "油腻" (greasy/try-hard). Let me analyze the text:

**Oil/try-hard symptoms:**

1. **"暴力碰撞"** as a section title - dramatic, trying too hard to be catchy
2. **"隔离舱"** - concept packaging, trying to sound cool
3. **"把环境变成了资产"** - buzzwordy slogan
4. **"天然具备服务化的潜力"** - buzzword: "服务化的潜力"
5. **"从玩具变成生产力的关键"** - dramatic framing

6. **"人和 AI 沟通最干净的契约"** - wrapping a simple idea (code as interface) in philosophical language
7. **The casual ending** - "给 AI 开个云端单间让他自己折腾去。搞坏了不心疼，跑通了就是资产。" - this fake-casual tone is actually another form of 油腻, the "装不在乎" attitude
8. **Numbered sections still there** - the review prompt explicitly asked to remove these, so the revision didn't actually work
9-12. **Defensive patterns repeating** - "很多人没意识到" and "很多人还在受苦于" keep showing up, plus absolute claims like "非常危险" and "最有效的手段" that sound like marketing speak. The text has swung from overly formal to trying too hard to sound like a seasoned insider, which is a different flavor of the same problem.

The core issues are that the review pass isn't catching everything it should - the numbered section headers are still there, the "很多人" pattern slipped through twice, and the ending is trying to be clever in a way that feels forced. I need to make the review prompt explicitly target "油腻" tone and try-hard casualness, and possibly adjust the generation prompt itself since it might be encouraging this over-compensating voice.


确实，从"正式 AI 味"变成了"装酷油腻味"。具体问题：

1. "暴力碰撞" "隔离舱" "最干净的契约" — 装酷的概念包装
2. "给 AI 开个云端单间让他自己折腾去" — 刻意耍帅的收尾
3. "一、二、三、四" 编号还在 — review 没杀掉
4. "很多人没意识到" "很多人还在受苦于" — 防御性写法也漏了
5. "从玩具变成生产力的关键" "把环境变成了资产" — 口号式金句

本质是 LLM 被要求"去正式感"后，矫枉过正变成了刻意随意。需要在 review prompt 里加一条反油腻规则。