@actalk/inkos-core 1.3.6 → 1.3.7-canary.36.1
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/dist/agent/agent-session.d.ts +7 -7
- package/dist/agent/agent-session.d.ts.map +1 -1
- package/dist/agent/agent-session.js +392 -63
- package/dist/agent/agent-session.js.map +1 -1
- package/dist/agent/agent-system-prompt.d.ts.map +1 -1
- package/dist/agent/agent-system-prompt.js +20 -16
- package/dist/agent/agent-system-prompt.js.map +1 -1
- package/dist/agent/agent-tools.d.ts +4 -1
- package/dist/agent/agent-tools.d.ts.map +1 -1
- package/dist/agent/agent-tools.js +81 -64
- package/dist/agent/agent-tools.js.map +1 -1
- package/dist/agents/architect.d.ts +18 -20
- package/dist/agents/architect.d.ts.map +1 -1
- package/dist/agents/architect.js +513 -632
- package/dist/agents/architect.js.map +1 -1
- package/dist/agents/chapter-analyzer.d.ts.map +1 -1
- package/dist/agents/chapter-analyzer.js +10 -6
- package/dist/agents/chapter-analyzer.js.map +1 -1
- package/dist/agents/composer.d.ts +1 -13
- package/dist/agents/composer.d.ts.map +1 -1
- package/dist/agents/composer.js +297 -290
- package/dist/agents/composer.js.map +1 -1
- package/dist/agents/consolidator.d.ts +17 -1
- package/dist/agents/consolidator.d.ts.map +1 -1
- package/dist/agents/consolidator.js +44 -6
- package/dist/agents/consolidator.js.map +1 -1
- package/dist/agents/continuity.d.ts +4 -1
- package/dist/agents/continuity.d.ts.map +1 -1
- package/dist/agents/continuity.js +111 -21
- package/dist/agents/continuity.js.map +1 -1
- package/dist/agents/length-normalizer.d.ts.map +1 -1
- package/dist/agents/length-normalizer.js +1 -4
- package/dist/agents/length-normalizer.js.map +1 -1
- package/dist/agents/planner-context.d.ts +54 -0
- package/dist/agents/planner-context.d.ts.map +1 -0
- package/dist/agents/planner-context.js +245 -0
- package/dist/agents/planner-context.js.map +1 -0
- package/dist/agents/planner-prompts.d.ts +36 -0
- package/dist/agents/planner-prompts.d.ts.map +1 -0
- package/dist/agents/planner-prompts.js +350 -0
- package/dist/agents/planner-prompts.js.map +1 -0
- package/dist/agents/planner.d.ts +39 -11
- package/dist/agents/planner.d.ts.map +1 -1
- package/dist/agents/planner.js +212 -195
- package/dist/agents/planner.js.map +1 -1
- package/dist/agents/polisher.d.ts +33 -0
- package/dist/agents/polisher.d.ts.map +1 -0
- package/dist/agents/polisher.js +122 -0
- package/dist/agents/polisher.js.map +1 -0
- package/dist/agents/post-write-validator.d.ts +1 -0
- package/dist/agents/post-write-validator.d.ts.map +1 -1
- package/dist/agents/post-write-validator.js +13 -0
- package/dist/agents/post-write-validator.js.map +1 -1
- package/dist/agents/reviser.d.ts +6 -2
- package/dist/agents/reviser.d.ts.map +1 -1
- package/dist/agents/reviser.js +379 -98
- package/dist/agents/reviser.js.map +1 -1
- package/dist/agents/rules-reader.d.ts +15 -2
- package/dist/agents/rules-reader.d.ts.map +1 -1
- package/dist/agents/rules-reader.js +49 -6
- package/dist/agents/rules-reader.js.map +1 -1
- package/dist/agents/state-validator.d.ts +9 -1
- package/dist/agents/state-validator.d.ts.map +1 -1
- package/dist/agents/state-validator.js +37 -1
- package/dist/agents/state-validator.js.map +1 -1
- package/dist/agents/writer-prompts.d.ts +1 -0
- package/dist/agents/writer-prompts.d.ts.map +1 -1
- package/dist/agents/writer-prompts.js +272 -29
- package/dist/agents/writer-prompts.js.map +1 -1
- package/dist/agents/writer.d.ts +12 -3
- package/dist/agents/writer.d.ts.map +1 -1
- package/dist/agents/writer.js +77 -107
- package/dist/agents/writer.js.map +1 -1
- package/dist/index.d.ts +20 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -5
- package/dist/index.js.map +1 -1
- package/dist/interaction/book-session-store.d.ts.map +1 -1
- package/dist/interaction/book-session-store.js +84 -69
- package/dist/interaction/book-session-store.js.map +1 -1
- package/dist/interaction/events.d.ts +2 -2
- package/dist/interaction/project-tools.d.ts +1 -0
- package/dist/interaction/project-tools.d.ts.map +1 -1
- package/dist/interaction/project-tools.js +51 -3
- package/dist/interaction/project-tools.js.map +1 -1
- package/dist/interaction/session-transcript-legacy.d.ts +4 -0
- package/dist/interaction/session-transcript-legacy.d.ts.map +1 -0
- package/dist/interaction/session-transcript-legacy.js +100 -0
- package/dist/interaction/session-transcript-legacy.js.map +1 -0
- package/dist/interaction/session-transcript-restore.d.ts +17 -0
- package/dist/interaction/session-transcript-restore.d.ts.map +1 -0
- package/dist/interaction/session-transcript-restore.js +493 -0
- package/dist/interaction/session-transcript-restore.js.map +1 -0
- package/dist/interaction/session-transcript-schema.d.ts +402 -0
- package/dist/interaction/session-transcript-schema.d.ts.map +1 -0
- package/dist/interaction/session-transcript-schema.js +59 -0
- package/dist/interaction/session-transcript-schema.js.map +1 -0
- package/dist/interaction/session-transcript.d.ts +14 -0
- package/dist/interaction/session-transcript.d.ts.map +1 -0
- package/dist/interaction/session-transcript.js +152 -0
- package/dist/interaction/session-transcript.js.map +1 -0
- package/dist/interaction/session.d.ts +10 -10
- package/dist/interaction/session.d.ts.map +1 -1
- package/dist/interaction/session.js +5 -3
- package/dist/interaction/session.js.map +1 -1
- package/dist/llm/provider.d.ts +6 -9
- package/dist/llm/provider.d.ts.map +1 -1
- package/dist/llm/provider.js +255 -63
- package/dist/llm/provider.js.map +1 -1
- package/dist/llm/providers/endpoints/ai360.d.ts +10 -0
- package/dist/llm/providers/endpoints/ai360.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/ai360.js +34 -0
- package/dist/llm/providers/endpoints/ai360.js.map +1 -0
- package/dist/llm/providers/endpoints/anthropic.d.ts +12 -0
- package/dist/llm/providers/endpoints/anthropic.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/anthropic.js +72 -0
- package/dist/llm/providers/endpoints/anthropic.js.map +1 -0
- package/dist/llm/providers/endpoints/astronCodingPlan.d.ts +16 -0
- package/dist/llm/providers/endpoints/astronCodingPlan.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/astronCodingPlan.js +16 -0
- package/dist/llm/providers/endpoints/astronCodingPlan.js.map +1 -0
- package/dist/llm/providers/endpoints/baichuan.d.ts +10 -0
- package/dist/llm/providers/endpoints/baichuan.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/baichuan.js +20 -0
- package/dist/llm/providers/endpoints/baichuan.js.map +1 -0
- package/dist/llm/providers/endpoints/bailian.d.ts +25 -0
- package/dist/llm/providers/endpoints/bailian.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/bailian.js +42 -0
- package/dist/llm/providers/endpoints/bailian.js.map +1 -0
- package/dist/llm/providers/endpoints/bailianCodingPlan.d.ts +10 -0
- package/dist/llm/providers/endpoints/bailianCodingPlan.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/bailianCodingPlan.js +22 -0
- package/dist/llm/providers/endpoints/bailianCodingPlan.js.map +1 -0
- package/dist/llm/providers/endpoints/custom.d.ts +16 -0
- package/dist/llm/providers/endpoints/custom.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/custom.js +15 -0
- package/dist/llm/providers/endpoints/custom.js.map +1 -0
- package/dist/llm/providers/endpoints/deepseek.d.ts +17 -0
- package/dist/llm/providers/endpoints/deepseek.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/deepseek.js +20 -0
- package/dist/llm/providers/endpoints/deepseek.js.map +1 -0
- package/dist/llm/providers/endpoints/giteeai.d.ts +10 -0
- package/dist/llm/providers/endpoints/giteeai.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/giteeai.js +33 -0
- package/dist/llm/providers/endpoints/giteeai.js.map +1 -0
- package/dist/llm/providers/endpoints/githubCopilot.d.ts +10 -0
- package/dist/llm/providers/endpoints/githubCopilot.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/githubCopilot.js +35 -0
- package/dist/llm/providers/endpoints/githubCopilot.js.map +1 -0
- package/dist/llm/providers/endpoints/glmCodingPlan.d.ts +9 -0
- package/dist/llm/providers/endpoints/glmCodingPlan.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/glmCodingPlan.js +21 -0
- package/dist/llm/providers/endpoints/glmCodingPlan.js.map +1 -0
- package/dist/llm/providers/endpoints/google.d.ts +12 -0
- package/dist/llm/providers/endpoints/google.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/google.js +41 -0
- package/dist/llm/providers/endpoints/google.js.map +1 -0
- package/dist/llm/providers/endpoints/hunyuan.d.ts +10 -0
- package/dist/llm/providers/endpoints/hunyuan.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/hunyuan.js +34 -0
- package/dist/llm/providers/endpoints/hunyuan.js.map +1 -0
- package/dist/llm/providers/endpoints/infiniai.d.ts +10 -0
- package/dist/llm/providers/endpoints/infiniai.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/infiniai.js +64 -0
- package/dist/llm/providers/endpoints/infiniai.js.map +1 -0
- package/dist/llm/providers/endpoints/internlm.d.ts +10 -0
- package/dist/llm/providers/endpoints/internlm.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/internlm.js +20 -0
- package/dist/llm/providers/endpoints/internlm.js.map +1 -0
- package/dist/llm/providers/endpoints/kimiCodingPlan.d.ts +9 -0
- package/dist/llm/providers/endpoints/kimiCodingPlan.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/kimiCodingPlan.js +17 -0
- package/dist/llm/providers/endpoints/kimiCodingPlan.js.map +1 -0
- package/dist/llm/providers/endpoints/longcat.d.ts +9 -0
- package/dist/llm/providers/endpoints/longcat.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/longcat.js +18 -0
- package/dist/llm/providers/endpoints/longcat.js.map +1 -0
- package/dist/llm/providers/endpoints/minimax.d.ts +15 -0
- package/dist/llm/providers/endpoints/minimax.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/minimax.js +26 -0
- package/dist/llm/providers/endpoints/minimax.js.map +1 -0
- package/dist/llm/providers/endpoints/minimaxCodingPlan.d.ts +9 -0
- package/dist/llm/providers/endpoints/minimaxCodingPlan.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/minimaxCodingPlan.js +21 -0
- package/dist/llm/providers/endpoints/minimaxCodingPlan.js.map +1 -0
- package/dist/llm/providers/endpoints/mistral.d.ts +11 -0
- package/dist/llm/providers/endpoints/mistral.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/mistral.js +31 -0
- package/dist/llm/providers/endpoints/mistral.js.map +1 -0
- package/dist/llm/providers/endpoints/modelscope.d.ts +10 -0
- package/dist/llm/providers/endpoints/modelscope.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/modelscope.js +22 -0
- package/dist/llm/providers/endpoints/modelscope.js.map +1 -0
- package/dist/llm/providers/endpoints/moonshot.d.ts +12 -0
- package/dist/llm/providers/endpoints/moonshot.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/moonshot.js +29 -0
- package/dist/llm/providers/endpoints/moonshot.js.map +1 -0
- package/dist/llm/providers/endpoints/newapi.d.ts +14 -0
- package/dist/llm/providers/endpoints/newapi.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/newapi.js +14 -0
- package/dist/llm/providers/endpoints/newapi.js.map +1 -0
- package/dist/llm/providers/endpoints/ollama.d.ts +12 -0
- package/dist/llm/providers/endpoints/ollama.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/ollama.js +63 -0
- package/dist/llm/providers/endpoints/ollama.js.map +1 -0
- package/dist/llm/providers/endpoints/openai.d.ts +12 -0
- package/dist/llm/providers/endpoints/openai.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/openai.js +67 -0
- package/dist/llm/providers/endpoints/openai.js.map +1 -0
- package/dist/llm/providers/endpoints/opencodeCodingPlan.d.ts +9 -0
- package/dist/llm/providers/endpoints/opencodeCodingPlan.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/opencodeCodingPlan.js +23 -0
- package/dist/llm/providers/endpoints/opencodeCodingPlan.js.map +1 -0
- package/dist/llm/providers/endpoints/openrouter.d.ts +15 -0
- package/dist/llm/providers/endpoints/openrouter.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/openrouter.js +74 -0
- package/dist/llm/providers/endpoints/openrouter.js.map +1 -0
- package/dist/llm/providers/endpoints/ppio.d.ts +15 -0
- package/dist/llm/providers/endpoints/ppio.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/ppio.js +73 -0
- package/dist/llm/providers/endpoints/ppio.js.map +1 -0
- package/dist/llm/providers/endpoints/qiniu.d.ts +10 -0
- package/dist/llm/providers/endpoints/qiniu.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/qiniu.js +24 -0
- package/dist/llm/providers/endpoints/qiniu.js.map +1 -0
- package/dist/llm/providers/endpoints/sensenova.d.ts +10 -0
- package/dist/llm/providers/endpoints/sensenova.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/sensenova.js +37 -0
- package/dist/llm/providers/endpoints/sensenova.js.map +1 -0
- package/dist/llm/providers/endpoints/siliconcloud.d.ts +14 -0
- package/dist/llm/providers/endpoints/siliconcloud.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/siliconcloud.js +114 -0
- package/dist/llm/providers/endpoints/siliconcloud.js.map +1 -0
- package/dist/llm/providers/endpoints/spark.d.ts +14 -0
- package/dist/llm/providers/endpoints/spark.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/spark.js +21 -0
- package/dist/llm/providers/endpoints/spark.js.map +1 -0
- package/dist/llm/providers/endpoints/stepfun.d.ts +10 -0
- package/dist/llm/providers/endpoints/stepfun.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/stepfun.js +27 -0
- package/dist/llm/providers/endpoints/stepfun.js.map +1 -0
- package/dist/llm/providers/endpoints/tencentcloud.d.ts +10 -0
- package/dist/llm/providers/endpoints/tencentcloud.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/tencentcloud.js +17 -0
- package/dist/llm/providers/endpoints/tencentcloud.js.map +1 -0
- package/dist/llm/providers/endpoints/volcengine.d.ts +10 -0
- package/dist/llm/providers/endpoints/volcengine.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/volcengine.js +44 -0
- package/dist/llm/providers/endpoints/volcengine.js.map +1 -0
- package/dist/llm/providers/endpoints/volcengineCodingPlan.d.ts +19 -0
- package/dist/llm/providers/endpoints/volcengineCodingPlan.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/volcengineCodingPlan.js +25 -0
- package/dist/llm/providers/endpoints/volcengineCodingPlan.js.map +1 -0
- package/dist/llm/providers/endpoints/wenxin.d.ts +10 -0
- package/dist/llm/providers/endpoints/wenxin.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/wenxin.js +98 -0
- package/dist/llm/providers/endpoints/wenxin.js.map +1 -0
- package/dist/llm/providers/endpoints/xai.d.ts +11 -0
- package/dist/llm/providers/endpoints/xai.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/xai.js +25 -0
- package/dist/llm/providers/endpoints/xai.js.map +1 -0
- package/dist/llm/providers/endpoints/xiaomimimo.d.ts +12 -0
- package/dist/llm/providers/endpoints/xiaomimimo.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/xiaomimimo.js +16 -0
- package/dist/llm/providers/endpoints/xiaomimimo.js.map +1 -0
- package/dist/llm/providers/endpoints/zeroone.d.ts +10 -0
- package/dist/llm/providers/endpoints/zeroone.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/zeroone.js +26 -0
- package/dist/llm/providers/endpoints/zeroone.js.map +1 -0
- package/dist/llm/providers/endpoints/zhipu.d.ts +12 -0
- package/dist/llm/providers/endpoints/zhipu.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/zhipu.js +51 -0
- package/dist/llm/providers/endpoints/zhipu.js.map +1 -0
- package/dist/llm/providers/index.d.ts +5 -0
- package/dist/llm/providers/index.d.ts.map +1 -0
- package/dist/llm/providers/index.js +67 -0
- package/dist/llm/providers/index.js.map +1 -0
- package/dist/llm/providers/lookup.d.ts +16 -0
- package/dist/llm/providers/lookup.d.ts.map +1 -0
- package/dist/llm/providers/lookup.js +68 -0
- package/dist/llm/providers/lookup.js.map +1 -0
- package/dist/llm/providers/probe.d.ts +11 -0
- package/dist/llm/providers/probe.d.ts.map +1 -0
- package/dist/llm/providers/probe.js +24 -0
- package/dist/llm/providers/probe.js.map +1 -0
- package/dist/llm/providers/types.d.ts +71 -0
- package/dist/llm/providers/types.d.ts.map +1 -0
- package/dist/llm/providers/types.js +9 -0
- package/dist/llm/providers/types.js.map +1 -0
- package/dist/llm/providers/verify.d.ts +27 -0
- package/dist/llm/providers/verify.d.ts.map +1 -0
- package/dist/llm/providers/verify.js +77 -0
- package/dist/llm/providers/verify.js.map +1 -0
- package/dist/llm/secrets.d.ts.map +1 -1
- package/dist/llm/secrets.js +27 -2
- package/dist/llm/secrets.js.map +1 -1
- package/dist/llm/service-presets.d.ts +13 -3
- package/dist/llm/service-presets.d.ts.map +1 -1
- package/dist/llm/service-presets.js +78 -42
- package/dist/llm/service-presets.js.map +1 -1
- package/dist/llm/service-resolver.d.ts +1 -1
- package/dist/llm/service-resolver.d.ts.map +1 -1
- package/dist/llm/service-resolver.js +13 -0
- package/dist/llm/service-resolver.js.map +1 -1
- package/dist/models/book-rules.d.ts +23 -1
- package/dist/models/book-rules.d.ts.map +1 -1
- package/dist/models/book-rules.js +54 -2
- package/dist/models/book-rules.js.map +1 -1
- package/dist/models/input-governance.d.ts +20 -244
- package/dist/models/input-governance.d.ts.map +1 -1
- package/dist/models/input-governance.js +7 -51
- package/dist/models/input-governance.js.map +1 -1
- package/dist/models/project.d.ts +29 -28
- package/dist/models/project.d.ts.map +1 -1
- package/dist/models/project.js +11 -9
- package/dist/models/project.js.map +1 -1
- package/dist/models/runtime-state.d.ts +120 -0
- package/dist/models/runtime-state.d.ts.map +1 -1
- package/dist/models/runtime-state.js +12 -0
- package/dist/models/runtime-state.js.map +1 -1
- package/dist/pipeline/agent.d.ts.map +1 -1
- package/dist/pipeline/agent.js +59 -10
- package/dist/pipeline/agent.js.map +1 -1
- package/dist/pipeline/chapter-review-cycle.d.ts +11 -4
- package/dist/pipeline/chapter-review-cycle.d.ts.map +1 -1
- package/dist/pipeline/chapter-review-cycle.js +156 -73
- package/dist/pipeline/chapter-review-cycle.js.map +1 -1
- package/dist/pipeline/chapter-truth-validation.d.ts +2 -1
- package/dist/pipeline/chapter-truth-validation.d.ts.map +1 -1
- package/dist/pipeline/chapter-truth-validation.js +1 -1
- package/dist/pipeline/chapter-truth-validation.js.map +1 -1
- package/dist/pipeline/persisted-governed-plan.d.ts +1 -0
- package/dist/pipeline/persisted-governed-plan.d.ts.map +1 -1
- package/dist/pipeline/persisted-governed-plan.js +181 -66
- package/dist/pipeline/persisted-governed-plan.js.map +1 -1
- package/dist/pipeline/runner.d.ts +20 -17
- package/dist/pipeline/runner.d.ts.map +1 -1
- package/dist/pipeline/runner.js +359 -84
- package/dist/pipeline/runner.js.map +1 -1
- package/dist/state/manager.d.ts.map +1 -1
- package/dist/state/manager.js +44 -4
- package/dist/state/manager.js.map +1 -1
- package/dist/state/memory-db.d.ts +6 -0
- package/dist/state/memory-db.d.ts.map +1 -1
- package/dist/state/memory-db.js.map +1 -1
- package/dist/state/runtime-state-store.d.ts.map +1 -1
- package/dist/state/runtime-state-store.js +4 -1
- package/dist/state/runtime-state-store.js.map +1 -1
- package/dist/state/state-projections.d.ts +3 -1
- package/dist/state/state-projections.d.ts.map +1 -1
- package/dist/state/state-projections.js +58 -15
- package/dist/state/state-projections.js.map +1 -1
- package/dist/utils/book-id.d.ts +4 -0
- package/dist/utils/book-id.d.ts.map +1 -0
- package/dist/utils/book-id.js +27 -0
- package/dist/utils/book-id.js.map +1 -0
- package/dist/utils/chapter-cadence.js +1 -0
- package/dist/utils/chapter-cadence.js.map +1 -1
- package/dist/utils/chapter-memo-parser.d.ts +19 -0
- package/dist/utils/chapter-memo-parser.d.ts.map +1 -0
- package/dist/utils/chapter-memo-parser.js +114 -0
- package/dist/utils/chapter-memo-parser.js.map +1 -0
- package/dist/utils/config-loader.d.ts +7 -11
- package/dist/utils/config-loader.d.ts.map +1 -1
- package/dist/utils/config-loader.js +13 -253
- package/dist/utils/config-loader.js.map +1 -1
- package/dist/utils/context-assembly.d.ts +24 -0
- package/dist/utils/context-assembly.d.ts.map +1 -0
- package/dist/utils/context-assembly.js +77 -0
- package/dist/utils/context-assembly.js.map +1 -0
- package/dist/utils/effective-llm-config.d.ts +34 -0
- package/dist/utils/effective-llm-config.d.ts.map +1 -0
- package/dist/utils/effective-llm-config.js +417 -0
- package/dist/utils/effective-llm-config.js.map +1 -0
- package/dist/utils/governed-working-set.js +1 -1
- package/dist/utils/governed-working-set.js.map +1 -1
- package/dist/utils/hook-ledger-validator.d.ts +82 -0
- package/dist/utils/hook-ledger-validator.d.ts.map +1 -0
- package/dist/utils/hook-ledger-validator.js +225 -0
- package/dist/utils/hook-ledger-validator.js.map +1 -0
- package/dist/utils/hook-lifecycle.d.ts +5 -0
- package/dist/utils/hook-lifecycle.d.ts.map +1 -1
- package/dist/utils/hook-lifecycle.js +32 -0
- package/dist/utils/hook-lifecycle.js.map +1 -1
- package/dist/utils/hook-policy.d.ts +0 -17
- package/dist/utils/hook-policy.d.ts.map +1 -1
- package/dist/utils/hook-policy.js +0 -30
- package/dist/utils/hook-policy.js.map +1 -1
- package/dist/utils/hook-promotion.d.ts +83 -0
- package/dist/utils/hook-promotion.d.ts.map +1 -0
- package/dist/utils/hook-promotion.js +241 -0
- package/dist/utils/hook-promotion.js.map +1 -0
- package/dist/utils/hook-stale-detection.d.ts +51 -0
- package/dist/utils/hook-stale-detection.d.ts.map +1 -0
- package/dist/utils/hook-stale-detection.js +125 -0
- package/dist/utils/hook-stale-detection.js.map +1 -0
- package/dist/utils/llm-endpoint-auth.d.ts +5 -0
- package/dist/utils/llm-endpoint-auth.d.ts.map +1 -0
- package/dist/utils/llm-endpoint-auth.js +36 -0
- package/dist/utils/llm-endpoint-auth.js.map +1 -0
- package/dist/utils/llm-env.d.ts +14 -0
- package/dist/utils/llm-env.d.ts.map +1 -0
- package/dist/utils/llm-env.js +53 -0
- package/dist/utils/llm-env.js.map +1 -0
- package/dist/utils/memory-retrieval.d.ts +23 -1
- package/dist/utils/memory-retrieval.d.ts.map +1 -1
- package/dist/utils/memory-retrieval.js +45 -2
- package/dist/utils/memory-retrieval.js.map +1 -1
- package/dist/utils/narrative-control.d.ts +16 -0
- package/dist/utils/narrative-control.d.ts.map +1 -0
- package/dist/utils/narrative-control.js +131 -0
- package/dist/utils/narrative-control.js.map +1 -0
- package/dist/utils/path-safety.d.ts +2 -0
- package/dist/utils/path-safety.d.ts.map +1 -0
- package/dist/utils/path-safety.js +11 -0
- package/dist/utils/path-safety.js.map +1 -0
- package/dist/utils/planning-materials.d.ts +35 -0
- package/dist/utils/planning-materials.d.ts.map +1 -0
- package/dist/utils/planning-materials.js +123 -0
- package/dist/utils/planning-materials.js.map +1 -0
- package/dist/utils/proxy-fetch.d.ts +9 -0
- package/dist/utils/proxy-fetch.d.ts.map +1 -0
- package/dist/utils/proxy-fetch.js +31 -0
- package/dist/utils/proxy-fetch.js.map +1 -0
- package/dist/utils/runtime-writer.d.ts +14 -0
- package/dist/utils/runtime-writer.d.ts.map +1 -0
- package/dist/utils/runtime-writer.js +21 -0
- package/dist/utils/runtime-writer.js.map +1 -0
- package/dist/utils/spot-fix-patches.d.ts +7 -0
- package/dist/utils/spot-fix-patches.d.ts.map +1 -1
- package/dist/utils/spot-fix-patches.js +109 -36
- package/dist/utils/spot-fix-patches.js.map +1 -1
- package/dist/utils/story-markdown.d.ts.map +1 -1
- package/dist/utils/story-markdown.js +104 -6
- package/dist/utils/story-markdown.js.map +1 -1
- package/dist/utils/writing-methodology.d.ts +10 -0
- package/dist/utils/writing-methodology.d.ts.map +1 -0
- package/dist/utils/writing-methodology.js +163 -0
- package/dist/utils/writing-methodology.js.map +1 -0
- package/package.json +3 -2
- package/dist/utils/hook-agenda.d.ts +0 -21
- package/dist/utils/hook-agenda.d.ts.map +0 -1
- package/dist/utils/hook-agenda.js +0 -95
- package/dist/utils/hook-agenda.js.map +0 -1
package/dist/agents/architect.js
CHANGED
|
@@ -3,7 +3,13 @@ import { readGenreProfile } from "./rules-reader.js";
|
|
|
3
3
|
import { writeFile, mkdir, rm } from "node:fs/promises";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { renderHookSnapshot } from "../utils/memory-retrieval.js";
|
|
6
|
-
|
|
6
|
+
import { shouldPromoteHook, } from "../utils/hook-promotion.js";
|
|
7
|
+
/**
|
|
8
|
+
* Split a markdown string into its leading YAML frontmatter block and the
|
|
9
|
+
* remaining body. Returns `frontmatter: null` when no frontmatter is present.
|
|
10
|
+
* Only recognises a frontmatter block that starts on the FIRST non-empty
|
|
11
|
+
* line — embedded `---` sections in prose are left alone.
|
|
12
|
+
*/
|
|
7
13
|
function extractYamlFrontmatter(raw) {
|
|
8
14
|
if (!raw)
|
|
9
15
|
return { frontmatter: null, body: "" };
|
|
@@ -32,19 +38,13 @@ export class ArchitectAgent extends BaseAgent {
|
|
|
32
38
|
? this.buildRevisePrompt(options.reviseFrom)
|
|
33
39
|
: "";
|
|
34
40
|
const numericalBlock = gp.numericalSystem
|
|
35
|
-
?
|
|
36
|
-
- 在 book_rules 中定义 numericalSystemOverrides(hardCap、resourceTypes)`
|
|
41
|
+
? "- 有明确的数值/资源体系可追踪\n- 在 book_rules 中定义 numericalSystemOverrides(hardCap、resourceTypes)"
|
|
37
42
|
: "- 本题材无数值系统,不需要资源账本";
|
|
38
|
-
const powerBlock = gp.powerScaling
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const eraBlock = gp.eraResearch
|
|
42
|
-
? "- 需要年代考据支撑(在 book_rules 中设置 eraConstraints)"
|
|
43
|
-
: "";
|
|
44
|
-
const basePrompt = resolvedLanguage === "en"
|
|
43
|
+
const powerBlock = gp.powerScaling ? "- 有明确的战力等级体系" : "";
|
|
44
|
+
const eraBlock = gp.eraResearch ? "- 需要年代考据支撑(在 book_rules 中设置 eraConstraints)" : "";
|
|
45
|
+
const systemPrompt = resolvedLanguage === "en"
|
|
45
46
|
? this.buildEnglishFoundationPrompt(book, gp, genreBody, contextBlock, reviewFeedbackBlock, numericalBlock, powerBlock, eraBlock)
|
|
46
47
|
: this.buildChineseFoundationPrompt(book, gp, genreBody, contextBlock, reviewFeedbackBlock, numericalBlock, powerBlock, eraBlock);
|
|
47
|
-
const systemPrompt = revisePrompt + basePrompt;
|
|
48
48
|
const langPrefix = resolvedLanguage === "en"
|
|
49
49
|
? `【LANGUAGE OVERRIDE】ALL output (story_frame, volume_map, roles, book_rules, pending_hooks) MUST be written in English. Character names, place names, and all prose must be in English. The === SECTION: === tags remain unchanged. Do NOT emit rhythm_principles or current_state sections — rhythm principles live inside the last paragraph of volume_map; environment/era anchors (when relevant) are woven into story_frame's world-tonal-ground paragraph.\n\n`
|
|
50
50
|
: "";
|
|
@@ -52,52 +52,46 @@ export class ArchitectAgent extends BaseAgent {
|
|
|
52
52
|
? `Generate the complete foundation for a ${gp.name} novel titled "${book.title}". Write everything in English.`
|
|
53
53
|
: `请为标题为"${book.title}"的${gp.name}小说生成完整基础设定。`;
|
|
54
54
|
const response = await this.chat([
|
|
55
|
-
{ role: "system", content: langPrefix + systemPrompt },
|
|
55
|
+
{ role: "system", content: langPrefix + systemPrompt + revisePrompt },
|
|
56
56
|
{ role: "user", content: userMessage },
|
|
57
|
-
], { temperature: 0.8
|
|
58
|
-
return this.parseSections(response.content);
|
|
57
|
+
], { temperature: 0.8 });
|
|
58
|
+
return this.parseSections(response.content, resolvedLanguage);
|
|
59
59
|
}
|
|
60
60
|
buildRevisePrompt(reviseFrom) {
|
|
61
|
-
return
|
|
61
|
+
return `\n\n## 既有架构稿修订模式
|
|
62
|
+
你在把一本已有书的架构稿从条目式升级为当前的段落式架构稿 + 一人一卡角色目录;如果它已经是 Phase 5 结构,则按用户反馈二次重写。
|
|
62
63
|
|
|
63
|
-
|
|
64
|
+
原书信息(这是权威内容,必须完整保留其中的世界观、角色、主线、伏笔和语气):
|
|
64
65
|
|
|
65
|
-
【story_bible
|
|
66
|
+
【story_bible / story_frame 全文】
|
|
66
67
|
${reviseFrom.storyBible || "(无)"}
|
|
67
68
|
|
|
68
|
-
【volume_outline
|
|
69
|
+
【volume_outline / volume_map 全文】
|
|
69
70
|
${reviseFrom.volumeOutline || "(无)"}
|
|
70
71
|
|
|
71
|
-
【book_rules
|
|
72
|
+
【book_rules 全文】
|
|
72
73
|
${reviseFrom.bookRules || "(无)"}
|
|
73
74
|
|
|
74
|
-
【character_matrix
|
|
75
|
+
【character_matrix / roles 全文】
|
|
75
76
|
${reviseFrom.characterMatrix || "(无)"}
|
|
76
77
|
|
|
77
78
|
你的任务:
|
|
78
|
-
1.
|
|
79
|
-
2.
|
|
80
|
-
3.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
- 如果原内容里有 bullet 点,把它们连成段落;不要只是把 bullet 转成句号分隔
|
|
85
|
-
- 主次角色的判断依据:character_matrix 里已标注的如果有,沿用;没有的话,把在 volume_outline 里出现频次高、或者承担主线冲突的列为主要,其余为次要
|
|
86
|
-
- 基调 / 语气不要改变
|
|
87
|
-
- 如果原内容里留有空白或未展开项,保留为空,不要主动补全
|
|
79
|
+
1. 把现有内容重新组织成当前 5 段 SECTION:story_frame / volume_map / roles / book_rules / pending_hooks
|
|
80
|
+
2. story_frame 使用段落式世界观与核心冲突,不要退回条目表格
|
|
81
|
+
3. volume_map 使用段落式卷/章级方向,并把节奏原则放进末段
|
|
82
|
+
4. roles 必须按一人一卡输出,主要/次要角色判断沿用原内容,缺失才按主线重要性推断
|
|
83
|
+
5. pending_hooks 必须保留原有未回收伏笔,不要因为重写架构稿而清空
|
|
84
|
+
6. 不要改动已写章节的运行时事实,不要重置 current_state / pending_hooks 之外的运行时日志
|
|
88
85
|
|
|
89
86
|
用户额外要求:
|
|
90
87
|
${reviseFrom.userFeedback || "(无)"}
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
88
|
`;
|
|
95
89
|
}
|
|
96
90
|
// -------------------------------------------------------------------------
|
|
97
|
-
//
|
|
91
|
+
// Prose prompt — zh (primary)
|
|
98
92
|
// -------------------------------------------------------------------------
|
|
99
93
|
buildChineseFoundationPrompt(book, gp, genreBody, contextBlock, reviewFeedbackBlock, numericalBlock, powerBlock, eraBlock) {
|
|
100
|
-
return
|
|
94
|
+
return `你是这本书的总架构师。你的唯一输出是**散文密度的基础设定**——不是表格、不是 schema、不是条目化 bullet。v6 以后这本书的"灵气"从哪里来?从你这里来。你的散文密度决定了后面 planner 能不能读出"稀疏 memo",writer 能不能写出活人,reviewer 能不能校准硬伤。${contextBlock}${reviewFeedbackBlock}
|
|
101
95
|
|
|
102
96
|
## 书籍元信息
|
|
103
97
|
- 平台:${book.platform}
|
|
@@ -117,46 +111,64 @@ ${eraBlock}
|
|
|
117
111
|
## 输出结构(5 个 SECTION,严格按 === SECTION: === 分块,不要漏任何一块)
|
|
118
112
|
|
|
119
113
|
## 去重铁律(必读)
|
|
120
|
-
|
|
114
|
+
禁止在多段里重复同一事实。主角弧线只写在 roles;世界铁律只写在 story_frame.世界观底色;节奏原则只写在 volume_map 最后一段;角色当前现状只写在 roles.当前现状;初始钩子只写在 pending_hooks(startChapter=0 行)。**如果本书是年代文/历史同人/都市重生等需要年份、季节、重大历史事件作为锚点的题材**,把环境/时代锚自然织进 story_frame.世界观底色("1985 年 7 月,非典刚过"这类);**修仙/玄幻/系统等没有真实年份的题材直接省略**,不要硬凑。如果一个段落写了另一段的内容,删掉。
|
|
121
115
|
|
|
122
116
|
## 预算(超预算必删)
|
|
123
|
-
- story_frame ≤ 3000
|
|
124
|
-
- volume_map ≤ 5000
|
|
125
|
-
- roles 总 ≤ 8000
|
|
126
|
-
- book_rules ≤ 500
|
|
127
|
-
- pending_hooks ≤ 2000
|
|
117
|
+
- story_frame ≤ 3000 chars
|
|
118
|
+
- volume_map ≤ 5000 chars
|
|
119
|
+
- roles 总 ≤ 8000 chars
|
|
120
|
+
- book_rules ≤ 500 chars(仅 YAML)
|
|
121
|
+
- pending_hooks ≤ 2000 chars
|
|
128
122
|
|
|
129
123
|
=== SECTION: story_frame ===
|
|
130
124
|
|
|
131
|
-
|
|
125
|
+
这是散文骨架。**4 段**,每段约 600-900 字,不要写表格,不要写 bullet list,写成能被人读下去的段落。段落标题用 \`## \` 开头,段落内部是正经段落。**主角弧线不写在本 section;它的权威来源是 roles/主要角色/<主角>.md。** 本段只需一句指针:"本书主角是 X,完整弧线详见 roles/主要角色/X.md"。
|
|
132
126
|
|
|
133
127
|
### 段 1:主题与基调
|
|
134
|
-
写这本书到底讲的是什么——不是"讲主角如何从弱到强"这种空话,而是具体的命题("一个被时代按在泥里的人,如何选择不被改写"、"当所有人都在撒谎时,坚持记录真相要付出什么代价")。主题下面跟着基调——温情冷冽悲壮肃杀,哪一种?为什么是这种而不是另一种?结尾用一句话指向主角并引向 roles(例:"
|
|
128
|
+
写这本书到底讲的是什么——不是"讲主角如何从弱到强"这种空话,而是具体的命题("一个被时代按在泥里的人,如何选择不被改写"、"当所有人都在撒谎时,坚持记录真相要付出什么代价")。主题下面跟着基调——温情冷冽悲壮肃杀,哪一种?为什么是这种而不是另一种?结尾用一句话指向主角并引向 roles(例:"本书主角是林辞,完整弧线详见 roles/主要角色/林辞.md")。
|
|
135
129
|
|
|
136
|
-
### 段 2
|
|
130
|
+
### 段 2:核心冲突、对手定性、前台/后台双层故事
|
|
137
131
|
这本书的主要矛盾是什么?不是"正邪对抗",而是"因为 A 相信 X、B 相信 Y,所以他们一定会在某件事上对撞"。主要对手是谁(至少 2 个:一个显性对手 + 一个结构性对手/体制),他们的动机从哪里长出来。对手不是工具,对手有自己的逻辑。
|
|
138
132
|
|
|
133
|
+
**本段必须显式写出"前台故事 / 后台故事"两条线**(番茄老师弈青锋的"台前台后"分层法):
|
|
134
|
+
- **前台故事**:读者每章看得到的表层冲突(查案、打怪、升级、谈恋爱、搞事业等),每个卷/arc 有独立的显性目标和完结点
|
|
135
|
+
- **后台故事**:贯穿全书的暗线——藏在所有前台事件背后的那台"机器"(幕后黑手、阴谋、身世秘密、体制压迫、命运诅咒等),读者只能通过碎片拼出来,大结局时才整体兑现
|
|
136
|
+
|
|
137
|
+
两条线必须有因果关联,不能是平行宇宙——每一段前台冲突的背后都应该能追溯到后台故事的某个齿轮在转。**如果只有前台没有后台,故事会散成"独立事件集",没有往前拉的引力;如果只有后台没有前台,故事会憋闷、看不到爽感**。本段用散文明确写出:本书前台是什么、后台是什么、两者怎么咬合。
|
|
138
|
+
|
|
139
139
|
### 段 3:世界观底色(铁律 + 质感 + 本书专属规则)
|
|
140
|
-
这个世界的运行规则是什么?3-5
|
|
140
|
+
这个世界的运行规则是什么?3-5 条**不可违反的铁律**——以 prose 写出,不要 bullet。这个世界的质感是什么——湿的还是干的、快的还是慢的、噪的还是静的?给 writer 一个明确的感官锚(这是原来 particle_ledger 承载的基调部分)。**这一段同时承担原先 book_rules 正文里写的"叙事视角 / 本书专属规则 / 核心冲突驱动"等 prose 内容**——全部合并到这里写一次就够,不要再去 book_rules 重复。
|
|
141
141
|
|
|
142
|
-
### 段 4:终局方向
|
|
142
|
+
### 段 4:终局方向 + 全书 Objective(OKR 大纲的根)
|
|
143
143
|
这本书最后一章大概是什么感觉——不是"主角登顶"、"大结局"这种套话,而是**最后一个镜头**大致长什么样。主角最后在哪、做什么、身边有谁、心里想什么。这是给全书所有后面的规划一个远方靶子。
|
|
144
144
|
|
|
145
|
+
**本段末尾必须明确写出全书 Objective 一句话**(番茄老师弈青锋的 OKR 递归大纲法):这本书讲完时,主角必须达成一个**可验证的终局状态**(例:"从一个杂役修士成为宗门长老并公开父辈冤案的真相"、"从黑户打工妹成为掌控三家皮草公司的老板娘并亲手送前夫进监狱")。不要写"变强"、"复仇"这类抽象词,要写**一个能被外部观察者判定"达成 / 未达成"的具体状态**。这个 Objective 是全书 OKR 递归大纲的根——下面 volume_map 的每一卷会分解出这个 O 对应的 Key Results。
|
|
146
|
+
|
|
145
147
|
=== SECTION: volume_map ===
|
|
146
148
|
|
|
147
|
-
|
|
149
|
+
这是分卷散文地图,**5 段主体 + 1 段节奏原则尾段**。**关键要求:只写到卷级 prose**——写清楚每卷的主题、情绪曲线、卷间钩子、角色阶段目标、卷尾不可逆事件。**禁止指定具体章号任务**(不要写"第 17 章让他回家"这种章级布局)。章级规划是 Phase 3 planner 的职责,架构师只搭骨架、不编章目。
|
|
148
150
|
|
|
149
151
|
### 段 1:各卷主题与情绪曲线
|
|
150
152
|
有几卷?每卷的主题一句话,每卷的情绪曲线一段(哪里压、哪里爽、哪里冷、哪里暖)。不要机械的"第一卷打小怪第二卷打大怪",写情绪的流动。
|
|
151
153
|
|
|
152
|
-
### 段 2
|
|
153
|
-
第 1 卷埋什么钩子、在哪一卷回收;第 2
|
|
154
|
+
### 段 2:卷间钩子与回收承诺(前台/后台双层都要覆盖)
|
|
155
|
+
第 1 卷埋什么钩子、在哪一卷回收;第 2 卷埋什么、在哪一卷回收。散文写,不要表格。**只写卷级**(如"第 1 卷埋的身世之谜在第 3 卷回收"),不要写具体章号。
|
|
156
|
+
|
|
157
|
+
**钩子必须覆盖前台 + 后台两层**(对应 story_frame.段 2 建立的双层故事):
|
|
158
|
+
- 前台钩子:当前卷内 arc 层面的短期钩子(查案谜题、对手身份、资源争夺等),预期在 1-2 卷内回收
|
|
159
|
+
- 后台钩子:贯穿全书的主线钩子(幕后真相、身世、体制秘密等),预期在终卷前后回收,核心的 3-7 条属于 core_hook=true
|
|
160
|
+
|
|
161
|
+
**如果本段只写前台钩子、没有后台钩子暗桩,说明你漏了整本书的引力轴,必须补上。**
|
|
154
162
|
|
|
155
|
-
### 段 3
|
|
156
|
-
|
|
163
|
+
### 段 3:各卷 OKR(Objective + Key Results)
|
|
164
|
+
用 OKR 递归大纲法分解全书 Objective(story_frame.段 4 末尾定的根 O):每一卷都必须明确给出:
|
|
165
|
+
- **Objective(卷级目标)**:本卷结束时主角必须达成的**可验证状态**,一句话,与全书 Objective 逻辑递进相连(例:全书 O = "成为宗门长老并公开冤案";卷 1 O = "从杂役转入正式弟子籍并拿到第一份能指向真相的线索")
|
|
166
|
+
- **Key Results(3 条,可量化/可观察)**:支撑该 O 达成的三个关键子成果,每条必须是外部观察者能判定是否完成的状态变更(例 KR1 = "拿下药园执事位置"、KR2 = "与灵安峰结成稳定盟约"、KR3 = "发现父辈案卷的第一半页残片")。不要写"变强"、"成长"这类模糊 KR
|
|
167
|
+
|
|
168
|
+
次要角色的阶段性变化也要点到(师父在第 2 卷会死、对手在第 3 卷会黑化等),写在 KR 条目下作为附注。写阶段性,不写完整弧线(完整弧线在 roles)。**每一卷 3 个 KR 是下游 planner 分解章节任务的直接依据——planner 拿到一卷的 3 个 KR 后,按每 3-5 章推进一个 KR 的节奏排章。**
|
|
157
169
|
|
|
158
170
|
### 段 4:卷尾必须发生的改变
|
|
159
|
-
|
|
171
|
+
每一卷最后一章必须发生什么不可逆的事——权力结构改变、关系破裂、秘密暴露、主角身份重定位。写散文,一卷一段。**只写"必须发生什么",不指定是第几章**。
|
|
160
172
|
|
|
161
173
|
### 段 5:节奏原则(具体化 + 通用)
|
|
162
174
|
**这是节奏原则的唯一归宿,不再有独立 rhythm_principles section。** 本段输出 6 条节奏原则。**至少 3 条必须具体化到本书**(例:"前 30 章每 5 章一个小爽点"),其余可保留通用原则(例:"拒绝机械降神"、"高潮前 3-5 章埋伏笔")。具体化 + 通用混合是合法的。反面例子:"节奏要张弛有度"(废话)。正面例子:"前 30 章每 5 章一个小爽点,且小爽点必须落在章末 300 字内"。6 条各写 2-3 句,覆盖(顺序不强制、可替换同权重议题):
|
|
@@ -167,20 +179,15 @@ ${eraBlock}
|
|
|
167
179
|
5. 爽点节奏——爽点间距多少章一个?什么类型为主?(具体化优先)
|
|
168
180
|
6. 情感节点递进——情感关系每多少章必须有一次实质推进?
|
|
169
181
|
|
|
170
|
-
### 黄金三章法则(前三章必须遵循)
|
|
171
|
-
- 第1章:抛出核心冲突(主角立即面临困境/危机/选择),禁止大段背景灌输
|
|
172
|
-
- 第2章:展示金手指/核心能力(主角如何应对第1章的困境),让读者看到爽点预期
|
|
173
|
-
- 第3章:明确短期目标(主角确立第一个具体可达成的目标),给读者追读理由
|
|
174
|
-
|
|
175
182
|
=== SECTION: roles ===
|
|
176
183
|
|
|
177
|
-
|
|
184
|
+
一人一卡 prose。**主角卡是本书角色弧线的唯一权威来源**——story_frame 不再写主角弧线,writer/planner 都从这里读。用以下格式分隔:
|
|
178
185
|
|
|
179
186
|
---ROLE---
|
|
180
187
|
tier: major
|
|
181
188
|
name: <角色名>
|
|
182
189
|
---CONTENT---
|
|
183
|
-
|
|
190
|
+
(这里写散文角色卡,下面的小标题必须全部出现,每段至少 3 行正经散文,不要写表格)
|
|
184
191
|
|
|
185
192
|
## 核心标签
|
|
186
193
|
(3-5 个关键词 + 一句话为什么是这些词)
|
|
@@ -189,10 +196,10 @@ name: <角色名>
|
|
|
189
196
|
(1-2 个与核心标签反差的具体细节——"冷酷杀手但会给流浪猫留鱼骨"。反差细节是人物立体化的公式,必须有。)
|
|
190
197
|
|
|
191
198
|
## 人物小传(过往经历)
|
|
192
|
-
|
|
199
|
+
(一段散文,说这个人怎么变成现在这样。童年/重大事件/塑造性格的那件事。只写关键过往,简版。)
|
|
193
200
|
|
|
194
|
-
##
|
|
195
|
-
**只有主角必须写本段;其他 major
|
|
201
|
+
## 主角弧线(起点 → 终点 → 代价)
|
|
202
|
+
**只有主角必须写本段;其他 major 角色如果弧线分量重也可以写,否则略过。**主角从哪里出发(身份、处境、核心缺陷、一开始最想要什么),到哪里落脚(最终变成什么样的人、拿到/失去什么),为了这个落脚他付出了什么不可逆的代价(关系、身体、信念、某段过去)。不要只写"变强"这种平面变化,要写**内在的位移**。本段是之前 story_frame.段 2 迁移过来的权威位置,写足写实。
|
|
196
203
|
|
|
197
204
|
## 当前现状(第 0 章初始状态)
|
|
198
205
|
(第 0 章时他在哪、做什么、处境如何、最近最烦心的事。**只写角色个人处境**——初始钩子写在 pending_hooks 的 startChapter=0 行;环境/时代锚(如果是需要年份的题材)织进 story_frame.世界观底色。不再有独立的 current_state section。)
|
|
@@ -203,7 +210,7 @@ name: <角色名>
|
|
|
203
210
|
## 内在驱动
|
|
204
211
|
(他想要什么、为什么想要、愿意付出什么代价。)
|
|
205
212
|
|
|
206
|
-
##
|
|
213
|
+
## 成长弧光
|
|
207
214
|
(他在这本书里会经历什么内在位移——变好变坏变复杂,落在哪里。非主角可短可长。)
|
|
208
215
|
|
|
209
216
|
---ROLE---
|
|
@@ -224,7 +231,7 @@ name: <次要角色名>
|
|
|
224
231
|
|
|
225
232
|
=== SECTION: book_rules ===
|
|
226
233
|
|
|
227
|
-
**只输出 YAML frontmatter
|
|
234
|
+
**只输出 YAML frontmatter 一块——零散文。** 所有的"叙事视角 / 本书专属规则 / 核心冲突驱动"等散文已经合并到 story_frame.世界观底色,不要在这里重复写。
|
|
228
235
|
\`\`\`
|
|
229
236
|
---
|
|
230
237
|
version: "1.0"
|
|
@@ -249,29 +256,34 @@ enableFullCastTracking: false
|
|
|
249
256
|
|
|
250
257
|
=== SECTION: pending_hooks ===
|
|
251
258
|
|
|
252
|
-
初始伏笔池(Markdown
|
|
253
|
-
| hook_id | 起始章节 | 类型 | 状态 | 最近推进 | 预期回收 | 回收节奏 | 备注 |
|
|
259
|
+
初始伏笔池(Markdown表格),Phase 7 扩展列:
|
|
260
|
+
| hook_id | 起始章节 | 类型 | 状态 | 最近推进 | 预期回收 | 回收节奏 | 上游依赖 | 回收卷 | 核心 | 半衰期 | 备注 |
|
|
254
261
|
|
|
255
262
|
伏笔表规则:
|
|
256
263
|
- 第5列必须是纯数字章节号,不能写自然语言描述
|
|
257
264
|
- 建书阶段所有伏笔都还没正式推进,所以第5列统一填 0
|
|
258
265
|
- 第7列必须填写:立即 / 近期 / 中程 / 慢烧 / 终局 之一
|
|
266
|
+
- 第8列「上游依赖」:列出必须在本伏笔之前种下/回收的上游 hook_id,格式如 [H003, H007];若无依赖填「无」
|
|
267
|
+
- 第9列「回收卷」:用自然语言写该伏笔计划在哪一卷哪一段回收(例:"第2卷中段"、"终卷终章前")。不强制解析为章号
|
|
268
|
+
- 第10列「核心」:是否主线承重伏笔 true / false。主线承重伏笔一本书最多 3-7 条(主谜团、身世、核心承诺),其余次要伏笔填 false
|
|
269
|
+
- 第11列「半衰期」:可选,整数章数。若不填自动按回收节奏推导(立即/近期 = 10、中程 = 30、慢烧/终局 = 80)
|
|
259
270
|
- 初始线索放备注列,不放第5列
|
|
260
|
-
- **初始世界状态 / 初始敌我关系** 如果有关键信息(例如"主角身上带着父亲的笔记本"、"体制已经开始监视码头"),可以作为 startChapter=0 的种子行录入,备注列说明其"初始状态"
|
|
271
|
+
- **初始世界状态 / 初始敌我关系** 如果有关键信息(例如"主角身上带着父亲的笔记本"、"体制已经开始监视码头"),可以作为 startChapter=0 的种子行录入,备注列说明其"初始状态"属性。
|
|
261
272
|
|
|
262
273
|
## 最后强调
|
|
263
274
|
- 符合${book.platform}平台口味、${gp.name}题材特征
|
|
264
275
|
- 主角人设鲜明、行为边界清晰
|
|
265
276
|
- 伏笔前后呼应、配角有独立动机不是工具人
|
|
266
|
-
- **story_frame / volume_map / roles
|
|
267
|
-
- **book_rules 只留 YAML
|
|
268
|
-
- **不要输出 rhythm_principles 或 current_state 独立 section**——节奏原则合并进 volume_map 尾段;角色初始状态写在 roles.当前现状,初始钩子写在 pending_hooks(startChapter=0 行),环境/时代锚(仅历史/年代/都市重生等需要年份的题材)织进 story_frame
|
|
277
|
+
- **story_frame / volume_map / roles 必须是散文密度,不要退化成 bullet**
|
|
278
|
+
- **book_rules 只留 YAML,不要写散文**
|
|
279
|
+
- **不要输出 rhythm_principles 或 current_state 独立 section**——节奏原则合并进 volume_map 尾段;角色初始状态写在 roles.当前现状,初始钩子写在 pending_hooks(startChapter=0 行),环境/时代锚(仅历史/年代/都市重生等需要年份的题材)织进 story_frame.世界观底色,不要硬凑
|
|
280
|
+
- **pending_hooks 表必须包含 Phase 7 扩展列——depends_on 标出因果链、pays_off_in_arc 锁定回收大致位置、core_hook 标记主线承重伏笔(3-7 条)、half_life 仅给重点伏笔设置**
|
|
281
|
+
|
|
282
|
+
## 硬性完结检查(生成前读一遍)
|
|
283
|
+
必须依次输出全部 **5 个 SECTION 块**:story_frame → volume_map → roles → book_rules → pending_hooks,不允许因为 story_frame 或 volume_map 写长了就不写后 3 段。哪怕 roles 只列 3 个角色、book_rules 只有 YAML 小块、pending_hooks 只有 3 行,也要完整输出。只有写完 pending_hooks 最后一行才算交付。`;
|
|
269
284
|
}
|
|
270
|
-
// -------------------------------------------------------------------------
|
|
271
|
-
// Phase 5 段落式 prompt — en
|
|
272
|
-
// -------------------------------------------------------------------------
|
|
273
285
|
buildEnglishFoundationPrompt(book, gp, genreBody, contextBlock, reviewFeedbackBlock, numericalBlock, powerBlock, eraBlock) {
|
|
274
|
-
return `You are the architect of this book. Your only job is to produce **prose-density foundation design** — not tables, not schema, not bullet lists. The book's aura comes from your prose density:
|
|
286
|
+
return `You are the architect of this book. Your only job is to produce **prose-density foundation design** — not tables, not schema, not bullet lists. The book's aura comes from your prose density: Phase 3 planner reads sparse memos out of your volume_map only if it was written to chapter-level prose; the writer only produces living characters because your role sheets carry contrast details; the reviewer only catches hard errors because your story_frame set the tonal anchors.${contextBlock}${reviewFeedbackBlock}
|
|
275
287
|
|
|
276
288
|
## Book metadata
|
|
277
289
|
- Platform: ${book.platform}
|
|
@@ -291,7 +303,7 @@ ${eraBlock}
|
|
|
291
303
|
## Output contract (5 === SECTION: === blocks)
|
|
292
304
|
|
|
293
305
|
## Deduplication rule (MANDATORY)
|
|
294
|
-
Do not duplicate the same fact across sections. The protagonist's
|
|
306
|
+
Do not duplicate the same fact across sections. The protagonist's arc lives only in roles; world hard-rules live only in story_frame; rhythm principles live only in the last paragraph of volume_map; character initial status lives only in roles.Current_State; initial hooks live only in pending_hooks (start_chapter=0 rows). **When the book is period fiction / historical fanfic / urban reincarnation** — anything pinned to a real year, season, or historic marker — weave the environment/era anchor into story_frame's world-tonal-ground paragraph (e.g. "July 1985, just after the SARS wave"). **For cultivation / high-fantasy / system genres that have no real-world year, skip it entirely** — do not fabricate an era anchor. If a section repeats content that belongs elsewhere, delete it.
|
|
295
307
|
|
|
296
308
|
## Output budget (over-budget means cut)
|
|
297
309
|
- story_frame ≤ 3000 chars
|
|
@@ -302,75 +314,88 @@ Do not duplicate the same fact across sections. The protagonist's full character
|
|
|
302
314
|
|
|
303
315
|
=== SECTION: story_frame ===
|
|
304
316
|
|
|
305
|
-
Four prose sections, ~600-900 chars each. No tables. No bullet lists. Real paragraphs. **Do NOT write the protagonist's full
|
|
317
|
+
Four prose sections, ~600-900 chars each. No tables. No bullet lists. Real paragraphs. **Do NOT write the protagonist's full arc here** — that is owned by roles/主要角色/<protagonist>.md. Use a single-line pointer inside this block (e.g. "The protagonist is X; full arc lives in roles/主要角色/X.md").
|
|
306
318
|
|
|
307
319
|
## 01_Theme_and_Tonal_Ground
|
|
308
320
|
What is this book actually about — not "hero grows from weak to strong" (empty), but a concrete proposition. Then the tonal ground: warm / cold / fierce / severe — which, and why this and not another. End with a one-line pointer to the protagonist role file.
|
|
309
321
|
|
|
310
|
-
##
|
|
322
|
+
## 02_Core_Conflict_and_Foreground_Background_Story_Layers
|
|
311
323
|
The book's main tension — not "good vs evil" but "because A believes X and B believes Y, they will inevitably collide on Z". At least two opponents: one visible, one structural/systemic. Opponents have their own logic.
|
|
312
324
|
|
|
325
|
+
**This section must explicitly write out the foreground story / background story layers**:
|
|
326
|
+
- **Foreground story**: the surface conflict the reader sees every chapter (cases, combat, leveling up, romance, business moves). Each volume / arc has its own visible goal and closure point.
|
|
327
|
+
- **Background story**: the hidden machine running through the whole book — the puppet master, conspiracy, origin secret, systemic oppression, fated curse. The reader assembles it from fragments; full payoff lands near the finale.
|
|
328
|
+
|
|
329
|
+
The two layers must be causally linked, not parallel universes — every foreground conflict should trace back to some gear of the background machine turning. **Foreground-only story collapses into a set of disconnected episodes with no forward pull; background-only story is suffocating and never delivers. Write both in prose here, and name how they interlock.**
|
|
330
|
+
|
|
313
331
|
## 03_World_Tonal_Ground (hard rules + sensory tone + book-specific rules)
|
|
314
|
-
The world's operating rules. 3-5 unbreakable laws written as prose, not bullets. Sensory texture: wet or dry, fast or slow, noisy or quiet — give the writer an anchor. **This paragraph also absorbs the narrative prose that used to live in book_rules
|
|
332
|
+
The world's operating rules. 3-5 unbreakable laws written as prose, not bullets. Sensory texture: wet or dry, fast or slow, noisy or quiet — give the writer an anchor. **This paragraph also absorbs the narrative prose that used to live in book_rules (narrative perspective, core conflict driver, book-specific rules).** Write them all here once. Do not repeat them in book_rules.
|
|
315
333
|
|
|
316
|
-
##
|
|
334
|
+
## 04_Endgame_Direction_and_Book_Objective
|
|
317
335
|
What the last chapter roughly feels like. The final shot: where, doing what, around whom, thinking what. A distant target for every planner call downstream.
|
|
318
336
|
|
|
337
|
+
**End this paragraph with a one-sentence Book Objective** (the root of the recursive OKR outline): when this book is done, the protagonist must reach a **verifiable end-state** (e.g., "rise from errand disciple to sect elder and publicly vindicate the parental case", "go from undocumented migrant worker to running three fur-trade companies and personally putting the ex-husband in prison"). Do NOT use vague words like "grow stronger" or "take revenge" — write a concrete state an outside observer can check "achieved / not achieved". This Book Objective is the root of the full-book OKR outline; volume_map will decompose it per volume below.
|
|
338
|
+
|
|
319
339
|
=== SECTION: volume_map ===
|
|
320
340
|
|
|
321
|
-
Prose volume map, **5 sections + 1 closing rhythm paragraph**. **Critical requirement: stay at volume-level prose only
|
|
341
|
+
Prose volume map, **5 sections + 1 closing rhythm paragraph**. **Critical requirement: stay at volume-level prose only** — specify each volume's theme, emotional curve, cross-volume hooks, character stage goals, and volume-end irreversible changes. **Do NOT prescribe chapter-level tasks** (no "chapter 17 sends him home"). Chapter planning is the Phase 3 planner's job; the architect builds the skeleton, not the chapter list.
|
|
322
342
|
|
|
323
343
|
## 01_Volume_Themes_and_Emotional_Curves
|
|
324
|
-
How many volumes? Each volume's theme in one sentence; each volume's emotional curve as a paragraph.
|
|
344
|
+
How many volumes? Each volume's theme in one sentence; each volume's emotional curve as a paragraph (where pressured, where rewarding, where cold, where warm). Not mechanical rotation.
|
|
325
345
|
|
|
326
|
-
## 02_Cross_Volume_Hooks_and_Payoff_Promises
|
|
327
|
-
Volume 1 plants hook A, paid off in volume N; volume 2 plants hook B, paid off in volume M. Prose, not tables. **Stay at volume-level
|
|
346
|
+
## 02_Cross_Volume_Hooks_and_Payoff_Promises (cover BOTH foreground and background layers)
|
|
347
|
+
Volume 1 plants hook A, paid off in volume N; volume 2 plants hook B, paid off in volume M. Prose, not tables. **Stay at volume-level** (e.g., "the origin mystery planted in volume 1 pays off in volume 3"); do not specify chapter numbers.
|
|
328
348
|
|
|
329
|
-
|
|
330
|
-
|
|
349
|
+
**Hooks must cover BOTH foreground and background layers** (matching the two-layer story established in story_frame.02):
|
|
350
|
+
- Foreground hooks: short-range arc-level hooks (case mystery, opponent identity, resource grab), paid off within 1-2 volumes
|
|
351
|
+
- Background hooks: full-book main-line hooks (ultimate truth, origin, systemic secret), paid off near the finale. The 3-7 load-bearing ones are core_hook=true
|
|
352
|
+
|
|
353
|
+
**If this paragraph only carries foreground hooks with no background seeds, you have lost the book's forward pull axis. Add them.**
|
|
354
|
+
|
|
355
|
+
## 03_Per_Volume_OKRs (Objective + 3 Key Results)
|
|
356
|
+
Recursive OKR outline that decomposes the Book Objective (root O set at the end of story_frame.04): every volume must explicitly state:
|
|
357
|
+
- **Objective (volume-level goal)**: a **verifiable state** the protagonist must reach by volume end, one sentence, logically chained to the Book Objective (e.g., if Book O = "become sect elder and vindicate the parental case", then Vol 1 O = "move from errand disciple into the registered disciple roster and recover the first lead pointing to the truth")
|
|
358
|
+
- **Key Results (3 items, quantifiable / observable)**: three concrete sub-achievements whose completion can be checked by an outside observer (e.g., KR1 = "take over the pharmacy garden steward seat", KR2 = "lock in a stable alliance with Lingan Peak", KR3 = "uncover the first half-page fragment of the parental case file"). No vague KRs like "gets stronger" / "matures".
|
|
359
|
+
|
|
360
|
+
Supporting characters' stage changes (master dies end of vol 2, opponent breaks bad in vol 3) go as notes under the relevant KR. Stage only — full arc lives in roles. **The 3 KRs per volume are the direct input for the planner: once it sees 3 KRs for a volume, it paces chapter tasks at roughly one KR advanced every 3-5 chapters.**
|
|
331
361
|
|
|
332
362
|
## 04_Volume_End_Mandatory_Changes
|
|
333
|
-
Each volume's last chapter must contain an irreversible event. Prose, one paragraph per volume.
|
|
363
|
+
Each volume's last chapter must contain an irreversible event. Prose, one paragraph per volume. **Write what must happen, not which chapter**.
|
|
334
364
|
|
|
335
365
|
## 05_Rhythm_Principles (concrete + universal)
|
|
336
|
-
**
|
|
337
|
-
|
|
338
|
-
### Golden First Three Chapters Rule
|
|
339
|
-
- Chapter 1: throw the core conflict immediately; no large background dump
|
|
340
|
-
- Chapter 2: show the core edge / ability / leverage that answers Chapter 1's pressure
|
|
341
|
-
- Chapter 3: establish the first concrete short-term goal that gives readers a reason to continue
|
|
366
|
+
**This is the single home for rhythm principles — no separate rhythm_principles section exists.** Output 6 rhythm principles. **At least 3 must be concretized for this book** (e.g., "every 5 chapters in the first 30, hit one small payoff"); the rest may stay as universal rules (e.g., "no deus ex machina", "plant the foreshadow 3-5 chapters before the climax"). A mix of concrete + universal is valid. Bad: "rhythm must balance tension and release". Good: "every 5 chapters in the first 30 carries a small payoff landing in the last 300 chars of the chapter". Cover (order flexible, substitutions of equal weight are allowed): (1) climax spacing, (2) breath frequency, (3) hook density, (4) information release pacing, (5) payoff rhythm, (6) relationship advancement — each 2-3 sentences.
|
|
342
367
|
|
|
343
368
|
=== SECTION: roles ===
|
|
344
369
|
|
|
345
|
-
One-file-per-character prose. **The protagonist card is the single source of truth for the protagonist's
|
|
370
|
+
One-file-per-character prose. **The protagonist card is the single source of truth for the protagonist's arc** — story_frame no longer carries it, and writer/planner both read it here.
|
|
346
371
|
|
|
347
372
|
---ROLE---
|
|
348
373
|
tier: major
|
|
349
374
|
name: <character name>
|
|
350
375
|
---CONTENT---
|
|
351
376
|
## Core_Tags
|
|
352
|
-
(3-5 tags + one sentence on why)
|
|
377
|
+
(3-5 tags + one sentence on why those tags)
|
|
353
378
|
|
|
354
379
|
## Contrast_Detail
|
|
355
|
-
(1-2 concrete details that contradict the core tags.)
|
|
380
|
+
(1-2 concrete details that contradict the core tags — "ice-cold killer but leaves fish bones for stray cats". Contrast detail is the formula for character dimensionality.)
|
|
356
381
|
|
|
357
382
|
## Back_Story
|
|
358
|
-
(Prose paragraph — how this person became who they are.)
|
|
383
|
+
(Prose paragraph — how this person became who they are. Key past only, keep it lean.)
|
|
359
384
|
|
|
360
385
|
## Protagonist_Arc (start → end → cost)
|
|
361
|
-
**Mandatory for the protagonist; optional for other majors with substantial arcs.**
|
|
386
|
+
**Mandatory for the protagonist; optional for other majors with substantial arcs.** Where they start (identity, situation, core flaw, initial desire); where they land (who they become, what they gain or lose); the irreversible cost they pay for that landing. Show internal displacement, not just growth. This section absorbs what used to live in story_frame.02_Protagonist_Arc.
|
|
362
387
|
|
|
363
388
|
## Current_State (initial state at chapter 0)
|
|
364
|
-
(Character-only
|
|
389
|
+
(Where they are at chapter 0, what's on their mind, most recent worry. **Character-only**: initial hooks go in pending_hooks start_chapter=0 rows; environment / era anchors (when the genre has a real year) are woven into story_frame's world-tonal-ground paragraph. No separate current_state section is produced.)
|
|
365
390
|
|
|
366
391
|
## Relationship_Network
|
|
367
|
-
(With protagonist, with other
|
|
392
|
+
(With protagonist, with other major characters. One line each. Relationships are dynamic, not labels.)
|
|
368
393
|
|
|
369
394
|
## Inner_Driver
|
|
370
395
|
(What they want, why, what they're willing to pay.)
|
|
371
396
|
|
|
372
397
|
## Growth_Arc
|
|
373
|
-
(Internal displacement across the book.)
|
|
398
|
+
(Internal displacement across the book. Can be short for non-protagonists.)
|
|
374
399
|
|
|
375
400
|
---ROLE---
|
|
376
401
|
tier: major
|
|
@@ -378,19 +403,19 @@ name: <next major>
|
|
|
378
403
|
---CONTENT---
|
|
379
404
|
...
|
|
380
405
|
|
|
381
|
-
(Aim for 2-3 majors + 2-3 supporting majors. Quality over quantity.)
|
|
406
|
+
(Aim for 2-3 majors + 2-3 supporting majors. Quality over quantity — do not pad.)
|
|
382
407
|
|
|
383
408
|
---ROLE---
|
|
384
409
|
tier: minor
|
|
385
410
|
name: <minor name>
|
|
386
411
|
---CONTENT---
|
|
387
|
-
(Simplified: Core_Tags / Contrast_Detail / Current_State / Relationship_to_Protagonist
|
|
412
|
+
(Simplified: only 4 sections — Core_Tags / Contrast_Detail / Current_State / Relationship_to_Protagonist, 1-2 lines each.)
|
|
388
413
|
|
|
389
414
|
(3-5 minors.)
|
|
390
415
|
|
|
391
416
|
=== SECTION: book_rules ===
|
|
392
417
|
|
|
393
|
-
**Output ONLY the YAML frontmatter block — zero prose.** All narrative guidance moved into story_frame.03_World_Tonal_Ground.
|
|
418
|
+
**Output ONLY the YAML frontmatter block — zero prose.** All narrative guidance (perspective, book-specific rules, core conflict driver) has moved into story_frame.03_World_Tonal_Ground. Do not repeat it here.
|
|
394
419
|
\`\`\`
|
|
395
420
|
---
|
|
396
421
|
version: "1.0"
|
|
@@ -415,15 +440,19 @@ enableFullCastTracking: false
|
|
|
415
440
|
|
|
416
441
|
=== SECTION: pending_hooks ===
|
|
417
442
|
|
|
418
|
-
Initial hook pool (Markdown table,
|
|
419
|
-
| hook_id | start_chapter | type | status | last_advanced_chapter | expected_payoff | payoff_timing | notes |
|
|
443
|
+
Initial hook pool (Markdown table), Phase 7 extended columns:
|
|
444
|
+
| hook_id | start_chapter | type | status | last_advanced_chapter | expected_payoff | payoff_timing | depends_on | pays_off_in_arc | core_hook | half_life | notes |
|
|
420
445
|
|
|
421
446
|
Rules:
|
|
422
447
|
- Column 5 is a pure chapter number, not narrative description
|
|
423
448
|
- At book creation all planned hooks have last_advanced_chapter = 0
|
|
424
449
|
- Column 7 must be: immediate / near-term / mid-arc / slow-burn / endgame
|
|
450
|
+
- Column 8 (depends_on): upstream hook ids that must be planted / paid off before this one fires, formatted [H003, H007]; write "none" if no upstream
|
|
451
|
+
- Column 9 (pays_off_in_arc): free-form prose on where this hook is scheduled to pay off (e.g. "mid of volume 2", "right before the finale"). NOT parsed into chapter numbers
|
|
452
|
+
- Column 10 (core_hook): true / false. Core hooks are main-line load-bearing (central mystery, identity, key promise). A book typically has 3-7 cores; everything else is false
|
|
453
|
+
- Column 11 (half_life): optional integer chapters. If blank, derived from payoff_timing (immediate/near-term = 10, mid-arc = 30, slow-burn/endgame = 80)
|
|
425
454
|
- Put initial signal text in notes, not column 5
|
|
426
|
-
- **Initial world / alliance state**: any load-bearing initial condition can be seeded as a start_chapter=0 row with a
|
|
455
|
+
- **Initial world / alliance state**: any load-bearing initial condition ("protagonist carries the father's notebook", "the regime already watches the harbor") can be seeded as a start_chapter=0 row with a note-column tag indicating its initial-state nature.
|
|
427
456
|
|
|
428
457
|
## Final emphasis
|
|
429
458
|
- Fit ${book.platform} platform taste and ${gp.name} genre traits
|
|
@@ -431,22 +460,156 @@ Rules:
|
|
|
431
460
|
- Hooks planted with payoff promises; supporting characters have independent motivation
|
|
432
461
|
- **story_frame / volume_map / roles must be prose density — no bullet-list degradation**
|
|
433
462
|
- **book_rules is YAML only — no prose body**
|
|
434
|
-
- **Do NOT emit rhythm_principles or current_state as separate sections** — rhythm principles live in the last paragraph of volume_map; character initial status goes in roles.Current_State; initial hooks go in pending_hooks (start_chapter=0 rows); environment/era anchors (only when the genre has a real year) are woven into story_frame
|
|
463
|
+
- **Do NOT emit rhythm_principles or current_state as separate sections** — rhythm principles live in the last paragraph of volume_map; character initial status goes in roles.Current_State; initial hooks go in pending_hooks (start_chapter=0 rows); environment / era anchors (only when the genre has a real year) are woven into story_frame's world-tonal-ground paragraph
|
|
464
|
+
- **pending_hooks table MUST carry Phase 7 extended columns — depends_on spells out the causal chain, pays_off_in_arc locks the approximate payoff location, core_hook marks main-line load-bearing hooks (3-7 per book), half_life only on priority hooks**
|
|
465
|
+
|
|
466
|
+
## Hard completeness check (read before generating)
|
|
467
|
+
You MUST emit all **5 SECTION blocks in order**: story_frame → volume_map → roles → book_rules → pending_hooks. Do NOT stop after story_frame or volume_map just because they ran long. Even if roles lists only 3 characters, book_rules is a tiny YAML block, and pending_hooks has only 3 rows, all five must appear. The output is only considered delivered after the last row of pending_hooks is written.`;
|
|
468
|
+
}
|
|
469
|
+
// -------------------------------------------------------------------------
|
|
470
|
+
// Parsing
|
|
471
|
+
// -------------------------------------------------------------------------
|
|
472
|
+
parseSections(content, language) {
|
|
473
|
+
const parsedSections = new Map();
|
|
474
|
+
const sectionPattern = /^\s*===\s*SECTION\s*[::]\s*([^\n=]+?)\s*===\s*$/gim;
|
|
475
|
+
const matches = [...content.matchAll(sectionPattern)];
|
|
476
|
+
for (let i = 0; i < matches.length; i++) {
|
|
477
|
+
const match = matches[i];
|
|
478
|
+
const rawName = match[1] ?? "";
|
|
479
|
+
const start = (match.index ?? 0) + match[0].length;
|
|
480
|
+
const end = matches[i + 1]?.index ?? content.length;
|
|
481
|
+
const normalizedName = this.normalizeSectionName(rawName);
|
|
482
|
+
parsedSections.set(normalizedName, content.slice(start, end).trim());
|
|
483
|
+
}
|
|
484
|
+
// Phase 5 new sections take precedence.
|
|
485
|
+
const storyFrame = parsedSections.get("story_frame") ?? "";
|
|
486
|
+
const volumeMap = parsedSections.get("volume_map") ?? "";
|
|
487
|
+
const rhythmPrinciples = parsedSections.get("rhythm_principles") ?? "";
|
|
488
|
+
const rolesRaw = parsedSections.get("roles") ?? "";
|
|
489
|
+
// Legacy sections (still produced for back-compat where needed).
|
|
490
|
+
// If the model used old section names we still accept them.
|
|
491
|
+
const legacyStoryBible = parsedSections.get("story_bible") ?? "";
|
|
492
|
+
const legacyVolumeOutline = parsedSections.get("volume_outline") ?? "";
|
|
493
|
+
const bookRules = parsedSections.get("book_rules");
|
|
494
|
+
// Phase 5 consolidation: current_state is no longer a required section.
|
|
495
|
+
// Legacy books (v12 / Phase 5 initial / pre-revert) and import/fanfic
|
|
496
|
+
// regenerations may still produce it — accept the value when present,
|
|
497
|
+
// fall through to empty seed when absent (consolidator will populate at
|
|
498
|
+
// runtime). Era/setting anchors that used to motivate a separate
|
|
499
|
+
// current_state block now live naturally inside story_frame.世界观底色
|
|
500
|
+
// for genres that have a real-world year anchor; other genres (修仙/玄幻/
|
|
501
|
+
// 系统文) omit them entirely.
|
|
502
|
+
const currentStateLegacy = parsedSections.get("current_state") ?? "";
|
|
503
|
+
const pendingHooksRaw = parsedSections.get("pending_hooks");
|
|
504
|
+
// 5-section required contract: story_frame (or legacy story_bible),
|
|
505
|
+
// volume_map (or legacy volume_outline), roles, book_rules, pending_hooks.
|
|
506
|
+
//
|
|
507
|
+
// Backward compat: v12 outputs used story_bible/volume_outline and
|
|
508
|
+
// embedded character data inside story_bible — they had no roles block.
|
|
509
|
+
// When the model uses ONLY legacy section names, we accept an empty roles
|
|
510
|
+
// list (consolidator/readers fall back to the character_matrix shim).
|
|
511
|
+
// When the new story_frame / volume_map names are used we require roles.
|
|
512
|
+
const usingLegacyOutlineNames = !storyFrame && !volumeMap
|
|
513
|
+
&& (legacyStoryBible.length > 0 || legacyVolumeOutline.length > 0);
|
|
514
|
+
const missing = [];
|
|
515
|
+
const effectiveStoryFrame = storyFrame || legacyStoryBible;
|
|
516
|
+
const effectiveVolumeMap = volumeMap || legacyVolumeOutline;
|
|
517
|
+
if (!effectiveStoryFrame)
|
|
518
|
+
missing.push("story_frame");
|
|
519
|
+
if (!effectiveVolumeMap)
|
|
520
|
+
missing.push("volume_map");
|
|
521
|
+
if (!rolesRaw.trim() && !usingLegacyOutlineNames)
|
|
522
|
+
missing.push("roles");
|
|
523
|
+
if (!bookRules)
|
|
524
|
+
missing.push("book_rules");
|
|
525
|
+
if (!pendingHooksRaw)
|
|
526
|
+
missing.push("pending_hooks");
|
|
527
|
+
if (missing.length > 0) {
|
|
528
|
+
throw new Error(`Architect output missing required section${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}`);
|
|
529
|
+
}
|
|
530
|
+
const roles = this.parseRoles(rolesRaw);
|
|
531
|
+
const pendingHooks = this.normalizePendingHooksSection(this.stripTrailingAssistantCoda(pendingHooksRaw), effectiveVolumeMap);
|
|
532
|
+
// Synthesize legacy-facing content from new prose (so back-compat callers
|
|
533
|
+
// still receive real content instead of empty strings).
|
|
534
|
+
const storyBible = legacyStoryBible || this.buildStoryBibleShim(effectiveStoryFrame, language);
|
|
535
|
+
const volumeOutline = legacyVolumeOutline || effectiveVolumeMap;
|
|
536
|
+
return {
|
|
537
|
+
storyBible,
|
|
538
|
+
volumeOutline,
|
|
539
|
+
bookRules: bookRules,
|
|
540
|
+
// currentState: empty string when architect no longer emits the section;
|
|
541
|
+
// writeFoundationFiles seeds current_state.md with a placeholder so
|
|
542
|
+
// consolidator / state-bootstrap readers find a valid file on first boot.
|
|
543
|
+
currentState: currentStateLegacy,
|
|
544
|
+
pendingHooks,
|
|
545
|
+
storyFrame: effectiveStoryFrame,
|
|
546
|
+
volumeMap: effectiveVolumeMap,
|
|
547
|
+
rhythmPrinciples,
|
|
548
|
+
roles,
|
|
549
|
+
};
|
|
435
550
|
}
|
|
436
551
|
/**
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
* - "init"(默认):首次建书。写架构稿 + 初始化所有运行时状态文件
|
|
441
|
-
* (current_state / pending_hooks / particle_ledger / subplot_board /
|
|
442
|
-
* emotional_arcs)为空模板。
|
|
443
|
-
* - "revise":在已有书上重写架构稿。**只改架构稿相关文件**——outline/ /
|
|
444
|
-
* roles/ / 4 个 legacy shim——**完全不动运行时状态文件**。这和
|
|
445
|
-
* context-transform 注入给 LLM 的 upgrade hint 承诺"只改架构稿不动已写
|
|
446
|
-
* 章节"一致;如果在 revise 模式下触动运行时文件,会把 consolidator 累积
|
|
447
|
-
* 的章节状态、伏笔推进、资源账本等全部重置。
|
|
552
|
+
* Parse ---ROLE---...---CONTENT---... blocks from the roles section.
|
|
553
|
+
* Drops malformed entries silently — this is prose the LLM produced,
|
|
554
|
+
* not machine input.
|
|
448
555
|
*/
|
|
449
|
-
|
|
556
|
+
parseRoles(raw) {
|
|
557
|
+
if (!raw.trim())
|
|
558
|
+
return [];
|
|
559
|
+
const blocks = raw.split(/^---ROLE---$/m).map((chunk) => chunk.trim()).filter(Boolean);
|
|
560
|
+
const roles = [];
|
|
561
|
+
for (const block of blocks) {
|
|
562
|
+
const contentSplit = block.split(/^---CONTENT---$/m);
|
|
563
|
+
if (contentSplit.length < 2)
|
|
564
|
+
continue;
|
|
565
|
+
const headerRaw = contentSplit[0].trim();
|
|
566
|
+
const content = contentSplit.slice(1).join("\n---CONTENT---\n").trim();
|
|
567
|
+
const tierMatch = headerRaw.match(/tier\s*[::]\s*(major|minor|主要|次要)/i);
|
|
568
|
+
const nameMatch = headerRaw.match(/name\s*[::]\s*(.+)/i);
|
|
569
|
+
if (!tierMatch || !nameMatch)
|
|
570
|
+
continue;
|
|
571
|
+
const tierValue = tierMatch[1].toLowerCase();
|
|
572
|
+
const tier = (tierValue === "major" || tierValue === "主要") ? "major" : "minor";
|
|
573
|
+
const name = nameMatch[1].trim();
|
|
574
|
+
if (!name || !content)
|
|
575
|
+
continue;
|
|
576
|
+
roles.push({ tier, name, content });
|
|
577
|
+
}
|
|
578
|
+
return roles;
|
|
579
|
+
}
|
|
580
|
+
buildStoryBibleShim(storyFrame, language) {
|
|
581
|
+
if (language === "en") {
|
|
582
|
+
return `# Story Bible (compat pointer — deprecated)\n\n> This file is kept for external readers only. The authoritative source is now:\n> - outline/story_frame.md (theme / tonal ground / core conflict / world rules / endgame)\n> - outline/volume_map.md (chapter-granular plot map)\n> - roles/ directory (one-file-per-character sheets)\n\n## Excerpt from story_frame\n\n${storyFrame.slice(0, 2000)}\n`;
|
|
583
|
+
}
|
|
584
|
+
return `# 故事圣经(兼容指针——已废弃)\n\n> 本文件仅为外部读取保留。权威来源已迁移至:\n> - outline/story_frame.md(主题 / 基调 / 核心冲突 / 世界铁律 / 终局)\n> - outline/volume_map.md(章级别的分卷地图)\n> - roles/ 文件夹(一人一卡角色档案)\n\n## story_frame 摘录\n\n${storyFrame.slice(0, 2000)}\n`;
|
|
585
|
+
}
|
|
586
|
+
buildCharacterMatrixShim(roles, language) {
|
|
587
|
+
const majorLines = roles.filter((role) => role.tier === "major")
|
|
588
|
+
.map((role) => `- roles/主要角色/${role.name}.md`);
|
|
589
|
+
const minorLines = roles.filter((role) => role.tier === "minor")
|
|
590
|
+
.map((role) => `- roles/次要角色/${role.name}.md`);
|
|
591
|
+
if (language === "en") {
|
|
592
|
+
return `# Character Matrix (compat pointer — deprecated)\n\n> This file is kept for external readers only. Authoritative source is now the roles/ directory (one-file-per-character).\n\n## Major characters\n\n${majorLines.join("\n") || "(none)"}\n\n## Minor characters\n\n${minorLines.join("\n") || "(none)"}\n`;
|
|
593
|
+
}
|
|
594
|
+
return `# 角色矩阵(兼容指针——已废弃)\n\n> 本文件仅为外部读取保留。权威来源已迁移至 roles/ 文件夹(一人一卡)。\n\n## 主要角色\n\n${majorLines.join("\n") || "(无)"}\n\n## 次要角色\n\n${minorLines.join("\n") || "(无)"}\n`;
|
|
595
|
+
}
|
|
596
|
+
buildBookRulesShim(bookRulesBody, language) {
|
|
597
|
+
const trimmedBody = bookRulesBody.trim();
|
|
598
|
+
if (language === "en") {
|
|
599
|
+
const excerpt = trimmedBody
|
|
600
|
+
? `\n\n## Narrative guidance excerpt\n\n${trimmedBody}\n`
|
|
601
|
+
: "";
|
|
602
|
+
return `# Book Rules (compat pointer — deprecated)\n\n> This file is kept for external readers only. The authoritative YAML frontmatter (protagonist / prohibitions / genreLock / ...) now lives at the top of outline/story_frame.md. readBookRules() prefers that location and only falls back here for books initialized before Phase 5 cleanup #3.${excerpt}`;
|
|
603
|
+
}
|
|
604
|
+
const excerpt = trimmedBody
|
|
605
|
+
? `\n\n## 叙事指引摘录\n\n${trimmedBody}\n`
|
|
606
|
+
: "";
|
|
607
|
+
return `# 本书规则(兼容指针——已废弃)\n\n> 本文件仅为外部读取保留。权威 YAML frontmatter(protagonist / prohibitions / genreLock / ...)已迁移至 outline/story_frame.md 顶部。readBookRules() 优先读那里,只有 Phase 5 cleanup #3 之前的老书才会回退到本文件。${excerpt}`;
|
|
608
|
+
}
|
|
609
|
+
// -------------------------------------------------------------------------
|
|
610
|
+
// File writing
|
|
611
|
+
// -------------------------------------------------------------------------
|
|
612
|
+
async writeFoundationFiles(bookDir, output, _numericalSystem = true, language = "zh", mode = "init") {
|
|
450
613
|
const storyDir = join(bookDir, "story");
|
|
451
614
|
const outlineDir = join(storyDir, "outline");
|
|
452
615
|
const rolesDir = join(storyDir, "roles");
|
|
@@ -459,84 +622,95 @@ Rules:
|
|
|
459
622
|
mkdir(rolesMinorDir, { recursive: true }),
|
|
460
623
|
]);
|
|
461
624
|
const writes = [];
|
|
462
|
-
const
|
|
463
|
-
const volumeMap = output.volumeMap ??
|
|
625
|
+
const storyFrameBody = output.storyFrame ?? output.storyBible;
|
|
626
|
+
const volumeMap = output.volumeMap ?? output.volumeOutline;
|
|
464
627
|
const rhythmPrinciples = output.rhythmPrinciples ?? "";
|
|
465
628
|
const roles = output.roles ?? [];
|
|
466
|
-
const isPhase5Output = storyFrame
|
|
467
|
-
// debug: 让排查时能一眼看出 LLM 按哪套格式输出、落到哪套文件布局。
|
|
468
|
-
// 如果用户新建书后发现只有 story_bible.md / 没有 outline/,看这行日志能
|
|
469
|
-
// 确认是 LLM 没按新 prompt 输出(走了 legacy 分支),而不是 writeFoundationFiles
|
|
470
|
-
// 本身的 bug。
|
|
471
|
-
this.ctx.logger?.info(`[architect] writeFoundationFiles layout=${isPhase5Output ? "phase5" : "legacy"} ` +
|
|
472
|
-
`storyFrame=${storyFrame.length}chars volumeMap=${volumeMap.length}chars roles=${roles.length}`);
|
|
473
|
-
// revise 模式 + LLM 回退 legacy 输出 → 必须抛错,不写任何文件。
|
|
474
|
-
// 原因:如果走 legacy 分支覆盖 story_bible.md 等 flat 文件,而 outline/
|
|
475
|
-
// 和 roles/ 保持不变,会让 Phase 5 书进入"outline 新 + shim 老 + roles
|
|
476
|
-
// 残留"的混乱状态;读取端的 fallback 逻辑会读到互相矛盾的内容。
|
|
477
|
-
// 抛错让用户立刻感知 LLM 响应异常,而不是默默破坏书的数据完整性。
|
|
629
|
+
const isPhase5Output = Boolean(output.storyFrame?.trim());
|
|
478
630
|
if (mode === "revise" && !isPhase5Output) {
|
|
479
631
|
throw new Error("Architect revise mode produced legacy-format output (storyFrame empty). " +
|
|
480
|
-
"
|
|
481
|
-
"The book's architecture files have NOT been modified. " +
|
|
482
|
-
"Check prompt / model / temperature and try again.");
|
|
632
|
+
"The book's architecture files have NOT been modified.");
|
|
483
633
|
}
|
|
484
|
-
// revise 模式下先清空旧 role 文件,再写本次 architect 输出——避免改名 / 删除 /
|
|
485
|
-
// 合并角色后的旧卡片残留被 readRoleCards 当作有效角色继续注入(见 Bug 3)。
|
|
486
|
-
// 备份由上游 runner.reviseFoundation 在调用前完成,这里可以安全清空。
|
|
487
|
-
// init 模式下目录本来就是空的,不需要清。
|
|
488
|
-
// 放在 if (isPhase5Output) 外面,保证所有 revise 场景都清。
|
|
489
634
|
if (mode === "revise") {
|
|
490
635
|
await rm(rolesMajorDir, { recursive: true, force: true });
|
|
491
636
|
await rm(rolesMinorDir, { recursive: true, force: true });
|
|
492
637
|
await mkdir(rolesMajorDir, { recursive: true });
|
|
493
638
|
await mkdir(rolesMinorDir, { recursive: true });
|
|
494
639
|
}
|
|
495
|
-
if (isPhase5Output) {
|
|
496
|
-
// book_rules 的 YAML frontmatter 提取后拼到 story_frame.md 顶部,作为权威位置。
|
|
497
|
-
const { frontmatter: bookRulesFrontmatter, body: bookRulesBody } = extractYamlFrontmatter(output.bookRules);
|
|
498
|
-
const storyFrameWithFrontmatter = bookRulesFrontmatter
|
|
499
|
-
? `${bookRulesFrontmatter}\n\n${storyFrame.trim()}\n`
|
|
500
|
-
: storyFrame;
|
|
501
|
-
// Phase 5 权威文件
|
|
502
|
-
writes.push(writeFile(join(outlineDir, "story_frame.md"), storyFrameWithFrontmatter, "utf-8"));
|
|
503
|
-
writes.push(writeFile(join(outlineDir, "volume_map.md"), volumeMap, "utf-8"));
|
|
504
|
-
if (rhythmPrinciples.trim()) {
|
|
505
|
-
const rhythmFileName = language === "en" ? "rhythm_principles.md" : "节奏原则.md";
|
|
506
|
-
writes.push(writeFile(join(outlineDir, rhythmFileName), rhythmPrinciples, "utf-8"));
|
|
507
|
-
}
|
|
508
|
-
// 一人一卡
|
|
509
|
-
for (const role of roles) {
|
|
510
|
-
const targetDir = role.tier === "major" ? rolesMajorDir : rolesMinorDir;
|
|
511
|
-
const safeName = role.name.replace(/[/\\:*?"<>|]/g, "_").trim();
|
|
512
|
-
if (!safeName)
|
|
513
|
-
continue;
|
|
514
|
-
writes.push(writeFile(join(targetDir, `${safeName}.md`), role.content, "utf-8"));
|
|
515
|
-
}
|
|
516
|
-
// Legacy shim 文件
|
|
517
|
-
writes.push(writeFile(join(storyDir, "story_bible.md"), this.buildStoryBibleShim(storyFrameWithFrontmatter, language), "utf-8"));
|
|
518
|
-
writes.push(writeFile(join(storyDir, "volume_outline.md"), volumeMap, "utf-8"));
|
|
519
|
-
writes.push(writeFile(join(storyDir, "character_matrix.md"), this.buildCharacterMatrixShim(roles, language), "utf-8"));
|
|
520
|
-
writes.push(writeFile(join(storyDir, "book_rules.md"), this.buildBookRulesShim(bookRulesBody, language), "utf-8"));
|
|
521
|
-
}
|
|
522
|
-
else {
|
|
523
|
-
// Legacy 输出路径(仅 init 模式——revise + legacy 已在上面抛错):
|
|
524
|
-
// LLM 还按老 prompt 输出 story_bible / volume_outline,走 Phase 4 落盘方式。
|
|
640
|
+
if (!isPhase5Output) {
|
|
525
641
|
writes.push(writeFile(join(storyDir, "story_bible.md"), output.storyBible, "utf-8"));
|
|
526
642
|
writes.push(writeFile(join(storyDir, "volume_outline.md"), output.volumeOutline, "utf-8"));
|
|
527
643
|
writes.push(writeFile(join(storyDir, "book_rules.md"), output.bookRules, "utf-8"));
|
|
528
644
|
writes.push(writeFile(join(storyDir, "character_matrix.md"), language === "en"
|
|
529
645
|
? "# Character Matrix\n\n<!-- One ## section per character. Add new characters as new ## blocks. -->\n"
|
|
530
646
|
: "# 角色矩阵\n\n<!-- 每个角色一个 ## 块,新角色追加新 ## 即可。 -->\n", "utf-8"));
|
|
647
|
+
if (mode === "init") {
|
|
648
|
+
const currentStateSeed = output.currentState?.trim()
|
|
649
|
+
? output.currentState
|
|
650
|
+
: (language === "en"
|
|
651
|
+
? "# Current State\n\n> Seeded at book creation. Runtime state is appended by the consolidator after each chapter.\n"
|
|
652
|
+
: "# 当前状态\n\n> 建书时占位。运行时每章之后由 consolidator 追加最新状态。\n");
|
|
653
|
+
writes.push(writeFile(join(storyDir, "current_state.md"), currentStateSeed, "utf-8"));
|
|
654
|
+
writes.push(writeFile(join(storyDir, "pending_hooks.md"), output.pendingHooks, "utf-8"));
|
|
655
|
+
writes.push(writeFile(join(storyDir, "emotional_arcs.md"), language === "en"
|
|
656
|
+
? "# Emotional Arcs\n\n| Character | Chapter | Emotional State | Trigger Event | Intensity (1-10) | Arc Direction |\n| --- | --- | --- | --- | --- | --- |\n"
|
|
657
|
+
: "# 情感弧线\n\n| 角色 | 章节 | 情绪状态 | 触发事件 | 强度(1-10) | 弧线方向 |\n|------|------|----------|----------|------------|----------|\n", "utf-8"));
|
|
658
|
+
}
|
|
659
|
+
await Promise.all(writes);
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
// Cleanup #3: book_rules YAML frontmatter is now the authoritative
|
|
663
|
+
// schema for structured fields (protagonist, prohibitions, …). We prepend
|
|
664
|
+
// it to story_frame.md so readers have one canonical place to look.
|
|
665
|
+
// book_rules.md becomes a compat shim.
|
|
666
|
+
const { frontmatter: bookRulesFrontmatter, body: bookRulesBody } = extractYamlFrontmatter(output.bookRules);
|
|
667
|
+
const storyFrame = bookRulesFrontmatter
|
|
668
|
+
? `${bookRulesFrontmatter}\n\n${storyFrameBody.trim()}\n`
|
|
669
|
+
: storyFrameBody;
|
|
670
|
+
// Phase 5 primary prose files
|
|
671
|
+
writes.push(writeFile(join(outlineDir, "story_frame.md"), storyFrame, "utf-8"));
|
|
672
|
+
writes.push(writeFile(join(outlineDir, "volume_map.md"), volumeMap, "utf-8"));
|
|
673
|
+
// Phase 5 consolidation: rhythm principles live inside the last paragraph
|
|
674
|
+
// of volume_map. A separate 节奏原则.md / rhythm_principles.md file is only
|
|
675
|
+
// written when the architect happened to produce a standalone block (legacy
|
|
676
|
+
// 7-section output / foundation-reviewer round-trips that still split it
|
|
677
|
+
// out). Skipping the empty write avoids 0-byte files that mislead the UI
|
|
678
|
+
// and fight against the "no duplication" rule — readers who need the rhythm
|
|
679
|
+
// content already pull it from volume_map's closing paragraph.
|
|
680
|
+
if (rhythmPrinciples.trim()) {
|
|
681
|
+
const rhythmFileName = language === "en" ? "rhythm_principles.md" : "节奏原则.md";
|
|
682
|
+
writes.push(writeFile(join(outlineDir, rhythmFileName), rhythmPrinciples, "utf-8"));
|
|
683
|
+
}
|
|
684
|
+
// Roles — one file per character
|
|
685
|
+
for (const role of roles) {
|
|
686
|
+
const targetDir = role.tier === "major" ? rolesMajorDir : rolesMinorDir;
|
|
687
|
+
const safeName = role.name.replace(/[/\\:*?"<>|]/g, "_").trim();
|
|
688
|
+
if (!safeName)
|
|
689
|
+
continue;
|
|
690
|
+
writes.push(writeFile(join(targetDir, `${safeName}.md`), role.content, "utf-8"));
|
|
531
691
|
}
|
|
532
|
-
//
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
//
|
|
692
|
+
// Compat shims — these are pointer files, not authoritative content.
|
|
693
|
+
writes.push(writeFile(join(storyDir, "story_bible.md"), this.buildStoryBibleShim(storyFrame, language), "utf-8"));
|
|
694
|
+
writes.push(writeFile(join(storyDir, "character_matrix.md"), this.buildCharacterMatrixShim(roles, language), "utf-8"));
|
|
695
|
+
// Cleanup #1: volume_outline.md mirror removed. All readers now resolve
|
|
696
|
+
// through readVolumeMap() in utils/outline-paths.ts, which prefers
|
|
697
|
+
// outline/volume_map.md and falls back to legacy volume_outline.md for
|
|
698
|
+
// books initialized before Phase 5.
|
|
699
|
+
// book_rules.md is now a compat shim — the authoritative YAML
|
|
700
|
+
// frontmatter lives on story_frame.md (cleanup #3). readBookRules()
|
|
701
|
+
// prefers story_frame.md but still falls back here for older books.
|
|
702
|
+
writes.push(writeFile(join(storyDir, "book_rules.md"), this.buildBookRulesShim(bookRulesBody, language), "utf-8"));
|
|
703
|
+
// Runtime state files.
|
|
704
|
+
// Phase 5 consolidation: the architect no longer emits a current_state
|
|
705
|
+
// section (only 3 genres — 港综同人/年代文/都市重生 — benefit from a
|
|
706
|
+
// separate era anchor, and those fold naturally into story_frame.世界观底色).
|
|
707
|
+
// We still write current_state.md with a seed placeholder so
|
|
708
|
+
// isCompleteBookDirectory() sees it on first boot and the runtime
|
|
709
|
+
// consolidator has a file to append each chapter's state into.
|
|
710
|
+
// Per-character state lives in roles/*.Current_State; initial hook rows
|
|
711
|
+
// live in pending_hooks with start_chapter=0. Legacy books / imports that
|
|
712
|
+
// still produced the section keep their content as-is.
|
|
536
713
|
if (mode === "init") {
|
|
537
|
-
// current_state.md — 架构师不再产出结构化初始状态,给占位 seed;运行时由
|
|
538
|
-
// consolidator 每章追加。如果 output 里带了内容(legacy 输出或 reviser
|
|
539
|
-
// 生成),直接用。
|
|
540
714
|
const currentStateSeed = output.currentState?.trim()
|
|
541
715
|
? output.currentState
|
|
542
716
|
: (language === "en"
|
|
@@ -544,25 +718,22 @@ Rules:
|
|
|
544
718
|
: "# 当前状态\n\n> 建书时占位。运行时每章之后由 consolidator 追加最新状态。每个角色的初始状态详见 roles/*.当前现状;承重的初始世界设定见 pending_hooks 里 startChapter=0 的行。\n");
|
|
545
719
|
writes.push(writeFile(join(storyDir, "current_state.md"), currentStateSeed, "utf-8"));
|
|
546
720
|
writes.push(writeFile(join(storyDir, "pending_hooks.md"), output.pendingHooks, "utf-8"));
|
|
547
|
-
// 运行时 append log 文件,下游 state-validator / consolidator 依赖这些存在。
|
|
548
|
-
if (numericalSystem) {
|
|
549
|
-
writes.push(writeFile(join(storyDir, "particle_ledger.md"), language === "en"
|
|
550
|
-
? "# Resource Ledger\n\n| Chapter | Opening Value | Source | Integrity | Delta | Closing Value | Evidence |\n| --- | --- | --- | --- | --- | --- | --- |\n| 0 | 0 | Initialization | - | 0 | 0 | Initial book state |\n"
|
|
551
|
-
: "# 资源账本\n\n| 章节 | 期初值 | 来源 | 完整度 | 增量 | 期末值 | 依据 |\n|------|--------|------|--------|------|--------|------|\n| 0 | 0 | 初始化 | - | 0 | 0 | 开书初始 |\n", "utf-8"));
|
|
552
|
-
}
|
|
553
|
-
writes.push(writeFile(join(storyDir, "subplot_board.md"), language === "en"
|
|
554
|
-
? "# Subplot Board\n\n| Subplot ID | Subplot | Related Characters | Start Chapter | Last Active Chapter | Chapters Since | Status | Progress Summary | Payoff ETA |\n| --- | --- | --- | --- | --- | --- | --- | --- | --- |\n"
|
|
555
|
-
: "# 支线进度板\n\n| 支线ID | 支线名 | 相关角色 | 起始章 | 最近活跃章 | 距今章数 | 状态 | 进度概述 | 回收ETA |\n|--------|--------|----------|--------|------------|----------|------|----------|---------|\n", "utf-8"));
|
|
556
721
|
writes.push(writeFile(join(storyDir, "emotional_arcs.md"), language === "en"
|
|
557
722
|
? "# Emotional Arcs\n\n| Character | Chapter | Emotional State | Trigger Event | Intensity (1-10) | Arc Direction |\n| --- | --- | --- | --- | --- | --- |\n"
|
|
558
723
|
: "# 情感弧线\n\n| 角色 | 章节 | 情绪状态 | 触发事件 | 强度(1-10) | 弧线方向 |\n|------|------|----------|----------|------------|----------|\n", "utf-8"));
|
|
559
724
|
}
|
|
725
|
+
// Cleanup #2 (Option B): particle_ledger.md / subplot_board.md /
|
|
726
|
+
// chapter_summaries.md are pure runtime logs appended by the writer's
|
|
727
|
+
// settlement phase. The architect no longer seeds them here — mixing a
|
|
728
|
+
// static "setting" seed with a runtime "append log" was the dual-purpose
|
|
729
|
+
// mess that prompted the cleanup. If they don't exist yet, downstream
|
|
730
|
+
// readers see the placeholder and the first chapter settlement creates
|
|
731
|
+
// them naturally. The `_numericalSystem` parameter is kept for API
|
|
732
|
+
// compatibility with existing callers.
|
|
560
733
|
await Promise.all(writes);
|
|
561
734
|
}
|
|
562
735
|
/**
|
|
563
736
|
* Reverse-engineer foundation from existing chapters.
|
|
564
|
-
* Reads all chapters as a single text block and asks LLM to extract story_bible,
|
|
565
|
-
* volume_outline, book_rules, current_state, and pending_hooks.
|
|
566
737
|
*/
|
|
567
738
|
async generateFoundationFromImport(book, chaptersText, externalContext, reviewFeedback, options) {
|
|
568
739
|
const { profile: gp, body: genreBody } = await readGenreProfile(this.ctx.projectRoot, book.genre);
|
|
@@ -575,306 +746,73 @@ Rules:
|
|
|
575
746
|
: "";
|
|
576
747
|
const numericalBlock = gp.numericalSystem
|
|
577
748
|
? (resolvedLanguage === "en"
|
|
578
|
-
?
|
|
579
|
-
-
|
|
580
|
-
: `- 有明确的数值/资源体系可追踪
|
|
581
|
-
- 在 book_rules 中定义 numericalSystemOverrides(hardCap、resourceTypes)`)
|
|
749
|
+
? "- The story uses a trackable numerical/resource system"
|
|
750
|
+
: "- 有明确的数值/资源体系可追踪")
|
|
582
751
|
: (resolvedLanguage === "en"
|
|
583
|
-
? "-
|
|
584
|
-
: "-
|
|
585
|
-
const powerBlock = gp.powerScaling
|
|
586
|
-
? (resolvedLanguage === "en" ? "- The story has an explicit power-scaling ladder" : "- 有明确的战力等级体系")
|
|
587
|
-
: "";
|
|
588
|
-
const eraBlock = gp.eraResearch
|
|
589
|
-
? (resolvedLanguage === "en"
|
|
590
|
-
? "- The story needs era/historical grounding (set eraConstraints in book_rules)"
|
|
591
|
-
: "- 需要年代考据支撑(在 book_rules 中设置 eraConstraints)")
|
|
592
|
-
: "";
|
|
593
|
-
const storyBiblePrompt = resolvedLanguage === "en"
|
|
594
|
-
? `Extract from the source text and organize with structured second-level headings:
|
|
595
|
-
## 01_Worldview
|
|
596
|
-
Extracted world setting, core rules, and frame
|
|
597
|
-
|
|
598
|
-
## 02_Protagonist
|
|
599
|
-
Inferred protagonist setup (identity / advantage / personality core / behavioral boundaries)
|
|
600
|
-
|
|
601
|
-
## 03_Factions_and_Characters
|
|
602
|
-
Factions and important supporting characters that appear in the source text
|
|
603
|
-
|
|
604
|
-
## 04_Geography_and_Environment
|
|
605
|
-
Locations, environments, and scene traits drawn from the source text
|
|
606
|
-
|
|
607
|
-
## 05_Title_and_Blurb
|
|
608
|
-
Keep the original title "${book.title}" and generate a matching blurb from the source text`
|
|
609
|
-
: `从正文中提取,用结构化二级标题组织:
|
|
610
|
-
## 01_世界观
|
|
611
|
-
从正文中提取的世界观设定、核心规则体系
|
|
612
|
-
|
|
613
|
-
## 02_主角
|
|
614
|
-
从正文中推断的主角设定(身份/金手指/性格底色/行为边界)
|
|
615
|
-
|
|
616
|
-
## 03_势力与人物
|
|
617
|
-
从正文中出现的势力分布、重要配角(每人:名字、身份、动机、与主角关系、独立目标)
|
|
618
|
-
|
|
619
|
-
## 04_地理与环境
|
|
620
|
-
从正文中出现的地图/场景设定、环境特色
|
|
621
|
-
|
|
622
|
-
## 05_书名与简介
|
|
623
|
-
保留原书名"${book.title}",根据正文内容生成简介`;
|
|
624
|
-
const volumeOutlinePrompt = resolvedLanguage === "en"
|
|
625
|
-
? `Infer the volume plan from existing text:
|
|
626
|
-
- Existing chapters: review the actual structure already present
|
|
627
|
-
- Future projection: predict later directions from active hooks and plot momentum
|
|
628
|
-
For each volume include: title, chapter range, core conflict, and key turning points`
|
|
629
|
-
: `基于已有正文反推卷纲:
|
|
630
|
-
- 已有章节部分:根据实际内容回顾每卷的结构
|
|
631
|
-
- 后续预测部分:基于已有伏笔和剧情走向预测未来方向
|
|
632
|
-
每卷包含:卷名、章节范围、核心冲突、关键转折`;
|
|
633
|
-
const bookRulesPrompt = resolvedLanguage === "en"
|
|
634
|
-
? `Infer book_rules.md as YAML frontmatter plus narrative guidance from character behavior in the source text:
|
|
635
|
-
\`\`\`
|
|
636
|
-
---
|
|
637
|
-
version: "1.0"
|
|
638
|
-
protagonist:
|
|
639
|
-
name: (extract protagonist name from the text)
|
|
640
|
-
personalityLock: [(infer 3-5 personality keywords from behavior)]
|
|
641
|
-
behavioralConstraints: [(infer 3-5 behavioral constraints from behavior)]
|
|
642
|
-
genreLock:
|
|
643
|
-
primary: ${book.genre}
|
|
644
|
-
forbidden: [(2-3 forbidden style intrusions)]
|
|
645
|
-
${gp.numericalSystem ? `numericalSystemOverrides:
|
|
646
|
-
hardCap: (infer from the text)
|
|
647
|
-
resourceTypes: [(extract core resource types from the text)]` : ""}
|
|
648
|
-
prohibitions:
|
|
649
|
-
- (infer 3-5 book-specific prohibitions from the text)
|
|
650
|
-
chapterTypesOverride: []
|
|
651
|
-
fatigueWordsOverride: []
|
|
652
|
-
additionalAuditDimensions: []
|
|
653
|
-
enableFullCastTracking: false
|
|
654
|
-
---
|
|
655
|
-
|
|
656
|
-
## Narrative Perspective
|
|
657
|
-
(Infer the narrative perspective and style from the text)
|
|
658
|
-
|
|
659
|
-
## Core Conflict Driver
|
|
660
|
-
(Infer the book's core conflict and propulsion from the text)
|
|
661
|
-
\`\`\``
|
|
662
|
-
: `从正文中角色行为反推 book_rules.md 格式的 YAML frontmatter + 叙事指导:
|
|
663
|
-
\`\`\`
|
|
664
|
-
---
|
|
665
|
-
version: "1.0"
|
|
666
|
-
protagonist:
|
|
667
|
-
name: (从正文提取主角名)
|
|
668
|
-
personalityLock: [(从行为推断3-5个性格关键词)]
|
|
669
|
-
behavioralConstraints: [(从行为推断3-5条行为约束)]
|
|
670
|
-
genreLock:
|
|
671
|
-
primary: ${book.genre}
|
|
672
|
-
forbidden: [(2-3种禁止混入的文风)]
|
|
673
|
-
${gp.numericalSystem ? `numericalSystemOverrides:
|
|
674
|
-
hardCap: (从正文推断)
|
|
675
|
-
resourceTypes: [(从正文提取核心资源类型)]` : ""}
|
|
676
|
-
prohibitions:
|
|
677
|
-
- (从正文推断3-5条本书禁忌)
|
|
678
|
-
chapterTypesOverride: []
|
|
679
|
-
fatigueWordsOverride: []
|
|
680
|
-
additionalAuditDimensions: []
|
|
681
|
-
enableFullCastTracking: false
|
|
682
|
-
---
|
|
683
|
-
|
|
684
|
-
## 叙事视角
|
|
685
|
-
(从正文推断本书叙事视角和风格)
|
|
686
|
-
|
|
687
|
-
## 核心冲突驱动
|
|
688
|
-
(从正文推断本书的核心矛盾和驱动力)
|
|
689
|
-
\`\`\``;
|
|
690
|
-
const currentStatePrompt = resolvedLanguage === "en"
|
|
691
|
-
? `Reflect the state at the end of the latest chapter:
|
|
692
|
-
| Field | Value |
|
|
693
|
-
| --- | --- |
|
|
694
|
-
| Current Chapter | (latest chapter number) |
|
|
695
|
-
| Current Location | (location at the end of the latest chapter) |
|
|
696
|
-
| Protagonist State | (state at the end of the latest chapter) |
|
|
697
|
-
| Current Goal | (current goal) |
|
|
698
|
-
| Current Constraint | (current constraint) |
|
|
699
|
-
| Current Alliances | (current alliances / opposition) |
|
|
700
|
-
| Current Conflict | (current conflict) |`
|
|
701
|
-
: `反映最后一章结束时的状态卡:
|
|
702
|
-
| 字段 | 值 |
|
|
703
|
-
|------|-----|
|
|
704
|
-
| 当前章节 | (最后一章章节号) |
|
|
705
|
-
| 当前位置 | (最后一章结束时的位置) |
|
|
706
|
-
| 主角状态 | (最后一章结束时的状态) |
|
|
707
|
-
| 当前目标 | (当前目标) |
|
|
708
|
-
| 当前限制 | (当前限制) |
|
|
709
|
-
| 当前敌我 | (当前敌我关系) |
|
|
710
|
-
| 当前冲突 | (当前冲突) |`;
|
|
711
|
-
const pendingHooksPrompt = resolvedLanguage === "en"
|
|
712
|
-
? `Identify all active hooks from the source text (Markdown table):
|
|
713
|
-
| hook_id | start_chapter | type | status | latest_progress | expected_payoff | payoff_timing | notes |`
|
|
714
|
-
: `从正文中识别的所有伏笔(Markdown表格):
|
|
715
|
-
| hook_id | 起始章节 | 类型 | 状态 | 最近推进 | 预期回收 | 回收节奏 | 备注 |`;
|
|
716
|
-
const keyPrinciplesPrompt = resolvedLanguage === "en"
|
|
717
|
-
? `## Key Principles
|
|
718
|
-
|
|
719
|
-
1. Derive everything from the source text; do not invent unsupported settings
|
|
720
|
-
2. Hook extraction must be complete: unresolved clues, hints, and foreshadowing all count
|
|
721
|
-
3. Character inference must come from dialogue and behavior, not assumption
|
|
722
|
-
4. Accuracy first; detailed is better than missing crucial information
|
|
723
|
-
${numericalBlock}
|
|
724
|
-
${powerBlock}
|
|
725
|
-
${eraBlock}`
|
|
726
|
-
: `## 关键原则
|
|
727
|
-
|
|
728
|
-
1. 一切从正文出发,不要臆造正文中没有的设定
|
|
729
|
-
2. 伏笔识别要完整:悬而未决的线索、暗示、预告都算
|
|
730
|
-
3. 角色推断要准确:从对话和行为推断性格,不要想当然
|
|
731
|
-
4. 准确性优先,宁可详细也不要遗漏
|
|
732
|
-
${numericalBlock}
|
|
733
|
-
${powerBlock}
|
|
734
|
-
${eraBlock}`;
|
|
752
|
+
? "- No explicit numerical system"
|
|
753
|
+
: "- 本题材无数值系统");
|
|
735
754
|
const isSeries = options?.importMode === "series";
|
|
736
|
-
const
|
|
737
|
-
?
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
续写部分(volume_outline 中尚未发生的章节)必须设计**新的叙事空间**:
|
|
748
|
-
1. **新冲突维度**:续写不能只是把导入章节的冲突继续拉长。必须引入至少一个原文未涉及的新冲突方向(新角色、新势力、新地点、新时间跨度)
|
|
749
|
-
2. **5章内引爆**:续写的第一卷必须在前5章内建立新悬念,不允许用3章回顾已知信息
|
|
750
|
-
3. **场景新鲜度**:续写部分至少50%的关键场景发生在导入章节未出现的地点或情境中
|
|
751
|
-
4. **不重复会议**:如果导入章节以会议/讨论结束,续写必须从行动开始,不能再开一轮会`
|
|
752
|
-
: `## 续写方向
|
|
753
|
-
卷纲应自然延续已有故事线。从导入章节的结尾处接续——推进现有冲突、兑现已埋伏笔、引入从当前局势中有机产生的新变数。不要回顾已知信息。`;
|
|
754
|
-
const workingModeEn = isSeries
|
|
755
|
-
? `## Working Mode
|
|
756
|
-
|
|
757
|
-
This is not a zero-to-one foundation pass. You must extract durable story truth from the imported chapters **and design a continuation path**. You need to:
|
|
758
|
-
1. Extract worldbuilding, factions, characters, and systems from the source text -> generate story_bible
|
|
759
|
-
2. Infer narrative structure and future arc direction -> generate volume_outline (review existing chapters + design a **new continuation direction**)
|
|
760
|
-
3. Infer protagonist lock, prohibitions, and narrative constraints from character behavior -> generate book_rules
|
|
761
|
-
4. Reflect the latest chapter state -> generate current_state
|
|
762
|
-
5. Extract all active hooks already planted in the text -> generate pending_hooks`
|
|
763
|
-
: `## Working Mode
|
|
764
|
-
|
|
765
|
-
This is not a zero-to-one foundation pass. You must extract durable story truth from the imported chapters **and preserve a clean continuation path**. You need to:
|
|
766
|
-
1. Extract worldbuilding, factions, characters, and systems from the source text -> generate story_bible
|
|
767
|
-
2. Infer narrative structure and near-future arc direction -> generate volume_outline (review existing chapters + continue naturally from where the imported chapters stop)
|
|
768
|
-
3. Infer protagonist lock, prohibitions, and narrative constraints from character behavior -> generate book_rules
|
|
769
|
-
4. Reflect the latest chapter state -> generate current_state
|
|
770
|
-
5. Extract all active hooks already planted in the text -> generate pending_hooks`;
|
|
771
|
-
const workingModeZh = isSeries
|
|
772
|
-
? `## 工作模式
|
|
773
|
-
|
|
774
|
-
这不是从零创建,而是从已有正文中提取和推导,**并设计续写方向**。你需要:
|
|
775
|
-
1. 从正文中提取世界观、势力、角色、力量体系 → 生成 story_bible
|
|
776
|
-
2. 从叙事结构推断卷纲 → 生成 volume_outline(已有章节的回顾 + **续写部分的新方向设计**)
|
|
777
|
-
3. 从角色行为推断主角锁定和禁忌 → 生成 book_rules
|
|
778
|
-
4. 从最新章节状态推断 current_state(反映最后一章结束时的状态)
|
|
779
|
-
5. 从正文中识别已埋伏笔 → 生成 pending_hooks`
|
|
780
|
-
: `## 工作模式
|
|
781
|
-
|
|
782
|
-
这不是从零创建,而是从已有正文中提取和推导,**并为自然续写保留清晰延续路径**。你需要:
|
|
783
|
-
1. 从正文中提取世界观、势力、角色、力量体系 → 生成 story_bible
|
|
784
|
-
2. 从叙事结构推断卷纲 → 生成 volume_outline(回顾已有章节,并从导入章节结束处自然接续)
|
|
785
|
-
3. 从角色行为推断主角锁定和禁忌 → 生成 book_rules
|
|
786
|
-
4. 从最新章节状态推断 current_state(反映最后一章结束时的状态)
|
|
787
|
-
5. 从正文中识别已埋伏笔 → 生成 pending_hooks`;
|
|
755
|
+
const continuationDirective = resolvedLanguage === "en"
|
|
756
|
+
? (isSeries
|
|
757
|
+
? `## Continuation Direction Requirements
|
|
758
|
+
The continuation portion must open up new narrative space — new conflict vector, new location, new time horizon. Ignite within 5 chapters; at least 50% fresh scenes.`
|
|
759
|
+
: `## Continuation Direction
|
|
760
|
+
Naturally extend the existing arc. Advance existing conflicts, pay off planted hooks, introduce new complications organically.`)
|
|
761
|
+
: (isSeries
|
|
762
|
+
? `## 续写方向要求
|
|
763
|
+
续写必须引入新叙事空间——新冲突、新地点、新时间。5章内引爆,50%以上场景新鲜。`
|
|
764
|
+
: `## 续写方向
|
|
765
|
+
自然延续已有叙事弧线。推进现有冲突、兑现已埋伏笔、引入有机新变数。`);
|
|
788
766
|
const systemPrompt = resolvedLanguage === "en"
|
|
789
|
-
? `You are a professional
|
|
790
|
-
|
|
791
|
-
${workingModeEn}
|
|
792
|
-
|
|
793
|
-
All output sections — story_bible, volume_outline, book_rules, current_state, and pending_hooks — MUST be written in English. Keep the === SECTION: === tags unchanged.
|
|
794
|
-
|
|
795
|
-
${continuationDirectiveEn}
|
|
796
|
-
${reviewFeedbackBlock}
|
|
797
|
-
## Book Metadata
|
|
767
|
+
? `You are a professional novel architect. Reverse-engineer a prose-density foundation from the source chapters and write the continuation path.${contextBlock}${reviewFeedbackBlock}
|
|
798
768
|
|
|
769
|
+
## Book metadata
|
|
799
770
|
- Title: ${book.title}
|
|
800
771
|
- Platform: ${book.platform}
|
|
801
772
|
- Genre: ${gp.name} (${book.genre})
|
|
802
|
-
- Target
|
|
803
|
-
- Chapter
|
|
804
|
-
|
|
805
|
-
## Genre Profile
|
|
773
|
+
- Target chapters: ${book.targetChapters}
|
|
774
|
+
- Chapter length: ${book.chapterWordCount}
|
|
806
775
|
|
|
776
|
+
## Genre body
|
|
807
777
|
${genreBody}
|
|
808
778
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
Generate the following sections. Separate every section with === SECTION: <name> ===:
|
|
812
|
-
|
|
813
|
-
=== SECTION: story_bible ===
|
|
814
|
-
${storyBiblePrompt}
|
|
815
|
-
|
|
816
|
-
=== SECTION: volume_outline ===
|
|
817
|
-
${volumeOutlinePrompt}
|
|
818
|
-
|
|
819
|
-
=== SECTION: book_rules ===
|
|
820
|
-
${bookRulesPrompt}
|
|
779
|
+
${numericalBlock}
|
|
821
780
|
|
|
822
|
-
|
|
823
|
-
${currentStatePrompt}
|
|
781
|
+
${continuationDirective}
|
|
824
782
|
|
|
825
|
-
|
|
826
|
-
|
|
783
|
+
## Output contract
|
|
784
|
+
Follow the consolidated 5-section === SECTION: === layout: story_frame, volume_map, roles, book_rules, pending_hooks. Do NOT emit rhythm_principles or current_state — rhythm principles live in the last paragraph of volume_map; character initial status lives in roles.Current_State; initial hooks live in pending_hooks start_chapter=0 rows; era / setting anchors (only when the genre pins to a real year) are woven into story_frame's world-tonal-ground paragraph.
|
|
827
785
|
|
|
828
|
-
|
|
829
|
-
: `你是一个专业的网络小说架构师。你的任务是从已有的小说正文中反向推导完整的基础设定。${contextBlock}
|
|
786
|
+
All prose must be derived from the source package. Do not invent settings. If the package says it is compressed, treat chapter catalog + excerpts as evidence for the foundation; the full chapters will be replayed later for detailed truth files. For volume_map, treat existing chapters as "review" (one paragraph) and continuation as prose chapter-level planning. Hook extraction must be complete for the evidence provided.
|
|
830
787
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
${continuationDirectiveZh}
|
|
834
|
-
${reviewFeedbackBlock}
|
|
835
|
-
## 书籍信息
|
|
788
|
+
All output MUST be written in English.`
|
|
789
|
+
: `你是专业的网络小说架构师。从已有章节中反向推导散文密度的基础设定,同时设计续写路径。${contextBlock}${reviewFeedbackBlock}
|
|
836
790
|
|
|
791
|
+
## 书籍元信息
|
|
837
792
|
- 标题:${book.title}
|
|
838
793
|
- 平台:${book.platform}
|
|
839
794
|
- 题材:${gp.name}(${book.genre})
|
|
840
795
|
- 目标章数:${book.targetChapters}章
|
|
841
|
-
- 每章字数:${book.chapterWordCount}字
|
|
842
|
-
|
|
843
|
-
## 题材特征
|
|
844
796
|
|
|
797
|
+
## 题材底色
|
|
845
798
|
${genreBody}
|
|
846
799
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
你需要生成以下内容,每个部分用 === SECTION: <name> === 分隔:
|
|
850
|
-
|
|
851
|
-
=== SECTION: story_bible ===
|
|
852
|
-
${storyBiblePrompt}
|
|
853
|
-
|
|
854
|
-
=== SECTION: volume_outline ===
|
|
855
|
-
${volumeOutlinePrompt}
|
|
856
|
-
|
|
857
|
-
=== SECTION: book_rules ===
|
|
858
|
-
${bookRulesPrompt}
|
|
800
|
+
${numericalBlock}
|
|
859
801
|
|
|
860
|
-
|
|
861
|
-
${currentStatePrompt}
|
|
802
|
+
${continuationDirective}
|
|
862
803
|
|
|
863
|
-
|
|
864
|
-
|
|
804
|
+
## 输出契约
|
|
805
|
+
合并后的 5 段 === SECTION: === 结构:story_frame / volume_map / roles / book_rules / pending_hooks。**不要输出 rhythm_principles 或 current_state 两个 section**——节奏原则合并进 volume_map 尾段,角色初始状态合并进 roles.当前现状,初始钩子写在 pending_hooks startChapter=0 行;环境/时代锚(只有年代文 / 历史同人 / 都市重生等真实年份题材需要)织进 story_frame.世界观底色,其他题材直接省略。
|
|
865
806
|
|
|
866
|
-
|
|
807
|
+
所有 prose 必须从资料包中推导,不得臆造。若资料包声明为压缩包,把章节目录和正文摘录当作基础设定证据;完整章节会在后续回放阶段逐章进入 truth files。volume_map 中,已有章节作为"回顾段"(一段散文),续写部分写到章级 prose。伏笔识别以资料包提供的证据为准,尽量完整。`;
|
|
867
808
|
const userMessage = resolvedLanguage === "en"
|
|
868
809
|
? `Generate the complete foundation for an imported ${gp.name} novel titled "${book.title}". Write everything in English.\n\n${chaptersText}`
|
|
869
|
-
: `以下是《${book.title}
|
|
810
|
+
: `以下是《${book.title}》的已有正文资料包,请从中反向推导完整基础设定:\n\n${chaptersText}`;
|
|
870
811
|
const response = await this.chat([
|
|
871
812
|
{ role: "system", content: systemPrompt },
|
|
872
|
-
{
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
},
|
|
876
|
-
], { temperature: 0.5, maxTokens: 16384 });
|
|
877
|
-
return this.parseSections(response.content);
|
|
813
|
+
{ role: "user", content: userMessage },
|
|
814
|
+
], { temperature: 0.5 });
|
|
815
|
+
return this.parseSections(response.content, resolvedLanguage);
|
|
878
816
|
}
|
|
879
817
|
async generateFanficFoundation(book, fanficCanon, fanficMode, reviewFeedback) {
|
|
880
818
|
const { profile: gp, body: genreBody } = await readGenreProfile(this.ctx.projectRoot, book.genre);
|
|
@@ -885,75 +823,46 @@ ${keyPrinciplesPrompt}`;
|
|
|
885
823
|
ooc: "标注角色性格偏离的起点和驱动事件。偏离必须有逻辑驱动。",
|
|
886
824
|
cp: "以配对角色的关系线为主线规划卷纲。每卷必须有关系推进节点。",
|
|
887
825
|
};
|
|
888
|
-
const systemPrompt =
|
|
826
|
+
const systemPrompt = `你是专业同人架构师。基于原作正典为同人生成散文密度的基础设定。
|
|
889
827
|
|
|
890
828
|
## 同人模式:${fanficMode}
|
|
891
829
|
${MODE_INSTRUCTIONS[fanficMode]}
|
|
892
830
|
|
|
893
|
-
##
|
|
894
|
-
|
|
895
|
-
1.
|
|
896
|
-
2.
|
|
897
|
-
3.
|
|
898
|
-
4.
|
|
899
|
-
|
|
831
|
+
## 新时空要求
|
|
832
|
+
必须为这本同人设计原创叙事空间,不是复述原作剧情:
|
|
833
|
+
1. 明确分岔点——story_frame 必须标注本作从原作的哪个节点分岔
|
|
834
|
+
2. 独立核心冲突——volume_map 的核心冲突必须是原创的
|
|
835
|
+
3. 5章内引爆
|
|
836
|
+
4. 场景新鲜度 ≥ 50%
|
|
900
837
|
${reviewFeedbackBlock}
|
|
901
838
|
|
|
902
839
|
## 原作正典
|
|
903
840
|
${fanficCanon}
|
|
904
841
|
|
|
905
|
-
##
|
|
842
|
+
## 题材底色
|
|
906
843
|
${genreBody}
|
|
907
844
|
|
|
908
|
-
##
|
|
909
|
-
|
|
910
|
-
2. 可以添加原创配角,但必须在 story_bible 中标注为"原创角色"
|
|
911
|
-
3. story_bible 保留原作世界观,标注同人的改动/扩展部分,并明确写出**分岔点**和**新时空设定**
|
|
912
|
-
4. volume_outline 不得复述原作剧情节拍。每卷的核心事件必须是原创的,标注"原创"
|
|
913
|
-
5. book_rules 的 fanficMode 必须设为 "${fanficMode}"
|
|
914
|
-
6. 主角设定来自原作角色档案中的第一个角色(或用户在标题中暗示的角色)
|
|
915
|
-
|
|
916
|
-
你需要生成以下内容,每个部分用 === SECTION: <name> === 分隔:
|
|
917
|
-
|
|
918
|
-
=== SECTION: story_bible ===
|
|
919
|
-
世界观(基于原作正典)+ 角色列表(原作角色标注来源,原创角色标注"原创")
|
|
920
|
-
|
|
921
|
-
=== SECTION: volume_outline ===
|
|
922
|
-
卷纲规划。每卷标注:卷名、章节范围、核心事件(标注原作/原创)、关系发展节点
|
|
845
|
+
## 输出契约
|
|
846
|
+
严格按合并后的 5 段 === SECTION: === 块输出:story_frame / volume_map / roles / book_rules / pending_hooks。**不要输出 rhythm_principles 或 current_state**:节奏原则合并进 volume_map 尾段;角色初始状态写在 roles.当前现状,初始钩子写在 pending_hooks startChapter=0 行;环境/时代锚(仅当同人的原作/本作锚定真实年份时)织进 story_frame.世界观底色,其他情况省略。
|
|
923
847
|
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
personalityLock: [(从正典角色档案提取)]
|
|
931
|
-
behavioralConstraints: [(基于原作行为模式)]
|
|
932
|
-
genreLock:
|
|
933
|
-
primary: ${book.genre}
|
|
934
|
-
forbidden: []
|
|
935
|
-
fanficMode: "${fanficMode}"
|
|
936
|
-
allowedDeviations: []
|
|
937
|
-
prohibitions:
|
|
938
|
-
- (3-5条同人特有禁忌)
|
|
939
|
-
---
|
|
940
|
-
(叙事视角和风格指导)
|
|
941
|
-
\`\`\`
|
|
942
|
-
|
|
943
|
-
=== SECTION: current_state ===
|
|
944
|
-
初始状态卡(基于正典起始点)
|
|
945
|
-
|
|
946
|
-
=== SECTION: pending_hooks ===
|
|
947
|
-
初始伏笔池(从正典关键事件和关系中提取)`;
|
|
848
|
+
- 主要角色必须来自原作正典
|
|
849
|
+
- 可添加原创配角,标注"原创"
|
|
850
|
+
- book_rules 的 fanficMode 必须设为 "${fanficMode}"
|
|
851
|
+
- book_rules 只输出 YAML frontmatter,散文写进 story_frame.世界观底色
|
|
852
|
+
- 主角弧线只写在 roles/主要角色/<主角>.md,不在 story_frame 重复
|
|
853
|
+
- 所有 outline 必须是散文密度`;
|
|
948
854
|
const response = await this.chat([
|
|
949
855
|
{ role: "system", content: systemPrompt },
|
|
950
856
|
{
|
|
951
857
|
role: "user",
|
|
952
858
|
content: `请为标题为"${book.title}"的${fanficMode}模式同人小说生成基础设定。目标${book.targetChapters}章,每章${book.chapterWordCount}字。`,
|
|
953
859
|
},
|
|
954
|
-
], { temperature: 0.7
|
|
955
|
-
return this.parseSections(response.content);
|
|
860
|
+
], { temperature: 0.7 });
|
|
861
|
+
return this.parseSections(response.content, book.language ?? "zh");
|
|
956
862
|
}
|
|
863
|
+
// -------------------------------------------------------------------------
|
|
864
|
+
// Helpers
|
|
865
|
+
// -------------------------------------------------------------------------
|
|
957
866
|
buildReviewFeedbackBlock(reviewFeedback, language) {
|
|
958
867
|
const trimmed = reviewFeedback?.trim();
|
|
959
868
|
if (!trimmed)
|
|
@@ -969,122 +878,6 @@ ${trimmed}\n`;
|
|
|
969
878
|
|
|
970
879
|
${trimmed}\n`;
|
|
971
880
|
}
|
|
972
|
-
parseSections(content) {
|
|
973
|
-
const parsedSections = new Map();
|
|
974
|
-
const sectionPattern = /^\s*===\s*SECTION\s*[::]\s*([^\n=]+?)\s*===\s*$/gim;
|
|
975
|
-
const matches = [...content.matchAll(sectionPattern)];
|
|
976
|
-
for (let i = 0; i < matches.length; i++) {
|
|
977
|
-
const match = matches[i];
|
|
978
|
-
const rawName = match[1] ?? "";
|
|
979
|
-
const start = (match.index ?? 0) + match[0].length;
|
|
980
|
-
const end = matches[i + 1]?.index ?? content.length;
|
|
981
|
-
const normalizedName = this.normalizeSectionName(rawName);
|
|
982
|
-
parsedSections.set(normalizedName, content.slice(start, end).trim());
|
|
983
|
-
}
|
|
984
|
-
// Phase 5 新 sections
|
|
985
|
-
const storyFrame = parsedSections.get("story_frame") ?? "";
|
|
986
|
-
const volumeMap = parsedSections.get("volume_map") ?? "";
|
|
987
|
-
const rhythmPrinciples = parsedSections.get("rhythm_principles") ?? "";
|
|
988
|
-
const rolesRaw = parsedSections.get("roles") ?? "";
|
|
989
|
-
// Legacy sections — 当 LLM 还按老 prompt 输出时兜底。
|
|
990
|
-
const legacyStoryBible = parsedSections.get("story_bible") ?? "";
|
|
991
|
-
const legacyVolumeOutline = parsedSections.get("volume_outline") ?? "";
|
|
992
|
-
const bookRules = parsedSections.get("book_rules");
|
|
993
|
-
const currentStateLegacy = parsedSections.get("current_state") ?? "";
|
|
994
|
-
const pendingHooksRaw = parsedSections.get("pending_hooks");
|
|
995
|
-
// 用老名字输出且没有 story_frame/volume_map 时,roles 可空(走 legacy shim fallback)。
|
|
996
|
-
const usingLegacyOutlineNames = !storyFrame && !volumeMap
|
|
997
|
-
&& (legacyStoryBible.length > 0 || legacyVolumeOutline.length > 0);
|
|
998
|
-
const effectiveStoryFrame = storyFrame || legacyStoryBible;
|
|
999
|
-
const effectiveVolumeMap = volumeMap || legacyVolumeOutline;
|
|
1000
|
-
const missing = [];
|
|
1001
|
-
if (!effectiveStoryFrame)
|
|
1002
|
-
missing.push("story_frame");
|
|
1003
|
-
if (!effectiveVolumeMap)
|
|
1004
|
-
missing.push("volume_map");
|
|
1005
|
-
if (!rolesRaw.trim() && !usingLegacyOutlineNames)
|
|
1006
|
-
missing.push("roles");
|
|
1007
|
-
if (!bookRules)
|
|
1008
|
-
missing.push("book_rules");
|
|
1009
|
-
if (!pendingHooksRaw)
|
|
1010
|
-
missing.push("pending_hooks");
|
|
1011
|
-
if (missing.length > 0) {
|
|
1012
|
-
throw new Error(`Architect output missing required section${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}`);
|
|
1013
|
-
}
|
|
1014
|
-
const roles = this.parseRoles(rolesRaw);
|
|
1015
|
-
const pendingHooks = this.normalizePendingHooksSection(this.stripTrailingAssistantCoda(pendingHooksRaw));
|
|
1016
|
-
// Shim-facing 老字段:新 prompt 下用 buildStoryBibleShim 生成指针内容;
|
|
1017
|
-
// 老 prompt 下直接用老内容。volumeOutline 也同理。
|
|
1018
|
-
const storyBible = legacyStoryBible || effectiveStoryFrame;
|
|
1019
|
-
const volumeOutline = legacyVolumeOutline || effectiveVolumeMap;
|
|
1020
|
-
return {
|
|
1021
|
-
storyBible,
|
|
1022
|
-
volumeOutline,
|
|
1023
|
-
bookRules: bookRules,
|
|
1024
|
-
// current_state 在 Phase 5 下不是必需 section——writeFoundationFiles 会 seed
|
|
1025
|
-
// 一个占位文件,consolidator 运行时按章追加。
|
|
1026
|
-
currentState: currentStateLegacy,
|
|
1027
|
-
pendingHooks,
|
|
1028
|
-
storyFrame: effectiveStoryFrame,
|
|
1029
|
-
volumeMap: effectiveVolumeMap,
|
|
1030
|
-
rhythmPrinciples,
|
|
1031
|
-
roles,
|
|
1032
|
-
};
|
|
1033
|
-
}
|
|
1034
|
-
/** Parse ---ROLE---...---CONTENT--- 块。畸形块静默丢弃。 */
|
|
1035
|
-
parseRoles(raw) {
|
|
1036
|
-
if (!raw.trim())
|
|
1037
|
-
return [];
|
|
1038
|
-
const blocks = raw.split(/^---ROLE---$/m).map((chunk) => chunk.trim()).filter(Boolean);
|
|
1039
|
-
const roles = [];
|
|
1040
|
-
for (const block of blocks) {
|
|
1041
|
-
const contentSplit = block.split(/^---CONTENT---$/m);
|
|
1042
|
-
if (contentSplit.length < 2)
|
|
1043
|
-
continue;
|
|
1044
|
-
const headerRaw = contentSplit[0].trim();
|
|
1045
|
-
const content = contentSplit.slice(1).join("\n---CONTENT---\n").trim();
|
|
1046
|
-
const tierMatch = headerRaw.match(/tier\s*[::]\s*(major|minor|主要|次要)/i);
|
|
1047
|
-
const nameMatch = headerRaw.match(/name\s*[::]\s*(.+)/i);
|
|
1048
|
-
if (!tierMatch || !nameMatch)
|
|
1049
|
-
continue;
|
|
1050
|
-
const tierValue = tierMatch[1].toLowerCase();
|
|
1051
|
-
const tier = (tierValue === "major" || tierValue === "主要") ? "major" : "minor";
|
|
1052
|
-
const name = nameMatch[1].trim();
|
|
1053
|
-
if (!name || !content)
|
|
1054
|
-
continue;
|
|
1055
|
-
roles.push({ tier, name, content });
|
|
1056
|
-
}
|
|
1057
|
-
return roles;
|
|
1058
|
-
}
|
|
1059
|
-
buildStoryBibleShim(storyFrame, language) {
|
|
1060
|
-
if (language === "en") {
|
|
1061
|
-
return `# Story Bible (compat pointer — deprecated)\n\n> This file is kept for external readers only. The authoritative source is now:\n> - outline/story_frame.md (theme / tonal ground / core conflict / world rules / endgame)\n> - outline/volume_map.md (volume-level plot map)\n> - roles/ directory (one-file-per-character sheets)\n\n## Excerpt from story_frame\n\n${storyFrame.slice(0, 2000)}\n`;
|
|
1062
|
-
}
|
|
1063
|
-
return `# 故事圣经(兼容指针——已废弃)\n\n> 本文件仅为外部读取保留。权威来源已迁移至:\n> - outline/story_frame.md(主题 / 基调 / 核心冲突 / 世界铁律 / 终局)\n> - outline/volume_map.md(卷级分卷地图)\n> - roles/ 文件夹(一人一卡角色档案)\n\n## story_frame 摘录\n\n${storyFrame.slice(0, 2000)}\n`;
|
|
1064
|
-
}
|
|
1065
|
-
buildCharacterMatrixShim(roles, language) {
|
|
1066
|
-
const majorLines = roles.filter((role) => role.tier === "major")
|
|
1067
|
-
.map((role) => `- roles/主要角色/${role.name}.md`);
|
|
1068
|
-
const minorLines = roles.filter((role) => role.tier === "minor")
|
|
1069
|
-
.map((role) => `- roles/次要角色/${role.name}.md`);
|
|
1070
|
-
if (language === "en") {
|
|
1071
|
-
return `# Character Matrix (compat pointer — deprecated)\n\n> This file is kept for external readers only. Authoritative source is now the roles/ directory (one-file-per-character).\n\n## Major characters\n\n${majorLines.join("\n") || "(none)"}\n\n## Minor characters\n\n${minorLines.join("\n") || "(none)"}\n`;
|
|
1072
|
-
}
|
|
1073
|
-
return `# 角色矩阵(兼容指针——已废弃)\n\n> 本文件仅为外部读取保留。权威来源已迁移至 roles/ 文件夹(一人一卡)。\n\n## 主要角色\n\n${majorLines.join("\n") || "(无)"}\n\n## 次要角色\n\n${minorLines.join("\n") || "(无)"}\n`;
|
|
1074
|
-
}
|
|
1075
|
-
buildBookRulesShim(bookRulesBody, language) {
|
|
1076
|
-
const trimmedBody = bookRulesBody.trim();
|
|
1077
|
-
if (language === "en") {
|
|
1078
|
-
const excerpt = trimmedBody
|
|
1079
|
-
? `\n\n## Narrative guidance excerpt\n\n${trimmedBody}\n`
|
|
1080
|
-
: "";
|
|
1081
|
-
return `# Book Rules (compat pointer — deprecated)\n\n> This file is kept for external readers only. The authoritative YAML frontmatter now lives at the top of outline/story_frame.md.${excerpt}`;
|
|
1082
|
-
}
|
|
1083
|
-
const excerpt = trimmedBody
|
|
1084
|
-
? `\n\n## 叙事指引摘录\n\n${trimmedBody}\n`
|
|
1085
|
-
: "";
|
|
1086
|
-
return `# 本书规则(兼容指针——已废弃)\n\n> 本文件仅为外部读取保留。权威 YAML frontmatter 已迁移至 outline/story_frame.md 顶部。${excerpt}`;
|
|
1087
|
-
}
|
|
1088
881
|
normalizeSectionName(name) {
|
|
1089
882
|
return name
|
|
1090
883
|
.normalize("NFKC")
|
|
@@ -1106,7 +899,7 @@ ${trimmed}\n`;
|
|
|
1106
899
|
}
|
|
1107
900
|
return lines.slice(0, cutoff).join("\n").trimEnd();
|
|
1108
901
|
}
|
|
1109
|
-
normalizePendingHooksSection(section) {
|
|
902
|
+
normalizePendingHooksSection(section, volumeMapRaw) {
|
|
1110
903
|
const rows = section
|
|
1111
904
|
.split("\n")
|
|
1112
905
|
.map((line) => line.trim())
|
|
@@ -1128,19 +921,78 @@ ${trimmed}\n`;
|
|
|
1128
921
|
const seedNote = normalizedProgress === 0 && this.hasNarrativeProgress(rawProgress)
|
|
1129
922
|
? (language === "zh" ? `初始线索:${rawProgress}` : `initial signal: ${rawProgress}`)
|
|
1130
923
|
: "";
|
|
1131
|
-
const
|
|
1132
|
-
|
|
924
|
+
const phase7 = row.length >= 12;
|
|
925
|
+
const phase6 = row.length >= 8;
|
|
926
|
+
const noteCellIndex = phase7 ? 11 : phase6 ? 7 : 6;
|
|
927
|
+
const notes = this.mergeHookNotes(row[noteCellIndex] ?? "", seedNote, language);
|
|
928
|
+
const base = {
|
|
1133
929
|
hookId: row[0] || `hook-${index + 1}`,
|
|
1134
930
|
startChapter: this.parseHookChapterNumber(row[1]),
|
|
1135
931
|
type: row[2] ?? "",
|
|
1136
932
|
status: row[3] ?? "open",
|
|
1137
933
|
lastAdvancedChapter: normalizedProgress,
|
|
1138
934
|
expectedPayoff: row[5] ?? "",
|
|
1139
|
-
payoffTiming:
|
|
1140
|
-
notes
|
|
935
|
+
payoffTiming: phase6 ? row[6] ?? "" : "",
|
|
936
|
+
notes,
|
|
1141
937
|
};
|
|
938
|
+
if (phase7) {
|
|
939
|
+
base.dependsOn = this.parseDependsOnCell(row[7] ?? "");
|
|
940
|
+
base.paysOffInArc = (row[8] ?? "").trim();
|
|
941
|
+
base.coreHook = this.parseBooleanCell(row[9]);
|
|
942
|
+
const halfLife = this.parseOptionalInt(row[10]);
|
|
943
|
+
if (halfLife !== undefined)
|
|
944
|
+
base.halfLifeChapters = halfLife;
|
|
945
|
+
}
|
|
946
|
+
return base;
|
|
947
|
+
});
|
|
948
|
+
// Phase 7 hotfix 2: pre-promote seeds based on the three structural rules
|
|
949
|
+
// that don't need runtime advanced_count (core_hook / depends_on /
|
|
950
|
+
// cross_volume). advanced_count-based promotion is applied later by the
|
|
951
|
+
// consolidator at volume boundaries.
|
|
952
|
+
const volumeBoundaries = this.parseVolumeBoundariesForPromotion(volumeMapRaw);
|
|
953
|
+
const allSeedStartChapters = new Map(normalizedHooks.map((hook) => [hook.hookId, hook.startChapter]));
|
|
954
|
+
const promotionContext = {
|
|
955
|
+
volumeBoundaries,
|
|
956
|
+
currentChapter: 0,
|
|
957
|
+
advancedCounts: new Map(),
|
|
958
|
+
allSeedStartChapters,
|
|
959
|
+
};
|
|
960
|
+
const promotedHooks = normalizedHooks.map((hook) => {
|
|
961
|
+
const decision = shouldPromoteHook(hook, promotionContext);
|
|
962
|
+
return { ...hook, promoted: decision.promote };
|
|
1142
963
|
});
|
|
1143
|
-
return renderHookSnapshot(
|
|
964
|
+
return renderHookSnapshot(promotedHooks, language);
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Parse `第N卷 (A-B章)` / `Volume N (chapters A-B)` headers from the
|
|
968
|
+
* architect's volume_map prose. Best-effort: missing / unparseable blocks
|
|
969
|
+
* return an empty list and cross-volume promotion simply never fires.
|
|
970
|
+
*/
|
|
971
|
+
parseVolumeBoundariesForPromotion(raw) {
|
|
972
|
+
if (!raw)
|
|
973
|
+
return [];
|
|
974
|
+
const lines = raw.split("\n");
|
|
975
|
+
const volumeHeader = /^(第[一二三四五六七八九十百千万零〇\d]+卷|Volume\s+\d+)/i;
|
|
976
|
+
const rangePattern = /[((]\s*(?:第|[Cc]hapters?\s+)?(\d+)\s*[-–~~—]\s*(\d+)\s*(?:章)?\s*[))]|(?:第|[Cc]hapters?\s+)(\d+)\s*[-–~~—]\s*(\d+)\s*(?:章)?/i;
|
|
977
|
+
const volumes = [];
|
|
978
|
+
for (const rawLine of lines) {
|
|
979
|
+
const line = rawLine.replace(/^#+\s*/, "").trim();
|
|
980
|
+
if (!volumeHeader.test(line))
|
|
981
|
+
continue;
|
|
982
|
+
const rangeMatch = line.match(rangePattern);
|
|
983
|
+
if (!rangeMatch)
|
|
984
|
+
continue;
|
|
985
|
+
const startCh = parseInt(rangeMatch[1] ?? rangeMatch[3] ?? "0", 10);
|
|
986
|
+
const endCh = parseInt(rangeMatch[2] ?? rangeMatch[4] ?? "0", 10);
|
|
987
|
+
if (startCh <= 0 || endCh <= 0)
|
|
988
|
+
continue;
|
|
989
|
+
const rangeIndex = rangeMatch.index ?? line.length;
|
|
990
|
+
const name = line.slice(0, rangeIndex).replace(/[((]\s*$/, "").trim();
|
|
991
|
+
if (name.length > 0) {
|
|
992
|
+
volumes.push({ name, startCh, endCh });
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
return volumes;
|
|
1144
996
|
}
|
|
1145
997
|
parseHookChapterNumber(value) {
|
|
1146
998
|
if (!value)
|
|
@@ -1148,6 +1000,35 @@ ${trimmed}\n`;
|
|
|
1148
1000
|
const match = value.match(/\d+/);
|
|
1149
1001
|
return match ? parseInt(match[0], 10) : 0;
|
|
1150
1002
|
}
|
|
1003
|
+
parseDependsOnCell(value) {
|
|
1004
|
+
const trimmed = value.trim();
|
|
1005
|
+
if (!trimmed)
|
|
1006
|
+
return [];
|
|
1007
|
+
const lower = trimmed.toLowerCase();
|
|
1008
|
+
if (lower === "none" || lower === "n/a" || lower === "-" || trimmed === "无")
|
|
1009
|
+
return [];
|
|
1010
|
+
const stripped = trimmed.replace(/^[\[\(]\s*/, "").replace(/\s*[\]\)]$/, "");
|
|
1011
|
+
return stripped
|
|
1012
|
+
.split(/[,,、\/]+/)
|
|
1013
|
+
.map((item) => item.trim().replace(/^\*\*(.+)\*\*$/, "$1").trim())
|
|
1014
|
+
.filter((item) => item.length > 0);
|
|
1015
|
+
}
|
|
1016
|
+
parseBooleanCell(value) {
|
|
1017
|
+
const normalized = (value ?? "").trim().toLowerCase();
|
|
1018
|
+
if (!normalized)
|
|
1019
|
+
return false;
|
|
1020
|
+
return /^(true|yes|y|是|核心|core|1|✓|✔)$/.test(normalized);
|
|
1021
|
+
}
|
|
1022
|
+
parseOptionalInt(value) {
|
|
1023
|
+
const normalized = (value ?? "").trim();
|
|
1024
|
+
if (!normalized)
|
|
1025
|
+
return undefined;
|
|
1026
|
+
const match = normalized.match(/\d+/);
|
|
1027
|
+
if (!match)
|
|
1028
|
+
return undefined;
|
|
1029
|
+
const parsed = parseInt(match[0], 10);
|
|
1030
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
1031
|
+
}
|
|
1151
1032
|
hasNarrativeProgress(value) {
|
|
1152
1033
|
const normalized = (value ?? "").trim().toLowerCase();
|
|
1153
1034
|
if (!normalized)
|