inkos-n-core 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/ai-tells.d.ts +26 -0
- package/dist/agents/ai-tells.d.ts.map +1 -0
- package/dist/agents/ai-tells.js +140 -0
- package/dist/agents/ai-tells.js.map +1 -0
- package/dist/agents/architect.d.ts +34 -0
- package/dist/agents/architect.d.ts.map +1 -0
- package/dist/agents/architect.js +906 -0
- package/dist/agents/architect.js.map +1 -0
- package/dist/agents/base.d.ts +30 -0
- package/dist/agents/base.d.ts.map +1 -0
- package/dist/agents/base.js +70 -0
- package/dist/agents/base.js.map +1 -0
- package/dist/agents/chapter-analyzer.d.ts +30 -0
- package/dist/agents/chapter-analyzer.d.ts.map +1 -0
- package/dist/agents/chapter-analyzer.js +476 -0
- package/dist/agents/chapter-analyzer.js.map +1 -0
- package/dist/agents/composer.d.ts +36 -0
- package/dist/agents/composer.d.ts.map +1 -0
- package/dist/agents/composer.js +319 -0
- package/dist/agents/composer.js.map +1 -0
- package/dist/agents/consolidator.d.ts +23 -0
- package/dist/agents/consolidator.d.ts.map +1 -0
- package/dist/agents/consolidator.js +141 -0
- package/dist/agents/consolidator.js.map +1 -0
- package/dist/agents/continuity.d.ts +39 -0
- package/dist/agents/continuity.d.ts.map +1 -0
- package/dist/agents/continuity.js +612 -0
- package/dist/agents/continuity.js.map +1 -0
- package/dist/agents/detection-insights.d.ts +9 -0
- package/dist/agents/detection-insights.d.ts.map +1 -0
- package/dist/agents/detection-insights.js +54 -0
- package/dist/agents/detection-insights.js.map +1 -0
- package/dist/agents/detector.d.ts +17 -0
- package/dist/agents/detector.d.ts.map +1 -0
- package/dist/agents/detector.js +77 -0
- package/dist/agents/detector.js.map +1 -0
- package/dist/agents/en-prompt-sections.d.ts +8 -0
- package/dist/agents/en-prompt-sections.d.ts.map +1 -0
- package/dist/agents/en-prompt-sections.js +120 -0
- package/dist/agents/en-prompt-sections.js.map +1 -0
- package/dist/agents/fanfic-canon-importer.d.ts +15 -0
- package/dist/agents/fanfic-canon-importer.d.ts.map +1 -0
- package/dist/agents/fanfic-canon-importer.js +117 -0
- package/dist/agents/fanfic-canon-importer.js.map +1 -0
- package/dist/agents/fanfic-dimensions.d.ts +14 -0
- package/dist/agents/fanfic-dimensions.d.ts.map +1 -0
- package/dist/agents/fanfic-dimensions.js +63 -0
- package/dist/agents/fanfic-dimensions.js.map +1 -0
- package/dist/agents/fanfic-prompt-sections.d.ts +5 -0
- package/dist/agents/fanfic-prompt-sections.d.ts.map +1 -0
- package/dist/agents/fanfic-prompt-sections.js +85 -0
- package/dist/agents/fanfic-prompt-sections.js.map +1 -0
- package/dist/agents/foundation-reviewer.d.ts +29 -0
- package/dist/agents/foundation-reviewer.d.ts.map +1 -0
- package/dist/agents/foundation-reviewer.js +153 -0
- package/dist/agents/foundation-reviewer.js.map +1 -0
- package/dist/agents/length-normalizer.d.ts +32 -0
- package/dist/agents/length-normalizer.d.ts.map +1 -0
- package/dist/agents/length-normalizer.js +156 -0
- package/dist/agents/length-normalizer.js.map +1 -0
- package/dist/agents/observer-prompts.d.ts +10 -0
- package/dist/agents/observer-prompts.d.ts.map +1 -0
- package/dist/agents/observer-prompts.js +113 -0
- package/dist/agents/observer-prompts.js.map +1 -0
- package/dist/agents/planner.d.ts +57 -0
- package/dist/agents/planner.d.ts.map +1 -0
- package/dist/agents/planner.js +594 -0
- package/dist/agents/planner.js.map +1 -0
- package/dist/agents/post-write-validator.d.ts +34 -0
- package/dist/agents/post-write-validator.d.ts.map +1 -0
- package/dist/agents/post-write-validator.js +696 -0
- package/dist/agents/post-write-validator.js.map +1 -0
- package/dist/agents/radar-source.d.ts +38 -0
- package/dist/agents/radar-source.d.ts.map +1 -0
- package/dist/agents/radar-source.js +92 -0
- package/dist/agents/radar-source.js.map +1 -0
- package/dist/agents/radar.d.ts +24 -0
- package/dist/agents/radar.d.ts.map +1 -0
- package/dist/agents/radar.js +85 -0
- package/dist/agents/radar.js.map +1 -0
- package/dist/agents/reviser.d.ts +32 -0
- package/dist/agents/reviser.d.ts.map +1 -0
- package/dist/agents/reviser.js +282 -0
- package/dist/agents/reviser.js.map +1 -0
- package/dist/agents/rules-reader.d.ts +27 -0
- package/dist/agents/rules-reader.d.ts.map +1 -0
- package/dist/agents/rules-reader.js +99 -0
- package/dist/agents/rules-reader.js.map +1 -0
- package/dist/agents/sensitive-words.d.ts +24 -0
- package/dist/agents/sensitive-words.d.ts.map +1 -0
- package/dist/agents/sensitive-words.js +103 -0
- package/dist/agents/sensitive-words.js.map +1 -0
- package/dist/agents/settler-delta-parser.d.ts +7 -0
- package/dist/agents/settler-delta-parser.d.ts.map +1 -0
- package/dist/agents/settler-delta-parser.js +40 -0
- package/dist/agents/settler-delta-parser.js.map +1 -0
- package/dist/agents/settler-parser.d.ts +13 -0
- package/dist/agents/settler-parser.d.ts.map +1 -0
- package/dist/agents/settler-parser.js +20 -0
- package/dist/agents/settler-parser.js.map +1 -0
- package/dist/agents/settler-prompts.d.ts +22 -0
- package/dist/agents/settler-prompts.d.ts.map +1 -0
- package/dist/agents/settler-prompts.js +193 -0
- package/dist/agents/settler-prompts.js.map +1 -0
- package/dist/agents/state-validator.d.ts +26 -0
- package/dist/agents/state-validator.d.ts.map +1 -0
- package/dist/agents/state-validator.js +229 -0
- package/dist/agents/state-validator.js.map +1 -0
- package/dist/agents/style-analyzer.d.ts +11 -0
- package/dist/agents/style-analyzer.d.ts.map +1 -0
- package/dist/agents/style-analyzer.js +81 -0
- package/dist/agents/style-analyzer.js.map +1 -0
- package/dist/agents/writer-parser.d.ts +17 -0
- package/dist/agents/writer-parser.d.ts.map +1 -0
- package/dist/agents/writer-parser.js +131 -0
- package/dist/agents/writer-parser.js.map +1 -0
- package/dist/agents/writer-prompts.d.ts +11 -0
- package/dist/agents/writer-prompts.d.ts.map +1 -0
- package/dist/agents/writer-prompts.js +549 -0
- package/dist/agents/writer-prompts.js.map +1 -0
- package/dist/agents/writer.d.ts +103 -0
- package/dist/agents/writer.d.ts.map +1 -0
- package/dist/agents/writer.js +1052 -0
- package/dist/agents/writer.js.map +1 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +84 -0
- package/dist/index.js.map +1 -0
- package/dist/interaction/edit-controller.d.ts +55 -0
- package/dist/interaction/edit-controller.d.ts.map +1 -0
- package/dist/interaction/edit-controller.js +187 -0
- package/dist/interaction/edit-controller.js.map +1 -0
- package/dist/interaction/events.d.ts +45 -0
- package/dist/interaction/events.d.ts.map +1 -0
- package/dist/interaction/events.js +32 -0
- package/dist/interaction/events.js.map +1 -0
- package/dist/interaction/intents.d.ts +96 -0
- package/dist/interaction/intents.d.ts.map +1 -0
- package/dist/interaction/intents.js +58 -0
- package/dist/interaction/intents.js.map +1 -0
- package/dist/interaction/modes.d.ts +5 -0
- package/dist/interaction/modes.d.ts.map +1 -0
- package/dist/interaction/modes.js +7 -0
- package/dist/interaction/modes.js.map +1 -0
- package/dist/interaction/nl-router.d.ts +8 -0
- package/dist/interaction/nl-router.d.ts.map +1 -0
- package/dist/interaction/nl-router.js +218 -0
- package/dist/interaction/nl-router.js.map +1 -0
- package/dist/interaction/project-control.d.ts +85 -0
- package/dist/interaction/project-control.d.ts.map +1 -0
- package/dist/interaction/project-control.js +123 -0
- package/dist/interaction/project-control.js.map +1 -0
- package/dist/interaction/project-session-store.d.ts +7 -0
- package/dist/interaction/project-session-store.d.ts.map +1 -0
- package/dist/interaction/project-session-store.js +46 -0
- package/dist/interaction/project-session-store.js.map +1 -0
- package/dist/interaction/project-tools.d.ts +20 -0
- package/dist/interaction/project-tools.d.ts.map +1 -0
- package/dist/interaction/project-tools.js +527 -0
- package/dist/interaction/project-tools.js.map +1 -0
- package/dist/interaction/request-router.d.ts +3 -0
- package/dist/interaction/request-router.d.ts.map +1 -0
- package/dist/interaction/request-router.js +5 -0
- package/dist/interaction/request-router.js.map +1 -0
- package/dist/interaction/runtime.d.ts +54 -0
- package/dist/interaction/runtime.d.ts.map +1 -0
- package/dist/interaction/runtime.js +943 -0
- package/dist/interaction/runtime.js.map +1 -0
- package/dist/interaction/session.d.ts +352 -0
- package/dist/interaction/session.d.ts.map +1 -0
- package/dist/interaction/session.js +98 -0
- package/dist/interaction/session.js.map +1 -0
- package/dist/interaction/truth-authority.d.ts +4 -0
- package/dist/interaction/truth-authority.d.ts.map +1 -0
- package/dist/interaction/truth-authority.js +37 -0
- package/dist/interaction/truth-authority.js.map +1 -0
- package/dist/llm/provider.d.ts +87 -0
- package/dist/llm/provider.d.ts.map +1 -0
- package/dist/llm/provider.js +798 -0
- package/dist/llm/provider.js.map +1 -0
- package/dist/models/book-rules.d.ts +118 -0
- package/dist/models/book-rules.d.ts.map +1 -0
- package/dist/models/book-rules.js +55 -0
- package/dist/models/book-rules.js.map +1 -0
- package/dist/models/book.d.ts +51 -0
- package/dist/models/book.d.ts.map +1 -0
- package/dist/models/book.js +27 -0
- package/dist/models/book.js.map +1 -0
- package/dist/models/chapter.d.ts +136 -0
- package/dist/models/chapter.d.ts.map +1 -0
- package/dist/models/chapter.js +38 -0
- package/dist/models/chapter.js.map +1 -0
- package/dist/models/detection.d.ts +25 -0
- package/dist/models/detection.d.ts.map +1 -0
- package/dist/models/detection.js +2 -0
- package/dist/models/detection.js.map +1 -0
- package/dist/models/genre-profile.d.ts +45 -0
- package/dist/models/genre-profile.d.ts.map +1 -0
- package/dist/models/genre-profile.js +26 -0
- package/dist/models/genre-profile.js.map +1 -0
- package/dist/models/input-governance.d.ts +516 -0
- package/dist/models/input-governance.d.ts.map +1 -0
- package/dist/models/input-governance.js +112 -0
- package/dist/models/input-governance.js.map +1 -0
- package/dist/models/length-governance.d.ts +93 -0
- package/dist/models/length-governance.d.ts.map +1 -0
- package/dist/models/length-governance.js +34 -0
- package/dist/models/length-governance.js.map +1 -0
- package/dist/models/project.d.ts +475 -0
- package/dist/models/project.d.ts.map +1 -0
- package/dist/models/project.js +100 -0
- package/dist/models/project.js.map +1 -0
- package/dist/models/runtime-state.d.ts +588 -0
- package/dist/models/runtime-state.d.ts.map +1 -0
- package/dist/models/runtime-state.js +93 -0
- package/dist/models/runtime-state.js.map +1 -0
- package/dist/models/state.d.ts +48 -0
- package/dist/models/state.d.ts.map +1 -0
- package/dist/models/state.js +6 -0
- package/dist/models/state.js.map +1 -0
- package/dist/models/style-profile.d.ts +16 -0
- package/dist/models/style-profile.d.ts.map +1 -0
- package/dist/models/style-profile.js +2 -0
- package/dist/models/style-profile.js.map +1 -0
- package/dist/notify/dispatcher.d.ts +10 -0
- package/dist/notify/dispatcher.d.ts.map +1 -0
- package/dist/notify/dispatcher.js +55 -0
- package/dist/notify/dispatcher.js.map +1 -0
- package/dist/notify/feishu.d.ts +5 -0
- package/dist/notify/feishu.d.ts.map +1 -0
- package/dist/notify/feishu.js +26 -0
- package/dist/notify/feishu.js.map +1 -0
- package/dist/notify/telegram.d.ts +6 -0
- package/dist/notify/telegram.d.ts.map +1 -0
- package/dist/notify/telegram.js +17 -0
- package/dist/notify/telegram.js.map +1 -0
- package/dist/notify/webhook.d.ts +15 -0
- package/dist/notify/webhook.d.ts.map +1 -0
- package/dist/notify/webhook.js +28 -0
- package/dist/notify/webhook.js.map +1 -0
- package/dist/notify/wechat-work.d.ts +5 -0
- package/dist/notify/wechat-work.d.ts.map +1 -0
- package/dist/notify/wechat-work.js +15 -0
- package/dist/notify/wechat-work.js.map +1 -0
- package/dist/pipeline/agent.d.ts +15 -0
- package/dist/pipeline/agent.d.ts.map +1 -0
- package/dist/pipeline/agent.js +550 -0
- package/dist/pipeline/agent.js.map +1 -0
- package/dist/pipeline/chapter-persistence.d.ts +33 -0
- package/dist/pipeline/chapter-persistence.d.ts.map +1 -0
- package/dist/pipeline/chapter-persistence.js +35 -0
- package/dist/pipeline/chapter-persistence.js.map +1 -0
- package/dist/pipeline/chapter-review-cycle.d.ts +79 -0
- package/dist/pipeline/chapter-review-cycle.d.ts.map +1 -0
- package/dist/pipeline/chapter-review-cycle.js +97 -0
- package/dist/pipeline/chapter-review-cycle.js.map +1 -0
- package/dist/pipeline/chapter-state-recovery.d.ts +59 -0
- package/dist/pipeline/chapter-state-recovery.d.ts.map +1 -0
- package/dist/pipeline/chapter-state-recovery.js +133 -0
- package/dist/pipeline/chapter-state-recovery.js.map +1 -0
- package/dist/pipeline/chapter-truth-validation.d.ts +41 -0
- package/dist/pipeline/chapter-truth-validation.d.ts.map +1 -0
- package/dist/pipeline/chapter-truth-validation.js +67 -0
- package/dist/pipeline/chapter-truth-validation.js.map +1 -0
- package/dist/pipeline/detection-runner.d.ts +31 -0
- package/dist/pipeline/detection-runner.d.ts.map +1 -0
- package/dist/pipeline/detection-runner.js +109 -0
- package/dist/pipeline/detection-runner.js.map +1 -0
- package/dist/pipeline/persisted-governed-plan.d.ts +4 -0
- package/dist/pipeline/persisted-governed-plan.d.ts.map +1 -0
- package/dist/pipeline/persisted-governed-plan.js +85 -0
- package/dist/pipeline/persisted-governed-plan.js.map +1 -0
- package/dist/pipeline/runner.d.ts +212 -0
- package/dist/pipeline/runner.d.ts.map +1 -0
- package/dist/pipeline/runner.js +2265 -0
- package/dist/pipeline/runner.js.map +1 -0
- package/dist/pipeline/scheduler.d.ts +58 -0
- package/dist/pipeline/scheduler.d.ts.map +1 -0
- package/dist/pipeline/scheduler.js +322 -0
- package/dist/pipeline/scheduler.js.map +1 -0
- package/dist/state/manager.d.ts +48 -0
- package/dist/state/manager.d.ts.map +1 -0
- package/dist/state/manager.js +435 -0
- package/dist/state/manager.js.map +1 -0
- package/dist/state/memory-db.d.ts +77 -0
- package/dist/state/memory-db.d.ts.map +1 -0
- package/dist/state/memory-db.js +249 -0
- package/dist/state/memory-db.js.map +1 -0
- package/dist/state/runtime-state-store.d.ts +25 -0
- package/dist/state/runtime-state-store.d.ts.map +1 -0
- package/dist/state/runtime-state-store.js +108 -0
- package/dist/state/runtime-state-store.js.map +1 -0
- package/dist/state/state-bootstrap.d.ts +21 -0
- package/dist/state/state-bootstrap.d.ts.map +1 -0
- package/dist/state/state-bootstrap.js +434 -0
- package/dist/state/state-bootstrap.js.map +1 -0
- package/dist/state/state-projections.d.ts +5 -0
- package/dist/state/state-projections.d.ts.map +1 -0
- package/dist/state/state-projections.js +166 -0
- package/dist/state/state-projections.js.map +1 -0
- package/dist/state/state-reducer.d.ts +13 -0
- package/dist/state/state-reducer.d.ts.map +1 -0
- package/dist/state/state-reducer.js +194 -0
- package/dist/state/state-reducer.js.map +1 -0
- package/dist/state/state-validator.d.ts +12 -0
- package/dist/state/state-validator.d.ts.map +1 -0
- package/dist/state/state-validator.js +67 -0
- package/dist/state/state-validator.js.map +1 -0
- package/dist/utils/analytics.d.ts +39 -0
- package/dist/utils/analytics.d.ts.map +1 -0
- package/dist/utils/analytics.js +50 -0
- package/dist/utils/analytics.js.map +1 -0
- package/dist/utils/cadence-policy.d.ts +36 -0
- package/dist/utils/cadence-policy.d.ts.map +1 -0
- package/dist/utils/cadence-policy.js +38 -0
- package/dist/utils/cadence-policy.js.map +1 -0
- package/dist/utils/chapter-cadence.d.ts +34 -0
- package/dist/utils/chapter-cadence.d.ts.map +1 -0
- package/dist/utils/chapter-cadence.js +142 -0
- package/dist/utils/chapter-cadence.js.map +1 -0
- package/dist/utils/chapter-splitter.d.ts +18 -0
- package/dist/utils/chapter-splitter.d.ts.map +1 -0
- package/dist/utils/chapter-splitter.js +60 -0
- package/dist/utils/chapter-splitter.js.map +1 -0
- package/dist/utils/config-loader.d.ts +15 -0
- package/dist/utils/config-loader.d.ts.map +1 -0
- package/dist/utils/config-loader.js +136 -0
- package/dist/utils/config-loader.js.map +1 -0
- package/dist/utils/context-filter.d.ts +20 -0
- package/dist/utils/context-filter.d.ts.map +1 -0
- package/dist/utils/context-filter.js +134 -0
- package/dist/utils/context-filter.js.map +1 -0
- package/dist/utils/governed-context.d.ts +11 -0
- package/dist/utils/governed-context.d.ts.map +1 -0
- package/dist/utils/governed-context.js +42 -0
- package/dist/utils/governed-context.js.map +1 -0
- package/dist/utils/governed-working-set.d.ts +18 -0
- package/dist/utils/governed-working-set.d.ts.map +1 -0
- package/dist/utils/governed-working-set.js +296 -0
- package/dist/utils/governed-working-set.js.map +1 -0
- package/dist/utils/hook-agenda.d.ts +21 -0
- package/dist/utils/hook-agenda.d.ts.map +1 -0
- package/dist/utils/hook-agenda.js +95 -0
- package/dist/utils/hook-agenda.js.map +1 -0
- package/dist/utils/hook-arbiter.d.ts +15 -0
- package/dist/utils/hook-arbiter.d.ts.map +1 -0
- package/dist/utils/hook-arbiter.js +268 -0
- package/dist/utils/hook-arbiter.js.map +1 -0
- package/dist/utils/hook-governance.d.ts +28 -0
- package/dist/utils/hook-governance.d.ts.map +1 -0
- package/dist/utils/hook-governance.js +144 -0
- package/dist/utils/hook-governance.js.map +1 -0
- package/dist/utils/hook-health.d.ts +15 -0
- package/dist/utils/hook-health.d.ts.map +1 -0
- package/dist/utils/hook-health.js +128 -0
- package/dist/utils/hook-health.js.map +1 -0
- package/dist/utils/hook-lifecycle.d.ts +34 -0
- package/dist/utils/hook-lifecycle.d.ts.map +1 -0
- package/dist/utils/hook-lifecycle.js +125 -0
- package/dist/utils/hook-lifecycle.js.map +1 -0
- package/dist/utils/hook-policy.d.ts +74 -0
- package/dist/utils/hook-policy.d.ts.map +1 -0
- package/dist/utils/hook-policy.js +126 -0
- package/dist/utils/hook-policy.js.map +1 -0
- package/dist/utils/length-metrics.d.ts +10 -0
- package/dist/utils/length-metrics.d.ts.map +1 -0
- package/dist/utils/length-metrics.js +85 -0
- package/dist/utils/length-metrics.js.map +1 -0
- package/dist/utils/logger.d.ts +31 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +79 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/long-span-fatigue.d.ts +28 -0
- package/dist/utils/long-span-fatigue.d.ts.map +1 -0
- package/dist/utils/long-span-fatigue.js +406 -0
- package/dist/utils/long-span-fatigue.js.map +1 -0
- package/dist/utils/memory-retrieval.d.ts +25 -0
- package/dist/utils/memory-retrieval.d.ts.map +1 -0
- package/dist/utils/memory-retrieval.js +319 -0
- package/dist/utils/memory-retrieval.js.map +1 -0
- package/dist/utils/pov-filter.d.ts +30 -0
- package/dist/utils/pov-filter.d.ts.map +1 -0
- package/dist/utils/pov-filter.js +129 -0
- package/dist/utils/pov-filter.js.map +1 -0
- package/dist/utils/spot-fix-patches.d.ts +14 -0
- package/dist/utils/spot-fix-patches.d.ts.map +1 -0
- package/dist/utils/spot-fix-patches.js +75 -0
- package/dist/utils/spot-fix-patches.js.map +1 -0
- package/dist/utils/story-markdown.d.ts +13 -0
- package/dist/utils/story-markdown.d.ts.map +1 -0
- package/dist/utils/story-markdown.js +218 -0
- package/dist/utils/story-markdown.js.map +1 -0
- package/dist/utils/web-search.d.ts +23 -0
- package/dist/utils/web-search.d.ts.map +1 -0
- package/dist/utils/web-search.js +68 -0
- package/dist/utils/web-search.js.map +1 -0
- package/genres/cozy.md +43 -0
- package/genres/cultivation.md +42 -0
- package/genres/dungeon-core.md +40 -0
- package/genres/horror.md +51 -0
- package/genres/isekai.md +43 -0
- package/genres/litrpg.md +43 -0
- package/genres/other.md +24 -0
- package/genres/progression.md +41 -0
- package/genres/romantasy.md +45 -0
- package/genres/sci-fi.md +42 -0
- package/genres/system-apocalypse.md +40 -0
- package/genres/tower-climber.md +41 -0
- package/genres/urban.md +53 -0
- package/genres/xianxia.md +46 -0
- package/genres/xuanhuan.md +64 -0
- package/package.json +58 -0
|
@@ -0,0 +1,1052 @@
|
|
|
1
|
+
import { BaseAgent } from "./base.js";
|
|
2
|
+
import { buildWriterSystemPrompt } from "./writer-prompts.js";
|
|
3
|
+
import { buildSettlerSystemPrompt, buildSettlerUserPrompt } from "./settler-prompts.js";
|
|
4
|
+
import { buildObserverSystemPrompt, buildObserverUserPrompt } from "./observer-prompts.js";
|
|
5
|
+
import { parseSettlerDeltaOutput } from "./settler-delta-parser.js";
|
|
6
|
+
import { parseSettlementOutput } from "./settler-parser.js";
|
|
7
|
+
import { readGenreProfile, readBookRules } from "./rules-reader.js";
|
|
8
|
+
import { detectCrossChapterRepetition, detectParagraphLengthDrift, validatePostWrite, } from "./post-write-validator.js";
|
|
9
|
+
import { analyzeAITells } from "./ai-tells.js";
|
|
10
|
+
import { buildLengthSpec, countChapterLength } from "../utils/length-metrics.js";
|
|
11
|
+
import { filterHooks, filterSummaries, filterSubplots, filterEmotionalArcs, filterCharacterMatrix } from "../utils/context-filter.js";
|
|
12
|
+
import { buildGovernedMemoryEvidenceBlocks } from "../utils/governed-context.js";
|
|
13
|
+
import { buildGovernedCharacterMatrixWorkingSet, buildGovernedHookWorkingSet, mergeCharacterMatrixMarkdown, mergeTableMarkdownByKey, } from "../utils/governed-working-set.js";
|
|
14
|
+
import { extractPOVFromOutline, filterMatrixByPOV, filterHooksByPOV } from "../utils/pov-filter.js";
|
|
15
|
+
import { parseCreativeOutput } from "./writer-parser.js";
|
|
16
|
+
import { buildRuntimeStateArtifacts, saveRuntimeStateSnapshot } from "../state/runtime-state-store.js";
|
|
17
|
+
import { parsePendingHooksMarkdown } from "../utils/memory-retrieval.js";
|
|
18
|
+
import { analyzeHookHealth } from "../utils/hook-health.js";
|
|
19
|
+
import { buildEnglishVarianceBrief } from "../utils/long-span-fatigue.js";
|
|
20
|
+
import { readFile, writeFile, mkdir, readdir } from "node:fs/promises";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
export class WriterAgent extends BaseAgent {
|
|
23
|
+
get name() {
|
|
24
|
+
return "writer";
|
|
25
|
+
}
|
|
26
|
+
localize(language, messages) {
|
|
27
|
+
return language === "en" ? messages.en : messages.zh;
|
|
28
|
+
}
|
|
29
|
+
logInfo(language, messages) {
|
|
30
|
+
this.ctx.logger?.info(this.localize(language, messages));
|
|
31
|
+
}
|
|
32
|
+
logWarn(language, messages) {
|
|
33
|
+
this.ctx.logger?.warn(this.localize(language, messages));
|
|
34
|
+
}
|
|
35
|
+
async writeChapter(input) {
|
|
36
|
+
const { book, bookDir, chapterNumber } = input;
|
|
37
|
+
const [storyBible, volumeOutline, styleGuide, currentState, ledger, hooks, chapterSummaries, subplotBoard, emotionalArcs, characterMatrix, styleProfileRaw, parentCanon, fanficCanonRaw,] = await Promise.all([
|
|
38
|
+
this.readFileOrDefault(join(bookDir, "story/story_bible.md")),
|
|
39
|
+
this.readFileOrDefault(join(bookDir, "story/volume_outline.md")),
|
|
40
|
+
this.readFileOrDefault(join(bookDir, "story/style_guide.md")),
|
|
41
|
+
this.readFileOrDefault(join(bookDir, "story/current_state.md")),
|
|
42
|
+
this.readFileOrDefault(join(bookDir, "story/particle_ledger.md")),
|
|
43
|
+
this.readFileOrDefault(join(bookDir, "story/pending_hooks.md")),
|
|
44
|
+
this.readFileOrDefault(join(bookDir, "story/chapter_summaries.md")),
|
|
45
|
+
this.readFileOrDefault(join(bookDir, "story/subplot_board.md")),
|
|
46
|
+
this.readFileOrDefault(join(bookDir, "story/emotional_arcs.md")),
|
|
47
|
+
this.readFileOrDefault(join(bookDir, "story/character_matrix.md")),
|
|
48
|
+
this.readFileOrDefault(join(bookDir, "story/style_profile.json")),
|
|
49
|
+
this.readFileOrDefault(join(bookDir, "story/parent_canon.md")),
|
|
50
|
+
this.readFileOrDefault(join(bookDir, "story/fanfic_canon.md")),
|
|
51
|
+
]);
|
|
52
|
+
const recentChapters = await this.loadRecentChapters(bookDir, chapterNumber);
|
|
53
|
+
// Load more chapters for dialogue fingerprint extraction (voice consistency over longer span)
|
|
54
|
+
const fingerprintChapters = await this.loadRecentChapters(bookDir, chapterNumber, 5);
|
|
55
|
+
// Load genre profile + book rules
|
|
56
|
+
const { profile: genreProfile, body: genreBody } = await readGenreProfile(this.ctx.projectRoot, book.genre);
|
|
57
|
+
const parsedBookRules = await readBookRules(bookDir);
|
|
58
|
+
const bookRules = parsedBookRules?.rules ?? null;
|
|
59
|
+
const bookRulesBody = parsedBookRules?.body ?? "";
|
|
60
|
+
const styleFingerprint = this.buildStyleFingerprint(styleProfileRaw);
|
|
61
|
+
const dialogueFingerprints = this.extractDialogueFingerprints(fingerprintChapters, storyBible);
|
|
62
|
+
const relevantSummaries = this.findRelevantSummaries(chapterSummaries, volumeOutline, chapterNumber);
|
|
63
|
+
const hasParentCanon = parentCanon !== "(文件尚未创建)";
|
|
64
|
+
const hasFanficCanon = fanficCanonRaw !== "(文件尚未创建)";
|
|
65
|
+
const resolvedLanguage = book.language ?? genreProfile.language;
|
|
66
|
+
const targetWords = input.lengthSpec?.target ?? input.wordCountOverride ?? book.chapterWordCount;
|
|
67
|
+
const resolvedLengthSpec = input.lengthSpec ?? buildLengthSpec(targetWords, resolvedLanguage);
|
|
68
|
+
const governedMemoryBlocks = input.contextPackage
|
|
69
|
+
? buildGovernedMemoryEvidenceBlocks(input.contextPackage, resolvedLanguage)
|
|
70
|
+
: undefined;
|
|
71
|
+
const englishVarianceBrief = resolvedLanguage === "en"
|
|
72
|
+
? await buildEnglishVarianceBrief({
|
|
73
|
+
bookDir,
|
|
74
|
+
chapterNumber,
|
|
75
|
+
})
|
|
76
|
+
: null;
|
|
77
|
+
// Build fanfic context if fanfic_canon.md exists
|
|
78
|
+
const fanficContext = hasFanficCanon && bookRules?.fanficMode
|
|
79
|
+
? {
|
|
80
|
+
fanficCanon: fanficCanonRaw,
|
|
81
|
+
fanficMode: bookRules.fanficMode,
|
|
82
|
+
allowedDeviations: bookRules.allowedDeviations ?? [],
|
|
83
|
+
}
|
|
84
|
+
: undefined;
|
|
85
|
+
// ── Phase 1: Creative writing (temperature 0.7) ──
|
|
86
|
+
const creativeSystemPrompt = buildWriterSystemPrompt(book, genreProfile, bookRules, bookRulesBody, genreBody, styleGuide, styleFingerprint, chapterNumber, "creative", fanficContext, resolvedLanguage, input.chapterIntent ? "governed" : "legacy", resolvedLengthSpec);
|
|
87
|
+
const creativeUserPrompt = input.chapterIntent && input.contextPackage && input.ruleStack
|
|
88
|
+
? this.buildGovernedUserPrompt({
|
|
89
|
+
chapterNumber,
|
|
90
|
+
chapterIntent: input.chapterIntent,
|
|
91
|
+
contextPackage: input.contextPackage,
|
|
92
|
+
ruleStack: input.ruleStack,
|
|
93
|
+
trace: input.trace,
|
|
94
|
+
lengthSpec: resolvedLengthSpec,
|
|
95
|
+
language: book.language ?? genreProfile.language,
|
|
96
|
+
varianceBrief: englishVarianceBrief?.text,
|
|
97
|
+
selectedEvidenceBlock: this.joinGovernedEvidenceBlocks(governedMemoryBlocks),
|
|
98
|
+
})
|
|
99
|
+
: (() => {
|
|
100
|
+
// Smart context filtering: inject only relevant parts of truth files
|
|
101
|
+
const filteredHooks = filterHooks(hooks);
|
|
102
|
+
const filteredSummaries = filterSummaries(chapterSummaries, chapterNumber);
|
|
103
|
+
const filteredSubplots = filterSubplots(subplotBoard);
|
|
104
|
+
const filteredArcs = filterEmotionalArcs(emotionalArcs, chapterNumber);
|
|
105
|
+
const filteredMatrix = filterCharacterMatrix(characterMatrix, volumeOutline, bookRules?.protagonist?.name);
|
|
106
|
+
// POV-aware filtering: limit context to what the POV character knows
|
|
107
|
+
const povCharacter = extractPOVFromOutline(volumeOutline, chapterNumber);
|
|
108
|
+
const povFilteredMatrix = povCharacter
|
|
109
|
+
? filterMatrixByPOV(filteredMatrix, povCharacter)
|
|
110
|
+
: filteredMatrix;
|
|
111
|
+
const povFilteredHooks = povCharacter
|
|
112
|
+
? filterHooksByPOV(filteredHooks, povCharacter, chapterSummaries)
|
|
113
|
+
: filteredHooks;
|
|
114
|
+
return this.buildUserPrompt({
|
|
115
|
+
chapterNumber,
|
|
116
|
+
storyBible,
|
|
117
|
+
volumeOutline,
|
|
118
|
+
currentState,
|
|
119
|
+
ledger: genreProfile.numericalSystem ? ledger : "",
|
|
120
|
+
hooks: povFilteredHooks,
|
|
121
|
+
recentChapters,
|
|
122
|
+
lengthSpec: resolvedLengthSpec,
|
|
123
|
+
externalContext: input.externalContext,
|
|
124
|
+
chapterSummaries: filteredSummaries,
|
|
125
|
+
subplotBoard: filteredSubplots,
|
|
126
|
+
emotionalArcs: filteredArcs,
|
|
127
|
+
characterMatrix: povFilteredMatrix,
|
|
128
|
+
dialogueFingerprints,
|
|
129
|
+
relevantSummaries,
|
|
130
|
+
parentCanon: hasParentCanon ? parentCanon : undefined,
|
|
131
|
+
language: book.language ?? genreProfile.language,
|
|
132
|
+
});
|
|
133
|
+
})();
|
|
134
|
+
const creativeTemperature = input.temperatureOverride ?? 0.7;
|
|
135
|
+
this.logInfo(resolvedLanguage, {
|
|
136
|
+
zh: `阶段 1:创作正文(第${chapterNumber}章)`,
|
|
137
|
+
en: `Phase 1: creative writing for chapter ${chapterNumber}`,
|
|
138
|
+
});
|
|
139
|
+
// Scale maxTokens to chapter word count (Chinese ≈ 1.5 tokens/char)
|
|
140
|
+
const creativeMaxTokens = Math.max(8192, Math.ceil(targetWords * 2));
|
|
141
|
+
const creativeResponse = await this.chat([
|
|
142
|
+
{ role: "system", content: creativeSystemPrompt },
|
|
143
|
+
{ role: "user", content: creativeUserPrompt },
|
|
144
|
+
], { maxTokens: creativeMaxTokens, temperature: creativeTemperature });
|
|
145
|
+
const creativeUsage = creativeResponse.usage;
|
|
146
|
+
const creative = parseCreativeOutput(chapterNumber, creativeResponse.content, resolvedLengthSpec.countingMode);
|
|
147
|
+
// ── Phase 2: State settlement (temperature 0.3) ──
|
|
148
|
+
this.logInfo(resolvedLanguage, {
|
|
149
|
+
zh: `阶段 2:状态结算(第${chapterNumber}章,${creative.wordCount}字)`,
|
|
150
|
+
en: `Phase 2: state settlement for chapter ${chapterNumber} (${creative.wordCount} words)`,
|
|
151
|
+
});
|
|
152
|
+
const isGovernedSettlement = Boolean(input.chapterIntent && input.contextPackage && input.ruleStack);
|
|
153
|
+
const filteredHooksForSettlement = isGovernedSettlement && input.contextPackage
|
|
154
|
+
? buildGovernedHookWorkingSet({
|
|
155
|
+
hooksMarkdown: hooks,
|
|
156
|
+
contextPackage: input.contextPackage,
|
|
157
|
+
chapterIntent: input.chapterIntent,
|
|
158
|
+
chapterNumber,
|
|
159
|
+
language: resolvedLanguage,
|
|
160
|
+
})
|
|
161
|
+
: hooks;
|
|
162
|
+
const filteredSubplotsForSettlement = isGovernedSettlement
|
|
163
|
+
? filterSubplots(subplotBoard)
|
|
164
|
+
: subplotBoard;
|
|
165
|
+
const filteredArcsForSettlement = isGovernedSettlement
|
|
166
|
+
? filterEmotionalArcs(emotionalArcs, chapterNumber)
|
|
167
|
+
: emotionalArcs;
|
|
168
|
+
const filteredMatrixForSettlement = isGovernedSettlement
|
|
169
|
+
? buildGovernedCharacterMatrixWorkingSet({
|
|
170
|
+
matrixMarkdown: characterMatrix,
|
|
171
|
+
chapterIntent: input.chapterIntent ?? volumeOutline,
|
|
172
|
+
contextPackage: input.contextPackage,
|
|
173
|
+
protagonistName: bookRules?.protagonist?.name,
|
|
174
|
+
})
|
|
175
|
+
: characterMatrix;
|
|
176
|
+
const settleResult = await this.settle({
|
|
177
|
+
book,
|
|
178
|
+
genreProfile,
|
|
179
|
+
bookRules,
|
|
180
|
+
chapterNumber,
|
|
181
|
+
title: creative.title,
|
|
182
|
+
content: creative.content,
|
|
183
|
+
currentState,
|
|
184
|
+
ledger: genreProfile.numericalSystem ? ledger : "",
|
|
185
|
+
hooks: filteredHooksForSettlement,
|
|
186
|
+
chapterSummaries: input.contextPackage ? filterSummaries(chapterSummaries, chapterNumber) : chapterSummaries,
|
|
187
|
+
subplotBoard: filteredSubplotsForSettlement,
|
|
188
|
+
emotionalArcs: filteredArcsForSettlement,
|
|
189
|
+
characterMatrix: filteredMatrixForSettlement,
|
|
190
|
+
volumeOutline,
|
|
191
|
+
selectedEvidenceBlock: governedMemoryBlocks
|
|
192
|
+
? this.joinGovernedEvidenceBlocks(governedMemoryBlocks)
|
|
193
|
+
: undefined,
|
|
194
|
+
chapterIntent: input.chapterIntent,
|
|
195
|
+
contextPackage: input.contextPackage,
|
|
196
|
+
ruleStack: input.ruleStack,
|
|
197
|
+
validationFeedback: undefined,
|
|
198
|
+
originalHooks: hooks,
|
|
199
|
+
originalSubplots: subplotBoard,
|
|
200
|
+
originalEmotionalArcs: emotionalArcs,
|
|
201
|
+
originalCharacterMatrix: characterMatrix,
|
|
202
|
+
});
|
|
203
|
+
const settlement = settleResult.settlement;
|
|
204
|
+
const settleUsage = settleResult.usage;
|
|
205
|
+
const runtimeStateArtifacts = await this.buildRuntimeStateArtifactsIfPresent(bookDir, settlement.runtimeStateDelta, resolvedLanguage, chapterNumber);
|
|
206
|
+
const resolvedRuntimeStateDelta = runtimeStateArtifacts?.resolvedDelta ?? settlement.runtimeStateDelta;
|
|
207
|
+
const priorHookIds = new Set(parsePendingHooksMarkdown(hooks).map((hook) => hook.hookId));
|
|
208
|
+
const hookHealthIssues = resolvedRuntimeStateDelta
|
|
209
|
+
&& (runtimeStateArtifacts?.snapshot ?? settlement.runtimeStateSnapshot)
|
|
210
|
+
? analyzeHookHealth({
|
|
211
|
+
language: resolvedLanguage,
|
|
212
|
+
chapterNumber,
|
|
213
|
+
targetChapters: book.targetChapters,
|
|
214
|
+
hooks: (runtimeStateArtifacts?.snapshot ?? settlement.runtimeStateSnapshot).hooks.hooks,
|
|
215
|
+
delta: resolvedRuntimeStateDelta,
|
|
216
|
+
existingHookIds: [...priorHookIds],
|
|
217
|
+
})
|
|
218
|
+
: [];
|
|
219
|
+
// ── Post-write validation (regex + rule-based, zero LLM cost) ──
|
|
220
|
+
const ruleViolations = [
|
|
221
|
+
...validatePostWrite(creative.content, genreProfile, bookRules, resolvedLanguage),
|
|
222
|
+
...detectCrossChapterRepetition(creative.content, fingerprintChapters, resolvedLanguage),
|
|
223
|
+
...detectParagraphLengthDrift(creative.content, fingerprintChapters, resolvedLanguage),
|
|
224
|
+
];
|
|
225
|
+
const aiTellIssues = analyzeAITells(creative.content, resolvedLanguage).issues;
|
|
226
|
+
const postWriteErrors = ruleViolations.filter(v => v.severity === "error");
|
|
227
|
+
const postWriteWarnings = ruleViolations.filter(v => v.severity === "warning");
|
|
228
|
+
if (ruleViolations.length > 0) {
|
|
229
|
+
this.logWarn(resolvedLanguage, {
|
|
230
|
+
zh: `后写校验:第${chapterNumber}章 ${postWriteErrors.length} 个错误,${postWriteWarnings.length} 个警告`,
|
|
231
|
+
en: `Post-write: ${postWriteErrors.length} errors, ${postWriteWarnings.length} warnings in chapter ${chapterNumber}`,
|
|
232
|
+
});
|
|
233
|
+
for (const v of ruleViolations) {
|
|
234
|
+
this.ctx.logger?.warn(`[${v.severity}] ${v.rule}: ${v.description}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (aiTellIssues.length > 0) {
|
|
238
|
+
this.logWarn(resolvedLanguage, {
|
|
239
|
+
zh: `AI 味检查:第${chapterNumber}章发现 ${aiTellIssues.length} 个问题`,
|
|
240
|
+
en: `AI-tell check: ${aiTellIssues.length} issues in chapter ${chapterNumber}`,
|
|
241
|
+
});
|
|
242
|
+
for (const issue of aiTellIssues) {
|
|
243
|
+
this.ctx.logger?.warn(`[${issue.severity}] ${issue.category}: ${issue.description}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (hookHealthIssues.length > 0) {
|
|
247
|
+
this.logWarn(resolvedLanguage, {
|
|
248
|
+
zh: `伏笔健康:第${chapterNumber}章发现 ${hookHealthIssues.length} 条警告`,
|
|
249
|
+
en: `Hook health: ${hookHealthIssues.length} warning(s) in chapter ${chapterNumber}`,
|
|
250
|
+
});
|
|
251
|
+
for (const issue of hookHealthIssues) {
|
|
252
|
+
this.ctx.logger?.warn(`[${issue.severity}] ${issue.category}: ${issue.description}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// ── Merge into WriteChapterOutput ──
|
|
256
|
+
const tokenUsage = {
|
|
257
|
+
promptTokens: creativeUsage.promptTokens + settleUsage.promptTokens,
|
|
258
|
+
completionTokens: creativeUsage.completionTokens + settleUsage.completionTokens,
|
|
259
|
+
totalTokens: creativeUsage.totalTokens + settleUsage.totalTokens,
|
|
260
|
+
};
|
|
261
|
+
return {
|
|
262
|
+
chapterNumber,
|
|
263
|
+
title: creative.title,
|
|
264
|
+
content: creative.content,
|
|
265
|
+
wordCount: creative.wordCount,
|
|
266
|
+
preWriteCheck: creative.preWriteCheck,
|
|
267
|
+
postSettlement: settlement.postSettlement,
|
|
268
|
+
runtimeStateDelta: resolvedRuntimeStateDelta,
|
|
269
|
+
runtimeStateSnapshot: runtimeStateArtifacts?.snapshot ?? settlement.runtimeStateSnapshot,
|
|
270
|
+
updatedState: runtimeStateArtifacts?.currentStateMarkdown ?? settlement.updatedState,
|
|
271
|
+
updatedLedger: settlement.updatedLedger,
|
|
272
|
+
updatedHooks: runtimeStateArtifacts?.hooksMarkdown ?? settlement.updatedHooks,
|
|
273
|
+
chapterSummary: resolvedRuntimeStateDelta
|
|
274
|
+
? this.renderDeltaSummaryRow(resolvedRuntimeStateDelta)
|
|
275
|
+
: settlement.chapterSummary,
|
|
276
|
+
updatedChapterSummaries: runtimeStateArtifacts?.chapterSummariesMarkdown,
|
|
277
|
+
updatedSubplots: settlement.updatedSubplots,
|
|
278
|
+
updatedEmotionalArcs: settlement.updatedEmotionalArcs,
|
|
279
|
+
updatedCharacterMatrix: settlement.updatedCharacterMatrix,
|
|
280
|
+
postWriteErrors,
|
|
281
|
+
postWriteWarnings,
|
|
282
|
+
hookHealthIssues,
|
|
283
|
+
tokenUsage,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
async settleChapterState(input) {
|
|
287
|
+
const [currentState, ledger, hooks, chapterSummaries, subplotBoard, emotionalArcs, characterMatrix, volumeOutline,] = await Promise.all([
|
|
288
|
+
this.readFileOrDefault(join(input.bookDir, "story/current_state.md")),
|
|
289
|
+
this.readFileOrDefault(join(input.bookDir, "story/particle_ledger.md")),
|
|
290
|
+
this.readFileOrDefault(join(input.bookDir, "story/pending_hooks.md")),
|
|
291
|
+
this.readFileOrDefault(join(input.bookDir, "story/chapter_summaries.md")),
|
|
292
|
+
this.readFileOrDefault(join(input.bookDir, "story/subplot_board.md")),
|
|
293
|
+
this.readFileOrDefault(join(input.bookDir, "story/emotional_arcs.md")),
|
|
294
|
+
this.readFileOrDefault(join(input.bookDir, "story/character_matrix.md")),
|
|
295
|
+
this.readFileOrDefault(join(input.bookDir, "story/volume_outline.md")),
|
|
296
|
+
]);
|
|
297
|
+
const { profile: genreProfile } = await readGenreProfile(this.ctx.projectRoot, input.book.genre);
|
|
298
|
+
const parsedBookRules = await readBookRules(input.bookDir);
|
|
299
|
+
const bookRules = parsedBookRules?.rules ?? null;
|
|
300
|
+
const resolvedLanguage = input.book.language ?? genreProfile.language;
|
|
301
|
+
const governedMemoryBlocks = input.contextPackage
|
|
302
|
+
? buildGovernedMemoryEvidenceBlocks(input.contextPackage, resolvedLanguage)
|
|
303
|
+
: undefined;
|
|
304
|
+
const settleResult = await this.settle({
|
|
305
|
+
book: input.book,
|
|
306
|
+
genreProfile,
|
|
307
|
+
bookRules,
|
|
308
|
+
chapterNumber: input.chapterNumber,
|
|
309
|
+
title: input.title,
|
|
310
|
+
content: input.content,
|
|
311
|
+
currentState,
|
|
312
|
+
ledger: genreProfile.numericalSystem ? ledger : "",
|
|
313
|
+
hooks,
|
|
314
|
+
chapterSummaries,
|
|
315
|
+
subplotBoard,
|
|
316
|
+
emotionalArcs,
|
|
317
|
+
characterMatrix,
|
|
318
|
+
volumeOutline,
|
|
319
|
+
selectedEvidenceBlock: governedMemoryBlocks
|
|
320
|
+
? this.joinGovernedEvidenceBlocks(governedMemoryBlocks)
|
|
321
|
+
: undefined,
|
|
322
|
+
chapterIntent: input.chapterIntent,
|
|
323
|
+
contextPackage: input.contextPackage,
|
|
324
|
+
ruleStack: input.ruleStack,
|
|
325
|
+
validationFeedback: input.validationFeedback,
|
|
326
|
+
originalHooks: hooks,
|
|
327
|
+
originalSubplots: subplotBoard,
|
|
328
|
+
originalEmotionalArcs: emotionalArcs,
|
|
329
|
+
originalCharacterMatrix: characterMatrix,
|
|
330
|
+
});
|
|
331
|
+
const settlement = settleResult.settlement;
|
|
332
|
+
const runtimeStateArtifacts = await this.buildRuntimeStateArtifactsIfPresent(input.bookDir, settlement.runtimeStateDelta, resolvedLanguage, input.chapterNumber, input.allowReapply);
|
|
333
|
+
return {
|
|
334
|
+
chapterNumber: input.chapterNumber,
|
|
335
|
+
title: input.title,
|
|
336
|
+
content: input.content,
|
|
337
|
+
wordCount: countChapterLength(input.content, resolvedLanguage === "en" ? "en_words" : "zh_chars"),
|
|
338
|
+
preWriteCheck: "",
|
|
339
|
+
postSettlement: settlement.postSettlement,
|
|
340
|
+
runtimeStateDelta: runtimeStateArtifacts?.resolvedDelta ?? settlement.runtimeStateDelta,
|
|
341
|
+
runtimeStateSnapshot: runtimeStateArtifacts?.snapshot ?? settlement.runtimeStateSnapshot,
|
|
342
|
+
updatedState: runtimeStateArtifacts?.currentStateMarkdown ?? settlement.updatedState,
|
|
343
|
+
updatedLedger: settlement.updatedLedger,
|
|
344
|
+
updatedHooks: runtimeStateArtifacts?.hooksMarkdown ?? settlement.updatedHooks,
|
|
345
|
+
chapterSummary: settlement.runtimeStateDelta
|
|
346
|
+
? this.renderDeltaSummaryRow(settlement.runtimeStateDelta)
|
|
347
|
+
: settlement.chapterSummary,
|
|
348
|
+
updatedChapterSummaries: runtimeStateArtifacts?.chapterSummariesMarkdown,
|
|
349
|
+
updatedSubplots: settlement.updatedSubplots,
|
|
350
|
+
updatedEmotionalArcs: settlement.updatedEmotionalArcs,
|
|
351
|
+
updatedCharacterMatrix: settlement.updatedCharacterMatrix,
|
|
352
|
+
postWriteErrors: [],
|
|
353
|
+
postWriteWarnings: [],
|
|
354
|
+
tokenUsage: settleResult.usage,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
async settle(params) {
|
|
358
|
+
// Phase 2a: Observer — extract all facts from the chapter
|
|
359
|
+
const resolvedLang = params.book.language ?? params.genreProfile.language;
|
|
360
|
+
const observerSystem = buildObserverSystemPrompt(params.book, params.genreProfile, resolvedLang);
|
|
361
|
+
const observerUser = buildObserverUserPrompt(params.chapterNumber, params.title, params.content, resolvedLang);
|
|
362
|
+
this.logInfo(resolvedLang, {
|
|
363
|
+
zh: `阶段 2a:提取第${params.chapterNumber}章事实`,
|
|
364
|
+
en: `Phase 2a: observing facts for chapter ${params.chapterNumber}`,
|
|
365
|
+
});
|
|
366
|
+
const observerResponse = await this.chat([
|
|
367
|
+
{ role: "system", content: observerSystem },
|
|
368
|
+
{ role: "user", content: observerUser },
|
|
369
|
+
], { maxTokens: 4096, temperature: 0.5 });
|
|
370
|
+
const observations = observerResponse.content;
|
|
371
|
+
// Phase 2b: Reflector — merge observations into truth files
|
|
372
|
+
this.logInfo(resolvedLang, {
|
|
373
|
+
zh: "阶段 2b:把观察结果回写到真相文件",
|
|
374
|
+
en: "Phase 2b: reflecting observations into truth files",
|
|
375
|
+
});
|
|
376
|
+
const settlerSystem = buildSettlerSystemPrompt(params.book, params.genreProfile, params.bookRules, resolvedLang);
|
|
377
|
+
const governedControlBlock = params.chapterIntent && params.contextPackage && params.ruleStack
|
|
378
|
+
? this.buildSettlerGovernedControlBlock(params.chapterIntent, params.contextPackage, params.ruleStack, resolvedLang)
|
|
379
|
+
: undefined;
|
|
380
|
+
const settlerUser = buildSettlerUserPrompt({
|
|
381
|
+
chapterNumber: params.chapterNumber,
|
|
382
|
+
title: params.title,
|
|
383
|
+
content: params.content,
|
|
384
|
+
currentState: params.currentState,
|
|
385
|
+
ledger: params.ledger,
|
|
386
|
+
hooks: params.hooks,
|
|
387
|
+
chapterSummaries: params.chapterSummaries,
|
|
388
|
+
subplotBoard: params.subplotBoard,
|
|
389
|
+
emotionalArcs: params.emotionalArcs,
|
|
390
|
+
characterMatrix: params.characterMatrix,
|
|
391
|
+
volumeOutline: params.volumeOutline,
|
|
392
|
+
observations,
|
|
393
|
+
selectedEvidenceBlock: params.selectedEvidenceBlock,
|
|
394
|
+
governedControlBlock,
|
|
395
|
+
validationFeedback: params.validationFeedback,
|
|
396
|
+
});
|
|
397
|
+
// Settler outputs all truth files — scale with content size
|
|
398
|
+
const settlerMaxTokens = Math.max(8192, Math.ceil(params.content.length * 0.8));
|
|
399
|
+
const response = await this.chat([
|
|
400
|
+
{ role: "system", content: settlerSystem },
|
|
401
|
+
{ role: "user", content: settlerUser },
|
|
402
|
+
], { maxTokens: settlerMaxTokens, temperature: 0.3 });
|
|
403
|
+
let mergedSettlement;
|
|
404
|
+
try {
|
|
405
|
+
const deltaOutput = parseSettlerDeltaOutput(response.content);
|
|
406
|
+
mergedSettlement = {
|
|
407
|
+
postSettlement: deltaOutput.postSettlement,
|
|
408
|
+
runtimeStateDelta: deltaOutput.runtimeStateDelta,
|
|
409
|
+
updatedState: "",
|
|
410
|
+
updatedLedger: "",
|
|
411
|
+
updatedHooks: "",
|
|
412
|
+
chapterSummary: "",
|
|
413
|
+
updatedSubplots: "",
|
|
414
|
+
updatedEmotionalArcs: "",
|
|
415
|
+
updatedCharacterMatrix: "",
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
const settlement = parseSettlementOutput(response.content, params.genreProfile);
|
|
420
|
+
mergedSettlement = governedControlBlock
|
|
421
|
+
? {
|
|
422
|
+
...settlement,
|
|
423
|
+
updatedHooks: mergeTableMarkdownByKey(params.originalHooks, settlement.updatedHooks, [0]),
|
|
424
|
+
updatedSubplots: settlement.updatedSubplots
|
|
425
|
+
? mergeTableMarkdownByKey(params.originalSubplots, settlement.updatedSubplots, [0])
|
|
426
|
+
: settlement.updatedSubplots,
|
|
427
|
+
updatedEmotionalArcs: settlement.updatedEmotionalArcs
|
|
428
|
+
? mergeTableMarkdownByKey(params.originalEmotionalArcs, settlement.updatedEmotionalArcs, [0, 1])
|
|
429
|
+
: settlement.updatedEmotionalArcs,
|
|
430
|
+
updatedCharacterMatrix: settlement.updatedCharacterMatrix
|
|
431
|
+
? mergeCharacterMatrixMarkdown(params.originalCharacterMatrix, settlement.updatedCharacterMatrix)
|
|
432
|
+
: settlement.updatedCharacterMatrix,
|
|
433
|
+
}
|
|
434
|
+
: settlement;
|
|
435
|
+
}
|
|
436
|
+
return {
|
|
437
|
+
settlement: mergedSettlement,
|
|
438
|
+
usage: response.usage,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
async saveChapter(bookDir, output, numericalSystem = true, language = "zh") {
|
|
442
|
+
const chaptersDir = join(bookDir, "chapters");
|
|
443
|
+
const storyDir = join(bookDir, "story");
|
|
444
|
+
await mkdir(chaptersDir, { recursive: true });
|
|
445
|
+
const paddedNum = String(output.chapterNumber).padStart(4, "0");
|
|
446
|
+
const filename = `${paddedNum}_${this.sanitizeFilename(output.title)}.md`;
|
|
447
|
+
const heading = language === "en"
|
|
448
|
+
? `# Chapter ${output.chapterNumber}: ${output.title}`
|
|
449
|
+
: `# 第${output.chapterNumber}章 ${output.title}`;
|
|
450
|
+
const chapterContent = [
|
|
451
|
+
heading,
|
|
452
|
+
"",
|
|
453
|
+
output.content,
|
|
454
|
+
].join("\n");
|
|
455
|
+
const runtimeStateArtifacts = await this.resolveRuntimeStateArtifactsForOutput(bookDir, output, language);
|
|
456
|
+
const writes = [
|
|
457
|
+
writeFile(join(chaptersDir, filename), chapterContent, "utf-8"),
|
|
458
|
+
writeFile(join(storyDir, "current_state.md"), runtimeStateArtifacts?.currentStateMarkdown ?? output.updatedState, "utf-8"),
|
|
459
|
+
writeFile(join(storyDir, "pending_hooks.md"), runtimeStateArtifacts?.hooksMarkdown ?? output.updatedHooks, "utf-8"),
|
|
460
|
+
];
|
|
461
|
+
if (runtimeStateArtifacts?.chapterSummariesMarkdown) {
|
|
462
|
+
writes.push(writeFile(join(storyDir, "chapter_summaries.md"), runtimeStateArtifacts.chapterSummariesMarkdown, "utf-8"));
|
|
463
|
+
}
|
|
464
|
+
if (runtimeStateArtifacts?.snapshot ?? output.runtimeStateSnapshot) {
|
|
465
|
+
writes.push(saveRuntimeStateSnapshot(bookDir, runtimeStateArtifacts?.snapshot ?? output.runtimeStateSnapshot));
|
|
466
|
+
}
|
|
467
|
+
if (numericalSystem) {
|
|
468
|
+
writes.push(writeFile(join(storyDir, "particle_ledger.md"), output.updatedLedger, "utf-8"));
|
|
469
|
+
}
|
|
470
|
+
await Promise.all(writes);
|
|
471
|
+
}
|
|
472
|
+
buildUserPrompt(params) {
|
|
473
|
+
const contextBlock = params.externalContext
|
|
474
|
+
? `\n## 外部指令\n以下是来自外部系统的创作指令,请在本章中融入:\n\n${params.externalContext}\n`
|
|
475
|
+
: "";
|
|
476
|
+
const ledgerBlock = params.ledger
|
|
477
|
+
? `\n## 资源账本\n${params.ledger}\n`
|
|
478
|
+
: "";
|
|
479
|
+
const summariesBlock = params.chapterSummaries !== "(文件尚未创建)"
|
|
480
|
+
? `\n## 章节摘要(全部历史章节压缩上下文)\n${params.chapterSummaries}\n`
|
|
481
|
+
: "";
|
|
482
|
+
const subplotBlock = params.subplotBoard !== "(文件尚未创建)"
|
|
483
|
+
? `\n## 支线进度板\n${params.subplotBoard}\n`
|
|
484
|
+
: "";
|
|
485
|
+
const emotionalBlock = params.emotionalArcs !== "(文件尚未创建)"
|
|
486
|
+
? `\n## 情感弧线\n${params.emotionalArcs}\n`
|
|
487
|
+
: "";
|
|
488
|
+
const matrixBlock = params.characterMatrix !== "(文件尚未创建)"
|
|
489
|
+
? `\n## 角色交互矩阵\n${params.characterMatrix}\n`
|
|
490
|
+
: "";
|
|
491
|
+
const fingerprintBlock = params.dialogueFingerprints
|
|
492
|
+
? `\n## 角色对话指纹\n${params.dialogueFingerprints}\n`
|
|
493
|
+
: "";
|
|
494
|
+
const relevantBlock = params.relevantSummaries
|
|
495
|
+
? `\n## 相关历史章节摘要\n${params.relevantSummaries}\n`
|
|
496
|
+
: "";
|
|
497
|
+
const canonBlock = params.parentCanon
|
|
498
|
+
? `\n## 正传正典参照(番外写作专用)
|
|
499
|
+
本书是番外作品。以下正典约束不可违反,角色不得引用超出其信息边界的信息。
|
|
500
|
+
${params.parentCanon}\n`
|
|
501
|
+
: "";
|
|
502
|
+
const lengthRequirementBlock = this.buildLengthRequirementBlock(params.lengthSpec, params.language ?? "zh");
|
|
503
|
+
if (params.language === "en") {
|
|
504
|
+
return `Write chapter ${params.chapterNumber}.
|
|
505
|
+
${contextBlock}
|
|
506
|
+
## Current State
|
|
507
|
+
${params.currentState}
|
|
508
|
+
${ledgerBlock}
|
|
509
|
+
## Plot Threads
|
|
510
|
+
${params.hooks}
|
|
511
|
+
${summariesBlock}${subplotBlock}${emotionalBlock}${matrixBlock}${fingerprintBlock}${relevantBlock}${canonBlock}
|
|
512
|
+
## Recent Chapters
|
|
513
|
+
${params.recentChapters || "(This is the first chapter, no previous text)"}
|
|
514
|
+
|
|
515
|
+
## Worldbuilding
|
|
516
|
+
${params.storyBible}
|
|
517
|
+
|
|
518
|
+
## Volume Outline (Hard Constraint — Must Follow)
|
|
519
|
+
${params.volumeOutline}
|
|
520
|
+
|
|
521
|
+
[Outline Rules]
|
|
522
|
+
- This chapter must advance the plot points assigned to it in the volume outline. Do not skip ahead or consume future plot points.
|
|
523
|
+
- If the outline specifies an event for chapter N, do not resolve it early.
|
|
524
|
+
- Pacing must match the outline's chapter span: if 5 chapters are planned for an arc, do not compress into 1-2.
|
|
525
|
+
- PRE_WRITE_CHECK must identify which outline node this chapter covers.
|
|
526
|
+
|
|
527
|
+
${lengthRequirementBlock}
|
|
528
|
+
- Output PRE_WRITE_CHECK first, then the chapter
|
|
529
|
+
- Output only PRE_WRITE_CHECK, CHAPTER_TITLE, and CHAPTER_CONTENT blocks`;
|
|
530
|
+
}
|
|
531
|
+
return `请续写第${params.chapterNumber}章。
|
|
532
|
+
${contextBlock}
|
|
533
|
+
## 当前状态卡
|
|
534
|
+
${params.currentState}
|
|
535
|
+
${ledgerBlock}
|
|
536
|
+
## 伏笔池
|
|
537
|
+
${params.hooks}
|
|
538
|
+
${summariesBlock}${subplotBlock}${emotionalBlock}${matrixBlock}${fingerprintBlock}${relevantBlock}${canonBlock}
|
|
539
|
+
## 最近章节
|
|
540
|
+
${params.recentChapters || "(这是第一章,无前文)"}
|
|
541
|
+
|
|
542
|
+
## 世界观设定
|
|
543
|
+
${params.storyBible}
|
|
544
|
+
|
|
545
|
+
## 卷纲(硬约束——必须遵守)
|
|
546
|
+
${params.volumeOutline}
|
|
547
|
+
|
|
548
|
+
【卷纲遵守规则】
|
|
549
|
+
- 本章内容必须对应卷纲中当前章节范围内的剧情节点,严禁跳过或提前消耗后续节点
|
|
550
|
+
- 如果卷纲指定了某个事件/转折发生在第N章,不得提前到本章完成
|
|
551
|
+
- 剧情推进速度必须与卷纲规划的章节跨度匹配:如果卷纲规划某段剧情跨5章,不得在1-2章内讲完
|
|
552
|
+
- PRE_WRITE_CHECK中必须明确标注本章对应的卷纲节点
|
|
553
|
+
|
|
554
|
+
${lengthRequirementBlock}
|
|
555
|
+
- 先输出写作自检表,再写正文
|
|
556
|
+
- 只需输出 PRE_WRITE_CHECK、CHAPTER_TITLE、CHAPTER_CONTENT 三个区块`;
|
|
557
|
+
}
|
|
558
|
+
buildGovernedUserPrompt(params) {
|
|
559
|
+
const contextSections = params.contextPackage.selectedContext
|
|
560
|
+
.map((entry) => [
|
|
561
|
+
`### ${entry.source}`,
|
|
562
|
+
`- reason: ${entry.reason}`,
|
|
563
|
+
entry.excerpt ? `- excerpt: ${entry.excerpt}` : "",
|
|
564
|
+
].filter(Boolean).join("\n"))
|
|
565
|
+
.join("\n\n");
|
|
566
|
+
const overrideLines = params.ruleStack.activeOverrides.length > 0
|
|
567
|
+
? params.ruleStack.activeOverrides
|
|
568
|
+
.map((override) => `- ${override.from} -> ${override.to}: ${override.reason} (${override.target})`)
|
|
569
|
+
.join("\n")
|
|
570
|
+
: "- none";
|
|
571
|
+
const diagnosticLines = params.ruleStack.sections.diagnostic.length > 0
|
|
572
|
+
? params.ruleStack.sections.diagnostic.join(", ")
|
|
573
|
+
: "none";
|
|
574
|
+
const traceNotes = params.trace && params.trace.notes.length > 0
|
|
575
|
+
? params.trace.notes.map((note) => `- ${note}`).join("\n")
|
|
576
|
+
: "- none";
|
|
577
|
+
const lengthRequirementBlock = this.buildLengthRequirementBlock(params.lengthSpec, params.language ?? "zh");
|
|
578
|
+
const varianceBlock = params.varianceBrief
|
|
579
|
+
? `\n${params.varianceBrief}\n`
|
|
580
|
+
: "";
|
|
581
|
+
const selectedEvidenceBlock = params.selectedEvidenceBlock
|
|
582
|
+
? `\n${params.selectedEvidenceBlock}\n`
|
|
583
|
+
: "";
|
|
584
|
+
const explicitHookAgenda = this.extractMarkdownSection(params.chapterIntent, "## Hook Agenda");
|
|
585
|
+
const hookAgendaBlock = explicitHookAgenda
|
|
586
|
+
? params.language === "en"
|
|
587
|
+
? `\n## Explicit Hook Agenda\n${explicitHookAgenda}\n`
|
|
588
|
+
: `\n## 显式 Hook Agenda\n${explicitHookAgenda}\n`
|
|
589
|
+
: "";
|
|
590
|
+
if (params.language === "en") {
|
|
591
|
+
return `Write chapter ${params.chapterNumber}.
|
|
592
|
+
|
|
593
|
+
## Chapter Intent
|
|
594
|
+
${params.chapterIntent}
|
|
595
|
+
|
|
596
|
+
## Selected Context
|
|
597
|
+
${contextSections || "(none)"}
|
|
598
|
+
${selectedEvidenceBlock}
|
|
599
|
+
${hookAgendaBlock}
|
|
600
|
+
|
|
601
|
+
## Rule Stack
|
|
602
|
+
- Hard: ${params.ruleStack.sections.hard.join(", ") || "(none)"}
|
|
603
|
+
- Soft: ${params.ruleStack.sections.soft.join(", ") || "(none)"}
|
|
604
|
+
- Diagnostic: ${diagnosticLines}
|
|
605
|
+
|
|
606
|
+
## Active Overrides
|
|
607
|
+
${overrideLines}
|
|
608
|
+
|
|
609
|
+
## Trace Notes
|
|
610
|
+
${traceNotes}
|
|
611
|
+
|
|
612
|
+
${varianceBlock}
|
|
613
|
+
${lengthRequirementBlock}
|
|
614
|
+
- Output PRE_WRITE_CHECK first, then the chapter
|
|
615
|
+
- Output only PRE_WRITE_CHECK, CHAPTER_TITLE, and CHAPTER_CONTENT blocks`;
|
|
616
|
+
}
|
|
617
|
+
return `请续写第${params.chapterNumber}章。
|
|
618
|
+
|
|
619
|
+
## 本章意图
|
|
620
|
+
${params.chapterIntent}
|
|
621
|
+
|
|
622
|
+
## 已选上下文
|
|
623
|
+
${contextSections || "(无)"}
|
|
624
|
+
${selectedEvidenceBlock}
|
|
625
|
+
${hookAgendaBlock}
|
|
626
|
+
|
|
627
|
+
## 规则栈
|
|
628
|
+
- 硬护栏:${params.ruleStack.sections.hard.join("、") || "(无)"}
|
|
629
|
+
- 软约束:${params.ruleStack.sections.soft.join("、") || "(无)"}
|
|
630
|
+
- 诊断规则:${diagnosticLines}
|
|
631
|
+
|
|
632
|
+
## 当前覆盖
|
|
633
|
+
${overrideLines}
|
|
634
|
+
|
|
635
|
+
## 追踪说明
|
|
636
|
+
${traceNotes}
|
|
637
|
+
|
|
638
|
+
${varianceBlock}
|
|
639
|
+
${lengthRequirementBlock}
|
|
640
|
+
- 先输出写作自检表,再写正文
|
|
641
|
+
- 只需输出 PRE_WRITE_CHECK、CHAPTER_TITLE、CHAPTER_CONTENT 三个区块`;
|
|
642
|
+
}
|
|
643
|
+
joinGovernedEvidenceBlocks(blocks) {
|
|
644
|
+
if (!blocks) {
|
|
645
|
+
return undefined;
|
|
646
|
+
}
|
|
647
|
+
const joined = [
|
|
648
|
+
blocks.titleHistoryBlock,
|
|
649
|
+
blocks.moodTrailBlock,
|
|
650
|
+
blocks.canonBlock,
|
|
651
|
+
blocks.hookDebtBlock,
|
|
652
|
+
blocks.hooksBlock,
|
|
653
|
+
blocks.summariesBlock,
|
|
654
|
+
blocks.volumeSummariesBlock,
|
|
655
|
+
]
|
|
656
|
+
.filter((block) => Boolean(block))
|
|
657
|
+
.join("\n");
|
|
658
|
+
return joined || undefined;
|
|
659
|
+
}
|
|
660
|
+
extractMarkdownSection(content, heading) {
|
|
661
|
+
const lines = content.split("\n");
|
|
662
|
+
let buffer = null;
|
|
663
|
+
for (const line of lines) {
|
|
664
|
+
if (line.trim() === heading) {
|
|
665
|
+
buffer = [];
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
if (buffer && line.startsWith("## ") && line.trim() !== heading) {
|
|
669
|
+
break;
|
|
670
|
+
}
|
|
671
|
+
if (buffer) {
|
|
672
|
+
buffer.push(line);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const section = buffer?.join("\n").trim();
|
|
676
|
+
return section && section.length > 0 ? section : undefined;
|
|
677
|
+
}
|
|
678
|
+
buildSettlerGovernedControlBlock(chapterIntent, contextPackage, ruleStack, language) {
|
|
679
|
+
const selectedContext = contextPackage.selectedContext
|
|
680
|
+
.map((entry) => `- ${entry.source}: ${entry.reason}${entry.excerpt ? ` | ${entry.excerpt}` : ""}`)
|
|
681
|
+
.join("\n");
|
|
682
|
+
const overrides = ruleStack.activeOverrides.length > 0
|
|
683
|
+
? ruleStack.activeOverrides
|
|
684
|
+
.map((override) => `- ${override.from} -> ${override.to}: ${override.reason} (${override.target})`)
|
|
685
|
+
.join("\n")
|
|
686
|
+
: "- none";
|
|
687
|
+
if (language === "en") {
|
|
688
|
+
return `\n## Chapter Control Inputs
|
|
689
|
+
${chapterIntent}
|
|
690
|
+
|
|
691
|
+
### Selected Context
|
|
692
|
+
${selectedContext || "- none"}
|
|
693
|
+
|
|
694
|
+
### Rule Stack
|
|
695
|
+
- Hard guardrails: ${ruleStack.sections.hard.join(", ") || "(none)"}
|
|
696
|
+
- Soft constraints: ${ruleStack.sections.soft.join(", ") || "(none)"}
|
|
697
|
+
- Diagnostic rules: ${ruleStack.sections.diagnostic.join(", ") || "(none)"}
|
|
698
|
+
|
|
699
|
+
### Active Overrides
|
|
700
|
+
${overrides}\n`;
|
|
701
|
+
}
|
|
702
|
+
return `\n## 本章控制输入
|
|
703
|
+
${chapterIntent}
|
|
704
|
+
|
|
705
|
+
### 已选上下文
|
|
706
|
+
${selectedContext || "- none"}
|
|
707
|
+
|
|
708
|
+
### 规则栈
|
|
709
|
+
- 硬护栏:${ruleStack.sections.hard.join("、") || "(无)"}
|
|
710
|
+
- 软约束:${ruleStack.sections.soft.join("、") || "(无)"}
|
|
711
|
+
- 诊断规则:${ruleStack.sections.diagnostic.join("、") || "(无)"}
|
|
712
|
+
|
|
713
|
+
### 当前覆盖
|
|
714
|
+
${overrides}\n`;
|
|
715
|
+
}
|
|
716
|
+
buildLengthRequirementBlock(lengthSpec, language) {
|
|
717
|
+
if (language === "en") {
|
|
718
|
+
return `Requirements:
|
|
719
|
+
- Target length: ${lengthSpec.target} words
|
|
720
|
+
- Acceptable range: ${lengthSpec.softMin}-${lengthSpec.softMax} words`;
|
|
721
|
+
}
|
|
722
|
+
return `要求:
|
|
723
|
+
- 目标字数:${lengthSpec.target}字
|
|
724
|
+
- 允许区间:${lengthSpec.softMin}-${lengthSpec.softMax}字`;
|
|
725
|
+
}
|
|
726
|
+
async loadRecentChapters(bookDir, currentChapter, count = 1) {
|
|
727
|
+
const chaptersDir = join(bookDir, "chapters");
|
|
728
|
+
try {
|
|
729
|
+
const files = await readdir(chaptersDir);
|
|
730
|
+
const mdFiles = files
|
|
731
|
+
.filter((f) => f.endsWith(".md") && !f.startsWith("index"))
|
|
732
|
+
.sort()
|
|
733
|
+
.slice(-count);
|
|
734
|
+
if (mdFiles.length === 0)
|
|
735
|
+
return "";
|
|
736
|
+
const contents = await Promise.all(mdFiles.map(async (f) => {
|
|
737
|
+
const content = await readFile(join(chaptersDir, f), "utf-8");
|
|
738
|
+
return content;
|
|
739
|
+
}));
|
|
740
|
+
return contents.join("\n\n---\n\n");
|
|
741
|
+
}
|
|
742
|
+
catch {
|
|
743
|
+
return "";
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
async readFileOrDefault(path) {
|
|
747
|
+
try {
|
|
748
|
+
return await readFile(path, "utf-8");
|
|
749
|
+
}
|
|
750
|
+
catch {
|
|
751
|
+
return "(文件尚未创建)";
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
/** Save new truth files (summaries, subplots, emotional arcs, character matrix). */
|
|
755
|
+
async saveNewTruthFiles(bookDir, output, language = "zh") {
|
|
756
|
+
const storyDir = join(bookDir, "story");
|
|
757
|
+
const writes = [];
|
|
758
|
+
// Append chapter summary to chapter_summaries.md
|
|
759
|
+
if (!output.runtimeStateDelta && output.updatedChapterSummaries) {
|
|
760
|
+
writes.push(writeFile(join(storyDir, "chapter_summaries.md"), output.updatedChapterSummaries, "utf-8"));
|
|
761
|
+
}
|
|
762
|
+
else if (!output.runtimeStateDelta && output.chapterSummary) {
|
|
763
|
+
writes.push(this.appendChapterSummary(storyDir, output.chapterSummary, language));
|
|
764
|
+
}
|
|
765
|
+
// Overwrite subplot board
|
|
766
|
+
if (output.updatedSubplots) {
|
|
767
|
+
writes.push(writeFile(join(storyDir, "subplot_board.md"), output.updatedSubplots, "utf-8"));
|
|
768
|
+
}
|
|
769
|
+
// Overwrite emotional arcs
|
|
770
|
+
if (output.updatedEmotionalArcs) {
|
|
771
|
+
writes.push(writeFile(join(storyDir, "emotional_arcs.md"), output.updatedEmotionalArcs, "utf-8"));
|
|
772
|
+
}
|
|
773
|
+
// Overwrite character matrix
|
|
774
|
+
if (output.updatedCharacterMatrix) {
|
|
775
|
+
writes.push(writeFile(join(storyDir, "character_matrix.md"), output.updatedCharacterMatrix, "utf-8"));
|
|
776
|
+
}
|
|
777
|
+
await Promise.all(writes);
|
|
778
|
+
}
|
|
779
|
+
renderDeltaSummaryRow(delta) {
|
|
780
|
+
if (!delta.chapterSummary)
|
|
781
|
+
return "";
|
|
782
|
+
const summary = delta.chapterSummary;
|
|
783
|
+
const row = [
|
|
784
|
+
summary.chapter,
|
|
785
|
+
summary.title,
|
|
786
|
+
summary.characters,
|
|
787
|
+
summary.events,
|
|
788
|
+
summary.stateChanges,
|
|
789
|
+
summary.hookActivity,
|
|
790
|
+
summary.mood,
|
|
791
|
+
summary.chapterType,
|
|
792
|
+
].map((value) => String(value).replace(/\|/g, "\\|").trim()).join(" | ");
|
|
793
|
+
return `| ${row} |`;
|
|
794
|
+
}
|
|
795
|
+
normalizeRuntimeStateDeltaChapter(delta, authoritativeChapterNumber) {
|
|
796
|
+
const hookOps = delta.hookOps ?? {
|
|
797
|
+
upsert: [],
|
|
798
|
+
mention: [],
|
|
799
|
+
resolve: [],
|
|
800
|
+
defer: [],
|
|
801
|
+
};
|
|
802
|
+
let changed = delta.chapter !== authoritativeChapterNumber;
|
|
803
|
+
const normalizedUpserts = hookOps.upsert.map((hook) => {
|
|
804
|
+
const startChapter = Math.min(hook.startChapter, authoritativeChapterNumber);
|
|
805
|
+
const lastAdvancedChapter = Math.min(hook.lastAdvancedChapter, authoritativeChapterNumber);
|
|
806
|
+
if (startChapter !== hook.startChapter || lastAdvancedChapter !== hook.lastAdvancedChapter) {
|
|
807
|
+
changed = true;
|
|
808
|
+
}
|
|
809
|
+
if (startChapter === hook.startChapter && lastAdvancedChapter === hook.lastAdvancedChapter) {
|
|
810
|
+
return hook;
|
|
811
|
+
}
|
|
812
|
+
return {
|
|
813
|
+
...hook,
|
|
814
|
+
startChapter,
|
|
815
|
+
lastAdvancedChapter,
|
|
816
|
+
};
|
|
817
|
+
});
|
|
818
|
+
if (delta.chapterSummary?.chapter !== undefined && delta.chapterSummary.chapter !== authoritativeChapterNumber) {
|
|
819
|
+
changed = true;
|
|
820
|
+
}
|
|
821
|
+
if (!changed) {
|
|
822
|
+
return delta;
|
|
823
|
+
}
|
|
824
|
+
return {
|
|
825
|
+
...delta,
|
|
826
|
+
chapter: authoritativeChapterNumber,
|
|
827
|
+
hookOps: {
|
|
828
|
+
...hookOps,
|
|
829
|
+
upsert: normalizedUpserts,
|
|
830
|
+
},
|
|
831
|
+
chapterSummary: delta.chapterSummary
|
|
832
|
+
? {
|
|
833
|
+
...delta.chapterSummary,
|
|
834
|
+
chapter: authoritativeChapterNumber,
|
|
835
|
+
}
|
|
836
|
+
: undefined,
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
async buildRuntimeStateArtifactsIfPresent(bookDir, delta, language, authoritativeChapterNumber, allowReapply) {
|
|
840
|
+
if (!delta)
|
|
841
|
+
return null;
|
|
842
|
+
const safeDelta = authoritativeChapterNumber === undefined
|
|
843
|
+
? delta
|
|
844
|
+
: this.normalizeRuntimeStateDeltaChapter(delta, authoritativeChapterNumber);
|
|
845
|
+
return buildRuntimeStateArtifacts({
|
|
846
|
+
bookDir,
|
|
847
|
+
delta: safeDelta,
|
|
848
|
+
language,
|
|
849
|
+
allowReapply,
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
async resolveRuntimeStateArtifactsForOutput(bookDir, output, language) {
|
|
853
|
+
if (!output.runtimeStateDelta)
|
|
854
|
+
return null;
|
|
855
|
+
const safeDelta = this.normalizeRuntimeStateDeltaChapter(output.runtimeStateDelta, output.chapterNumber);
|
|
856
|
+
if (safeDelta === output.runtimeStateDelta
|
|
857
|
+
&& output.runtimeStateSnapshot
|
|
858
|
+
&& output.updatedChapterSummaries
|
|
859
|
+
&& output.updatedState
|
|
860
|
+
&& output.updatedHooks) {
|
|
861
|
+
return {
|
|
862
|
+
snapshot: output.runtimeStateSnapshot,
|
|
863
|
+
resolvedDelta: safeDelta,
|
|
864
|
+
currentStateMarkdown: output.updatedState,
|
|
865
|
+
hooksMarkdown: output.updatedHooks,
|
|
866
|
+
chapterSummariesMarkdown: output.updatedChapterSummaries,
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
return buildRuntimeStateArtifacts({
|
|
870
|
+
bookDir,
|
|
871
|
+
delta: safeDelta,
|
|
872
|
+
language,
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
async appendChapterSummary(storyDir, summary, language) {
|
|
876
|
+
const summaryPath = join(storyDir, "chapter_summaries.md");
|
|
877
|
+
let existing = "";
|
|
878
|
+
try {
|
|
879
|
+
existing = await readFile(summaryPath, "utf-8");
|
|
880
|
+
}
|
|
881
|
+
catch {
|
|
882
|
+
// File doesn't exist yet — start with header
|
|
883
|
+
existing = language === "en"
|
|
884
|
+
? "# Chapter Summaries\n\n| Chapter | Title | Characters | Key Events | State Changes | Hook Activity | Mood | Chapter Type |\n| --- | --- | --- | --- | --- | --- | --- | --- |\n"
|
|
885
|
+
: "# 章节摘要\n\n| 章节 | 标题 | 出场人物 | 关键事件 | 状态变化 | 伏笔动态 | 情绪基调 | 章节类型 |\n|------|------|----------|----------|----------|----------|----------|----------|\n";
|
|
886
|
+
}
|
|
887
|
+
// Extract only the data row(s) from the summary (skip header lines)
|
|
888
|
+
const dataRows = summary
|
|
889
|
+
.split("\n")
|
|
890
|
+
.filter((line) => line.startsWith("|")
|
|
891
|
+
&& !line.startsWith("| 章节")
|
|
892
|
+
&& !line.startsWith("| Chapter")
|
|
893
|
+
&& !line.startsWith("|--")
|
|
894
|
+
&& !line.startsWith("| ---"))
|
|
895
|
+
.join("\n");
|
|
896
|
+
if (dataRows) {
|
|
897
|
+
// Deduplicate: remove existing rows with the same chapter number before appending
|
|
898
|
+
const newChapterNums = new Set(dataRows.split("\n")
|
|
899
|
+
.map((line) => line.split("|")[1]?.trim())
|
|
900
|
+
.filter((ch) => ch && /^\d+$/.test(ch)));
|
|
901
|
+
const deduped = existing
|
|
902
|
+
.split("\n")
|
|
903
|
+
.filter((line) => {
|
|
904
|
+
if (!line.startsWith("|"))
|
|
905
|
+
return true;
|
|
906
|
+
const chNum = line.split("|")[1]?.trim();
|
|
907
|
+
return !chNum || !newChapterNums.has(chNum);
|
|
908
|
+
})
|
|
909
|
+
.join("\n");
|
|
910
|
+
await writeFile(summaryPath, `${deduped.trimEnd()}\n${dataRows}\n`, "utf-8");
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
buildStyleFingerprint(styleProfileRaw) {
|
|
914
|
+
if (!styleProfileRaw || styleProfileRaw === "(文件尚未创建)")
|
|
915
|
+
return undefined;
|
|
916
|
+
try {
|
|
917
|
+
const profile = JSON.parse(styleProfileRaw);
|
|
918
|
+
const lines = [];
|
|
919
|
+
if (profile.avgSentenceLength)
|
|
920
|
+
lines.push(`- 平均句长:${profile.avgSentenceLength}字`);
|
|
921
|
+
if (profile.sentenceLengthStdDev)
|
|
922
|
+
lines.push(`- 句长标准差:${profile.sentenceLengthStdDev}`);
|
|
923
|
+
if (profile.avgParagraphLength)
|
|
924
|
+
lines.push(`- 平均段落长度:${profile.avgParagraphLength}字`);
|
|
925
|
+
if (profile.paragraphLengthRange)
|
|
926
|
+
lines.push(`- 段落长度范围:${profile.paragraphLengthRange.min}-${profile.paragraphLengthRange.max}字`);
|
|
927
|
+
if (profile.vocabularyDiversity)
|
|
928
|
+
lines.push(`- 词汇多样性(TTR):${profile.vocabularyDiversity}`);
|
|
929
|
+
if (profile.topPatterns?.length > 0)
|
|
930
|
+
lines.push(`- 高频句式:${profile.topPatterns.join("、")}`);
|
|
931
|
+
if (profile.rhetoricalFeatures?.length > 0)
|
|
932
|
+
lines.push(`- 修辞特征:${profile.rhetoricalFeatures.join("、")}`);
|
|
933
|
+
return lines.length > 0 ? lines.join("\n") : undefined;
|
|
934
|
+
}
|
|
935
|
+
catch {
|
|
936
|
+
return undefined;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Extract dialogue fingerprints from recent chapters.
|
|
941
|
+
* For each character with multiple dialogue lines, compute speaking style markers.
|
|
942
|
+
*/
|
|
943
|
+
extractDialogueFingerprints(recentChapters, _storyBible) {
|
|
944
|
+
if (!recentChapters)
|
|
945
|
+
return "";
|
|
946
|
+
// Match dialogue patterns:
|
|
947
|
+
// Chinese: "speaker说道:" or dialogue in ""「」
|
|
948
|
+
// English: "dialogue," speaker said. or "dialogue."
|
|
949
|
+
const dialogueRegex = /(?:(.{1,6})(?:说道|道|喝道|冷声道|笑道|怒道|低声道|大声道|喝骂道|冷笑道|沉声道|喊道|叫道|问道|答道)\s*[::]\s*["""「]([^"""」]+)["""」])|["""「]([^"""」]{2,})["""」]|"([^"]{2,})"/g;
|
|
950
|
+
const characterDialogues = new Map();
|
|
951
|
+
let match;
|
|
952
|
+
while ((match = dialogueRegex.exec(recentChapters)) !== null) {
|
|
953
|
+
const speaker = match[1]?.trim();
|
|
954
|
+
const line = match[2] ?? match[3] ?? "";
|
|
955
|
+
if (speaker && line.length > 1) {
|
|
956
|
+
const existing = characterDialogues.get(speaker) ?? [];
|
|
957
|
+
characterDialogues.set(speaker, [...existing, line]);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
// Only include characters with >=2 dialogue lines
|
|
961
|
+
const fingerprints = [];
|
|
962
|
+
for (const [character, lines] of characterDialogues) {
|
|
963
|
+
if (lines.length < 2)
|
|
964
|
+
continue;
|
|
965
|
+
const avgLen = Math.round(lines.reduce((sum, l) => sum + l.length, 0) / lines.length);
|
|
966
|
+
const isShort = avgLen < 15;
|
|
967
|
+
// Find frequent words/phrases (2+ occurrences)
|
|
968
|
+
const wordCounts = new Map();
|
|
969
|
+
for (const line of lines) {
|
|
970
|
+
// Extract 2-3 char segments as "words"
|
|
971
|
+
for (let i = 0; i < line.length - 1; i++) {
|
|
972
|
+
const bigram = line.slice(i, i + 2);
|
|
973
|
+
wordCounts.set(bigram, (wordCounts.get(bigram) ?? 0) + 1);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
const frequentWords = [...wordCounts.entries()]
|
|
977
|
+
.filter(([, count]) => count >= 2)
|
|
978
|
+
.sort((a, b) => b[1] - a[1])
|
|
979
|
+
.slice(0, 3)
|
|
980
|
+
.map(([w]) => `「${w}」`);
|
|
981
|
+
// Detect style markers
|
|
982
|
+
const markers = [];
|
|
983
|
+
if (isShort)
|
|
984
|
+
markers.push("短句为主");
|
|
985
|
+
else
|
|
986
|
+
markers.push("长句为主");
|
|
987
|
+
const questionCount = lines.filter((l) => l.includes("?") || l.includes("?")).length;
|
|
988
|
+
if (questionCount > lines.length * 0.3)
|
|
989
|
+
markers.push("反问多");
|
|
990
|
+
if (frequentWords.length > 0)
|
|
991
|
+
markers.push(`常用${frequentWords.join("")}`);
|
|
992
|
+
fingerprints.push(`${character}:${markers.join(",")}`);
|
|
993
|
+
}
|
|
994
|
+
return fingerprints.length > 0 ? fingerprints.join(";") : "";
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Find relevant chapter summaries based on volume outline context.
|
|
998
|
+
* Extracts character names and hook IDs from the current volume's outline,
|
|
999
|
+
* then searches chapter summaries for matching entries.
|
|
1000
|
+
*/
|
|
1001
|
+
findRelevantSummaries(chapterSummaries, volumeOutline, chapterNumber) {
|
|
1002
|
+
if (!chapterSummaries || chapterSummaries === "(文件尚未创建)")
|
|
1003
|
+
return "";
|
|
1004
|
+
if (!volumeOutline || volumeOutline === "(文件尚未创建)")
|
|
1005
|
+
return "";
|
|
1006
|
+
// Extract character names from volume outline (Chinese name patterns)
|
|
1007
|
+
const nameRegex = /[\u4e00-\u9fff]{2,4}(?=[,、。:]|$)/g;
|
|
1008
|
+
const outlineNames = new Set();
|
|
1009
|
+
let nameMatch;
|
|
1010
|
+
while ((nameMatch = nameRegex.exec(volumeOutline)) !== null) {
|
|
1011
|
+
outlineNames.add(nameMatch[0]);
|
|
1012
|
+
}
|
|
1013
|
+
// Extract hook IDs from volume outline
|
|
1014
|
+
const hookRegex = /H\d{2,}/g;
|
|
1015
|
+
const hookIds = new Set();
|
|
1016
|
+
let hookMatch;
|
|
1017
|
+
while ((hookMatch = hookRegex.exec(volumeOutline)) !== null) {
|
|
1018
|
+
hookIds.add(hookMatch[0]);
|
|
1019
|
+
}
|
|
1020
|
+
if (outlineNames.size === 0 && hookIds.size === 0)
|
|
1021
|
+
return "";
|
|
1022
|
+
// Search chapter summaries for matching rows
|
|
1023
|
+
const rows = chapterSummaries.split("\n").filter((line) => line.startsWith("|") && !line.startsWith("| 章节") && !line.startsWith("|--") && !line.startsWith("| -"));
|
|
1024
|
+
const matchedRows = rows.filter((row) => {
|
|
1025
|
+
for (const name of outlineNames) {
|
|
1026
|
+
if (row.includes(name))
|
|
1027
|
+
return true;
|
|
1028
|
+
}
|
|
1029
|
+
for (const hookId of hookIds) {
|
|
1030
|
+
if (row.includes(hookId))
|
|
1031
|
+
return true;
|
|
1032
|
+
}
|
|
1033
|
+
return false;
|
|
1034
|
+
});
|
|
1035
|
+
// Skip only the last chapter (its full text is already in context via loadRecentChapters)
|
|
1036
|
+
const filteredRows = matchedRows.filter((row) => {
|
|
1037
|
+
const chNumMatch = row.match(/\|\s*(\d+)\s*\|/);
|
|
1038
|
+
if (!chNumMatch)
|
|
1039
|
+
return true;
|
|
1040
|
+
const num = parseInt(chNumMatch[1], 10);
|
|
1041
|
+
return num < chapterNumber - 1;
|
|
1042
|
+
});
|
|
1043
|
+
return filteredRows.length > 0 ? filteredRows.join("\n") : "";
|
|
1044
|
+
}
|
|
1045
|
+
sanitizeFilename(title) {
|
|
1046
|
+
return title
|
|
1047
|
+
.replace(/[/\\?%*:|"<>]/g, "")
|
|
1048
|
+
.replace(/\s+/g, "_")
|
|
1049
|
+
.slice(0, 50);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
//# sourceMappingURL=writer.js.map
|