# 文风生成 SOP

> **何时加载**：story-long-analyze Stage 6 执行时。前置依赖：Stage 0-5 已完成，`拆文报告.md` + `章节/*_摘要.md` + `章节/第1-3章_深度拆解.md` + `原文/原文.txt`（或 `.md`）齐全。
>
> **输出**：`拆文库/{书名}/文风.md`（模板见 [style-profile-protocol.md](style-profile-protocol.md)）。

## 6 步流程

### Step 1: 读拆文报告核心字段

读 `拆文库/{书名}/拆文报告.md`，提取：

- **「写法技巧」段** → 用于填文风「可借鉴技巧」的「写法技巧 Top 5」
- **「可借鉴套路」段** → 用于填文风「可借鉴技巧」的「可借鉴套路 Top 3」
- **基本信息**（书名、题材、总章数）→ 填写文风标题与「生成记录」
- 「生成记录」只写作者能看懂的信息：参考了哪些资料、抽样看了哪几章、生成时间、文风是否可用；不要写文件时间戳或内部降级标记这类实现术语。

### Step 2: 读黄金三章深度拆解

读 `章节/第1章_深度拆解.md`、`第2章_深度拆解.md`、`第3章_深度拆解.md`，提取：

- **开篇钩子类型 + 手法**
- **反应层拆解表**（对话潜台词样本）
- **爽点铺放比**（情绪交替节奏样本）
- **可借鉴要素**（与 Step 1 合并去重，喂「写法技巧 Top 5」）

### Step 3: 提取章节基调/主题标签序列

用 Grep 读所有 `章节/*_摘要.md`：

```bash
grep -hE '基调：(紧张|轻松|悲伤|热血|爽|甜|温馨|恐怖|压抑|其他)' 章节/*_摘要.md
```

**关键格式注意**：`章节/*_摘要.md` 的实际格式是 `主题标签：X | 基调：Y` 内联在每个情节点行（一章 11-19 行），用**全角冒号**，**不在行首**。grep 模式不能用 `^基调:` 这种锚定。

**章基调聚合规则**（每章一个章基调，写入文风「情绪交替模式」）：

- 对该章所有情节点的「基调」字段做众数统计
- 并列时（如 5 紧张 vs 5 热血）取章节内**最早出现**的基调（按 `_摘要.md` 中行号）
- 输出格式：`第N章: {章基调}`，连成全书序列

**章内情节点基调序列**（用于「章内基调切换」分析）：

- 不做聚合，按 `_摘要.md` 中情节点出现顺序保留
- 用于统计切换频率：相邻情节点基调不同的次数 / 总情节点数

### Step 4: 原文采样

`原文/` 下**只有一个全书单文件** `原文.txt`（或 `.md`），**不是按章拆分**。采样必须先定位章节分隔符。

**正确 Grep 模式**：

```bash
grep -nE '^第[一二三四五六七八九十百千两零0-9]+章' 原文/原文.txt
```

模式覆盖中文数字（`第一章`）+ 阿拉伯数字（`第1章`）+ 千位数（`第一千零一章` / `第两千五百章`，盘龙 / 诡秘之主级别的长篇必需），锚定行首防误匹配正文内提及。

**与 Stage 0.5 章节边界表的关系**：Stage 0.5 已用同一模式产出权威「章节边界」表（在 `_progress.md`）。Stage 6 优先读该表，跳过本步 grep；只有当 `_progress.md` 缺失或 schema v1 未 migrate 时才退回这里现跑。

**如果模式不匹配**：

- 用 Read 前 100 行看实际章节前缀（如 `Chapter 1`、`卷一 第一章`），相应调整 regex
- 极端情况无法识别章节分隔符 → 「生成记录」写 `文风可用：否：无法识别章节分隔符`，跳过 Step 4，但 Step 1-3 仍可继续

**采样切片**：

- 拿到 grep 的 `行号:第N章` 列表后，选第 1 章、第 10 章、第 20 章（如总章数 <20，按 1/3、2/3、收尾比例挑）
- 每章用 `Read offset={该章起始行} limit=50` 切出约 1000 字
- 把 3 段拼接写入 `/tmp/style-sample.txt`（追加 `>>`，不要换文件名）

**确定性句长/标点统计**（替代旧版「眼测」）：

Stage 6 由**主线程**执行，Bash 工具可用。把上一步拼好的 `/tmp/style-sample.txt` 喂给下面的脚本（heredoc 作 Python 源，脚本内 open 样本文件，避免 stdin heredoc 与 `< file` 双重重定向冲突）。先探测可用解释器再跑——**勿直接用 `python3`**，Windows 上它会触发 Microsoft Store 占位程序、exit 49 失败：

```bash
for PYBIN in python3 python py; do "$PYBIN" -c "" 2>/dev/null && break; done
"$PYBIN" <<'PYEOF'
import re
with open('/tmp/style-sample.txt', 'r', encoding='utf-8') as f:
    text = f.read()
sents = [s for s in re.split(r'[。！？]+', text) if s.strip()]
total = max(len(sents), 1)
short = sum(1 for s in sents if len(s) < 15)
mid   = sum(1 for s in sents if 15 <= len(s) <= 30)
lng   = sum(1 for s in sents if len(s) > 30)
chars = max(sum(1 for c in text if not c.isspace()), 1)
puncts = sum(1 for c in text if c in '，。！？；：、…—""\'\'')
avg = sum(len(s) for s in sents) // total
print(f'sentences={total}; short_lt15={100*short//total}%; mid_15to30={100*mid//total}%; long_gt30={100*lng//total}%; avg_len={avg}; punct_density={100*puncts//chars}%')
PYEOF
```

实测输出形如 `sentences=6; short_lt15=66%; mid_15to30=33%; long_gt30=0%; avg_len=12; punct_density=15%`。

把输出的 `short_lt15 / mid_15to30 / long_gt30 / avg_len / punct_density` 数值直接填进 `style-profile-protocol.md` 模板第 40 行的 `{...X% / Y% / Z%}` 占位符——`confidence: high`，因为是确定性测量，不是抽样估计。

**Bash 不可用时的降级**（仅子代理上下文等极端情况，主线程不会触发）：

- 跳过本步骤；句长段写「Bash 工具不可用，跳过确定性统计」
- `confidence: low`，narrative-writer 让位回默认 Gate D（句长拆短）

### Step 5: 选原文锚点片段 (4-6 段)

从 Step 3 输出的章节基调里挑覆盖度最高、且项目可能需要的 4-6 类基调（优先覆盖：紧张/悲伤或压抑/轻松或温馨/热血）。若某类基调在对标书中少于 3 章，不强行编造；在文风文件中说明跳过。每类基调选 1 章作为锚点章。

**同基调多章时的选择规则**：

1. **L1 爽点类型最强匹配**：参考该章 `_摘要.md` 的「关键事件」+ 基调序列，挑爽点最突出的章节
2. **L2 原文章节长度最接近日更目标**：若 Step 4 已切出章节边界，用原文章节切片估算字数；若无法切原文，只用 `_摘要.md` 的情节点数量近似复杂度，不把摘要文件长度当作原文章节字数
3. **L3 章节号最小**：最早期的章节 = 作者最 canonical 的 voice（未受连载漂移影响）

锚点切片：

- 用 Step 4 grep 拿到的章节行号
- 该章原文从中选 1 段 300-500 字（优先选对话+动作交织的段落，纯独白/纯设定段不选）
- 用 `Read offset limit` 切出，保留原标点和段落断行
- **锚点必须逐字连续切片，禁止改写/缩写/跳段/拼接**：narrative-writer 拿锚点当 few-shot 直接学，标注的行号要能回查原文。落盘前逐段抽 1-2 句 `grep -F` 回 `原文/原文.txt`，grep 不到即说明被改写或拼接——重切为忠实连续片段。确需跳过中间过渡段时，分别标各自真实行号（如「行264-267 + 行269-270」）并在引用块内用「（……中略……）」显式断开，不得用一个连续行号区间假装连续

### Step 6: 落盘

按 [style-profile-protocol.md](style-profile-protocol.md) 模板填写 `拆文库/{书名}/文风.md`：

- **文风文件必须留在拆文库**（`拆文库/{书名}/文风.md`），**永不写入** `对标/` 或写作项目目录——拆文库是 analyze 的数据源，写作项目的 `对标/{书名}/` 由 story-import 从拆文库同步
- 每段标 `confidence: high/med/low`（内部给写作 agent 判断强弱，普通用户可忽略）：
  - `high`：数据直接来自拆文产物（如「写法技巧」直接引用拆文报告）
  - `med`：从样本归纳且样本充足（如基调序列从 ≥10 章摘要统计）
  - `low`：样本不足或采样失败（如锚点缺失、Bash 不可用导致 Step 4 句长统计跳过）
- 字数预算：硬上限 ~4000 字。**描述段 ≤ 1500 字 + 锚点 4-6 段 × 300-500 字**
- 如果 Step 4 失败（章节分隔符识别不出）→ 「生成记录」写 `文风可用：否：无法识别章节分隔符`；原文锚点段全填占位符 "原文缺失，需手动补充"，confidence 全 low

## 失败模式与降级

| 场景 | 降级策略 |
|---|---|
| `原文/原文.txt` 不存在 | 跳过 Step 4-5；文风文件仅含描述段；「生成记录」写 `文风可用：否：原文缺失` |
| `章节/*_摘要.md` 数量 <3 | 跳过 Step 3 基调序列；情绪交替段标 confidence: low |
| `章节/第1-3章_深度拆解.md` 缺失 | 跳过 Step 2；对话潜台词段从拆文报告兜底；confidence: low |
| `拆文报告.md` 不存在 | **停止 Stage 6**，提示用户拆文未完成，先跑完 Stage 5 |

## 与 chapter-extractor 的关系

**不修改 chapter-extractor**。文风直接从既有字段（基调/主题标签/可借鉴要素）整理生成即可。

句长 / 标点密度由 Step 4 的跨平台 Python 1-liner 在 Stage 6 主线程直接算出，不依赖 chapter-extractor。若将来需要章级精细分布（如「第 N 章 短句占比」），再考虑给 chapter-extractor 加 `punctuation_density` / `sentence_length_distribution` 字段——但**不在本次范围内**。

## 与写作端的关系

- analyze Stage 6 写 `拆文库/{书名}/文风.md`
- story-import 把整个 `拆文库/{书名}/` 同步到项目 `{项目}/对标/{书名}/` 时**自动包含**文风（与拆文报告同等待遇）
- 写作端（story-long-write）的日更循环读 `{项目}/对标/{书名}/文风.md`（按对标书路径查找规则，回退 `拆文库/{书名}/`）

## 重生策略（旧拆文库无文风）

旧 `拆文库/{书名}/` 没有文风文件时：

- **完整重跑** `/story-long-analyze`：开销大，会重跑 Stage 0-5（不必要）
- **仅跑 Stage 6**：用户直接说 "为对标书 X 生成文风" 或 "重生 文风"，主会话/agent 直接按本 SOP 跑 6 步，无需重做拆文。这是推荐路径
