novelforge-agent 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -13
- package/dist/src/cli/index.js +71 -2
- package/dist/src/core/bibleStore.js +36 -0
- package/dist/src/core/characterStore.js +74 -0
- package/dist/src/core/contextBuilder.js +44 -1
- package/dist/src/core/fileNames.js +4 -0
- package/dist/src/core/index.js +4 -0
- package/dist/src/core/projectOps.js +187 -0
- package/dist/src/core/projectStore.js +11 -0
- package/dist/src/core/prompts/en-US.js +117 -13
- package/dist/src/core/prompts/zh-CN.js +116 -12
- package/dist/src/core/retrieval/index.js +8 -0
- package/dist/src/core/schemas.js +98 -1
- package/dist/src/core/steps/architecture.js +7 -1
- package/dist/src/core/steps/chapter.js +11 -1
- package/dist/src/core/steps/chapterReview.js +25 -1
- package/dist/src/core/steps/chapterRevision.js +17 -0
- package/dist/src/core/steps/memoryCard.js +4 -0
- package/dist/src/core/steps/novelMetadata.js +4 -2
- package/dist/src/core/threadStore.js +150 -0
- package/dist/src/core/workflow.js +3 -3
- package/dist/src/mcp/tools.js +198 -18
- package/package.json +5 -1
- package/src/cli/index.ts +74 -1
- package/src/core/bibleStore.ts +57 -0
- package/src/core/characterStore.ts +93 -0
- package/src/core/contextBuilder.ts +44 -4
- package/src/core/fileNames.ts +5 -0
- package/src/core/index.ts +4 -0
- package/src/core/projectOps.ts +243 -0
- package/src/core/projectStore.ts +11 -0
- package/src/core/prompts/en-US.ts +126 -22
- package/src/core/prompts/types.ts +2 -1
- package/src/core/prompts/zh-CN.ts +118 -14
- package/src/core/retrieval/index.ts +10 -0
- package/src/core/schemas.ts +108 -1
- package/src/core/steps/architecture.ts +7 -1
- package/src/core/steps/chapter.ts +11 -1
- package/src/core/steps/chapterReview.ts +27 -1
- package/src/core/steps/chapterRevision.ts +18 -0
- package/src/core/steps/memoryCard.ts +4 -0
- package/src/core/steps/novelMetadata.ts +4 -2
- package/src/core/threadStore.ts +173 -0
- package/src/core/types.ts +102 -1
- package/src/core/workflow.ts +3 -3
- package/src/mcp/tools.ts +322 -19
|
@@ -101,6 +101,18 @@ Output valid JSON only, in this shape:
|
|
|
101
101
|
"order": 1
|
|
102
102
|
}
|
|
103
103
|
],
|
|
104
|
+
"volumePacing": [
|
|
105
|
+
{
|
|
106
|
+
"volumeId": "v1",
|
|
107
|
+
"start": "Volume starting state: protagonist/world/conflict",
|
|
108
|
+
"promise": "Core reader promise or question for this volume",
|
|
109
|
+
"keyTurns": ["Key turn 1", "Key turn 2"],
|
|
110
|
+
"midpoint": "Midpoint turn or changed understanding",
|
|
111
|
+
"climax": "Volume climax",
|
|
112
|
+
"payoffs": ["Threads or promises this volume plans to pay off"],
|
|
113
|
+
"lingeringMysteries": ["Mysteries intentionally left open at volume end"]
|
|
114
|
+
}
|
|
115
|
+
],
|
|
104
116
|
"chapters": [
|
|
105
117
|
{
|
|
106
118
|
"chapterNumber": 1,
|
|
@@ -116,36 +128,50 @@ Rules:
|
|
|
116
128
|
- chapters.length must be at least ${input.state.targetChapters}.
|
|
117
129
|
- chapterNumber must start at 1 and increase contiguously.
|
|
118
130
|
- volumeId must reference an id from volumes.
|
|
131
|
+
- volumePacing must provide one pacing board for every volume.
|
|
119
132
|
- requiredBeats must include at least one concrete, actionable beat.
|
|
120
133
|
${strictJsonOutputRules()}`,
|
|
121
134
|
};
|
|
122
135
|
}
|
|
123
136
|
function buildChapterPrompt(input) {
|
|
137
|
+
const ch = input.state.currentChapter;
|
|
138
|
+
const isFirstChapter = ch <= 1;
|
|
124
139
|
return {
|
|
125
140
|
purpose: 'chapter',
|
|
126
141
|
expectedFormat: 'Markdown',
|
|
127
|
-
prompt: `You are a professional long-form fiction writer. Write chapter ${
|
|
142
|
+
prompt: `You are a professional long-form fiction writer. Write chapter ${ch} directly.
|
|
128
143
|
|
|
129
144
|
## Priority Order
|
|
130
145
|
1. Strictly follow the current chapter architecture, user additions, story bible hard constraints, and previous-chapter continuity.
|
|
131
|
-
2. Use relevant memory
|
|
146
|
+
2. Use relevant memory, prior text evidence, and active foreshadow threads.
|
|
132
147
|
3. Treat full-book and volume plans as distant planning context only. Do not write concrete future events early.
|
|
133
148
|
|
|
134
|
-
##
|
|
149
|
+
## Length Target
|
|
150
|
+
- Default target: ~2500 words (±20%). If the chapter architecture specifies targetWords, follow it.
|
|
151
|
+
- Do not pad to hit the target; do not under-write to be brief at the cost of conflict.
|
|
152
|
+
|
|
153
|
+
## Structure
|
|
154
|
+
${isFirstChapter
|
|
155
|
+
? '- This is chapter 1. No recap needed. Open with character and situation directly.'
|
|
156
|
+
: '- Start with a 2-3 sentence recap or bridge so a reader who skipped the last chapter can re-enter (unless the chapter architecture has requireRecap=false). Make it natural, not meta-narration like "previously...".'}
|
|
157
|
+
- The chapter must end on a clear hook: cliffhanger, mystery, emotional resonance, reveal, or volume close — per the chapter architecture endHookFocus. Default: cliffhanger.
|
|
158
|
+
|
|
159
|
+
## Style
|
|
135
160
|
- Match the novel's genre, world, character identities, and emotional tone.
|
|
136
|
-
-
|
|
137
|
-
- Dialogue
|
|
138
|
-
- Important emotion
|
|
139
|
-
- Scene description
|
|
140
|
-
-
|
|
161
|
+
- Natural, stable, readable language; prioritize narrative progress, character work, and emotional accumulation.
|
|
162
|
+
- Dialogue fits each character's identity, relationship, and situation.
|
|
163
|
+
- Important emotion comes through action, body language, pacing, and subtext.
|
|
164
|
+
- Scene description has useful sensory detail without stalling.
|
|
165
|
+
- POV: strictly follow the chapter architecture povCharacter (if set). No mid-chapter POV switch.
|
|
141
166
|
|
|
142
167
|
## Execution Rules
|
|
143
168
|
- Write only what the current chapter architecture authorizes.
|
|
144
|
-
- Do not introduce unauthorized major characters. Functional background characters
|
|
145
|
-
- Keep names, items, places, abilities,
|
|
146
|
-
- If the previous chapter ends mid-action
|
|
169
|
+
- Do not introduce unauthorized major characters. Functional background characters stay light.
|
|
170
|
+
- Keep names, items, places, abilities, timelines, injuries, relationships, and knowledge boundaries consistent.
|
|
171
|
+
- If the previous chapter ends mid-action or mid-scene, this chapter must continue from that point.
|
|
172
|
+
- Active foreshadow threads may be advanced or paid off this chapter, but **never silently dropped** — even if you choose not to touch them, leave them coherent.
|
|
147
173
|
- Avoid cost-free power jumps, forced stupidity, mechanical twists, info-dumps, and empty lyricism.
|
|
148
|
-
- Do not output summaries, bullet points, lectures, or
|
|
174
|
+
- Do not output summaries, bullet points, lectures, explanatory prefaces, or meta-text like "what I changed".
|
|
149
175
|
|
|
150
176
|
${input.context ? `## Generation Context\n${input.context}\n` : ''}## Output Requirements
|
|
151
177
|
- Output Markdown.
|
|
@@ -185,13 +211,41 @@ Output valid JSON only, in this shape:
|
|
|
185
211
|
"after": "After"
|
|
186
212
|
}
|
|
187
213
|
],
|
|
188
|
-
"openThreads": ["Unresolved promise, danger, question, or plot thread"]
|
|
214
|
+
"openThreads": ["Unresolved promise, danger, question, or plot thread"],
|
|
215
|
+
"wordCount": <approximate word count of this chapter as an integer>,
|
|
216
|
+
"threadActions": [
|
|
217
|
+
{
|
|
218
|
+
"kind": "plant | build | pay | drop",
|
|
219
|
+
"threadId": "id of an existing active thread (required for build/pay/drop; leave empty for plant — the system will assign one)",
|
|
220
|
+
"description": "for plant: what the new thread is; for others: one sentence on how this chapter advanced/paid/dropped that thread"
|
|
221
|
+
}
|
|
222
|
+
],
|
|
223
|
+
"characterUpdates": [
|
|
224
|
+
{
|
|
225
|
+
"name": "Character name",
|
|
226
|
+
"role": "Role if confirmed or changed this chapter",
|
|
227
|
+
"goal": "Current goal at chapter end",
|
|
228
|
+
"belief": "Core belief or understanding driving them at chapter end",
|
|
229
|
+
"relationships": [
|
|
230
|
+
{ "name": "Related character", "dynamic": "Relationship state at chapter end" }
|
|
231
|
+
],
|
|
232
|
+
"abilities": ["Abilities, resources, or limits confirmed at chapter end"],
|
|
233
|
+
"secrets": ["Secrets still hidden or only partially known at chapter end"],
|
|
234
|
+
"emotionalState": "Emotional state at chapter end"
|
|
235
|
+
}
|
|
236
|
+
]
|
|
189
237
|
}
|
|
190
238
|
|
|
191
239
|
Rules:
|
|
192
240
|
- Record only information that happened or was confirmed in this chapter.
|
|
193
241
|
- Do not speculate about future plot.
|
|
194
242
|
- Make facts and stateChanges concrete enough for later chapter reference.
|
|
243
|
+
- wordCount: approximate word count (English) or character count (CJK). An integer estimate is fine.
|
|
244
|
+
- threadActions is critical:
|
|
245
|
+
· For any active foreshadow thread in the context's "Active Foreshadow Threads" section, if this chapter advanced it emit kind="build"; if this chapter paid it off emit kind="pay"; if this chapter abandoned it emit kind="drop". threadId is required.
|
|
246
|
+
· For any new thread this chapter plants, emit kind="plant" with a clear description.
|
|
247
|
+
· If an active thread was not touched, no action needed — but never silently delete it. Without a drop action, the thread stays active.
|
|
248
|
+
- characterUpdates maintains a separate character state table. Emit only important characters whose state changed or was reconfirmed in this chapter; goal, belief, relationships, abilities, secrets, and emotionalState must reflect the chapter ending.
|
|
195
249
|
${strictJsonOutputRules()}`,
|
|
196
250
|
};
|
|
197
251
|
}
|
|
@@ -241,6 +295,12 @@ function buildChapterReviewPrompt(input) {
|
|
|
241
295
|
prompt: `You are a strict editor reviewing a single chapter of a serial novel for in-chapter problems and conflicts with established context.
|
|
242
296
|
|
|
243
297
|
${input.context ? `## Review Context\n${input.context}\n` : ''}## Review Focus
|
|
298
|
+
- This is a mandatory chapter acceptance gate. If any acceptance item fails, status must be "issues_found" and the workflow must revise before continuing.
|
|
299
|
+
- Whether every requiredBeat is fulfilled; missing beats must appear in acceptance.requiredBeats.missingBeats.
|
|
300
|
+
- Whether this chapter advances the main line, character state, or active foreshadow threads. If it is static, at least one of narrativeProgress/characterProgress/foreshadowProgress must fail.
|
|
301
|
+
- Whether it violates the story bible, character state table, volume pacing board, or prior memory.
|
|
302
|
+
- Whether the ending has a clear hook that matches the chapter architecture endHookFocus.
|
|
303
|
+
- Whether it repeats prior chapter beats, conflict patterns, reveals, or dialogue functions.
|
|
244
304
|
- Character voice, motivation, and state vs the story bible and prior memory.
|
|
245
305
|
- World rules, item ownership, and ability limits.
|
|
246
306
|
- Timeline, location, and continuity with the previous chapter ending.
|
|
@@ -252,6 +312,37 @@ Output valid JSON only, in this shape:
|
|
|
252
312
|
{
|
|
253
313
|
"chapterNumber": ${chapter},
|
|
254
314
|
"status": "clean",
|
|
315
|
+
"acceptance": {
|
|
316
|
+
"requiredBeats": {
|
|
317
|
+
"status": "pass | fail",
|
|
318
|
+
"evidence": "Evidence for each requiredBeat",
|
|
319
|
+
"missingBeats": []
|
|
320
|
+
},
|
|
321
|
+
"narrativeProgress": {
|
|
322
|
+
"status": "pass | fail",
|
|
323
|
+
"evidence": "How this chapter advances the main line or phase objective"
|
|
324
|
+
},
|
|
325
|
+
"characterProgress": {
|
|
326
|
+
"status": "pass | fail",
|
|
327
|
+
"evidence": "How this chapter changes or confirms key character goal, belief, relationship, ability, secret, or emotion"
|
|
328
|
+
},
|
|
329
|
+
"foreshadowProgress": {
|
|
330
|
+
"status": "pass | fail",
|
|
331
|
+
"evidence": "How this chapter plants, advances, pays, or deliberately preserves foreshadow threads"
|
|
332
|
+
},
|
|
333
|
+
"storyBibleConsistency": {
|
|
334
|
+
"status": "pass | fail",
|
|
335
|
+
"evidence": "Whether it matches the story bible, character state table, and world rules"
|
|
336
|
+
},
|
|
337
|
+
"endingHook": {
|
|
338
|
+
"status": "pass | fail",
|
|
339
|
+
"evidence": "The ending hook passage and its function"
|
|
340
|
+
},
|
|
341
|
+
"repetition": {
|
|
342
|
+
"status": "pass | fail",
|
|
343
|
+
"evidence": "Whether it repeats prior beats; if not, explain why"
|
|
344
|
+
}
|
|
345
|
+
},
|
|
255
346
|
"issues": [
|
|
256
347
|
{
|
|
257
348
|
"severity": "low | medium | high",
|
|
@@ -266,6 +357,8 @@ Output valid JSON only, in this shape:
|
|
|
266
357
|
Rules:
|
|
267
358
|
- If there are no issues, use status "clean" with an empty issues array.
|
|
268
359
|
- Otherwise use status "issues_found".
|
|
360
|
+
- status may be "clean" only when every acceptance item is "pass".
|
|
361
|
+
- If any acceptance item is "fail", include a matching issue with a concrete fix.
|
|
269
362
|
- evidence must be specific; do not write "possibly" or "maybe".
|
|
270
363
|
${strictJsonOutputRules()}`,
|
|
271
364
|
};
|
|
@@ -349,6 +442,17 @@ function buildPromptForStep(input) {
|
|
|
349
442
|
return buildChapterRevisionPrompt(input);
|
|
350
443
|
case 'cross_chapter_review':
|
|
351
444
|
return buildCrossChapterReviewPrompt(input);
|
|
445
|
+
case 'story_bible_amend':
|
|
446
|
+
return {
|
|
447
|
+
purpose: 'story_bible',
|
|
448
|
+
expectedFormat: 'Markdown',
|
|
449
|
+
prompt: `Based on the current story bible and the amendment context, output the FULL revised story bible Markdown.
|
|
450
|
+
|
|
451
|
+
${input.context ? `## Amendment Context\n${input.context}\n` : ''}## Output Requirements
|
|
452
|
+
- Output the entire story-bible.md content — it replaces the old one (old version auto-archived under story-bible-versions/).
|
|
453
|
+
- Preserve everything that still holds; modify / add / remove only what the amendment context justifies.
|
|
454
|
+
- Do not output diff markers, change logs, or bullet summaries — just the new full bible.`,
|
|
455
|
+
};
|
|
352
456
|
case 'complete':
|
|
353
457
|
return {
|
|
354
458
|
purpose: 'continuity_review',
|
|
@@ -101,6 +101,18 @@ ${input.context ? `## 已有上下文\n${input.context}\n` : ''}## 输出要求
|
|
|
101
101
|
"order": 1
|
|
102
102
|
}
|
|
103
103
|
],
|
|
104
|
+
"volumePacing": [
|
|
105
|
+
{
|
|
106
|
+
"volumeId": "v1",
|
|
107
|
+
"start": "本卷起点:主角/世界/冲突处于什么状态",
|
|
108
|
+
"promise": "本卷向读者承诺的核心看点或问题",
|
|
109
|
+
"keyTurns": ["关键转折1", "关键转折2"],
|
|
110
|
+
"midpoint": "本卷中点转折或认知变化",
|
|
111
|
+
"climax": "本卷高潮",
|
|
112
|
+
"payoffs": ["本卷计划回收的伏笔或承诺"],
|
|
113
|
+
"lingeringMysteries": ["卷末仍要保留的悬念"]
|
|
114
|
+
}
|
|
115
|
+
],
|
|
104
116
|
"chapters": [
|
|
105
117
|
{
|
|
106
118
|
"chapterNumber": 1,
|
|
@@ -116,35 +128,49 @@ ${input.context ? `## 已有上下文\n${input.context}\n` : ''}## 输出要求
|
|
|
116
128
|
- chapters.length 必须大于等于 ${input.state.targetChapters}。
|
|
117
129
|
- chapterNumber 从 1 开始连续递增。
|
|
118
130
|
- volumeId 必须引用 volumes 中存在的 id。
|
|
131
|
+
- volumePacing 必须为每个 volume 提供节奏板。
|
|
119
132
|
- requiredBeats 至少 1 条,且必须具体可执行。
|
|
120
133
|
${strictJsonOutputRules()}`,
|
|
121
134
|
};
|
|
122
135
|
}
|
|
123
136
|
function buildChapterPrompt(input) {
|
|
137
|
+
const ch = input.state.currentChapter;
|
|
138
|
+
const isFirstChapter = ch <= 1;
|
|
124
139
|
return {
|
|
125
140
|
purpose: 'chapter',
|
|
126
141
|
expectedFormat: 'Markdown',
|
|
127
|
-
prompt: `你是一位擅长创作长篇网络小说的职业作者。请直接完成第 ${
|
|
142
|
+
prompt: `你是一位擅长创作长篇网络小说的职业作者。请直接完成第 ${ch} 章正文。
|
|
128
143
|
|
|
129
144
|
## 执行优先级
|
|
130
|
-
1.
|
|
131
|
-
2.
|
|
132
|
-
3.
|
|
145
|
+
1. 先严格遵守"本章架构、用户补充要求、故事圣经硬约束、上一章承接"。
|
|
146
|
+
2. 再参考"历史相关记忆、历史原文证据、活跃伏笔"保证一致性。
|
|
147
|
+
3. 最后才参考"全本/本卷远场规划",且不得提前写出尚未发生的情节。
|
|
148
|
+
|
|
149
|
+
## 字数目标
|
|
150
|
+
- 默认目标 3000 字(±20%)。如果本章架构里指定了 targetWords,按那个目标。
|
|
151
|
+
- 不要为了凑字数注水;也不要为了简洁牺牲冲突推进。
|
|
133
152
|
|
|
134
|
-
##
|
|
153
|
+
## 结构要求
|
|
154
|
+
${isFirstChapter
|
|
155
|
+
? '- 这是第 1 章,不需要"上回提要"。开篇直接立人物、立情境。'
|
|
156
|
+
: '- 章首需要 2-3 句"上回提要"或"承接段",让没读上一章的读者能续上(除非本章架构 requireRecap=false)。要自然带入,不要写成"上一章里……"的元叙述。'}
|
|
157
|
+
- 章末必须有清晰的"钩子":可以是悬念、反转、剧情承诺、情绪余韵或卷末高潮——按本章架构 endHookFocus 字段决定。如果未指定,默认用悬念。
|
|
158
|
+
|
|
159
|
+
## 风格
|
|
135
160
|
- 文风必须与本书题材、世界观、人物身份、情感基调一致。
|
|
136
|
-
-
|
|
137
|
-
-
|
|
138
|
-
-
|
|
139
|
-
-
|
|
161
|
+
- 语言自然、稳定、可读,优先服务叙事推进、人物塑造和情绪积累。
|
|
162
|
+
- 对话符合人物身份、关系和处境;重要情绪通过动作、神态、节奏、潜台词体现。
|
|
163
|
+
- 场景描写有必要的感官细节与氛围支撑,但篇幅服务剧情。
|
|
164
|
+
- POV 严格按本章架构 povCharacter(如有),中途不切换视角。
|
|
140
165
|
|
|
141
166
|
## 执行规则
|
|
142
167
|
- 只写本章架构明确覆盖的内容,不得提前写后续章节具体事件或人物揭示。
|
|
143
|
-
-
|
|
168
|
+
- 不得新增本章架构未授权的主要人物;功能性角色轻描淡写。
|
|
144
169
|
- 所有人物称谓、物品、场景、能力、时间线必须与既有设定一致。
|
|
145
170
|
- 如果上一章结尾仍在动作、对话或同一场景中,本章开头必须连续衔接。
|
|
171
|
+
- 活跃伏笔列表中的条目本章可以"推进"或"回收",但**不得无声无息地删除**——若选择不触碰也要让它仍然成立。
|
|
146
172
|
- 禁止无代价越级碾压、强行降智配角、突兀机械反转、硬灌设定、空洞抒情。
|
|
147
|
-
-
|
|
173
|
+
- 禁止总结腔、条目腔、说教腔,不要输出解释性前言或"我修改了什么"之类的元文本。
|
|
148
174
|
|
|
149
175
|
${input.context ? `## 生成上下文\n${input.context}\n` : ''}## 输出要求
|
|
150
176
|
- 输出 Markdown。
|
|
@@ -184,13 +210,41 @@ ${input.context ? `## 当前章上下文\n${input.context}\n` : ''}## 输出要
|
|
|
184
210
|
"after": "变化后"
|
|
185
211
|
}
|
|
186
212
|
],
|
|
187
|
-
"openThreads": ["尚未解决的伏笔、承诺、危险或疑问"]
|
|
213
|
+
"openThreads": ["尚未解决的伏笔、承诺、危险或疑问"],
|
|
214
|
+
"wordCount": 本章实际字数(整数估算即可),
|
|
215
|
+
"threadActions": [
|
|
216
|
+
{
|
|
217
|
+
"kind": "plant | build | pay | drop",
|
|
218
|
+
"threadId": "已有伏笔的 id(如果是 plant 留空让系统分配;build/pay/drop 必须填活跃伏笔列表里的 id)",
|
|
219
|
+
"description": "新伏笔的描述(plant 时必填)或本章如何推进/回收/丢弃这条伏笔的一句话"
|
|
220
|
+
}
|
|
221
|
+
],
|
|
222
|
+
"characterUpdates": [
|
|
223
|
+
{
|
|
224
|
+
"name": "人物姓名",
|
|
225
|
+
"role": "角色定位(如本章确认或改变)",
|
|
226
|
+
"goal": "本章结束时的当前目标",
|
|
227
|
+
"belief": "本章结束时影响其行动的核心认知/信念",
|
|
228
|
+
"relationships": [
|
|
229
|
+
{ "name": "相关人物", "dynamic": "本章结束时的关系状态" }
|
|
230
|
+
],
|
|
231
|
+
"abilities": ["本章结束时确认拥有的能力、资源或限制"],
|
|
232
|
+
"secrets": ["本章结束时仍未公开或只被部分人知道的秘密"],
|
|
233
|
+
"emotionalState": "本章结束时的情绪状态"
|
|
234
|
+
}
|
|
235
|
+
]
|
|
188
236
|
}
|
|
189
237
|
|
|
190
238
|
要求:
|
|
191
239
|
- 只记录已经在本章发生或被确认的信息。
|
|
192
240
|
- 不要推测后续剧情。
|
|
193
241
|
- facts 和 stateChanges 要具体,便于后续章节引用。
|
|
242
|
+
- wordCount 用中文字符数(去掉空格和 Markdown 标记),近似估算即可。
|
|
243
|
+
- threadActions 是关键:
|
|
244
|
+
· 任何"上下文里 Active Foreshadow Threads"列出的活跃伏笔,如果本章推进了,请发 kind="build";如果本章正式回收/兑现,请发 kind="pay";如果本章决定放弃,请发 kind="drop"。这三种情况 threadId 必填。
|
|
245
|
+
· 本章新埋设的伏笔,请发 kind="plant",description 写清楚是什么伏笔。
|
|
246
|
+
· 活跃伏笔本章没动也没关系,不需要为它发动作;但**不要悄悄删除**——只要不发 drop,它就继续保留活跃。
|
|
247
|
+
- characterUpdates 用于维护独立角色状态表:只输出本章有明确变化或被重新确认的重要人物;目标、信念、关系、能力、秘密、情绪状态必须以本章结尾为准。
|
|
194
248
|
${strictJsonOutputRules()}`,
|
|
195
249
|
};
|
|
196
250
|
}
|
|
@@ -240,6 +294,12 @@ function buildChapterReviewPrompt(input) {
|
|
|
240
294
|
prompt: `你是一名严格的章节审稿编辑。请审阅指定章节是否存在内部问题以及与既有设定的冲突。
|
|
241
295
|
|
|
242
296
|
${input.context ? `## 审阅上下文\n${input.context}\n` : ''}## 审阅重点
|
|
297
|
+
- 这是强制章节验收门槛:只要任一验收项 fail,status 必须是 "issues_found",不能进入下一章。
|
|
298
|
+
- requiredBeats 是否全部完成;缺失项必须写入 acceptance.requiredBeats.missingBeats。
|
|
299
|
+
- 本章是否推进主线、人物状态或活跃伏笔;如果完全原地踏步,narrativeProgress/characterProgress/foreshadowProgress 至少一项必须 fail。
|
|
300
|
+
- 是否违反故事圣经、角色状态表、卷级节奏板或历史记忆。
|
|
301
|
+
- 章末是否有清晰钩子,且符合本章 endHookFocus。
|
|
302
|
+
- 是否重复之前章节已经完成的桥段、冲突结构、信息揭示或对话功能。
|
|
243
303
|
- 人物声音、动机、状态是否符合故事圣经与历史记忆。
|
|
244
304
|
- 世界规则、物品归属、能力边界是否被破坏。
|
|
245
305
|
- 时间线、地点、与上一章结尾的衔接是否一致。
|
|
@@ -251,6 +311,37 @@ ${input.context ? `## 审阅上下文\n${input.context}\n` : ''}## 审阅重点
|
|
|
251
311
|
{
|
|
252
312
|
"chapterNumber": ${chapter},
|
|
253
313
|
"status": "clean",
|
|
314
|
+
"acceptance": {
|
|
315
|
+
"requiredBeats": {
|
|
316
|
+
"status": "pass | fail",
|
|
317
|
+
"evidence": "逐条说明 requiredBeats 完成证据",
|
|
318
|
+
"missingBeats": []
|
|
319
|
+
},
|
|
320
|
+
"narrativeProgress": {
|
|
321
|
+
"status": "pass | fail",
|
|
322
|
+
"evidence": "本章如何推进主线/阶段目标"
|
|
323
|
+
},
|
|
324
|
+
"characterProgress": {
|
|
325
|
+
"status": "pass | fail",
|
|
326
|
+
"evidence": "本章如何改变或确认关键人物目标、信念、关系、能力、秘密或情绪"
|
|
327
|
+
},
|
|
328
|
+
"foreshadowProgress": {
|
|
329
|
+
"status": "pass | fail",
|
|
330
|
+
"evidence": "本章如何埋设、推进、回收或有意识保留伏笔"
|
|
331
|
+
},
|
|
332
|
+
"storyBibleConsistency": {
|
|
333
|
+
"status": "pass | fail",
|
|
334
|
+
"evidence": "是否符合故事圣经、角色状态表和世界规则"
|
|
335
|
+
},
|
|
336
|
+
"endingHook": {
|
|
337
|
+
"status": "pass | fail",
|
|
338
|
+
"evidence": "章末钩子的具体段落和作用"
|
|
339
|
+
},
|
|
340
|
+
"repetition": {
|
|
341
|
+
"status": "pass | fail",
|
|
342
|
+
"evidence": "是否重复既有桥段;无重复也要说明依据"
|
|
343
|
+
}
|
|
344
|
+
},
|
|
254
345
|
"issues": [
|
|
255
346
|
{
|
|
256
347
|
"severity": "low | medium | high",
|
|
@@ -265,6 +356,8 @@ ${input.context ? `## 审阅上下文\n${input.context}\n` : ''}## 审阅重点
|
|
|
265
356
|
要求:
|
|
266
357
|
- 没有问题时 status 为 "clean",issues 输出空数组。
|
|
267
358
|
- 有问题时 status 为 "issues_found"。
|
|
359
|
+
- 只有所有 acceptance 项都是 "pass" 时,status 才能为 "clean"。
|
|
360
|
+
- 任一 acceptance 项为 "fail" 时,必须在 issues 中写出对应问题与修复建议。
|
|
268
361
|
- evidence 必须具体,不能写"疑似"、"可能"。
|
|
269
362
|
${strictJsonOutputRules()}`,
|
|
270
363
|
};
|
|
@@ -348,6 +441,17 @@ function buildPromptForStep(input) {
|
|
|
348
441
|
return buildChapterRevisionPrompt(input);
|
|
349
442
|
case 'cross_chapter_review':
|
|
350
443
|
return buildCrossChapterReviewPrompt(input);
|
|
444
|
+
case 'story_bible_amend':
|
|
445
|
+
return {
|
|
446
|
+
purpose: 'story_bible',
|
|
447
|
+
expectedFormat: 'Markdown',
|
|
448
|
+
prompt: `请基于当前已有故事圣经与本次反馈,输出"修订后的完整故事圣经 Markdown"。
|
|
449
|
+
|
|
450
|
+
${input.context ? `## 修订上下文\n${input.context}\n` : ''}## 输出要求
|
|
451
|
+
- 输出完整的 story-bible.md 内容,覆盖式替换旧版(旧版会自动归档到 story-bible-versions/)。
|
|
452
|
+
- 保留旧版仍然成立的内容,仅修改 / 新增 / 删除有明确依据的部分。
|
|
453
|
+
- 不要输出 diff、变更说明、bullet 总结,直接输出新 bible 全文。`,
|
|
454
|
+
};
|
|
351
455
|
case 'complete':
|
|
352
456
|
return {
|
|
353
457
|
purpose: 'continuity_review',
|
|
@@ -82,6 +82,14 @@ export async function indexMemoryCard(projectPath, chapterNumber, card) {
|
|
|
82
82
|
const id = `memory:${chapterNumber}`;
|
|
83
83
|
await upsert(projectPath, (existing) => existing === id, chunkMemoryCard(chapterNumber, card));
|
|
84
84
|
}
|
|
85
|
+
export async function removeChapterFromIndex(projectPath, chapterNumber) {
|
|
86
|
+
const prefix = `chapter:${chapterNumber}:`;
|
|
87
|
+
await upsert(projectPath, (id) => id.startsWith(prefix), []);
|
|
88
|
+
}
|
|
89
|
+
export async function removeMemoryCardFromIndex(projectPath, chapterNumber) {
|
|
90
|
+
const id = `memory:${chapterNumber}`;
|
|
91
|
+
await upsert(projectPath, (existing) => existing === id, []);
|
|
92
|
+
}
|
|
85
93
|
export async function retrieve(projectPath, query, options = {}) {
|
|
86
94
|
if (!query.trim())
|
|
87
95
|
return [];
|
package/dist/src/core/schemas.js
CHANGED
|
@@ -18,18 +18,83 @@ export const VolumeArchitectureSchema = z.object({
|
|
|
18
18
|
summary: z.string().min(1),
|
|
19
19
|
order: z.number().int().positive(),
|
|
20
20
|
});
|
|
21
|
+
export const VolumePacingBoardSchema = z.object({
|
|
22
|
+
volumeId: z.string().min(1),
|
|
23
|
+
start: z.string().min(1),
|
|
24
|
+
promise: z.string().min(1),
|
|
25
|
+
keyTurns: z.array(z.string().min(1)).min(1),
|
|
26
|
+
midpoint: z.string().min(1),
|
|
27
|
+
climax: z.string().min(1),
|
|
28
|
+
payoffs: z.array(z.string().min(1)),
|
|
29
|
+
lingeringMysteries: z.array(z.string().min(1)),
|
|
30
|
+
});
|
|
31
|
+
export const EndHookFocusSchema = z.enum([
|
|
32
|
+
'cliffhanger',
|
|
33
|
+
'mystery',
|
|
34
|
+
'emotional',
|
|
35
|
+
'reveal',
|
|
36
|
+
'volume_close',
|
|
37
|
+
'gentle',
|
|
38
|
+
]);
|
|
21
39
|
export const ChapterArchitectureSchema = z.object({
|
|
22
40
|
chapterNumber: z.number().int().positive(),
|
|
23
41
|
title: z.string().min(1),
|
|
24
42
|
volumeId: z.string().min(1),
|
|
25
43
|
summary: z.string().min(1),
|
|
26
44
|
requiredBeats: z.array(z.string().min(1)).min(1),
|
|
45
|
+
targetWords: z.number().int().positive().optional(),
|
|
46
|
+
requireRecap: z.boolean().optional(),
|
|
47
|
+
endHookFocus: EndHookFocusSchema.optional(),
|
|
48
|
+
povCharacter: z.string().min(1).optional(),
|
|
27
49
|
});
|
|
28
50
|
export const ArchitecturePayloadSchema = z.object({
|
|
29
51
|
full: z.string().min(1),
|
|
30
52
|
volumes: z.array(VolumeArchitectureSchema).min(1),
|
|
53
|
+
volumePacing: z.array(VolumePacingBoardSchema).optional(),
|
|
31
54
|
chapters: z.array(ChapterArchitectureSchema).min(1),
|
|
32
55
|
});
|
|
56
|
+
export const ThreadActionSchema = z.object({
|
|
57
|
+
kind: z.enum(['plant', 'build', 'pay', 'drop']),
|
|
58
|
+
threadId: z.string().min(1).optional(),
|
|
59
|
+
description: z.string().min(1),
|
|
60
|
+
});
|
|
61
|
+
export const ThreadStatusSchema = z.enum(['planted', 'building', 'paid', 'dropped']);
|
|
62
|
+
export const ThreadSchema = z.object({
|
|
63
|
+
id: z.string().min(1),
|
|
64
|
+
description: z.string().min(1),
|
|
65
|
+
status: ThreadStatusSchema,
|
|
66
|
+
plantedAt: z.number().int().positive(),
|
|
67
|
+
lastTouchedAt: z.number().int().positive(),
|
|
68
|
+
plannedPayoffAt: z.number().int().positive().optional(),
|
|
69
|
+
paidOffAt: z.number().int().positive().optional(),
|
|
70
|
+
droppedAt: z.number().int().positive().optional(),
|
|
71
|
+
notes: z.string().optional(),
|
|
72
|
+
});
|
|
73
|
+
export const CharacterRelationshipStateSchema = z.object({
|
|
74
|
+
name: z.string().min(1),
|
|
75
|
+
dynamic: z.string().min(1),
|
|
76
|
+
});
|
|
77
|
+
export const CharacterStateSchema = z.object({
|
|
78
|
+
name: z.string().min(1),
|
|
79
|
+
role: z.string().min(1).optional(),
|
|
80
|
+
goal: z.string().min(1),
|
|
81
|
+
belief: z.string().min(1),
|
|
82
|
+
relationships: z.array(CharacterRelationshipStateSchema),
|
|
83
|
+
abilities: z.array(z.string().min(1)),
|
|
84
|
+
secrets: z.array(z.string().min(1)),
|
|
85
|
+
emotionalState: z.string().min(1),
|
|
86
|
+
lastUpdatedAt: z.number().int().nonnegative(),
|
|
87
|
+
});
|
|
88
|
+
export const CharacterStateUpdateSchema = z.object({
|
|
89
|
+
name: z.string().min(1),
|
|
90
|
+
role: z.string().min(1).optional(),
|
|
91
|
+
goal: z.string().min(1).optional(),
|
|
92
|
+
belief: z.string().min(1).optional(),
|
|
93
|
+
relationships: z.array(CharacterRelationshipStateSchema).optional(),
|
|
94
|
+
abilities: z.array(z.string().min(1)).optional(),
|
|
95
|
+
secrets: z.array(z.string().min(1)).optional(),
|
|
96
|
+
emotionalState: z.string().min(1).optional(),
|
|
97
|
+
});
|
|
33
98
|
export const MemoryCardSchema = z.object({
|
|
34
99
|
summary: z.string().min(1),
|
|
35
100
|
keyEvents: z.array(z.string().min(1)),
|
|
@@ -49,6 +114,9 @@ export const MemoryCardSchema = z.object({
|
|
|
49
114
|
after: z.string().min(1),
|
|
50
115
|
})),
|
|
51
116
|
openThreads: z.array(z.string().min(1)),
|
|
117
|
+
wordCount: z.number().int().nonnegative().optional(),
|
|
118
|
+
threadActions: z.array(ThreadActionSchema).optional(),
|
|
119
|
+
characterUpdates: z.array(CharacterStateUpdateSchema).optional(),
|
|
52
120
|
});
|
|
53
121
|
export const ContinuityReviewSchema = z.object({
|
|
54
122
|
range: z.object({
|
|
@@ -65,14 +133,43 @@ export const ContinuityReviewSchema = z.object({
|
|
|
65
133
|
});
|
|
66
134
|
export const ChapterReviewIssueSchema = z.object({
|
|
67
135
|
severity: z.enum(['low', 'medium', 'high']),
|
|
68
|
-
category: z.enum([
|
|
136
|
+
category: z.enum([
|
|
137
|
+
'character',
|
|
138
|
+
'world',
|
|
139
|
+
'timeline',
|
|
140
|
+
'item',
|
|
141
|
+
'knowledge',
|
|
142
|
+
'pacing',
|
|
143
|
+
'style',
|
|
144
|
+
'architecture',
|
|
145
|
+
'plot',
|
|
146
|
+
'foreshadow',
|
|
147
|
+
'hook',
|
|
148
|
+
'repetition',
|
|
149
|
+
]),
|
|
69
150
|
description: z.string().min(1),
|
|
70
151
|
evidence: z.string().min(1),
|
|
71
152
|
suggestion: z.string().min(1),
|
|
72
153
|
});
|
|
154
|
+
export const ChapterAcceptanceCheckSchema = z.object({
|
|
155
|
+
status: z.enum(['pass', 'fail']),
|
|
156
|
+
evidence: z.string().min(1),
|
|
157
|
+
});
|
|
158
|
+
export const ChapterAcceptanceGateSchema = z.object({
|
|
159
|
+
requiredBeats: ChapterAcceptanceCheckSchema.extend({
|
|
160
|
+
missingBeats: z.array(z.string().min(1)),
|
|
161
|
+
}),
|
|
162
|
+
narrativeProgress: ChapterAcceptanceCheckSchema,
|
|
163
|
+
characterProgress: ChapterAcceptanceCheckSchema,
|
|
164
|
+
foreshadowProgress: ChapterAcceptanceCheckSchema,
|
|
165
|
+
storyBibleConsistency: ChapterAcceptanceCheckSchema,
|
|
166
|
+
endingHook: ChapterAcceptanceCheckSchema,
|
|
167
|
+
repetition: ChapterAcceptanceCheckSchema,
|
|
168
|
+
});
|
|
73
169
|
export const ChapterReviewSchema = z.object({
|
|
74
170
|
chapterNumber: z.number().int().positive(),
|
|
75
171
|
status: z.enum(['clean', 'issues_found']),
|
|
172
|
+
acceptance: ChapterAcceptanceGateSchema,
|
|
76
173
|
issues: z.array(ChapterReviewIssueSchema),
|
|
77
174
|
});
|
|
78
175
|
export const CrossChapterReviewSchema = z.object({
|
|
@@ -8,9 +8,15 @@ export const architectureHandler = async (state, content) => {
|
|
|
8
8
|
await saveJsonFile(state.projectPath, 'architecture/volumes.json', parsed.volumes),
|
|
9
9
|
await saveJsonFile(state.projectPath, 'architecture/chapters.json', parsed.chapters),
|
|
10
10
|
];
|
|
11
|
+
if (parsed.volumePacing) {
|
|
12
|
+
savedPaths.push(await saveJsonFile(state.projectPath, 'architecture/volume-pacing.json', parsed.volumePacing));
|
|
13
|
+
}
|
|
11
14
|
return {
|
|
12
15
|
savedPaths,
|
|
13
|
-
fileEntries: {
|
|
16
|
+
fileEntries: {
|
|
17
|
+
architecture: 'architecture/chapters.json',
|
|
18
|
+
...(parsed.volumePacing ? { volumePacing: 'architecture/volume-pacing.json' } : {}),
|
|
19
|
+
},
|
|
14
20
|
next: { kind: 'linear', nextStep: 'chapter' },
|
|
15
21
|
};
|
|
16
22
|
};
|
|
@@ -11,6 +11,16 @@ export const chapterHandler = async (state, content) => {
|
|
|
11
11
|
return {
|
|
12
12
|
savedPaths: [path],
|
|
13
13
|
fileEntries: { [`chapter-${state.currentChapter}`]: relative },
|
|
14
|
-
next: {
|
|
14
|
+
next: {
|
|
15
|
+
kind: 'linear',
|
|
16
|
+
nextStep: 'chapter_review',
|
|
17
|
+
statePatch: {
|
|
18
|
+
pendingAction: {
|
|
19
|
+
step: 'chapter_review',
|
|
20
|
+
mode: 'gate',
|
|
21
|
+
chapterNumber: state.currentChapter,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
15
25
|
};
|
|
16
26
|
};
|
|
@@ -8,9 +8,33 @@ export const chapterReviewHandler = async (state, content) => {
|
|
|
8
8
|
const target = state.pendingAction?.chapterNumber ?? parsed.chapterNumber;
|
|
9
9
|
const relative = join('reviews/chapter', chapterReviewFileName(target));
|
|
10
10
|
const path = await saveJsonFile(state.projectPath, relative, parsed);
|
|
11
|
+
if (state.pendingAction?.mode === 'side_track') {
|
|
12
|
+
return {
|
|
13
|
+
savedPaths: [path],
|
|
14
|
+
fileEntries: { [`review-chapter-${target}`]: relative },
|
|
15
|
+
next: { kind: 'sideTrackReturn' },
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
if (parsed.status === 'clean') {
|
|
19
|
+
return {
|
|
20
|
+
savedPaths: [path],
|
|
21
|
+
fileEntries: { [`review-chapter-${target}`]: relative },
|
|
22
|
+
next: { kind: 'linear', nextStep: 'memory_card' },
|
|
23
|
+
};
|
|
24
|
+
}
|
|
11
25
|
return {
|
|
12
26
|
savedPaths: [path],
|
|
13
27
|
fileEntries: { [`review-chapter-${target}`]: relative },
|
|
14
|
-
next: {
|
|
28
|
+
next: {
|
|
29
|
+
kind: 'linear',
|
|
30
|
+
nextStep: 'chapter_revision',
|
|
31
|
+
statePatch: {
|
|
32
|
+
pendingAction: {
|
|
33
|
+
step: 'chapter_revision',
|
|
34
|
+
mode: 'gate',
|
|
35
|
+
chapterNumber: target,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
15
39
|
};
|
|
16
40
|
};
|
|
@@ -12,6 +12,23 @@ export const chapterRevisionHandler = async (state, content) => {
|
|
|
12
12
|
const savedPaths = archived ? [archived] : [];
|
|
13
13
|
savedPaths.push(await saveMarkdownFile(state.projectPath, chapterRelative, content));
|
|
14
14
|
await indexChapter(state.projectPath, target, content);
|
|
15
|
+
if (state.pendingAction?.mode === 'gate') {
|
|
16
|
+
return {
|
|
17
|
+
savedPaths,
|
|
18
|
+
fileEntries: { [`chapter-${target}`]: chapterRelative },
|
|
19
|
+
next: {
|
|
20
|
+
kind: 'linear',
|
|
21
|
+
nextStep: 'chapter_review',
|
|
22
|
+
statePatch: {
|
|
23
|
+
pendingAction: {
|
|
24
|
+
step: 'chapter_review',
|
|
25
|
+
mode: 'gate',
|
|
26
|
+
chapterNumber: target,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
15
32
|
return {
|
|
16
33
|
savedPaths,
|
|
17
34
|
fileEntries: { [`chapter-${target}`]: chapterRelative },
|