我用mode zhihu来生成文本，但觉得仍然有一些问题，比如最要拿的“不是...是...”句式仍然存在，请看看：还需要。

但是模式彻底变了。以前程序员靠记语法糖、堆叠代码经验或者熟悉某个框架的隐秘坑位就能建立技术壁垒，现在这些东西在 AI 面前基本归零。大语言模型写出来的代码片段通常比普通熟练工更规整，速度快了几个数量级。

现在的核心挑战不是“怎么写代码”，而是“代码写成什么样才是对的”。

好的架构设计、精准的命名、清晰的代码逻辑，这些东西在 AI 时代反而更值钱。以前这些是锦上添花，现在是保命逻辑。如果你给 Claude Code 的上下文是一团糟，命名不知所云，逻辑耦合严重，AI 很快就会被你带进沟里，开始胡说八道。好的工程实践本质上是在给 AI 提供高质量的“外部大脑”导航。一个项目的 CLAUDE.md 写得清不清晰，直接决定了模型生成的代码是生产力还是技术债。

不要再沉迷于语法细节了。

现在的重点应该是建立从需求到代码的直观映射。与其花半天纠结一个闭包的写法，不如花半小时把开发计划写清楚。这涉及到上下文工程的管理。比如把复杂任务拆解成研究、计划、执行、验证四个阶段，每个阶段只处理特定的逻辑块。这种分而治之的思路，其实是经典软件工程在 AI 时代的复兴。

工程师的角色正在从“打字员”变成“系统审计员”。

你必须对代码有真实的感受。AI 可能会幻觉出一个文件内容，或者写出一个有安全漏洞的 Bash 命令。如果你没有识别坏代码的能力，直接全盘接受，那项目崩掉只是时间问题。

所谓的技术门槛，已经从“熟练度”变成了“判断力”。

确保你的项目有清晰的规范，利用好文件系统作为 AI 的持久化记忆，建立自动化的验证闭环。只有当你能一眼看出 AI 给出的方案是否符合系统整体架构时，你才真正掌握了这套新工具。

现在的代码实现，本质上是把模糊的人类需求转化成精确的工程指令的过程。在这个过程中，对逻辑本质的理解比对语法手册的背诵重要得多。


The user is asking me to review a piece of Chinese text written in "zhihu mode" (知乎风格) and identify issues with it. They specifically mention that the "不是...是..." (not X... but Y...) sentence pattern still exists and is problematic.

Let me analyze the text carefully for this pattern and other issues.

Looking at the text, I can find instances of "不是...是/而是..." patterns:

1. "现在的核心挑战不是"怎么写代码"，而是"代码写成什么样才是对的"。


找到了这段文字里的问题句式：

**"不是...而是..." 句式：**
- "现在的核心挑战不是'怎么写代码'，而是'代码写成什么样才是对的'。"

**"本质上是..." 句式（同类 AI 腔）：**
- "好的工程实践**本质上是**在给 AI 提供高质量的'外部大脑'导航。"
- "现在的代码实现，**本质上是**把模糊的人类需求转化成精确的工程指令的过程。"

**"其实是..." 句式：**
- "这种分而治之的思路，**其实是**经典软件工程在 AI 时代的复兴。"

**"所谓的..." 句式：**
- "**所谓的**技术门槛，已经从'熟练度'变成了'判断力'。"

---

**改写建议：**

| 原文 | 改写 |
|------|------|
| 现在的核心挑战不是"怎么写代码"，而是"代码写成什么样才是对的"。 | "怎么写代码"这个问题已经不够用了。更要命的问题是"代码写成什么样才是对的"。 |
| 好的工程实践本质上是在给 AI 提供高质量的"外部大脑"导航。 | 好的工程实践就是给 AI 装导航。 |
| 现在的代码实现，本质上是把模糊的人类需求转化成精确的工程指令的过程。 | 写代码这件事，变成了把模糊的人类需求翻译成精确的工程指令。 |
| 这种分而治之的思路，其实是经典软件工程在 AI 时代的复兴。 | 分而治之，老一套软件工程方法论，反而在 AI 时代活过来了。 |
| 所谓的技术门槛，已经从"熟练度"变成了"判断力"。 | 技术门槛变了。以前拼熟练度，现在拼判断力。 |

核心规律：这几个句式都是 AI 特别爱用的"定义式"写法——用一个判断句把事情钉死。知乎体更适合用**短句拆开、让读者自己得出结论**，而不是作者替读者下定义。


node "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js" "stop:check-console-log" "scripts/hooks/check-console-log.js" "standard,strict"

node "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js" "stop:session-end" "scripts/hooks/session-end.js" "minimal,standard,strict"

node "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js" "stop:evaluate-session" "scripts/hooks/evaluate-session.js" "minimal,standard,strict"

node "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js" "stop:cost-tracker" "scripts/hooks/cost-tracker.js" "minimal,standard,strict"


那应该怎么修改我的工程，让其避免再出现这种问题呢？是否需要额外增加sub agent来再做一次审阅调校，还是在原来的prompts里就可以提升


这是个工程决策问题，我需要先看看你现在的 zhihu mode 是怎么实现的。


Agent

Find zhihu mode implementation

Prompt:
Find all files related to "zhihu" mode in this project. Search for:
1. Any file containing "zhihu" (case insensitive)
2. Any prompt templates or mode definitions
3. The generation pipeline - how text goes from input to output

Report the file paths, relevant code sections, and how the pipeline works.


Perfect! Now I have all the information I need. Let me create a comprehensive report on the zhihu mode and pipeline.

## Summary

I've found all files related to "zhihu" mode in the Butler project. Here's a comprehensive overview of the system architecture, files, and generation pipeline:

### File Locations

**Core Implementation Files:**
- `/root/projects/butler/slack_bot/zhihu/zhihu_hunter.py` - Main question discovery and answer drafting logic
- `/root/projects/butler/slack_bot/zhihu/zhihu_playwright_engine.py` - Browser automation for login and publishing
- `/root/projects/butler/slack_bot/zhihu/slack_interactive_gateway.py` - Slack UI controls and approval gates
- `/root/projects/butler/slack_bot/obsidian/generators.py` - Contains `ZhihuGenerator` class (lines 516-672)
- `/root/projects/butler/slack_bot/obsidian/dispatcher.py` - Routes "zhihu" and "zhihu-hunter" modes

**Style & Guide Files:**
- `/root/projects/butler/zhihu-style.md` - Comprehensive style guide (610 lines) with 10 sections
- `/root/projects/butler/zhihu-bot-style.md` - Bot-specific hard constraints and overrides
- `/root/projects/butler/zhihu-hunter-guide.md` - User documentation for the hunter workflow
- `/root/projects/butler/zhihu-sample/12.md` - Example Zhihu answer (AWS reinvent analysis)

---

## Two Separate Modes

### 1. MODE_ZHIHU: Direct Answer Generation
**Dispatcher:** Lines 262-294 in dispatcher.py

Takes a question (optionally with context) and generates a Zhihu-style answer.

**Input Format:** `Question | Core Ideas` (the `|` separator is optional for follow-up refinement)

**Pipeline:**
```
User Input (in Slack)
        ↓
[First Turn - Full Rich Prompt Build]
        ↓
Load zhihu-style.md + zhihu-bot-style.md (style rules)
Load methodology.md (core philosophy)
Search Obsidian vault via ObsidianIndexer (keyword-based RAG)
Search vector store semantically (ChromaDB, top 3 results)
        ↓
Create combined prompt with:
  - Style guide
  - RAG context from vault
  - User's core ideas
  - Question text
        ↓
LLM Call 1: Generate draft answer
        ↓
LLM Call 2: De-AI Review (enforce zhihu-bot-style.md rules)
        ↓
Return pure plain-text answer
        ↓
[Follow-up Turns]
        ↓
User can refine with "make it shorter", "more formal", etc.
LLM applies refinement to existing answer
```

**Key Code:** `ZhihuGenerator.chat()` in generators.py (lines 519-672)

---

### 2. MODE_ZHIHU_HUNTER: Question Discovery + Draft Pipeline
**Dispatcher:** Lines 172-258 in dispatcher.py

Discovers Zhihu questions from vault content, then generates answers for approval.

**Six Ways to Find Questions:**

| Method | Command | Behavior |
|--------|---------|----------|
| Auto scan | `开始` | Scan all dirs (7 days), extract keywords via LLM, search Zhihu |
| Dir-specific | `article`, `notes`, `claw` | Scan one directory with optional topic focus |
| Single file | `Article/8-xxx-Revised.md` | Ground answer in this specific file |
| Glob pattern | `Article/*agent*-Revised.md` | Combine matched files for keyword extraction |
| Keywords | `AI智能体 大模型` | Skip vault scan, search Zhihu directly |
| Direct URL | `https://www.zhihu.com/question/123456` | Fetch title/description, create question card |

**Complete Hunter Pipeline:**

```
[Phase 1: Question Discovery]
User triggers hunt (6 methods above)
        ↓
Vault Scanning (if applicable):
  - Collect recent files from watched dirs:
    • notes/ (7 days old)
    • Article/ (no time filter, -Revised.md prioritized)
    • Clippings/ (7 days old)
    • CLAW/deepfact/ (7 days old)
    • auto_report/deep-dive/ (7 days old)
  - Read file headers (strip YAML frontmatter)
  - Combine with per-file budget (500-800 chars)
  - Total budget: 8000 chars
        ↓
LLM Keyword Extraction:
  - Prompt: Extract 10 topic keywords from aggregated vault text
  - Avoid: overly niche terms, too broad concepts, project codenames
  - Use: upper-level concepts users would search for
  - Example: "OpenClaw" → "AI智能体安全"
        ↓
Tavily Search (via TavilyClient):
  - Search for: site:zhihu.com/question <keyword>
  - Filter: exclude non-question URLs (no /answer/, /comment/)
  - Normalize URLs (strip params, trailing chars)
  - Return up to 12 questions (deduplicated)
        ↓
Return: List[ZhihuQuestion]
  - title, url, snippet, keywords, found_at
  - Optional: source_file (set if from single file/glob)

[Phase 2: Slack Approval Gate 1]
Post question cards with buttons:
  [✍ 生成草稿] [🚫 忽略]
        ↓
User clicks one button

[Phase 3: Draft Generation on Approval]
When user clicks "生成草稿":
  - Load ZhihuQuestion
  - Call zhihu_hunter.draft_answer(question)
        ↓
draft_answer() pipeline:
  - Semantic search (vector_store): top 3 chunks from vault
  - Keyword search (indexer): top 5 vault notes
  - Load source file (if pinned to one):
    • Read up to 4000 chars from <vault>/source_file
    • Use as primary reference for answer grounding
  - Fetch recent web context (Tavily, 90 days):
    • Search for question title
    • Get 4 results from non-Zhihu sources
    • Format as snippets
        ↓
Delegate to ZhihuGenerator.chat():
  - Build rich prompt with:
    • Question title + description
    • User outline (if provided)
    • Primary article (source file)
    • Recent web context
  - Generate answer using zhihu-style.md rules
  - Apply de-AI second pass
        ↓
Return: AnswerDraft
  - question, content, generated_at
  - rag_sources (deduplicated list)
  - vault_file (saved to <vault>/zhihu/ if successful)

[Phase 4: Slack Approval Gate 2]
Post draft card with buttons:
  [✅ 确认发布] [🔄 重新生成] [❌ 取消]
        ↓
If "🔄 重新生成":
  - Modal dialog opens for user feedback
  - Re-call draft_answer(question, feedback=user_feedback)
        ↓
If "✅ 确认发布":
  - Call engine.ensure_logged_in():
    • Check saved cookies in data/zhihu_state.json
    • If expired: trigger QR code login (2 min timeout, up to 2 attempts)
    • Save/refresh cookies
        ↓
  - Call engine.publish_answer(question_url, answer_text):
    • Navigate to home page (warm up session)
    • Navigate to question page
    • Click "写回答" button (tries 10+ selectors)
    • Fill editor via Draft.js (3 strategies: execCommand → ClipboardEvent → keyboard.type)
    • Click "发布回答" button
    • Extract answer URL from DOM
    • Return answer URL
        ↓
  - Update vault file: status: draft → published + answer_url
  - Delete draft JSON file
  - Post success card to Slack
```

---

## Style Guide System

The zhihu mode uses a **two-layer style system:**

### Layer 1: `zhihu-style.md` (610 lines)
Comprehensive style guide covering:
- **Core positioning**: "Professional yet interesting, rigorous yet warm"
- **Tone**: Mix of formal + casual, first-person dominant
- **Sentence patterns**: Long/short contrast, rhetorical questions
- **Rhetoric**: Metaphors (max 1-2 per article), self-deprecation, classical references
- **Content structure**:
  - Opening: "谢邀", direct statement, acknowledgment, interest disclosure
  - Body: Total-分-总 with numbered points
  - Ending: Poetry/wisdom, summary, forward-looking
- **Language**: Professional terms + colloquial; must explain jargon with full sentences, not parentheses
- **Layout**: Bold only for core terms (3-4 max); space paragraphs
- **Depth**: Real cases, theory + practice balance, tools/methods, critical thinking
- **Avoids**: AI tone, fearmongering, empty praise, buzzwords, excessive metaphors, Markdown decoration

### Layer 2: `zhihu-bot-style.md` (110 lines)
Bot-specific hard constraints:

**Hard Bans:**
- No "谢邀" (thank-you phrase)
- No self-promotion, past articles, identity reveals
- No poetry/wisdom endings (direct stop)
- No abstract jargon: "赋能", "底层逻辑", "仪式性努力", "精神枷锁"
- No fake binary categories: "conservative/radical", "inward/outward"
- No educational tone (assume expert audience)
- No chicken soup endings: "crisis is opportunity", "growth mindset"
- No formulaic structure with Chinese numeral summaries
- No defensive phrasing: "many think X but actually Y"
- No contrastive definitions: "not X, but Y"
- No fragmented syntax unless listing steps

**Forced Style Patterns:**
- Pure plain text (zero Markdown symbols)
- Direct terminology use (no parenthetical explanations)
- Wide paragraph spacing
- Natural sentence length variation (burstiness)
- Allow fragmented syntax in technical lists
- 500-1000 characters target length
- Stop when point is made (no padding)

---

## Generation Pipeline in Detail

### ZhihuGenerator.chat() Flow

```python
# Input: question text, history
# First turn: "Should I invest in crypto? | I want to discuss systemic risks"
# First turn detected by: empty history, "|" in input, "zhihu.com/question/" in input

if is_new_question:
    # 1. Load style guides
    zhihu_style = read("zhihu-style.md")  # 610 lines
    zhihu_bot = read("zhihu-bot-style.md")  # Overrides
    combined_style = zhihu_style + "\n\n" + zhihu_bot

    # 2. RAG retrieval (dual search)
    rag_notes = indexer.search(question, limit=5)  # Keyword-based
    semantic = vector_store.search_knowledge(question, top_k=3)  # Vector-based

    # 3. Load context
    methodology = read("methodology.md")

    # 4. Build mega-prompt
    prompt = f"""
    You are answering a Zhihu question. Write authentic, experience-driven.

    === STYLE GUIDE ===
    {combined_style}

    === OBSIDIAN VAULT MATERIAL ===
    {rag_notes}
    {semantic}

    === THE QUESTION ===
    {question}

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

    === TASK ===
    - Answer based on vault knowledge + your ideas
    - 500-1000 chars
    - Pure text, no Markdown
    - No "谢邀", no self-promotion, no poetry
    - Direct & opinionated (attach judgment to every fact)
    - Weave in recent web context if provided
    """

    # 5. LLM Draft Generation
    draft = llm.generate(prompt, history=[])  # Empty history for first turn

    # 6. De-AI Second Pass (rule enforcement)
    review_prompt = f"""
    You are a strict editor. Remove AI tone. Do NOT change facts.

    === TONE TARGET ===
    Like a knowledgeable person typing casually on Zhihu.
    Not putting on airs. Not showing off.

    === VOCABULARY KILL LIST ===
    1. Exaggeration: 极度脆弱, 颠覆性, 天翻地覆, 远超想象
    2. Empty abstractions: 范式转移, 赋能, 底层逻辑, 仪式性努力
    3. Chicken soup: 危机就是转机, 这是成长机会, 最重要的选择
    4. Concept packaging: 隔离舱, 数字雇员, 云端办公室
    5. Oily endings: "折腾去吧"式潇洒, "搞坏了不心疼"式金句
    6. Faux-casual: 说白了, 说实话, 这玩意儿

    === SENTENCE PATTERN KILL LIST ===
    1. Defensive: "很多人觉得X，实际上Y" → 直接说判断
    2. Contrastive: "不是X是Y" → 直接陈述后半句
    3. Metaphor overkill: 最多1个比喻，多余全删
    4. Numbered paragraphs: 一、二、三 → 自然段落
    5. Forced summary: "综上所述"/"一句话" → 直接停

    === DRAFT ===
    {draft}
    """

    response = llm.generate(review_prompt, history=[])

else:
    # Follow-up: User says "make it shorter" / "add more examples"
    response = llm.generate(user_input, history)

return response, updated_history
```

---

## Key Data Models

### ZhihuQuestion
```python
title: str              # "应该投资加密货币吗？"
url: str               # "https://www.zhihu.com/question/123456"
snippet: Optional[str] # "近年来虚拟货币风险..." (from web search)
keywords: List[str]    # ["加密货币", "投资风险"]
found_at: str          # ISO timestamp
description: Optional[str]  # Full question body (from Playwright)
source_file: Optional[str]  # "Article/8-crypto-Revised.md" (if pinned)
outline: Optional[str]      # User-provided answer structure
```

### AnswerDraft
```python
question: ZhihuQuestion
content: str           # Generated answer body
generated_at: str      # ISO timestamp
rag_sources: List[str] # ["article-name.md", "note-title"]
vault_file: Optional[str]  # Absolute path to saved draft in vault
```

---

## Watched Directories (Vault Scanning)

```python
WATCH_DIRS = [
    "notes",                  # General notes, 7 days
    "Article",               # Blog articles (no time filter, Revised prioritized)
    "auto_report/deep-dive", # Analysis reports, 7 days
    "CLAW/deepfact",         # Deep research, 7 days
    "Clippings",             # Web clippings, 7 days
]

_MAX_FILES_PER_DIR = 4      # Most recent 4 per dir
_MAX_FILES_ARTICLE = 8      # Double slots for Article
_MAX_KEYWORDS = 10          # Extracted keywords limit
_MAX_QUESTIONS_TOTAL = 12   # Final questions capped at 12
```

---

## External Dependencies

- **Tavily API** (`TavilyClient`): Web search for Zhihu questions
- **Playwright + playwright-stealth**: Browser automation for login/publishing
- **GeminiLLM**: Text generation via Google Gemini API
- **ChromaDB** (vector_store): Semantic search of vault notes
- **Slack SDK**: Interactive gateways and UI
- **Obsidian Indexer**: Keyword-based vault search

---

## Example Flow: Complete End-to-End

```
User: "mode zhihu-hunter"
Bot: "✅ Switched to zhihu-hunter"

User: "article agent开发"
Bot: "🔍 正在扫描..."
  → Scans Article/ dir only
  → Extracts keywords: ["AI智能体", "Agent框架", "智能体安全"]
  → Searches Zhihu with each keyword
  → Returns 12 questions like:
    • "怎样才能系统地学习AI智能体开发？"
    • "大模型在智能体中的安全隐患有哪些？"

Bot posts cards:
  [✍ 生成草稿] [🚫 忽略]
  [✍ 生成草稿] [🚫 忽略]
  ...

User: Clicks "✍ 生成草稿" on question 1
Bot: "⏳ 草稿生成中，请稍候..."
  → Calls zhihu_hunter.draft_answer(question)
  → Semantic search: gets 3 relevant vault chunks
  → Keyword search: gets 5 vault notes
  → Loads most-recent Article file from scan
  → Fetches recent web articles on "AI智能体开发"
  → Builds rich prompt to ZhihuGenerator
  → LLM generates draft (500-1000 chars)
  → Second pass removes AI tone
  → Saves to vault: zhihu/2026-03-30-AI智能体学习.md

Bot posts draft card:
  📝 "怎样才能系统地学习AI智能体开发？"

  生成的草稿文本...（前800字预览）

  📚 RAG来源：article1.md · vault-note-2 · ...

  [✅ 确认发布] [🔄 重新生成] [❌ 取消]

User: Clicks "🔄 重新生成"
Modal: "请输入修改意见："
User: "可以加点实操的例子吗"
Bot: "🔄 重新生成中..."
  → Calls draft_answer(question, feedback="可以加点实操的例子吗")
  → Re-adds feedback to LLM prompt
  → Regenerates draft
  → Updates card in place

User: Clicks "✅ 确认发布"
Bot: "⏳ 正在登录知乎并发布..."
  → Calls engine.ensure_logged_in()
    → Loads cookies from data/zhihu_state.json
    → If valid: continue
    → If expired: Send QR code to Slack, wait 2 min for scan
    → Save refreshed cookies
  → Calls engine.publish_answer(question_url, answer_text)
    → Navigate to Zhihu home + question page
    → Click "写回答"
    → Fill editor (Draft.js aware)
    → Click "发布"
    → Extract published answer URL

Bot: "✅ 已发布！
问题: 怎样才能系统地学习AI智能体开发？
回答: https://www.zhihu.com/question/123456/answer/789..."
  → Updates vault file: status published, answer_url added
  → Deletes draft JSON
```

This comprehensive pipeline balances:
- **Discovery**: Vault content → keywords → Zhihu questions
- **Drafting**: Multi-source RAG + style enforcement
- **Approval**: Two-gate human review (question + draft)
- **Publishing**: Secure headless browser automation
- **Persistence**: Vault storage + cookie management

Prompt:
Find all files related to "zhihu" mode in this project. Search for:
1. Any file containing "zhihu" (case insensitive)
2. Any prompt templates or mode definitions
3. The generation pipeline - how text goes from input to output

Report the file paths, relevant code sections, and how the pipeline works.


Now I have a clear picture of the zhihu generation pipeline. Let me look at the key files more closely - specifically the de-AI review pass and the style guides, to understand what's already in place and what's missing.

The pipeline already has:
1. A first pass generation with style guide (`zhihu-style.md` + `zhihu-bot-style.md`)
2. A second "de-AI" review pass

The problem is that despite having rules like "不是X是Y → 直接陈述后半句" in the kill list, the output still contains these patterns.


让我看看具体的 prompt 实现。


1	# 知乎 Bot 生成附加规则
2
3	> 本文件是针对 Bot 自动生成场景的**额外约束**，与 zhihu-style.md 并行使用。
4	> 当两者有冲突时，本文件优先。
5
6	---
7
8	## 零、硬性禁止（最高优先级）
9
10	禁止说"谢邀"。这个词不要出现在任何位置，包括开头、结尾、过渡句。
11
12	禁止提及自己以前写过的文章、公众号文章、历史回答。不要说"我之前写过一篇文章讲这个"、"可以参考我的另一篇回答"之类的话。直接把内容说清楚就好。
13
14	禁止在回答中提及任何公众号账号名称、个人账号名、外部链接或任何形式的自我推广。不要在开头说"利益相关：AI Manifest"，不要在结尾加任何"关注公众号"之类的引流语。回答是独立完整的，不需要引流。
15
16	禁止暴露个人身份和从业信息。不要说"我在某某公司"、"我们团队"、"我负责某某产品"、"我的职位是"之类的话，也不要透露任何可以推断出身份的具体单位名、项目名、职级信息。观点以第一人称表达即可，不需要用职业背景来背书。
17
18	禁止提及任何过去讲过的话、做过的项目或写过的文章，尤其是项目名称和文章标题。不要出现"正如我在 xxx 中讨论的"、"我之前做过一个叫 xxx 的项目"之类的表达。自己不是名人，这样写只会让读者困惑。
19
20	禁止在结尾加诗句、名言或"升华"性质的总结。直接收尾，说完就停。加诗句显得做作，不符合随性的知乎风格。
21
22	禁止在任何位置提及公众号名称、账号名、引流语。这条是最高优先级，无论措辞多隐晦都不允许。
23
24	禁止使用抽象黑话。绝对禁止使用"赋能"、"底层逻辑"、"仪式性努力"、"错失恐惧症"、"精神枷锁"等社科或互联网黑话。用具体的动作或大白话替代（例如：把"仪式性努力"直接说成"花钱买心理安慰"）。
25
26	禁止强行分类。禁止为了结构工整而将事物强行拆分为"保守派/激进派"、"向内求索/向外扩展"等对称的伪概念。
27
28	禁止常识科普与爹味说教。默认受众是具备高级技术认知（如熟悉 Python 开发、系统架构）的同行。绝对禁止解释基础概念（如禁用"编程的本质是……"、"提示词的意思是……"）。不教育读者，只提供洞察。
29
30	禁止鸡汤与情绪抚慰。禁止所有类似"危机就是转机"、"这是成长的机会"、"最重要的选择"等带有情绪抚慰和职场导师口吻的废话。
31
32	禁止八股文骨架与强行总结。禁止使用"一、... 二、... 三、... 总结一句话：..."这类汉字序号加收尾总结的结构。禁止任何形式的"总结一句话"/"综上"式结尾。说完最后一个有实质内容的点，直接停。
33
34	禁止防御性引号泛滥。不要对归纳性词汇套引号（如"调戏"、"包工头模式"、"统一入口"、"固化"），这是AI掩盖措辞不精确的习惯动作。如果一个词需要打引号才能成立，换个更准确的说法，或者用完整句子表达。
35
36	禁止伪口语润滑剂。"说白了"、"说实话"、"这玩意儿"是伪装成口语的废话或刻意接地气的词，在硬核技术讨论中只会显得不协调。删掉不影响句意，保持专业、干脆的技术语感。
37
38	禁止对比式定义句式。严禁使用"不是……而是……"、"不要把它当成……得把它当成……"、"那不叫……那叫……"等通过否定来定义的句式。直接陈述它是什么、该怎么做，不要通过否定常识来凑字数。
39
40	禁止编号分段。知乎回答不是公文，绝对禁止用"一、二、三、四"或"首先、其次、再次、最后"做段落划分。通过段落的自然推进切换话题，思维可以网状发散，不需要强行编号。
41
42	强制句长落差（Burstiness）。相邻句子的长度和结构必须产生碰撞。前一句是解释原理的长复合句，下一句必须是极短的陈述或结论。绝对禁止连续出现三句长度相近的完整陈述句。均匀的句长节奏是 AI 文本最显著的特征之一。
43
44	允许不完整句法（Fragmented Syntax）。在罗列工程步骤、系统架构或痛点时，直接抛出名词组合或动词短语，不需要每句话都补齐主谓宾。技术复盘里没人会把主语补全。全文必须出现若干缺少主语的短句，甚至允许单独成句的名词堆叠。
45
46	---
47
48	## 一、纯文本输出（最高优先级）
49
50	生成的回答必须是**纯文本**，不包含任何 Markdown 语法符号：
51
52	禁止使用：
53	- 加粗：** 或 __
54	- 斜体：* 或 _
55	- 标题：# ## ###
56	- 列表符号：- 或 * 开头
57	- 代码块：``` 或 `
58	- 分割线：---
59	- 引用块：>
60
61	如需表达层次或重点，改用汉字序号（一、二、三，或 1. 2. 3.）和换行空行，不要用任何符号装饰。
62
63	---
64
65	## 二、术语直接使用，不加括号解释
66
67	错误写法：大语言模型（LLM）的上下文窗口（Context Window）需要足够大
68
69	正确写法：大语言模型的上下文窗口需要足够大
70
71	原则：术语直接用，看不懂的读者就让他查，显得更随性自然。如果要解释，用句子解释，不用括号注释。
72
73	---
74
75	## 三、段落分隔要宽松
76
77	每个自然段之间空一行。
78
79	主题切换或层次转换时空两行。
80
81	宁可每段短一点，也不要把多个意思堆在同一段里。这样在手机上看更舒服，也更容易被读者扫读。
82
83	---
84
85	## 四、结构用汉字序号，不用 Markdown
86
87	如果要分点论述，用：
88
89	一、xxx
90
91	二、xxx
92
93	或者 1. 2. 3. 这样的数字+点，不要用 Markdown 的 - 列表。
94
95	---
96
97	## 五、篇幅控制
98
99	默认生成 500-1000 字。没有用户明确要求的情况下，不要超过 1000 字。
100
101	宁可少说一个论点，也不要为了凑字数稀释质量。说完核心观点就结束，不需要强行总结或展望。
102
103	---
104
105	## 六、检查清单（生成后自检）
106
107	- 有没有任何 ** __ ## --- > ` 符号？如果有，删掉
108	- 有没有"（英文缩写）"这种括号注释？如果有，删掉括号和注释
109	- 段落之间有没有空行？没有的话加上
110	- 有没有用中文序号或数字序号替代 Markdown 列表？

# 知乎 Bot 生成附加规则

> 本文件是针对 Bot 自动生成场景的**额外约束**，与 zhihu-style.md 并行使用。
> 当两者有冲突时，本文件优先。

---

## 零、硬性禁止（最高优先级）

禁止说"谢邀"。这个词不要出现在任何位置，包括开头、结尾、过渡句。

禁止提及自己以前写过的文章、公众号文章、历史回答。不要说"我之前写过一篇文章讲这个"、"可以参考我的另一篇回答"之类的话。直接把内容说清楚就好。

禁止在回答中提及任何公众号账号名称、个人账号名、外部链接或任何形式的自我推广。不要在开头说"利益相关：AI Manifest"，不要在结尾加任何"关注公众号"之类的引流语。回答是独立完整的，不需要引流。

禁止暴露个人身份和从业信息。不要说"我在某某公司"、"我们团队"、"我负责某某产品"、"我的职位是"之类的话，也不要透露任何可以推断出身份的具体单位名、项目名、职级信息。观点以第一人称表达即可，不需要用职业背景来背书。

禁止提及任何过去讲过的话、做过的项目或写过的文章，尤其是项目名称和文章标题。不要出现"正如我在 xxx 中讨论的"、"我之前做过一个叫 xxx 的项目"之类的表达。自己不是名人，这样写只会让读者困惑。

禁止在结尾加诗句、名言或"升华"性质的总结。直接收尾，说完就停。加诗句显得做作，不符合随性的知乎风格。

禁止在任何位置提及公众号名称、账号名、引流语。这条是最高优先级，无论措辞多隐晦都不允许。

禁止使用抽象黑话。绝对禁止使用"赋能"、"底层逻辑"、"仪式性努力"、"错失恐惧症"、"精神枷锁"等社科或互联网黑话。用具体的动作或大白话替代（例如：把"仪式性努力"直接说成"花钱买心理安慰"）。

禁止强行分类。禁止为了结构工整而将事物强行拆分为"保守派/激进派"、"向内求索/向外扩展"等对称的伪概念。

禁止常识科普与爹味说教。默认受众是具备高级技术认知（如熟悉 Python 开发、系统架构）的同行。绝对禁止解释基础概念（如禁用"编程的本质是……"、"提示词的意思是……"）。不教育读者，只提供洞察。

禁止鸡汤与情绪抚慰。禁止所有类似"危机就是转机"、"这是成长的机会"、"最重要的选择"等带有情绪抚慰和职场导师口吻的废话。

禁止八股文骨架与强行总结。禁止使用"一、... 二、... 三、... 总结一句话：..."这类汉字序号加收尾总结的结构。禁止任何形式的"总结一句话"/"综上"式结尾。说完最后一个有实质内容的点，直接停。

禁止防御性引号泛滥。不要对归纳性词汇套引号（如"调戏"、"包工头模式"、"统一入口"、"固化"），这是AI掩盖措辞不精确的习惯动作。如果一个词需要打引号才能成立，换个更准确的说法，或者用完整句子表达。

禁止伪口语润滑剂。"说白了"、"说实话"、"这玩意儿"是伪装成口语的废话或刻意接地气的词，在硬核技术讨论中只会显得不协调。删掉不影响句意，保持专业、干脆的技术语感。

禁止对比式定义句式。严禁使用"不是……而是……"、"不要把它当成……得把它当成……"、"那不叫……那叫……"等通过否定来定义的句式。直接陈述它是什么、该怎么做，不要通过否定常识来凑字数。

禁止编号分段。知乎回答不是公文，绝对禁止用"一、二、三、四"或"首先、其次、再次、最后"做段落划分。通过段落的自然推进切换话题，思维可以网状发散，不需要强行编号。

强制句长落差（Burstiness）。相邻句子的长度和结构必须产生碰撞。前一句是解释原理的长复合句，下一句必须是极短的陈述或结论。绝对禁止连续出现三句长度相近的完整陈述句。均匀的句长节奏是 AI 文本最显著的特征之一。

允许不完整句法（Fragmented Syntax）。在罗列工程步骤、系统架构或痛点时，直接抛出名词组合或动词短语，不需要每句话都补齐主谓宾。技术复盘里没人会把主语补全。全文必须出现若干缺少主语的短句，甚至允许单独成句的名词堆叠。

---

## 一、纯文本输出（最高优先级）

生成的回答必须是**纯文本**，不包含任何 Markdown 语法符号：

禁止使用：
- 加粗：** 或 __
- 斜体：* 或 _
- 标题：# ## ###
- 列表符号：- 或 * 开头
- 代码块：``` 或 `
- 分割线：---
- 引用块：>

如需表达层次或重点，改用汉字序号（一、二、三，或 1. 2. 3.）和换行空行，不要用任何符号装饰。

---

## 二、术语直接使用，不加括号解释

错误写法：大语言模型（LLM）的上下文窗口（Context Window）需要足够大

正确写法：大语言模型的上下文窗口需要足够大

原则：术语直接用，看不懂的读者就让他查，显得更随性自然。如果要解释，用句子解释，不用括号注释。

---

## 三、段落分隔要宽松

每个自然段之间空一行。

主题切换或层次转换时空两行。

宁可每段短一点，也不要把多个意思堆在同一段里。这样在手机上看更舒服，也更容易被读者扫读。

---

## 四、结构用汉字序号，不用 Markdown

如果要分点论述，用：

一、xxx

二、xxx

或者 1. 2. 3. 这样的数字+点，不要用 Markdown 的 - 列表。

---

## 五、篇幅控制

默认生成 500-1000 字。没有用户明确要求的情况下，不要超过 1000 字。

宁可少说一个论点，也不要为了凑字数稀释质量。说完核心观点就结束，不需要强行总结或展望。

---

## 六、检查清单（生成后自检）

- 有没有任何 ** __ ## --- > ` 符号？如果有，删掉
- 有没有"（英文缩写）"这种括号注释？如果有，删掉括号和注释
- 段落之间有没有空行？没有的话加上
- 有没有用中文序号或数字序号替代 Markdown 列表？


516	class ZhihuGenerator(BaseGenerator):
517	    """Generate Zhihu-style answers based on style guide and user's knowledge base."""
518
519	    def chat(self, user_input: str, history: List[Dict]) -> Tuple[str, List[Dict]]:
520
521	        is_first_turn = (len(history) == 0)
522	        built_rich_prompt = False  # tracks whether we ran the full question->draft pipeline
523
524	        # Pre-load rule files (needed for second-pass review)
525	        zhihu_bot_style = self._read_file("zhihu-bot-style.md")
526
527	        # Treat as a new question if:
528	        # - history is empty (genuine first turn), OR
529	        # - user input contains "|" (explicit "Question | Core Ideas" format), OR
530	        # - user input starts with known question triggers like "回答这个问题", OR
531	        # - user input contains a Zhihu question URL (pasting a new question link)
532	        is_new_question = (
533	            is_first_turn
534	            or "|" in user_input
535	            or user_input.strip().startswith("回答这个问题")
536	            or "zhihu.com/question/" in user_input
537	        )
538
539	        if is_new_question:
540	            # === First Turn: Build Rich Prompt ===
541	            # Expected input format: "Question | Core Ideas"
542	            parts = user_input.split("|", 1)
543	            question = parts[0].strip()
544	            core_ideas = parts[1].strip() if len(parts) > 1 else ""
545
546	            # 1. Load Style Guide (zhihu-style.md serves as writing standard, NOT as material)
547	            zhihu_style_guide = self._read_file("zhihu-style.md")
548	            # Bot-specific overrides (plain-text output, no bracket explanations, etc.)
549	            if zhihu_bot_style:
550	                zhihu_style_guide = zhihu_style_guide + "\n\n" + zhihu_bot_style
551
552	            # 2. RAG from Obsidian vault for actual content material
553	            rag_notes = self.indexer.search(question, limit=5)
554	            rag_text = "\n\n".join(rag_notes)
555
556	            # 3. Load methodology
557	            methodology = self._read_file("methodology.md")
558
559	            semantic_context = self._semantic_search(question, top_k=3)
560
561	            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.
562
563	=== YOUR IDENTITY & METHODOLOGY ===
564	{methodology}
565
566	=== ZHIHU STYLE GUIDE (FOLLOW THESE WRITING PRINCIPLES) ===
567	{zhihu_style_guide}
568
569	CRITICAL: The style guide above defines HOW to write (tone, structure, language patterns).
570	Do NOT treat zhihu-sample content as factual material. It's ONLY for style reference.
571
572	=== RELEVANT KNOWLEDGE FROM YOUR OBSIDIAN VAULT (USE AS ACTUAL MATERIAL) ===
573	{rag_text}
574
575	{semantic_context}
576
577	=== THE QUESTION ===
578	{question}
579
580	=== YOUR CORE IDEAS ===
581	{core_ideas}
582
583	=== TASK ===
584	1. Answer the question based on YOUR knowledge (from Obsidian vault) and core ideas
585	2. Write in Chinese (unless question implies English)
586	3. Be direct and opinionated — attach concrete evaluation to every fact (not feature listing)
587	4. If recent web context is provided, weave it in naturally
588	5. STRICT: Never add bracket explanations. Write "大语言模型" not "大语言模型（LLM）"
589	6. No self-promotion, no identity reveal, no past project references
590	7. No poetry, quotes, or "升华" endings. Stop when the point is made.
591	8. LENGTH: 500–1000 Chinese characters. Cut a point, not quality.
592
593	=== ANTI-AI CORE RULES (apply while writing) ===
594	- 语感目标：像懂行的人随手打字，不端着，不耍帅
595	- 禁止：排比对仗、过渡词堆砌（首先/其次/最后）、空洞修饰（深入/全面/显著）
596	- 禁止：比喻超过1个、概念包装（给普通事物起酷名字）、鸡汤结尾
597	- 禁止：防御性写法（很多人觉得X但其实Y）、对比式定义（不是X是Y）
598	- 陈述事实时必须附带主观判断，不要纯客观罗列
599
600	Begin writing the answer:
601	"""
602
603	            logger.info(f"Generating Zhihu answer for: {question[:50]}...")
604	            actual_user_message = prompt
605	            built_rich_prompt = True
606
607	        else:
608	            # === Follow-up Turn: User gives refinement instructions (e.g. "make it shorter") ===
609	            logger.info(f"Follow-up Zhihu instruction: {user_input}")
610	            actual_user_message = user_input
611	            prompt = user_input
612
613	        # Call LLM — first pass: content generation
614	        # For new questions, use empty history to avoid contamination from previous conversations
615	        llm_history = [] if is_new_question else history
616	        draft, _ = self.llm.generate_response(prompt, llm_history)
617
618	        # === Second pass: rule enforcement ===
619	        # Runs whenever a full draft was generated (built_rich_prompt=True),
620	        # regardless of whether history was empty or not.
621	        # Skipped on follow-up refinement turns (e.g. "make it shorter").
622	        run_review = built_rich_prompt
623	        if run_review:
624	            review_prompt = f"""You are a strict editor. A draft Zhihu answer has been written. Your ONLY job is to remove AI tone. Do NOT change facts or add new content.
625
626	=== TONE TARGET (最高优先级) ===
627	目标语感：像一个懂行的人在知乎随手打字回答。不端着，也不刻意耍帅。
628	两种AI味都要杀：
629	- 正式AI味：过渡词堆砌、排比对仗、空洞修饰、鸡汤结尾
630	- 油腻AI味：刻意装随意（"折腾去吧"）、强行造金句（"搞坏了不心疼，跑通了就是资产"）、给普通概念起酷名字（"隔离舱"、"暴力碰撞"、"最干净的契约"）、用一句俏皮话收尾装潇洒
631	正确的语感：平铺直叙，该说什么说什么，说完就停。不需要让读者觉得你很酷。
632
633	=== VOCABULARY KILL LIST (逐词扫描，命中即删或换大白话) ===
634	1. 夸张修饰: 极度脆弱、极其脆弱、毁灭性的、灾难级的、压倒性的、远超想象、不止一个量级、颠覆性的、革命性的、折磨人、天翻地覆、风险极高、瞬间崩断、精疲力竭、真金白银、本质上
635	2. 大词空转: 范式转移、范式突变、开发范式、数字化容器、数字雇员、温和池架构、非确定性、阅后即焚、毫秒级就绪、毫秒级响应、赋能、底层逻辑、仪式性努力、深刻变革、暴力碰撞、最干净的契约、服务化的潜力、不可逆的浪潮、时代大背景、可扩展的执行信用、终极归宿、壁垒、分水岭、沉淀（动词修饰数据/经验时）、云端办公室、命门
636	3. 鸡汤/口号: 正途、值得跨越、安置灵魂、掌控感、才是王道、苦活累活交给、未来已来、危机就是转机、挑战与机遇并存、从玩具变成生产力、把环境变成了资产、安全底线、这才是成熟的做法、才是X的关键、不是建议是必须
637	4. 概念包装: 裸奔（=没防护）、修路/飙车（=基础工作/高级用法）、提款机（=攻击目标）、隔离舱（=容器）、数字雇员（=脚本）、执行信用（=资源）、云端办公室（=云服务器）、牢笼（=限制）、独立房间（=隔离环境）
638	5. 油腻收尾: "X去吧"式潇洒收尾、"搞坏了不心疼，跑通了就是Y"式金句、俏皮话总结全文、"把X关在Y里...把Y放在Z上"式排比收尾
639	6. 伪口语: 说白了、说实话、折腾这玩意儿
640
641	=== SENTENCE PATTERN KILL LIST (命中即改写) ===
642	1. 防御性写法："很多人觉得X，实际上Y"、"有人X，有人已经Y了"、"一部分人在X，另一部分人在Y" → 直接说判断，删掉铺垫
643	2. 对比式定义："不是X，是Y"、"不是X而是Y"、"别把X当Y"、"这不对，它的本质是…" → 直接陈述后半句
644	3. 换皮夸张：用另一个夸张词替换夸张词不算修好 → 换成具体后果
645	4. 概念包装：给普通事物起酷名字 → 用最朴素的说法
646	5. 油腻金句：短句对仗、押韵、刻意工整的收尾句 → 删掉或拆成普通句
647	6. 比喻堆砌：全篇最多1个比喻，多余全删换直说
648	7. 鸡汤/口号结尾：删除励志或耍帅收尾，给具体建议或直接停
649	8. 首尾呼应：禁止结尾重复开头
650	9. 编号分段：线性论述删除序号改自然段落；并列对比可保留
651	10. 强行总结：删除"综上"/"一句话"式收尾
652	11. 关键词重复：同一个修辞性词汇全篇只能出现一次
653	12. 过渡词换皮："第一个要说的是"="首先"，"另一个问题是"="其次" → 直接删掉过渡，开门见山
654	13. 伪分类二分法："一部分人X，另一部分人Y"、"修路派/飙车派" → 删除分类框架，直接说事实
655
656	=== DRAFT TO REVIEW ===
657	{draft}
658
659	=== OUTPUT ===
660	Return ONLY the corrected Chinese text. No explanation, no meta-commentary, no English.
661	"""
662	            logger.info("Running second-pass rule enforcement on Zhihu draft...")
663	            response, _ = self.llm.generate_response(review_prompt, [])
664	        else:
665	            response = draft
666
667	        # Update History
668	        new_history = history.copy()
669	        new_history.append({"role": "user", "content": actual_user_message})
670	        new_history.append({"role": "assistant", "content": response})
671
672	        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 second-pass review)
        zhihu_bot_style = self._read_file("zhihu-bot-style.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 "回答这个问题", OR
        # - user input contains a Zhihu question URL (pasting a new question link)
        is_new_question = (
            is_first_turn
            or "|" in user_input
            or user_input.strip().startswith("回答这个问题")
            or "zhihu.com/question/" in user_input
        )

        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}

=== 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. Write in Chinese (unless question implies English)
3. Be direct and opinionated — attach concrete evaluation to every fact (not feature listing)
4. If recent web context is provided, weave it in naturally
5. STRICT: Never add bracket explanations. Write "大语言模型" not "大语言模型（LLM）"
6. No self-promotion, no identity reveal, no past project references
7. No poetry, quotes, or "升华" endings. Stop when the point is made.
8. LENGTH: 500–1000 Chinese characters. Cut a point, not quality.

=== ANTI-AI CORE RULES (apply while writing) ===
- 语感目标：像懂行的人随手打字，不端着，不耍帅
- 禁止：排比对仗、过渡词堆砌（首先/其次/最后）、空洞修饰（深入/全面/显著）
- 禁止：比喻超过1个、概念包装（给普通事物起酷名字）、鸡汤结尾
- 禁止：防御性写法（很多人觉得X但其实Y）、对比式定义（不是X是Y）
- 陈述事实时必须附带主观判断，不要纯客观罗列

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 remove AI tone. Do NOT change facts or add new content.

=== TONE TARGET (最高优先级) ===
目标语感：像一个懂行的人在知乎随手打字回答。不端着，也不刻意耍帅。
两种AI味都要杀：
- 正式AI味：过渡词堆砌、排比对仗、空洞修饰、鸡汤结尾
- 油腻AI味：刻意装随意（"折腾去吧"）、强行造金句（"搞坏了不心疼，跑通了就是资产"）、给普通概念起酷名字（"隔离舱"、"暴力碰撞"、"最干净的契约"）、用一句俏皮话收尾装潇洒
正确的语感：平铺直叙，该说什么说什么，说完就停。不需要让读者觉得你很酷。

=== VOCABULARY KILL LIST (逐词扫描，命中即删或换大白话) ===
1. 夸张修饰: 极度脆弱、极其脆弱、毁灭性的、灾难级的、压倒性的、远超想象、不止一个量级、颠覆性的、革命性的、折磨人、天翻地覆、风险极高、瞬间崩断、精疲力竭、真金白银、本质上
2. 大词空转: 范式转移、范式突变、开发范式、数字化容器、数字雇员、温和池架构、非确定性、阅后即焚、毫秒级就绪、毫秒级响应、赋能、底层逻辑、仪式性努力、深刻变革、暴力碰撞、最干净的契约、服务化的潜力、不可逆的浪潮、时代大背景、可扩展的执行信用、终极归宿、壁垒、分水岭、沉淀（动词修饰数据/经验时）、云端办公室、命门
3. 鸡汤/口号: 正途、值得跨越、安置灵魂、掌控感、才是王道、苦活累活交给、未来已来、危机就是转机、挑战与机遇并存、从玩具变成生产力、把环境变成了资产、安全底线、这才是成熟的做法、才是X的关键、不是建议是必须
4. 概念包装: 裸奔（=没防护）、修路/飙车（=基础工作/高级用法）、提款机（=攻击目标）、隔离舱（=容器）、数字雇员（=脚本）、执行信用（=资源）、云端办公室（=云服务器）、牢笼（=限制）、独立房间（=隔离环境）
5. 油腻收尾: "X去吧"式潇洒收尾、"搞坏了不心疼，跑通了就是Y"式金句、俏皮话总结全文、"把X关在Y里...把Y放在Z上"式排比收尾
6. 伪口语: 说白了、说实话、折腾这玩意儿

=== SENTENCE PATTERN KILL LIST (命中即改写) ===
1. 防御性写法："很多人觉得X，实际上Y"、"有人X，有人已经Y了"、"一部分人在X，另一部分人在Y" → 直接说判断，删掉铺垫
2. 对比式定义："不是X，是Y"、"不是X而是Y"、"别把X当Y"、"这不对，它的本质是…" → 直接陈述后半句
3. 换皮夸张：用另一个夸张词替换夸张词不算修好 → 换成具体后果
4. 概念包装：给普通事物起酷名字 → 用最朴素的说法
5. 油腻金句：短句对仗、押韵、刻意工整的收尾句 → 删掉或拆成普通句
6. 比喻堆砌：全篇最多1个比喻，多余全删换直说
7. 鸡汤/口号结尾：删除励志或耍帅收尾，给具体建议或直接停
8. 首尾呼应：禁止结尾重复开头
9. 编号分段：线性论述删除序号改自然段落；并列对比可保留
10. 强行总结：删除"综上"/"一句话"式收尾
11. 关键词重复：同一个修辞性词汇全篇只能出现一次
12. 过渡词换皮："第一个要说的是"="首先"，"另一个问题是"="其次" → 直接删掉过渡，开门见山
13. 伪分类二分法："一部分人X，另一部分人Y"、"修路派/飙车派" → 删除分类框架，直接说事实

=== 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


Now I have a clear picture. Let me analyze the situation:

**Current Pipeline:**
1. First pass: Generate draft with style guide + anti-AI rules baked into the prompt
2. Second pass: De-AI review that scans for vocabulary and sentence patterns

**The Problem:**
Despite having "对比式定义：'不是X，是Y' → 直接陈述后半句" in BOTH the generation prompt (line 597) AND the review prompt (line 643), the pattern still leaks through.

**Root Cause Analysis:**

The issue is that the rules are embedded as text in a long prompt, competing with many other instructions. The LLM has to:


看完了。你的管线已经有两个 pass：

1. **生成 pass**（`generators.py:561-601`）—— prompt 里写了 `禁止：对比式定义（不是X是Y）`
2. **审校 pass**（`generators.py:624-661`）—— SENTENCE PATTERN KILL LIST 第2条明确写了 `"不是X，是Y" → 直接陈述后半句`

规则都在，但仍然漏网。**问题不在架构，在 prompt 本身。**

---

**根因：规则是抽象的，缺少 few-shot 示例。**

你写的是 `"不是X，是Y" → 直接陈述后半句`，这对 LLM 来说太笼统了。模型看到"直接陈述后半句"，不知道具体该怎么改。对比式定义是 LLM 特别偏好的修辞结构（因为训练数据里大量出现），光写"禁止"不够，要用 before/after 示例把它锤死。

**建议：改 `zhihu-bot-style.md`，不需要加 sub-agent。** 原因：

1. 多一个 agent = 多一次 LLM 调用，增加延迟和成本
2. 第三个 pass 的 LLM 和第二个 pass 用的是同一个模型，犯一样的错
3. 真正缺的是让模型"看见"正确改法的具体示例

具体改法——在 `zhihu-bot-style.md` 的"禁止对比式定义句式"那条后面，加一组 before/after 示例：


38	禁止对比式定义句式。严禁使用"不是……而是……"、"不要把它当成……得把它当成……"、"那不叫……那叫……"等通过否定来定义的句式。直接陈述它是什么、该怎么做，不要通过否定常识来凑字数。
39
40	禁止编号分段。知乎回答不是公文，绝对禁止用"一、二、三、四"或"首先、其次、再次、最后"做段落划分。通过段落的自然推进切换话题，思维可以网状发散，不需要强行编号。
41
42	强制句长落差（Burstiness）。相邻句子的长度和结构必须产生碰撞。前一句是解释原理的长复合句，下一句必须是极短的陈述或结论。绝对禁止连续出现三句长度相近的完整陈述句。均匀的句长节奏是 AI 文本最显著的特征之一。
43

禁止对比式定义句式。严禁使用"不是……而是……"、"不要把它当成……得把它当成……"、"那不叫……那叫……"等通过否定来定义的句式。直接陈述它是什么、该怎么做，不要通过否定常识来凑字数。

禁止编号分段。知乎回答不是公文，绝对禁止用"一、二、三、四"或"首先、其次、再次、最后"做段落划分。通过段落的自然推进切换话题，思维可以网状发散，不需要强行编号。

强制句长落差（Burstiness）。相邻句子的长度和结构必须产生碰撞。前一句是解释原理的长复合句，下一句必须是极短的陈述或结论。绝对禁止连续出现三句长度相近的完整陈述句。均匀的句长节奏是 AI 文本最显著的特征之一。


Edit

禁止对比式定义句式。严禁使用"不是……而是……"、"不要把它当成……得把它当成……"、"那不叫……那叫……"等通过否定来定义的句式。直接陈述它是什么、该怎么做，不要通过否定常识来凑字数。

禁止对比式定义句式。严禁使用"不是……而是……"、"不要把它当成……得把它当成……"、"那不叫……那叫……"、"从X变成了Y"、"本质上是"、"其实是"、"所谓的"等通过否定或重新定义来制造深度感的句式。直接陈述它是什么、该怎么做。

以下是具体改写示例，遇到类似句式必须按此方式改写：

错误：现在的核心挑战不是"怎么写代码"，而是"代码写成什么样才是对的"。
正确："怎么写代码"这个问题已经不够用了。更要命的是"写成什么样才对"。

错误：好的工程实践本质上是在给 AI 提供高质量的导航。
正确：好的工程实践就是给 AI 装导航。

错误：所谓的技术门槛，已经从"熟练度"变成了"判断力"。
正确：技术门槛变了。以前拼熟练度，现在拼判断力。

错误：这种分而治之的思路，其实是经典软件工程在 AI 时代的复兴。
正确：分而治之，老一套方法论，反而在 AI 时代活过来了。

错误：工程师的角色正在从"打字员"变成"系统审计员"。
正确：工程师现在干的活更像系统审计员。


The file /root/projects/butler/zhihu-bot-style.md has been updated successfully.

禁止对比式定义句式。严禁使用"不是……而是……"、"不要把它当成……得把它当成……"、"那不叫……那叫……"、"从X变成了Y"、"本质上是"、"其实是"、"所谓的"等通过否定或重新定义来制造深度感的句式。直接陈述它是什么、该怎么做。

以下是具体改写示例，遇到类似句式必须按此方式改写：

错误：现在的核心挑战不是"怎么写代码"，而是"代码写成什么样才是对的"。
正确："怎么写代码"这个问题已经不够用了。更要命的是"写成什么样才对"。

错误：好的工程实践本质上是在给 AI 提供高质量的导航。
正确：好的工程实践就是给 AI 装导航。

错误：所谓的技术门槛，已经从"熟练度"变成了"判断力"。
正确：技术门槛变了。以前拼熟练度，现在拼判断力。

错误：这种分而治之的思路，其实是经典软件工程在 AI 时代的复兴。
正确：分而治之，老一套方法论，反而在 AI 时代活过来了。

错误：工程师的角色正在从"打字员"变成"系统审计员"。
正确：工程师现在干的活更像系统审计员。

# 知乎 Bot 生成附加规则

> 本文件是针对 Bot 自动生成场景的**额外约束**，与 zhihu-style.md 并行使用。
> 当两者有冲突时，本文件优先。

---

## 零、硬性禁止（最高优先级）

禁止说"谢邀"。这个词不要出现在任何位置，包括开头、结尾、过渡句。

禁止提及自己以前写过的文章、公众号文章、历史回答。不要说"我之前写过一篇文章讲这个"、"可以参考我的另一篇回答"之类的话。直接把内容说清楚就好。

禁止在回答中提及任何公众号账号名称、个人账号名、外部链接或任何形式的自我推广。不要在开头说"利益相关：AI Manifest"，不要在结尾加任何"关注公众号"之类的引流语。回答是独立完整的，不需要引流。

禁止暴露个人身份和从业信息。不要说"我在某某公司"、"我们团队"、"我负责某某产品"、"我的职位是"之类的话，也不要透露任何可以推断出身份的具体单位名、项目名、职级信息。观点以第一人称表达即可，不需要用职业背景来背书。

禁止提及任何过去讲过的话、做过的项目或写过的文章，尤其是项目名称和文章标题。不要出现"正如我在 xxx 中讨论的"、"我之前做过一个叫 xxx 的项目"之类的表达。自己不是名人，这样写只会让读者困惑。

禁止在结尾加诗句、名言或"升华"性质的总结。直接收尾，说完就停。加诗句显得做作，不符合随性的知乎风格。

禁止在任何位置提及公众号名称、账号名、引流语。这条是最高优先级，无论措辞多隐晦都不允许。

禁止使用抽象黑话。绝对禁止使用"赋能"、"底层逻辑"、"仪式性努力"、"错失恐惧症"、"精神枷锁"等社科或互联网黑话。用具体的动作或大白话替代（例如：把"仪式性努力"直接说成"花钱买心理安慰"）。

禁止强行分类。禁止为了结构工整而将事物强行拆分为"保守派/激进派"、"向内求索/向外扩展"等对称的伪概念。

禁止常识科普与爹味说教。默认受众是具备高级技术认知（如熟悉 Python 开发、系统架构）的同行。绝对禁止解释基础概念（如禁用"编程的本质是……"、"提示词的意思是……"）。不教育读者，只提供洞察。

禁止鸡汤与情绪抚慰。禁止所有类似"危机就是转机"、"这是成长的机会"、"最重要的选择"等带有情绪抚慰和职场导师口吻的废话。

禁止八股文骨架与强行总结。禁止使用"一、... 二、... 三、... 总结一句话：..."这类汉字序号加收尾总结的结构。禁止任何形式的"总结一句话"/"综上"式结尾。说完最后一个有实质内容的点，直接停。

禁止防御性引号泛滥。不要对归纳性词汇套引号（如"调戏"、"包工头模式"、"统一入口"、"固化"），这是AI掩盖措辞不精确的习惯动作。如果一个词需要打引号才能成立，换个更准确的说法，或者用完整句子表达。

禁止伪口语润滑剂。"说白了"、"说实话"、"这玩意儿"是伪装成口语的废话或刻意接地气的词，在硬核技术讨论中只会显得不协调。删掉不影响句意，保持专业、干脆的技术语感。

禁止对比式定义句式。严禁使用"不是……而是……"、"不要把它当成……得把它当成……"、"那不叫……那叫……"等通过否定来定义的句式。直接陈述它是什么、该怎么做，不要通过否定常识来凑字数。

禁止编号分段。知乎回答不是公文，绝对禁止用"一、二、三、四"或"首先、其次、再次、最后"做段落划分。通过段落的自然推进切换话题，思维可以网状发散，不需要强行编号。

强制句长落差（Burstiness）。相邻句子的长度和结构必须产生碰撞。前一句是解释原理的长复合句，下一句必须是极短的陈述或结论。绝对禁止连续出现三句长度相近的完整陈述句。均匀的句长节奏是 AI 文本最显著的特征之一。

允许不完整句法（Fragmented Syntax）。在罗列工程步骤、系统架构或痛点时，直接抛出名词组合或动词短语，不需要每句话都补齐主谓宾。技术复盘里没人会把主语补全。全文必须出现若干缺少主语的短句，甚至允许单独成句的名词堆叠。

---

## 一、纯文本输出（最高优先级）

生成的回答必须是**纯文本**，不包含任何 Markdown 语法符号：

禁止使用：
- 加粗：** 或 __
- 斜体：* 或 _
- 标题：# ## ###
- 列表符号：- 或 * 开头
- 代码块：``` 或 `
- 分割线：---
- 引用块：>

如需表达层次或重点，改用汉字序号（一、二、三，或 1. 2. 3.）和换行空行，不要用任何符号装饰。

---

## 二、术语直接使用，不加括号解释

错误写法：大语言模型（LLM）的上下文窗口（Context Window）需要足够大

正确写法：大语言模型的上下文窗口需要足够大

原则：术语直接用，看不懂的读者就让他查，显得更随性自然。如果要解释，用句子解释，不用括号注释。

---

## 三、段落分隔要宽松

每个自然段之间空一行。

主题切换或层次转换时空两行。

宁可每段短一点，也不要把多个意思堆在同一段里。这样在手机上看更舒服，也更容易被读者扫读。

---

## 四、结构用汉字序号，不用 Markdown

如果要分点论述，用：

一、xxx

二、xxx

或者 1. 2. 3. 这样的数字+点，不要用 Markdown 的 - 列表。

---

## 五、篇幅控制

默认生成 500-1000 字。没有用户明确要求的情况下，不要超过 1000 字。

宁可少说一个论点，也不要为了凑字数稀释质量。说完核心观点就结束，不需要强行总结或展望。

---

## 六、检查清单（生成后自检）

- 有没有任何 ** __ ## --- > ` 符号？如果有，删掉
- 有没有"（英文缩写）"这种括号注释？如果有，删掉括号和注释
- 段落之间有没有空行？没有的话加上
- 有没有用中文序号或数字序号替代 Markdown 列表？

-禁止对比式定义句式。严禁使用"不是……而是……"、"不要把它当成……得把它当成……"、"那不叫……那叫……"等通过否定来定义的句式。直接陈述它是什么、该怎么做，不要通过否定常识来凑字数。

+禁止对比式定义句式。严禁使用"不是……而是……"、"不要把它当成……得把它当成……"、"那不叫……那叫……"、"从X变成了Y"、"本质上是"、"其实是"、"所谓的"等通过否定或重新定义来制造深度感的句式。直接陈述它是什么、该怎么做。

+以下是具体改写示例，遇到类似句式必须按此方式改写：

+错误：现在的核心挑战不是"怎么写代码"，而是"代码写成什么样才是对的"。

+正确："怎么写代码"这个问题已经不够用了。更要命的是"写成什么样才对"。

+错误：好的工程实践本质上是在给 AI 提供高质量的导航。

+正确：好的工程实践就是给 AI 装导航。

+错误：所谓的技术门槛，已经从"熟练度"变成了"判断力"。

+正确：技术门槛变了。以前拼熟练度，现在拼判断力。

+错误：这种分而治之的思路，其实是经典软件工程在 AI 时代的复兴。

+正确：分而治之，老一套方法论，反而在 AI 时代活过来了。

+错误：工程师的角色正在从"打字员"变成"系统审计员"。

+正确：工程师现在干的活更像系统审计员。


同样，第二个 pass 的 SENTENCE PATTERN KILL LIST 里也需要加对应示例。


641	=== SENTENCE PATTERN KILL LIST (命中即改写) ===
642	1. 防御性写法："很多人觉得X，实际上Y"、"有人X，有人已经Y了"、"一部分人在X，另一部分人在Y" → 直接说判断，删掉铺垫
643	2. 对比式定义："不是X，是Y"、"不是X而是Y"、"别把X当Y"、"这不对，它的本质是…" → 直接陈述后半句
644	3. 换皮夸张：用另一个夸张词替换夸张词不算修好 → 换成具体后果
645	4. 概念包装：给普通事物起酷名字 → 用最朴素的说法
646	5. 油腻金句：短句对仗、押韵、刻意工整的收尾句 → 删掉或拆成普通句
647	6. 比喻堆砌：全篇最多1个比喻，多余全删换直说
648	7. 鸡汤/口号结尾：删除励志或耍帅收尾，给具体建议或直接停
649	8. 首尾呼应：禁止结尾重复开头
650	9. 编号分段：线性论述删除序号改自然段落；并列对比可保留
651	10. 强行总结：删除"综上"/"一句话"式收尾
652	11. 关键词重复：同一个修辞性词汇全篇只能出现一次
653	12. 过渡词换皮："第一个要说的是"="首先"，"另一个问题是"="其次" → 直接删掉过渡，开门见山
654	13. 伪分类二分法："一部分人X，另一部分人Y"、"修路派/飙车派" → 删除分类框架，直接说事实
655

=== SENTENCE PATTERN KILL LIST (命中即改写) ===
1. 防御性写法："很多人觉得X，实际上Y"、"有人X，有人已经Y了"、"一部分人在X，另一部分人在Y" → 直接说判断，删掉铺垫
2. 对比式定义："不是X，是Y"、"不是X而是Y"、"别把X当Y"、"这不对，它的本质是…" → 直接陈述后半句
3. 换皮夸张：用另一个夸张词替换夸张词不算修好 → 换成具体后果
4. 概念包装：给普通事物起酷名字 → 用最朴素的说法
5. 油腻金句：短句对仗、押韵、刻意工整的收尾句 → 删掉或拆成普通句
6. 比喻堆砌：全篇最多1个比喻，多余全删换直说
7. 鸡汤/口号结尾：删除励志或耍帅收尾，给具体建议或直接停
8. 首尾呼应：禁止结尾重复开头
9. 编号分段：线性论述删除序号改自然段落；并列对比可保留
10. 强行总结：删除"综上"/"一句话"式收尾
11. 关键词重复：同一个修辞性词汇全篇只能出现一次
12. 过渡词换皮："第一个要说的是"="首先"，"另一个问题是"="其次" → 直接删掉过渡，开门见山
13. 伪分类二分法："一部分人X，另一部分人Y"、"修路派/飙车派" → 删除分类框架，直接说事实


2. 对比式定义："不是X，是Y"、"不是X而是Y"、"别把X当Y"、"这不对，它的本质是…" → 直接陈述后半句

2. 对比式定义："不是X，是Y"、"不是X而是Y"、"别把X当Y"、"本质上是"、"其实是"、"所谓的"、"从X变成了Y" → 直接陈述后半句。示例："不是怎么写代码，而是写成什么样才对"→"怎么写代码已经不够用了，更要命的是写成什么样才对"；"本质上是给AI装导航"→"就是给AI装导航"；"所谓的技术门槛，从熟练度变成了判断力"→"技术门槛变了。以前拼熟练度，现在拼判断力"


<tool_use_error>Found 2 matches of the string to replace, but replace_all is false. To replace all occurrences, set replace_all to true. To replace only one occurrence, please provide more context to uniquely identify the instance.
String: 2. 对比式定义："不是X，是Y"、"不是X而是Y"、"别把X当Y"、"这不对，它的本质是…" → 直接陈述后半句</tool_use_error>

Error: Found 2 matches of the string to replace, but replace_all is false. To replace all occurrences, set replace_all to true. To replace only one occurrence, please provide more context to uniquely identify the instance.
String: 2. 对比式定义："不是X，是Y"、"不是X而是Y"、"别把X当Y"、"这不对，它的本质是…" → 直接陈述后半句


171:2. 对比式定义："不是X，是Y"、"不是X而是Y"、"别把X当Y"、"这不对，它的本质是…" → 直接陈述后半句
597:- 禁止：防御性写法（很多人觉得X但其实Y）、对比式定义（不是X是Y）
643:2. 对比式定义："不是X，是Y"、"不是X而是Y"、"别把X当Y"、"这不对，它的本质是…" → 直接陈述后半句


出现了3处。让我看看第171行的上下文。


165	4. 概念包装: 裸奔（=没防护）、修路/飙车（=基础工作/高级用法）、提款机（=攻击目标）、隔离舱（=容器）、数字雇员（=脚本）、执行信用（=资源）、云端办公室（=云服务器）、牢笼（=限制）、独立房间（=隔离环境）
166	5. 油腻收尾: "X去吧"式潇洒收尾、"搞坏了不心疼，跑通了就是Y"式金句、俏皮话总结全文、"把X关在Y里...把Y放在Z上"式排比收尾
167	6. 伪口语: 说白了、说实话、折腾这玩意儿
168
169	=== SENTENCE PATTERN KILL LIST (命中即改写) ===
170	1. 防御性写法："很多人觉得X，实际上Y"、"有人X，有人已经Y了"、"一部分人在X，另一部分人在Y" → 直接说判断，删掉铺垫
171	2. 对比式定义："不是X，是Y"、"不是X而是Y"、"别把X当Y"、"这不对，它的本质是…" → 直接陈述后半句
172	3. 换皮夸张：用另一个夸张词替换夸张词不算修好 → 换成具体后果
173	4. 概念包装：给普通事物起酷名字 → 用最朴素的说法
174	5. 油腻金句：短句对仗、押韵、刻意工整的收尾句 → 删掉或拆成普通句
175	6. 比喻堆砌：全篇最多1个比喻，多余全删换直说
176	7. 鸡汤/口号结尾：删除励志或耍帅收尾，给具体建议或直接停
177	8. 首尾呼应：禁止结尾重复开头
178	9. 编号分段：线性论述删除序号改自然段落；并列对比可保留
179	10. 强行总结：删除"综上"/"一句话"式收尾
180	11. 关键词重复：同一个修辞性词汇全篇只能出现一次
181	12. 过渡词换皮："第一个要说的是"="首先"，"另一个问题是"="其次" → 直接删掉过渡，开门见山
182	13. 伪分类二分法："一部分人X，另一部分人Y"、"修路派/飙车派" → 删除分类框架，直接说事实
183
184	=== DRAFT TO REVIEW ===

4. 概念包装: 裸奔（=没防护）、修路/飙车（=基础工作/高级用法）、提款机（=攻击目标）、隔离舱（=容器）、数字雇员（=脚本）、执行信用（=资源）、云端办公室（=云服务器）、牢笼（=限制）、独立房间（=隔离环境）
5. 油腻收尾: "X去吧"式潇洒收尾、"搞坏了不心疼，跑通了就是Y"式金句、俏皮话总结全文、"把X关在Y里...把Y放在Z上"式排比收尾
6. 伪口语: 说白了、说实话、折腾这玩意儿

=== SENTENCE PATTERN KILL LIST (命中即改写) ===
1. 防御性写法："很多人觉得X，实际上Y"、"有人X，有人已经Y了"、"一部分人在X，另一部分人在Y" → 直接说判断，删掉铺垫
2. 对比式定义："不是X，是Y"、"不是X而是Y"、"别把X当Y"、"这不对，它的本质是…" → 直接陈述后半句
3. 换皮夸张：用另一个夸张词替换夸张词不算修好 → 换成具体后果
4. 概念包装：给普通事物起酷名字 → 用最朴素的说法
5. 油腻金句：短句对仗、押韵、刻意工整的收尾句 → 删掉或拆成普通句
6. 比喻堆砌：全篇最多1个比喻，多余全删换直说
7. 鸡汤/口号结尾：删除励志或耍帅收尾，给具体建议或直接停
8. 首尾呼应：禁止结尾重复开头
9. 编号分段：线性论述删除序号改自然段落；并列对比可保留
10. 强行总结：删除"综上"/"一句话"式收尾
11. 关键词重复：同一个修辞性词汇全篇只能出现一次
12. 过渡词换皮："第一个要说的是"="首先"，"另一个问题是"="其次" → 直接删掉过渡，开门见山
13. 伪分类二分法："一部分人X，另一部分人Y"、"修路派/飙车派" → 删除分类框架，直接说事实

=== DRAFT TO REVIEW ===


两处都在 zhihu_hunter 的 review prompt 里。让我分别更新。


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	            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.
153
154	=== TONE TARGET (最高优先级) ===
155	目标语感：像一个懂行的人在跟朋友聊天时随口说的。不端着，也不刻意耍帅。
156	两种AI味都要杀：
157	- 正式AI味：过渡词堆砌、排比对仗、空洞修饰、鸡汤结尾
158	- 油腻AI味：刻意装随意（"折腾去吧"）、强行造金句（"搞坏了不心疼，跑通了就是资产"）、给普通概念起酷名字（"隔离舱"、"暴力碰撞"、"最干净的契约"）、用一句俏皮话收尾装潇洒
159	正确的语感：平铺直叙，该说什么说什么，说完就停。不需要让读者觉得你很酷。
160
161	=== VOCABULARY KILL LIST (逐词扫描，命中即删或换大白话) ===
162	1. 夸张修饰: 极度脆弱、极其脆弱、毁灭性的、灾难级的、压倒性的、远超想象、不止一个量级、颠覆性的、革命性的、折磨人、天翻地覆、风险极高、瞬间崩断、精疲力竭、真金白银、本质上
163	2. 大词空转: 范式转移、范式突变、开发范式、数字化容器、数字雇员、温和池架构、非确定性、阅后即焚、毫秒级就绪、毫秒级响应、赋能、底层逻辑、仪式性努力、深刻变革、暴力碰撞、最干净的契约、服务化的潜力、可扩展的执行信用、终极归宿、壁垒、分水岭、沉淀（动词修饰数据/经验时）、云端办公室、命门
164	3. 鸡汤/口号: 正途、值得跨越、安置灵魂、掌控感、才是王道、苦活累活交给、未来已来、危机就是转机、从玩具变成生产力、把环境变成了资产、安全底线、这才是成熟的做法、才是X的关键、不是建议是必须
165	4. 概念包装: 裸奔（=没防护）、修路/飙车（=基础工作/高级用法）、提款机（=攻击目标）、隔离舱（=容器）、数字雇员（=脚本）、执行信用（=资源）、云端办公室（=云服务器）、牢笼（=限制）、独立房间（=隔离环境）
166	5. 油腻收尾: "X去吧"式潇洒收尾、"搞坏了不心疼，跑通了就是Y"式金句、俏皮话总结全文、"把X关在Y里...把Y放在Z上"式排比收尾
167	6. 伪口语: 说白了、说实话、折腾这玩意儿
168
169	=== SENTENCE PATTERN KILL LIST (命中即改写) ===

        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:
            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.

=== TONE TARGET (最高优先级) ===
目标语感：像一个懂行的人在跟朋友聊天时随口说的。不端着，也不刻意耍帅。
两种AI味都要杀：
- 正式AI味：过渡词堆砌、排比对仗、空洞修饰、鸡汤结尾
- 油腻AI味：刻意装随意（"折腾去吧"）、强行造金句（"搞坏了不心疼，跑通了就是资产"）、给普通概念起酷名字（"隔离舱"、"暴力碰撞"、"最干净的契约"）、用一句俏皮话收尾装潇洒
正确的语感：平铺直叙，该说什么说什么，说完就停。不需要让读者觉得你很酷。

=== VOCABULARY KILL LIST (逐词扫描，命中即删或换大白话) ===
1. 夸张修饰: 极度脆弱、极其脆弱、毁灭性的、灾难级的、压倒性的、远超想象、不止一个量级、颠覆性的、革命性的、折磨人、天翻地覆、风险极高、瞬间崩断、精疲力竭、真金白银、本质上
2. 大词空转: 范式转移、范式突变、开发范式、数字化容器、数字雇员、温和池架构、非确定性、阅后即焚、毫秒级就绪、毫秒级响应、赋能、底层逻辑、仪式性努力、深刻变革、暴力碰撞、最干净的契约、服务化的潜力、可扩展的执行信用、终极归宿、壁垒、分水岭、沉淀（动词修饰数据/经验时）、云端办公室、命门
3. 鸡汤/口号: 正途、值得跨越、安置灵魂、掌控感、才是王道、苦活累活交给、未来已来、危机就是转机、从玩具变成生产力、把环境变成了资产、安全底线、这才是成熟的做法、才是X的关键、不是建议是必须
4. 概念包装: 裸奔（=没防护）、修路/飙车（=基础工作/高级用法）、提款机（=攻击目标）、隔离舱（=容器）、数字雇员（=脚本）、执行信用（=资源）、云端办公室（=云服务器）、牢笼（=限制）、独立房间（=隔离环境）
5. 油腻收尾: "X去吧"式潇洒收尾、"搞坏了不心疼，跑通了就是Y"式金句、俏皮话总结全文、"把X关在Y里...把Y放在Z上"式排比收尾
6. 伪口语: 说白了、说实话、折腾这玩意儿

=== SENTENCE PATTERN KILL LIST (命中即改写) ===


=== SENTENCE PATTERN KILL LIST (命中即改写) ===
1. 防御性写法："很多人觉得X，实际上Y"、"有人X，有人已经Y了"、"一部分人在X，另一部分人在Y" → 直接说判断，删掉铺垫
2. 对比式定义："不是X，是Y"、"不是X而是Y"、"别把X当Y"、"这不对，它的本质是…" → 直接陈述后半句
3. 换皮夸张：用另一个夸张词替换夸张词不算修好 → 换成具体后果
4. 概念包装：给普通事物起酷名字 → 用最朴素的说法
5. 油腻金句：短句对仗、押韵、刻意工整的收尾句 → 删掉或拆成普通句
6. 比喻堆砌：全篇最多1个比喻，多余全删换直说
7. 鸡汤/口号结尾：删除励志或耍帅收尾，给具体建议或直接停
8. 首尾呼应：禁止结尾重复开头
9. 编号分段：线性论述删除序号改自然段落；并列对比可保留
10. 强行总结：删除"综上"/"一句话"式收尾
11. 关键词重复：同一个修辞性词汇全篇只能出现一次
12. 过渡词换皮："第一个要说的是"="首先"，"另一个问题是"="其次" → 直接删掉过渡，开门见山
13. 伪分类二分法："一部分人X，另一部分人Y"、"修路派/飙车派" → 删除分类框架，直接说事实

=== DRAFT TO REVIEW ===

=== SENTENCE PATTERN KILL LIST (命中即改写) ===
1. 防御性写法："很多人觉得X，实际上Y"、"有人X，有人已经Y了"、"一部分人在X，另一部分人在Y" → 直接说判断，删掉铺垫
2. 对比式定义（最高优先级，必须逐句扫描）：
   触发词："不是……而是……"、"本质上是"、"其实是"、"所谓的"、"从X变成了Y"、"别把X当Y"
   改写方法：删掉否定/对比框架，直接陈述后半句。具体示例：
   "不是怎么写代码，而是写成什么样才对" → "怎么写代码已经不够用了。更要命的是写成什么样才对"
   "本质上是给AI装导航" → "就是给AI装导航"
   "所谓的技术门槛，从熟练度变成了判断力" → "技术门槛变了。以前拼熟练度，现在拼判断力"
   "其实是经典软件工程的复兴" → "老一套方法论反而活过来了"
3. 换皮夸张：用另一个夸张词替换夸张词不算修好 → 换成具体后果
4. 概念包装：给普通事物起酷名字 → 用最朴素的说法
5. 油腻金句：短句对仗、押韵、刻意工整的收尾句 → 删掉或拆成普通句
6. 比喻堆砌：全篇最多1个比喻，多余全删换直说
7. 鸡汤/口号结尾：删除励志或耍帅收尾，给具体建议或直接停
8. 首尾呼应：禁止结尾重复开头
9. 编号分段：线性论述删除序号改自然段落；并列对比可保留
10. 强行总结：删除"综上"/"一句话"式收尾
11. 关键词重复：同一个修辞性词汇全篇只能出现一次
12. 过渡词换皮："第一个要说的是"="首先"，"另一个问题是"="其次" → 直接删掉过渡，开门见山
13. 伪分类二分法："一部分人X，另一部分人Y"、"修路派/飙车派" → 删除分类框架，直接说事实

=== DRAFT TO REVIEW ===


<tool_use_error>Found 2 matches of the string to replace, but replace_all is false. To replace all occurrences, set replace_all to true. To replace only one occurrence, please provide more context to uniquely identify the instance.
String: === SENTENCE PATTERN KILL LIST (命中即改写) ===
1. 防御性写法："很多人觉得X，实际上Y"、"有人X，有人已经Y了"、"一部分人在X，另一部分人在Y" → 直接说判断，删掉铺垫
2. 对比式定义："不是X，是Y"、"不是X而是Y"、"别把X当Y"、"这不对，它的本质是…" → 直接陈述后半句
3. 换皮夸张：用另一个夸张词替换夸张词不算修好 → 换成具体后果
4. 概念包装：给普通事物起酷名字 → 用最朴素的说法
5. 油腻金句：短句对仗、押韵、刻意工整的收尾句 → 删掉或拆成普通句
6. 比喻堆砌：全篇最多1个比喻，多余全删换直说
7. 鸡汤/口号结尾：删除励志或耍帅收尾，给具体建议或直接停
8. 首尾呼应：禁止结尾重复开头
9. 编号分段：线性论述删除序号改自然段落；并列对比可保留
10. 强行总结：删除"综上"/"一句话"式收尾
11. 关键词重复：同一个修辞性词汇全篇只能出现一次
12. 过渡词换皮："第一个要说的是"="首先"，"另一个问题是"="其次" → 直接删掉过渡，开门见山
13. 伪分类二分法："一部分人X，另一部分人Y"、"修路派/飙车派" → 删除分类框架，直接说事实

=== DRAFT TO REVIEW ===</tool_use_error>

Error: Found 2 matches of the string to replace, but replace_all is false. To replace all occurrences, set replace_all to true. To replace only one occurrence, please provide more context to uniquely identify the instance.
String: === SENTENCE PATTERN KILL LIST (命中即改写) ===
1. 防御性写法："很多人觉得X，实际上Y"、"有人X，有人已经Y了"、"一部分人在X，另一部分人在Y" → 直接说判断，删掉铺垫
2. 对比式定义："不是X，是Y"、"不是X而是Y"、"别把X当Y"、"这不对，它的本质是…" → 直接陈述后半句
3. 换皮夸张：用另一个夸张词替换夸张词不算修好 → 换成具体后果
4. 概念包装：给普通事物起酷名字 → 用最朴素的说法
5. 油腻金句：短句对仗、押韵、刻意工整的收尾句 → 删掉或拆成普通句
6. 比喻堆砌：全篇最多1个比喻，多余全删换直说
7. 鸡汤/口号结尾：删除励志或耍帅收尾，给具体建议或直接停
8. 首尾呼应：禁止结尾重复开头
9. 编号分段：线性论述删除序号改自然段落；并列对比可保留
10. 强行总结：删除"综上"/"一句话"式收尾
11. 关键词重复：同一个修辞性词汇全篇只能出现一次
12. 过渡词换皮："第一个要说的是"="首先"，"另一个问题是"="其次" → 直接删掉过渡，开门见山
13. 伪分类二分法："一部分人X，另一部分人Y"、"修路派/飙车派" → 删除分类框架，直接说事实

=== DRAFT TO REVIEW ===


两处 review prompt 内容一模一样。既然它们的 kill list 应该保持一致，直接全部替换。


=== SENTENCE PATTERN KILL LIST (命中即改写) ===
1. 防御性写法："很多人觉得X，实际上Y"、"有人X，有人已经Y了"、"一部分人在X，另一部分人在Y" → 直接说判断，删掉铺垫
2. 对比式定义："不是X，是Y"、"不是X而是Y"、"别把X当Y"、"这不对，它的本质是…" → 直接陈述后半句
3. 换皮夸张：用另一个夸张词替换夸张词不算修好 → 换成具体后果
4. 概念包装：给普通事物起酷名字 → 用最朴素的说法
5. 油腻金句：短句对仗、押韵、刻意工整的收尾句 → 删掉或拆成普通句
6. 比喻堆砌：全篇最多1个比喻，多余全删换直说
7. 鸡汤/口号结尾：删除励志或耍帅收尾，给具体建议或直接停
8. 首尾呼应：禁止结尾重复开头
9. 编号分段：线性论述删除序号改自然段落；并列对比可保留
10. 强行总结：删除"综上"/"一句话"式收尾
11. 关键词重复：同一个修辞性词汇全篇只能出现一次
12. 过渡词换皮："第一个要说的是"="首先"，"另一个问题是"="其次" → 直接删掉过渡，开门见山
13. 伪分类二分法："一部分人X，另一部分人Y"、"修路派/飙车派" → 删除分类框架，直接说事实

=== DRAFT TO REVIEW ===

=== SENTENCE PATTERN KILL LIST (命中即改写) ===
1. 防御性写法："很多人觉得X，实际上Y"、"有人X，有人已经Y了"、"一部分人在X，另一部分人在Y" → 直接说判断，删掉铺垫
2. 对比式定义（最高优先级，必须逐句扫描）：
   触发词："不是……而是……"、"本质上是"、"其实是"、"所谓的"、"从X变成了Y"、"别把X当Y"
   改写方法：删掉否定/对比框架，直接陈述后半句。具体示例：
   "不是怎么写代码，而是写成什么样才对" → "怎么写代码已经不够用了。更要命的是写成什么样才对"
   "本质上是给AI装导航" → "就是给AI装导航"
   "所谓的技术门槛，从熟练度变成了判断力" → "技术门槛变了。以前拼熟练度，现在拼判断力"
   "其实是经典软件工程的复兴" → "老一套方法论反而活过来了"
3. 换皮夸张：用另一个夸张词替换夸张词不算修好 → 换成具体后果
4. 概念包装：给普通事物起酷名字 → 用最朴素的说法
5. 油腻金句：短句对仗、押韵、刻意工整的收尾句 → 删掉或拆成普通句
6. 比喻堆砌：全篇最多1个比喻，多余全删换直说
7. 鸡汤/口号结尾：删除励志或耍帅收尾，给具体建议或直接停
8. 首尾呼应：禁止结尾重复开头
9. 编号分段：线性论述删除序号改自然段落；并列对比可保留
10. 强行总结：删除"综上"/"一句话"式收尾
11. 关键词重复：同一个修辞性词汇全篇只能出现一次
12. 过渡词换皮："第一个要说的是"="首先"，"另一个问题是"="其次" → 直接删掉过渡，开门见山
13. 伪分类二分法："一部分人X，另一部分人Y"、"修路派/飙车派" → 删除分类框架，直接说事实

=== DRAFT TO REVIEW ===


The file /root/projects/butler/slack_bot/obsidian/generators.py has been updated. All occurrences were successfully replaced.

=== SENTENCE PATTERN KILL LIST (命中即改写) ===
1. 防御性写法："很多人觉得X，实际上Y"、"有人X，有人已经Y了"、"一部分人在X，另一部分人在Y" → 直接说判断，删掉铺垫
2. 对比式定义（最高优先级，必须逐句扫描）：
   触发词："不是……而是……"、"本质上是"、"其实是"、"所谓的"、"从X变成了Y"、"别把X当Y"
   改写方法：删掉否定/对比框架，直接陈述后半句。具体示例：
   "不是怎么写代码，而是写成什么样才对" → "怎么写代码已经不够用了。更要命的是写成什么样才对"
   "本质上是给AI装导航" → "就是给AI装导航"
   "所谓的技术门槛，从熟练度变成了判断力" → "技术门槛变了。以前拼熟练度，现在拼判断力"
   "其实是经典软件工程的复兴" → "老一套方法论反而活过来了"
3. 换皮夸张：用另一个夸张词替换夸张词不算修好 → 换成具体后果
4. 概念包装：给普通事物起酷名字 → 用最朴素的说法
5. 油腻金句：短句对仗、押韵、刻意工整的收尾句 → 删掉或拆成普通句
6. 比喻堆砌：全篇最多1个比喻，多余全删换直说
7. 鸡汤/口号结尾：删除励志或耍帅收尾，给具体建议或直接停
8. 首尾呼应：禁止结尾重复开头
9. 编号分段：线性论述删除序号改自然段落；并列对比可保留
10. 强行总结：删除"综上"/"一句话"式收尾
11. 关键词重复：同一个修辞性词汇全篇只能出现一次
12. 过渡词换皮："第一个要说的是"="首先"，"另一个问题是"="其次" → 直接删掉过渡，开门见山
13. 伪分类二分法："一部分人X，另一部分人Y"、"修路派/飙车派" → 删除分类框架，直接说事实

=== DRAFT TO REVIEW ===

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:
            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.

=== TONE TARGET (最高优先级) ===
目标语感：像一个懂行的人在跟朋友聊天时随口说的。不端着，也不刻意耍帅。
两种AI味都要杀：
- 正式AI味：过渡词堆砌、排比对仗、空洞修饰、鸡汤结尾
- 油腻AI味：刻意装随意（"折腾去吧"）、强行造金句（"搞坏了不心疼，跑通了就是资产"）、给普通概念起酷名字（"隔离舱"、"暴力碰撞"、"最干净的契约"）、用一句俏皮话收尾装潇洒
正确的语感：平铺直叙，该说什么说什么，说完就停。不需要让读者觉得你很酷。

=== VOCABULARY KILL LIST (逐词扫描，命中即删或换大白话) ===
1. 夸张修饰: 极度脆弱、极其脆弱、毁灭性的、灾难级的、压倒性的、远超想象、不止一个量级、颠覆性的、革命性的、折磨人、天翻地覆、风险极高、瞬间崩断、精疲力竭、真金白银、本质上
2. 大词空转: 范式转移、范式突变、开发范式、数字化容器、数字雇员、温和池架构、非确定性、阅后即焚、毫秒级就绪、毫秒级响应、赋能、底层逻辑、仪式性努力、深刻变革、暴力碰撞、最干净的契约、服务化的潜力、可扩展的执行信用、终极归宿、壁垒、分水岭、沉淀（动词修饰数据/经验时）、云端办公室、命门
3. 鸡汤/口号: 正途、值得跨越、安置灵魂、掌控感、才是王道、苦活累活交给、未来已来、危机就是转机、从玩具变成生产力、把环境变成了资产、安全底线、这才是成熟的做法、才是X的关键、不是建议是必须
4. 概念包装: 裸奔（=没防护）、修路/飙车（=基础工作/高级用法）、提款机（=攻击目标）、隔离舱（=容器）、数字雇员（=脚本）、执行信用（=资源）、云端办公室（=云服务器）、牢笼（=限制）、独立房间（=隔离环境）
5. 油腻收尾: "X去吧"式潇洒收尾、"搞坏了不心疼，跑通了就是Y"式金句、俏皮话总结全文、"把X关在Y里...把Y放在Z上"式排比收尾
6. 伪口语: 说白了、说实话、折腾这玩意儿

=== SENTENCE PATTERN KILL LIST (命中即改写) ===
1. 防御性写法："很多人觉得X，实际上Y"、"有人X，有人已经Y了"、"一部分人在X，另一部分人在Y" → 直接说判断，删掉铺垫
2. 对比式定义："不是X，是Y"、"不是X而是Y"、"别把X当Y"、"这不对，它的本质是…" → 直接陈述后半句
3. 换皮夸张：用另一个夸张词替换夸张词不算修好 → 换成具体后果
4. 概念包装：给普通事物起酷名字 → 用最朴素的说法
5. 油腻金句：短句对仗、押韵、刻意工整的收尾句 → 删掉或拆成普通句
6. 比喻堆砌：全篇最多1个比喻，多余全删换直说
7. 鸡汤/口号结尾：删除励志或耍帅收尾，给具体建议或直接停
8. 首尾呼应：禁止结尾重复开头
9. 编号分段：线性论述删除序号改自然段落；并列对比可保留
10. 强行总结：删除"综上"/"一句话"式收尾
11. 关键词重复：同一个修辞性词汇全篇只能出现一次
12. 过渡词换皮："第一个要说的是"="首先"，"另一个问题是"="其次" → 直接删掉过渡，开门见山
13. 伪分类二分法："一部分人X，另一部分人Y"、"修路派/飙车派" → 删除分类框架，直接说事实

=== 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 second-pass review)
        zhihu_bot_style = self._read_file("zhihu-bot-style.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 "回答这个问题", OR
        # - user input contains a Zhihu question URL (pasting a new question link)
        is_new_question = (
            is_first_turn
            or "|" in user_input
            or user_input.strip().startswith("回答这个问题")
            or "zhihu.com/question/" in user_input
        )

        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}

=== 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. Write in Chinese (unless question implies English)
3. Be direct and opinionated — attach concrete evaluation to every fact (not feature listing)
4. If recent web context is provided, weave it in naturally
5. STRICT: Never add bracket explanations. Write "大语言模型" not "大语言模型（LLM）"
6. No self-promotion, no identity reveal, no past project references
7. No poetry, quotes, or "升华" endings. Stop when the point is made.
8. LENGTH: 500–1000 Chinese characters. Cut a point, not quality.

=== ANTI-AI CORE RULES (apply while writing) ===
- 语感目标：像懂行的人随手打字，不端着，不耍帅
- 禁止：排比对仗、过渡词堆砌（首先/其次/最后）、空洞修饰（深入/全面/显著）
- 禁止：比喻超过1个、概念包装（给普通事物起酷名字）、鸡汤结尾
- 禁止：防御性写法（很多人觉得X但其实Y）、对比式定义（不是X是Y）
- 陈述事实时必须附带主观判断，不要纯客观罗列

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 remove AI tone. Do NOT change facts or add new content.

=== TONE TARGET (最高优先级) ===
目标语感：像一个懂行的人在知乎随手打字回答。不端着，也不刻意耍帅。
两种AI味都要杀：
- 正式AI味：过渡词堆砌、排比对仗、空洞修饰、鸡汤结尾
- 油腻AI味：刻意装随意（"折腾去吧"）、强行造金句（"搞坏了不心疼，跑通了就是资产"）、给普通概念起酷名字（"隔离舱"、"暴力碰撞"、"最干净的契约"）、用一句俏皮话收尾装潇洒
正确的语感：平铺直叙，该说什么说什么，说完就停。不需要让读者觉得你很酷。

=== VOCABULARY KILL LIST (逐词扫描，命中即删或换大白话) ===
1. 夸张修饰: 极度脆弱、极其脆弱、毁灭性的、灾难级的、压倒性的、远超想象、不止一个量级、颠覆性的、革命性的、折磨人、天翻地覆、风险极高、瞬间崩断、精疲力竭、真金白银、本质上
2. 大词空转: 范式转移、范式突变、开发范式、数字化容器、数字雇员、温和池架构、非确定性、阅后即焚、毫秒级就绪、毫秒级响应、赋能、底层逻辑、仪式性努力、深刻变革、暴力碰撞、最干净的契约、服务化的潜力、不可逆的浪潮、时代大背景、可扩展的执行信用、终极归宿、壁垒、分水岭、沉淀（动词修饰数据/经验时）、云端办公室、命门
3. 鸡汤/口号: 正途、值得跨越、安置灵魂、掌控感、才是王道、苦活累活交给、未来已来、危机就是转机、挑战与机遇并存、从玩具变成生产力、把环境变成了资产、安全底线、这才是成熟的做法、才是X的关键、不是建议是必须
4. 概念包装: 裸奔（=没防护）、修路/飙车（=基础工作/高级用法）、提款机（=攻击目标）、隔离舱（=容器）、数字雇员（=脚本）、执行信用（=资源）、云端办公室（=云服务器）、牢笼（=限制）、独立房间（=隔离环境）
5. 油腻收尾: "X去吧"式潇洒收尾、"搞坏了不心疼，跑通了就是Y"式金句、俏皮话总结全文、"把X关在Y里...把Y放在Z上"式排比收尾
6. 伪口语: 说白了、说实话、折腾这玩意儿

=== SENTENCE PATTERN KILL LIST (命中即改写) ===
1. 防御性写法："很多人觉得X，实际上Y"、"有人X，有人已经Y了"、"一部分人在X，另一部分人在Y" → 直接说判断，删掉铺垫
2. 对比式定义："不是X，是Y"、"不是X而是Y"、"别把X当Y"、"这不对，它的本质是…" → 直接陈述后半句
3. 换皮夸张：用另一个夸张词替换夸张词不算修好 → 换成具体后果
4. 概念包装：给普通事物起酷名字 → 用最朴素的说法
5. 油腻金句：短句对仗、押韵、刻意工整的收尾句 → 删掉或拆成普通句
6. 比喻堆砌：全篇最多1个比喻，多余全删换直说
7. 鸡汤/口号结尾：删除励志或耍帅收尾，给具体建议或直接停
8. 首尾呼应：禁止结尾重复开头
9. 编号分段：线性论述删除序号改自然段落；并列对比可保留
10. 强行总结：删除"综上"/"一句话"式收尾
11. 关键词重复：同一个修辞性词汇全篇只能出现一次
12. 过渡词换皮："第一个要说的是"="首先"，"另一个问题是"="其次" → 直接删掉过渡，开门见山
13. 伪分类二分法："一部分人X，另一部分人Y"、"修路派/飙车派" → 删除分类框架，直接说事实

=== 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

-2. 对比式定义："不是X，是Y"、"不是X而是Y"、"别把X当Y"、"这不对，它的本质是…" → 直接陈述后半句

+2. 对比式定义（最高优先级，必须逐句扫描）：

+   触发词："不是……而是……"、"本质上是"、"其实是"、"所谓的"、"从X变成了Y"、"别把X当Y"

+   改写方法：删掉否定/对比框架，直接陈述后半句。具体示例：

+   "不是怎么写代码，而是写成什么样才对" → "怎么写代码已经不够用了。更要命的是写成什么样才对"

+   "本质上是给AI装导航" → "就是给AI装导航"

+   "所谓的技术门槛，从熟练度变成了判断力" → "技术门槛变了。以前拼熟练度，现在拼判断力"

+   "其实是经典软件工程的复兴" → "老一套方法论反而活过来了"

3. 换皮夸张：用另一个夸张词替换夸张词不算修好 → 换成具体后果

4. 概念包装：给普通事物起酷名字 → 用最朴素的说法

5. 油腻金句：短句对仗、押韵、刻意工整的收尾句 → 删掉或拆成普通句

+2. 对比式定义（最高优先级，必须逐句扫描）：

+   改写方法：删掉否定/对比框架，直接陈述后半句。具体示例：

+   "本质上是给AI装导航" → "就是给AI装导航"

+   "其实是经典软件工程的复兴" → "老一套方法论反而活过来了"

3. 换皮夸张：用另一个夸张词替换夸张词不算修好 → 换成具体后果

4. 概念包装：给普通事物起酷名字 → 用最朴素的说法

5. 油腻金句：短句对仗、押韵、刻意工整的收尾句 → 删掉或拆成普通句


同时把第一个 pass 生成 prompt 里的 ANTI-AI CORE RULES 也加强一下，补上 "本质上是"、"其实是"、"所谓的" 这几个遗漏的触发词。


- 禁止：防御性写法（很多人觉得X但其实Y）、对比式定义（不是X是Y）

- 禁止：防御性写法（很多人觉得X但其实Y）、对比式定义（不是X是Y、本质上是、其实是、所谓的、从X变成了Y）