# 文风生成 SOP

> **何时加载**：story-long-analyze Stage 6 执行时。`拆文报告.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` 单独成行、跟在每个情节点后（一章 10-40 行）。`基调` 用**全角冒号**，`主题标签` 后**不带冒号**，两者都**不在行首**。grep 模式不能用 `^基调:` 这种锚定。

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

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

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

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

### Step 4: 原文采样

`原文/` 下**只有一个全书单文件** `原文.txt`（或 `.md`），**不是按章拆分**。Stage 0 已把校验通过的章节起止行写入 `拆文库/{书名}/_progress.md`，该表是 Stage 1/2/6 共用的唯一切片真值。

**Stage 6 只读 `_progress.md` 里的「章节边界」表，不重新搜索章节标题、不另行推断边界，也不自行修正规则。** 如果表缺失、目标章缺边界、边界不连续或指向原文范围之外，立即停止 Stage 6，提示重新执行 Stage 0 重建进度文件；不得用临时切片继续生成文风。

**采样切片**：

- 从 `_progress.md` 的章节边界表选第 1 章、第 10 章、第 20 章（如总章数 <20，按 1/3、2/3、收尾比例挑）
- 每章用 `Read offset={该章起始行} limit=50` 切出约 1000 字
- 把 3 段拼接写入 `拆文库/{书名}/_style-sample.txt`（固定这个文件名，不要换）：**第一段用 `>` 覆盖写，后两段才用 `>>` 追加**。全程用 `>>` 会让重跑 Stage 6 时把上一轮的样本累积进来，统计出的句长分布是两轮混合的结果

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

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

样本路径**必须用项目内相对路径**，不要写 `/tmp/...`：下面探测到的解释器可能是 Windows 原生 python（`py` 启动器），它把 `/tmp/x.txt` 解析成 `C:\tmp\x.txt`，和 Git Bash 写入的位置不是同一个文件，脚本会直接 FileNotFoundError。

```bash
SAMPLE="拆文库/{书名}/_style-sample.txt"   # {书名} 换成实际书名，别照抄占位符
for PYBIN in python3 python py; do "$PYBIN" -c "" 2>/dev/null && break; done
"$PYBIN" - "$SAMPLE" <<'PYEOF'
import re
import sys
with open(sys.argv[1], '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 从 `_progress.md` 读取的章节起止行
- 该章原文从中选 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 或 story-long-write 首次引用同步到 `对标/{书名}/`
- 每段标 `confidence: high/med/low`（内部给写作 agent 判断强弱，普通用户可忽略）：
  - `high`：数据直接来自拆文产物（如「写法技巧」直接引用拆文报告）
  - `med`：从样本归纳且样本充足（如基调序列从 ≥10 章摘要统计）
  - `low`：样本不足或采样失败（如锚点缺失、Bash 不可用导致 Step 4 句长统计跳过）
- 「分层模仿建议」必须分基础层 / 进阶层 / 适配层；基础层只写词汇、句式、描写和对话习惯，进阶层写节奏、伏笔、视角和场景衔接，适配层写哪些可用、哪些容易让本项目错位；明确不复制专名、标志性台词、独特桥段和事件顺序
- 字数预算：硬上限 ~4000 字。**描述段 ≤ 1800 字 + 锚点 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。

## 与写作端的关系

- analyze Stage 6 写 `拆文库/{书名}/文风.md`
- story-import 显式绑定外部对标或 story-long-write 首次引用 `拆文库/{书名}/` 时，同步到项目 `{项目}/对标/{书名}/` 并**包含**文风（与拆文报告同等待遇）；不得把正在导入的本书自身作为该外部对标
- 写作端（story-long-write）的日更循环读 `{项目}/对标/{书名}/文风.md`（按对标书路径查找规则，回退 `拆文库/{书名}/`）

## 单独重建文风

当前拆文产物仅缺 `文风.md` 时，可直接按本 SOP 跑 6 步（仅 Stage 6），无需重跑 Stage 0-5；前提是 `_progress.md` 满足当前章节边界契约。触发：用户直接说 "为对标书 X 生成文风" 或 "重生 文风"。
