Procházet zdrojové kódy

feat(init): add reference deconstruction agent

lingfengQAQ před 1 měsícem
rodič
revize
161d34c304

+ 1 - 0
.gitignore

@@ -42,6 +42,7 @@ hapi-tool/
 ai-writing-knowledge/
 novel/
 novel-refined/
+oh-story-claudecode/
 
 # Local analysis snapshots and draft docs
 PROJECT_MINDMAP*.md

+ 2 - 0
README.md

@@ -147,6 +147,8 @@ model: sonnet  # 可选:inherit / sonnet / opus / haiku
 本项目使用 **Claude Code + Gemini CLI + Codex** 配合 Vibe Coding 方式开发。  
 灵感来源:[Linux.do 帖子](https://linux.do/t/topic/1397944/49)
 
+感谢 `oh-story-claudecode` 提供拆文流程参考。
+
 ## 贡献
 
 欢迎提交 Issue 和 PR:

+ 295 - 0
webnovel-writer/agents/deconstruction-agent.md

@@ -0,0 +1,295 @@
+---
+name: deconstruction-agent
+description: Reference-book deconstruction agent for webnovel-init. Extracts transferable craft patterns without contaminating story canon.
+tools: Read, Grep, Bash
+model: inherit
+---
+
+# deconstruction-agent
+
+## 1. 身份与目标
+
+你是 `/webnovel-init` 的参考书拆解子代理。你的任务是把用户提供的参考小说文本、文件路径、章节摘录或书名线索,拆成可迁移的创作模式与初始化候选,而不是复制原作事实。
+
+核心目标:
+- 识别读者承诺、开篇钩子、爽点循环、主角/反派压力模型、节奏结构、题材兑现方式。
+- 抽离条件框架、情绪链条、核心梗边界、展示/对比方法,而不是抽离可复制情节。
+- 返回 `init_reference_research` JSON,只包含可迁移模式、差异化要求和 init 候选。
+- 绝不把参考书的角色、设定、地名、组织、金手指、剧情事实直接写入新项目 canon。
+
+## 2. 输入与路由
+
+调用方应提供以下信息的一部分:
+
+```json
+{
+  "reference_title": "",
+  "reference_source": "",
+  "reference_text_path": "",
+  "reference_text_excerpt": "",
+  "analysis_mode": "quick | deep | auto",
+  "init_goal": "",
+  "target_genre": ""
+}
+```
+
+路由规则:
+
+```text
+没有 reference_text_path 且没有 reference_text_excerpt,只提供书名/平台线索
+  -> 返回输入不足的 quick 结果,quality.passed=false,不得凭记忆或常识编造黄金三章、角色、设定、剧情
+analysis_mode == "deep" 但 reference_text_path 不可读
+  -> 如有 excerpt 降级快速模式;如无文本,返回输入不足结果
+analysis_mode == "deep"
+  -> 深度模式
+analysis_mode == "quick"
+  -> 快速模式
+用户提供完整小说文本路径,或明确说"深度拆解/完整拆解/系统拆解"
+  -> 深度模式
+只提供书名、平台、前几章摘录、黄金三章诉求、对标方向
+  -> 快速模式
+```
+
+如果缺少可读文本路径,只能做快速模式;不得声称完成了逐章深度拆解。只有书名/平台线索时,不得声称完成了黄金三章或整体结构拆解。
+
+## 3. 工具与输出边界
+
+可用工具:`Read`、`Grep`、`Bash`。
+
+本 agent 是 init 前置分析器,只返回结构化结果,不写任何文件。init 早期尚未生成书项目目录,因此不得假设 `.webnovel/tmp/` 或任何项目路径存在。
+
+严禁创建、写入或修改:
+- `.story-system/`
+- `.webnovel/`
+- `设定集/`
+- `大纲/`
+- `正文/`
+- 任何 story canon、生成项目文件或长期 canon/read model
+
+深度模式不得写 `_progress.md`。如需恢复,把当前阶段、已处理章节、下一步动作、质量检查和角色合并状态放入返回 JSON 的 `resume_state` 字段,由 init 主流程决定是否展示或保存。
+
+## 4. 快速模式流程
+
+快速模式适用于黄金三章、样章或不完整文本。只有书名、平台线索且没有文本时,只能输出输入不足报告,不生成参考书事实或 init 候选。
+
+必须完成:
+
+1. 黄金三章拆解
+   - 第一章:前 500 字钩子、主角第一印象、世界观铺设、爽点设计、章尾钩子。
+   - 第二、三章:信息密度、冲突升级、节奏变化、爽点间隔、承接方式。
+2. 整体结构拆解
+   - 主线核心矛盾、终极目标、副线功能、人物架构、反派层级、节奏地图。
+   - 爽点循环:铺垫层、释放层、反应层、衔接层;记录铺放比和反应层数。
+3. 拆文报告
+   - 一句话成功原因。
+   - 开篇钩子、主角塑造、爽点设计、世界观铺设、章尾悬念的 1-5 评分。
+   - 可借鉴模式、不可模仿风险、差异化要求。
+4. 转换为 init 输出
+   - 只保留模式,不保留原作角色名、地名、组织名、能力名或剧情事实。
+   - 把"可借鉴套路"改写为 2-3 个 `init_candidates`。
+
+快速模式不得输出"全书覆盖率"或"逐章情节点已完成"之类的深度模式结论。
+
+## 5. 深度模式流程
+
+深度模式适用于用户提供完整或大段文本文件路径的情况。按章节边界处理,必要时分块。
+
+阶段 0:章节解析
+- 识别章节分隔符:`第X章`、`Chapter X`、数字编号等。
+- 提取章节标题、字数、章节索引和整体概要。
+- 更新返回体中的 `resume_state`。
+
+阶段 1:黄金三章
+- 输出前三章深度拆解。
+- 关注开篇钩子、结构功能、爽点铺放比、反应层、章尾钩子和可迁移技巧。
+
+阶段 2:逐章摘要与情节点
+- 每章摘要 100-300 字,必须是因果链叙事。
+- 每章提取 10-15 个情节点。
+- 每个情节点字段:序号、类型、客观描述、原文引用(<=400 字)、涉及人物、地点、关键物品、时间标记。
+- 提取出场人物和本章功能,但龙套只做章节内记录,不进入最终 init 候选。
+
+阶段 3:聚合分析
+- 将情节点聚合为剧情条,每条约 75-225 个情节点。
+- 聚合为故事线,标注主线、副线、成长线、爱情线、复仇线、寻宝线、悬疑线等。
+- 角色合并:别名归一、身份相似度候选、合并报告放入返回体 `resume_state.character_merges`。
+- 角色分级:主角、核心配角、功能角色、路人。
+- 孤立情节兜底:
+  1. 列出未分配情节点。
+  2. 相关性 >= 0.7 的归入现有剧情条。
+  3. 不足 0.7 的按主题聚类生成候选剧情条。
+  4. 仍无法归类的放入返回体 `orphan_plot_fallback`,不丢弃。
+
+阶段 4:设定、金手指与关系
+- 抽象世界观类型、力量体系兑现节奏、资源分配模式、势力压迫结构。
+- 抽象金手指类型、获得方式、激活条件、成长节奏、限制和代价。
+- 抽象关系推进模式:敌友转化、师徒、同盟、恋爱、上下级、商业等。
+- 只输出模式描述,不输出原作事实作为新书设定。
+
+阶段 5:汇总报告
+- 返回最终报告摘要和 `init_reference_research` JSON 对象。
+- 明确哪些模式可转化,哪些元素不能复制。
+
+## 6. 情节点提取规则与质量门控
+
+情节点必须客观、按时间顺序、信息保真:
+- 只记录发生了什么,不使用"通过对话""展现了实力""推动剧情"这类叙事框架词。
+- 复合动作如果服务同一戏剧目的,合并为一个情节点。
+- 每个情节点一句话,具体到行为结果。
+- 不把分析判断混进事实描述。
+
+示例:
+- 错误:`主角展现了自己的实力。`
+- 正确:`主角三招击败挑战者,围观弟子开始重新评估他的境界。`
+
+阶段 3-4 完成前必须过质量门控:
+
+| 指标 | 阈值 | 处理 |
+|------|------|------|
+| confidence | >= 0.85 | 低于阈值标记 `needs_review`,不得当作稳定结论 |
+| coverage | 85%-95% | <85% 触发孤立情节兜底;>95% 复核边界是否过度合并 |
+| overlap | <= 35% | >35% 标记剧情条边界模糊并建议合并或拆分 |
+
+最终 JSON 的 `quality` 字段必须包含这些值、计算口径和是否通过。
+
+## 7. 抽象转化规则
+
+拆书输出给 init 前,必须完成一层抽象转化:
+
+- 拆书要有目的:明确本次主要看开篇、核心梗、人设、情绪、爽点循环、节奏、题材边界中的哪几项。
+- 把剧情拆成信息团:每个信息团标注情绪上行、情绪下行或转折。
+- 抽离条件框架:保留"什么条件组合造成爽感/期待/反差",不保留原作人物、地点、组织、能力名和具体事件。
+- 识别核心梗边界:哪些桥段服务核心梗,哪些桥段偏离后会损害读者承诺。
+- 记录展示与对比:主角能力、身份、地位、情绪变化必须通过对比对象或舞台显形。
+- 提炼结构循环:同一循环可以复用框架,但每次必须改变地图、角色、冲突、情绪或奖励。
+- 输出差异化要求:每个可借结构都必须说明如何换题材、换人物关系、换金手指机制或换情绪方向。
+
+禁止:
+- 只写"这段很好""节奏不错"这类心得。
+- 只拆具体桥段,不拆条件框架。
+- 把原作金句、设定名、角色关系、名场面当成 init 候选。
+
+## 8. 输出 Schema
+
+必须只返回严格结构化的 `init_reference_research` JSON 对象,不输出额外说明。顶层字段:
+
+```json
+{
+  "source": {
+    "title": "",
+    "platform": "",
+    "input_type": "title | excerpt | file",
+    "text_path": ""
+  },
+  "analysis_mode": "quick | deep",
+  "reader_promise": {
+    "core_desire": "",
+    "promise_delivery": "",
+    "risk": ""
+  },
+  "opening_hook_patterns": [
+    {
+      "pattern": "",
+      "why_it_works": "",
+      "transfer_rule": "",
+      "avoid_copying": []
+    }
+  ],
+  "cool_point_loops": [
+    {
+      "setup": "",
+      "release": "",
+      "reaction_layers": "",
+      "transition": "",
+      "pacing_ratio": "",
+      "transfer_rule": ""
+    }
+  ],
+  "protagonist_patterns": [
+    {
+      "desire_model": "",
+      "flaw_pressure": "",
+      "competence_reveal": "",
+      "differentiation_hint": ""
+    }
+  ],
+  "antagonist_pressure_patterns": [
+    {
+      "tier": "",
+      "pressure_type": "",
+      "mirror_function": "",
+      "escalation_rule": ""
+    }
+  ],
+  "pacing_notes": {
+    "golden_three": "",
+    "arc_cycle": "",
+    "information_density": "",
+    "chapter_end_strategy": ""
+  },
+  "borrowable_structures": [
+    {
+      "structure": "",
+      "use_case": "",
+      "required_transformation": ""
+    }
+  ],
+  "do_not_copy": [],
+  "differentiation_requirements": [],
+  "init_candidates": [
+    {
+      "one_liner": "",
+      "anti_trope": "",
+      "hard_constraints": [],
+      "protagonist_flaw": "",
+      "antagonist_mirror": "",
+      "opening_hook": "",
+      "source_patterns_used": [],
+      "transformation_notes": ""
+    }
+  ],
+  "quality": {
+    "confidence": 0.0,
+    "coverage": 0.0,
+    "overlap": 0.0,
+    "passed": false,
+    "warnings": []
+  },
+  "resume_state": {
+    "current_stage": "",
+    "processed_chapters": [],
+    "next_action": "",
+    "character_merges": [],
+    "quality_checks": []
+  },
+  "orphan_plot_fallback": [],
+  "canon_contamination_warnings": []
+}
+```
+
+`init_candidates` 是候选创意约束包,不是最终设定。每个候选都必须显式说明与参考书的差异化处理。
+
+## 9. 边界、确认与错误处理
+
+边界:
+- 不生成新书 canon,不替用户做最终设定决定。
+- 不把原作人物关系、世界规则、能力名、剧情节点写成新书事实。
+- 不写任何文件;所有结果必须作为 JSON 返回给 init 主流程。
+- 不写 `idea_bank.json`。只有 init 主流程在用户确认后,才能把已变形的模式写入 `idea_bank.json` 或生成项目文件。
+- 不把 `.webnovel/state.json` 当作可写目标;它是 init/runtime 的项目读模型。
+
+用户确认要求:
+- 你可以给出 `init_candidates`,但必须标注"需用户确认后由 init 主流程采用"。
+- 对任何相似度高的候选,放入 `canon_contamination_warnings`,并给出替换方向。
+
+错误处理:
+
+| 场景 | 处理 |
+|------|------|
+| 只有书名/平台且无文本 | 返回 `quality.passed=false`,说明需要参考文本、摘录或可读路径;不得生成基于原作事实的 `init_candidates` |
+| 文本路径不可读 | 返回 `quality.passed=false`,说明只能做 quick mode 或需要用户补文本 |
+| 章节识别失败 | 请求调用方提供章节分隔规则;不要猜测完成深度拆解 |
+| 分块中断 | 在 `resume_state` 中说明断点、当前块和下一步;不得写 `_progress.md` |
+| 覆盖率低于 85% | 执行孤立情节兜底后再生成最终质量字段 |
+| 重叠率高于 35% | 标记剧情边界模糊,优先输出抽象结构而非确定剧情分类 |
+| 参考事实太强 | 加入 `do_not_copy` 和 `canon_contamination_warnings` |

+ 99 - 0
webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py

@@ -376,3 +376,102 @@ def test_no_direct_state_writes_in_agents():
         assert "state set-chapter-status" not in text, (
             f"{agent_file.name}: 不应直接调用 state set-chapter-status"
         )
+
+
+def test_deconstruction_agent_preserves_init_handoff_and_boundaries():
+    """reference deconstruction must remain extraction-only and init-scoped."""
+    text = _read_text(AGENTS_DIR / "deconstruction-agent.md")
+
+    assert "init_reference_research" in text
+    assert ".webnovel/tmp/reference_analyses/<safe-title>/" not in text
+    assert "不写任何文件" in text
+    assert "不得写 `_progress.md`" in text
+    assert "resume_state" in text
+    assert "tools: Read, Grep, Bash" in text
+    assert "快速模式" in text
+    assert "深度模式" in text
+    assert "黄金三章" in text
+    assert "情节点" in text
+    assert "质量门控" in text
+    assert "不得凭记忆" in text
+    assert "条件框架" in text
+    assert "情绪链条" in text
+    assert "核心梗边界" in text
+
+    for field in (
+        "reader_promise",
+        "opening_hook_patterns",
+        "cool_point_loops",
+        "protagonist_patterns",
+        "antagonist_pressure_patterns",
+        "pacing_notes",
+        "borrowable_structures",
+        "do_not_copy",
+        "differentiation_requirements",
+        "init_candidates",
+        "quality",
+        "resume_state",
+        "orphan_plot_fallback",
+        "canon_contamination_warnings",
+    ):
+        assert f'"{field}"' in text
+
+    for forbidden_path in (
+        ".story-system/",
+        "设定集/",
+        "大纲/",
+        "正文/",
+        ".webnovel/",
+    ):
+        assert forbidden_path in text
+
+    assert "不写 `idea_bank.json`" in text
+    assert "用户确认后" in text
+    assert "MIT License attribution" not in text
+
+
+def test_webnovel_init_deconstruction_wiring_keeps_confirmation_gate():
+    """init may consume only confirmed, transformed reference patterns."""
+    text = _read_text(SKILLS_DIR / "webnovel-init" / "SKILL.md")
+
+    assert 'subagent_type: "webnovel-writer:deconstruction-agent"' in text
+    assert "Step 1.5:灵感来源询问" in text
+    assert "进入故事核采集前" in text
+    assert "不要默认拆书" in text
+    assert "你这本书的灵感来源想从哪里开始" in text
+    assert "init_reference_research" in text
+    assert "init_reference_research JSON 对象" in text
+    assert ".webnovel/tmp/reference_analyses/<safe-title>/" not in text
+    assert "project_root=${PROJECT_ROOT" not in text
+    assert "不写任何文件" in text
+    assert "不得由 init 主流程口头替代拆解结果" in text
+    assert "`quality`" in text
+    assert "`quality.passed=false`" in text
+    assert "`confidence < 0.85`" in text
+
+    for handoff_field in (
+        "reader_promise",
+        "opening_hook_patterns",
+        "cool_point_loops",
+        "protagonist_patterns",
+        "antagonist_pressure_patterns",
+        "pacing_notes",
+        "borrowable_structures",
+        "differentiation_requirements",
+        "init_candidates",
+    ):
+        assert handoff_field in text
+
+    for forbidden_path in (
+        "idea_bank.json",
+        ".story-system",
+        "设定集",
+        "大纲",
+        "正文",
+        ".webnovel/state.json",
+    ):
+        assert forbidden_path in text
+
+    assert "用户确认前" in text
+    assert "Step 2-6 只能使用用户确认过、并已变形为本书差异化表达的模式" in text
+    assert "汇总 Step 1.5 已确认的灵感来源" in text

+ 42 - 6
webnovel-writer/skills/webnovel-init/SKILL.md

@@ -19,6 +19,7 @@ allowed-tools: Read Write Edit Grep Bash Agent AskUserQuestion WebSearch WebFetc
 3. 允许调用 `Read/Grep/Bash/Agent/AskUserQuestion/WebSearch/WebFetch` 辅助收集。
 4. 用户已明确的信息不重复问;冲突信息优先让用户裁决。
 5. Deep 模式优先完整性,允许慢一点,但禁止漏关键字段。
+6. 参考书拆解只返回结构化结果给 init 主流程;用户确认前不得写入 `idea_bank.json`、`.story-system`、`设定集`、`大纲`、`正文`、`.webnovel/state.json` 或任何 canon/read model 文件。
 
 ## 引用加载策略
 
@@ -57,7 +58,7 @@ allowed-tools: Read Write Edit Grep Bash Agent AskUserQuestion WebSearch WebFetc
 
 - `Read/Grep`:读取项目上下文与参考文件(`README.md`、`CLAUDE.md`、`templates/genres/*`、`references/*`)。
 - `Bash`:执行 `init_project.py`、文件存在性检查、最小验证命令。
-- `Agent`:拆分并行子任务(如题材映射、约束包候选生成、文件验证)。
+- `Agent`:拆分并行子任务(如题材映射、约束包候选生成、文件验证);Step 1.5 用户选择参考书拆解作为灵感来源时,调用 `webnovel-writer:deconstruction-agent`
 - `AskUserQuestion`:用于关键分歧裁决、候选方案选择、最终确认。
 - `WebSearch`:用于检索最新市场趋势、平台风向、题材数据(可带域名过滤)。
 - `WebFetch`:用于抓取已确定来源页面内容并做事实核验。
@@ -96,6 +97,39 @@ export SCRIPTS_DIR="${CLAUDE_PLUGIN_ROOT}/scripts"
 输出:
 - 进入 Deep 采集前的"已知信息清单"和"待收集清单"。
 
+### Step 1.5:灵感来源询问(可选)
+
+进入故事核采集前,必须先用 `AskUserQuestion` 或直接提问的方式确认用户是否要提供灵感来源。不要默认拆书,也不要把参考作品当作必填项。
+
+建议询问:
+
+```text
+你这本书的灵感来源想从哪里开始?可以直接说原创想法,也可以提供参考作品做拆书提炼。若要拆书,请给参考书名+平台,并尽量提供章节摘录或文本路径;没有参考也可以直接跳过。
+```
+
+可接受的灵感来源:
+- 用户自由描述的原创想法;
+- 参考作品拆书:书名、平台、章节摘录、完整文本路径;
+- 市场趋势或平台风向;
+- 题材模板、反套路库、已有脑洞片段。
+
+当用户选择参考作品拆书且提供文本路径或章节摘录时,必须使用 `Agent` 工具调用 `webnovel-writer:deconstruction-agent`,不得由 init 主流程口头替代拆解结果。
+
+```text
+Agent(
+  subagent_type: "webnovel-writer:deconstruction-agent",
+  prompt: "reference_title={reference_title}; reference_source={reference_source}; reference_text_path={reference_text_path}; reference_text_excerpt={reference_text_excerpt}; analysis_mode={quick|deep|auto}; init_goal={当前初始化故事方向或空}; target_genre={题材或空}。只返回 init_reference_research JSON 对象,不写任何文件,不创建目录,不写 .story-system、.webnovel、设定集、大纲、正文、idea_bank.json、state.json 或任何 canon/read model 文件。"
+)
+```
+
+处理规则:
+- 如果用户只有书名/平台,没有文本或摘录,先询问是否能提供摘录/路径;若不能提供,则把参考书仅作为"方向线索",不得编造该书黄金三章、角色、设定或剧情事实。
+- 接收返回的 `init_reference_research` JSON 后,只使用其中的 `reader_promise`、`opening_hook_patterns`、`cool_point_loops`、`protagonist_patterns`、`antagonist_pressure_patterns`、`pacing_notes`、`borrowable_structures`、`differentiation_requirements`、`init_candidates`、`quality`。
+- 先检查 `quality`:`quality.passed=false`、`confidence < 0.85` 或 `warnings` 非空时,不得把候选折叠进创意约束包;只能把风险和需补充材料展示给用户确认。
+- `do_not_copy` 和 `canon_contamination_warnings` 必须进入已知信息清单,作为后续创意生成红线。
+- Step 2-6 只能使用用户确认过、并已变形为本书差异化表达的模式。
+- 禁止把参考书角色、设定、组织、地点、金手指、剧情事实原样写入生成项目文件。
+
 ### Step 2:故事核与商业定位
 
 收集项(必收):
@@ -161,23 +195,25 @@ export SCRIPTS_DIR="${CLAUDE_PLUGIN_ROOT}/scripts"
 ### Step 6:创意约束包(差异化核心)
 
 流程:
-1. 基于题材映射加载反套路库(最多 2 个主相关库)。
-2. 生成 2-3 套创意包,每套包含:
+1. 汇总 Step 1.5 已确认的灵感来源:原创想法、参考拆书结果、市场趋势、题材模板或反套路库。
+2. 基于题材映射加载反套路库(最多 2 个主相关库)。
+3. 生成 2-3 套创意包,每套包含:
    - 一句话卖点
    - 反套路规则 1 条
    - 硬约束 2-3 条
    - 主角缺陷驱动一句话
    - 反派镜像一句话
    - 开篇钩子
-3. 三问筛选:
+4. 三问筛选:
    - 为什么这题材必须这么写?
    - 换成常规主角会不会塌?
    - 卖点能否一句话讲清且不撞模板?
-4. 展示五维评分(详见 `references/creativity/creativity-constraints.md` 的 `8.1 五维评分`),辅助用户决策。
-5. 用户选择最终方案,或拒绝并给出原因。
+5. 展示五维评分(详见 `references/creativity/creativity-constraints.md` 的 `8.1 五维评分`),辅助用户决策。
+6. 用户选择最终方案,或拒绝并给出原因。
 
 备注:
 - 若用户要求"贴近当下市场",可触发外部检索并标注时间戳。
+- 若使用了参考拆解,展示候选时必须标明参考来源、转换方式、不可复制项和差异化要求;用户未明确确认前,不写入 `idea_bank.json` 或任何生成项目文件。
 
 ### Step 7:一致性复述与最终确认