@oh-story/dsh 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -14
- package/lib/client.js +209 -73
- package/lib/client.js.map +3 -3
- package/lib/drama/manifest.json +248 -168
- package/lib/drama/skills/short-drama/SKILL.md +16 -4
- package/lib/drama/skills/short-drama/assets/project-template/short-drama.json +3 -0
- package/lib/drama/skills/short-drama/references/creator-documents.md +3 -0
- package/lib/drama/skills/short-drama/references/knowhow-index.md +15 -0
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +14 -0
- package/lib/drama/skills/short-drama/scripts/project_tool.py +131 -0
- package/lib/drama/skills/short-drama-assets/SKILL.md +2 -1
- package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +2 -0
- package/lib/drama/skills/short-drama-assets/references/character-and-look.md +10 -0
- package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +6 -9
- package/lib/drama/skills/short-drama-assets/references/era-anchors.md +37 -0
- package/lib/drama/skills/short-drama-assets/references/stage-contract.md +2 -0
- package/lib/drama/skills/short-drama-assets/references/voice-direction.md +28 -0
- package/lib/drama/skills/short-drama-develop/SKILL.md +10 -4
- package/lib/drama/skills/short-drama-develop/assets/adaptation-map.example.jsonl +1 -1
- package/lib/drama/skills/short-drama-develop/assets/creative-brief.md +8 -0
- package/lib/drama/skills/short-drama-develop/references/adaptation-craft.md +80 -13
- package/lib/drama/skills/short-drama-develop/references/episode-design.md +95 -12
- package/lib/drama/skills/short-drama-develop/references/genre-and-hook-playbook.md +9 -5
- package/lib/drama/skills/short-drama-develop/references/genre-cards//345/244/215/344/273/207/346/211/223/350/204/270.md +1 -1
- package/lib/drama/skills/short-drama-develop/references/genre-cards//347/263/273/347/273/237/346/265/201.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards//351/200/206/350/242/255.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards//351/207/215/347/224/237/347/251/277/350/266/212.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards.md +10 -7
- package/lib/drama/skills/short-drama-develop/references/premise-devices.md +15 -9
- package/lib/drama/skills/short-drama-develop/references/serial-character-and-memory.md +3 -2
- package/lib/drama/skills/short-drama-develop/references/stage-contract.md +14 -7
- package/lib/drama/skills/short-drama-develop/references/story-craft.md +7 -3
- package/lib/drama/skills/short-drama-edit/SKILL.md +65 -44
- package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +49 -13
- package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +4 -2
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Overlay.tsx +31 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +5 -5
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +68 -69
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +39 -29
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/rules.mjs +188 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +26 -13
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/Card.tsx +72 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/CornerChip.tsx +39 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/ScreenText.tsx +36 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/SystemPanel.tsx +44 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/TaskPanel.tsx +100 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/chrome.tsx +197 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/tokens.ts +62 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +1 -0
- package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +6 -4
- package/lib/drama/skills/short-drama-edit/references/cut-craft.md +26 -67
- package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +31 -56
- package/lib/drama/skills/short-drama-edit/references/generated-footage.md +42 -78
- package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +44 -92
- package/lib/drama/skills/short-drama-edit/references/stage-contract.md +14 -16
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1272 -125
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +113 -9
- package/lib/drama/skills/short-drama-image-prompts/SKILL.md +2 -0
- package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +11 -0
- package/lib/drama/skills/short-drama-novel-analyze/SKILL.md +3 -3
- package/lib/drama/skills/short-drama-novel-analyze/assets/episode-candidate.example.jsonl +2 -2
- package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-triage.md +8 -4
- package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-value.md +10 -3
- package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +25 -1
- package/lib/drama/skills/short-drama-novel-analyze/references/stage-contract.md +1 -0
- package/lib/drama/skills/short-drama-produce/SKILL.md +5 -1
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +14 -20
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +8 -26
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-speech.md +41 -0
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +142 -1
- package/lib/drama/skills/short-drama-review/SKILL.md +4 -5
- package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +6 -19
- package/lib/drama/skills/short-drama-review/references/review-method.md +6 -1
- package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +11 -0
- package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +77 -3
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +24 -35
- package/lib/drama/skills/short-drama-review/references/stage-contract.md +3 -0
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +23 -11
- package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +1 -1
- package/lib/drama/skills/short-drama-storyboard/references/lighting-craft.md +39 -0
- package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +2 -2
- package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +139 -34
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +5 -3
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +15 -12
- package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +29 -0
- package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +4 -5
- package/lib/drama/skills/short-drama-video-prompts/references/generability.md +4 -2
- package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +52 -39
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +24 -21
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +9 -23
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +14 -26
- package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +1 -1
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +2 -3
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +34 -9
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +3 -2
- package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +8 -1
- package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +103 -0
- package/lib/drama/skills/short-drama-video-prompts/scripts/motion_timing_check.py +8 -7
- package/lib/drama/skills/short-drama-write/SKILL.md +10 -7
- package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +1 -1
- package/lib/drama/skills/short-drama-write/references/script-craft.md +56 -3
- package/lib/drama/skills/short-drama-write/references/stage-contract.md +4 -0
- package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +48 -2
- package/lib/index.js +346 -98
- package/lib/index.js.map +4 -4
- package/lib/novel-to-game/README.md +299 -59
- package/lib/novel-to-game/README_ZH.md +299 -56
- package/lib/novel-to-game/VERSION +1 -1
- package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +3 -1
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +11 -3
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +397 -8
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +0 -31
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +71 -73
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +78 -89
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +37 -14
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +5 -5
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -1
- package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +11 -16
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +11 -11
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +23 -0
- package/lib/novel-to-game/manifest.json +77 -87
- package/lib/novel-to-game/skills/game-art-direction/SKILL.md +7 -12
- package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +27 -106
- package/lib/novel-to-game/skills/game-build/SKILL.md +13 -14
- package/lib/novel-to-game/skills/game-build/references/blender-asset-workflow.md +24 -0
- package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +45 -82
- package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +14 -45
- package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +30 -65
- package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +13 -27
- package/lib/novel-to-game/skills/game-concept/SKILL.md +13 -15
- package/lib/novel-to-game/skills/game-concept/references/concept-method.md +28 -75
- package/lib/novel-to-game/skills/game-qa/SKILL.md +6 -7
- package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +9 -5
- package/lib/novel-to-game/skills/game-world-design/SKILL.md +12 -16
- package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +23 -72
- package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +14 -48
- package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +36 -103
- package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +4 -10
- package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +4 -4
- package/lib/novel-to-game/skills/novel-to-game/SKILL.md +9 -21
- package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +5 -4
- package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +5 -8
- package/lib/oh-story/manifest.json +607 -327
- package/lib/oh-story/roles/chapter-extractor.md +177 -237
- package/lib/oh-story/roles/character-designer.md +1 -2
- package/lib/oh-story/roles/consistency-checker.md +15 -13
- package/lib/oh-story/roles/narrative-writer.md +36 -38
- package/lib/oh-story/roles/story-architect.md +9 -61
- package/lib/oh-story/roles/story-explorer.md +20 -125
- package/lib/oh-story/roles/story-researcher.md +0 -1
- package/lib/oh-story/skills/story/SKILL.md +15 -9
- package/lib/oh-story/skills/story/VERSION +1 -1
- package/lib/oh-story/skills/story/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story/references/author-memory.md +46 -94
- package/lib/oh-story/skills/story/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-cover/SKILL.md +10 -1
- package/lib/oh-story/skills/story-deslop/SKILL.md +59 -73
- package/lib/oh-story/skills/story-deslop/references/agent-calls.md +5 -0
- package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +12 -11
- package/lib/oh-story/skills/story-deslop/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story-deslop/references/author-memory.md +46 -94
- package/lib/oh-story/skills/story-deslop/references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +22 -5
- package/lib/oh-story/skills/story-deslop/references/style-resolution.md +4 -4
- package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +155 -8
- package/lib/oh-story/skills/story-import/SKILL.md +64 -584
- package/lib/oh-story/skills/story-import/references/character-state-reverse.md +1 -1
- package/lib/oh-story/skills/story-import/references/deep-analysis.md +47 -0
- package/lib/oh-story/skills/story-import/references/format-and-structure.md +1 -1
- package/lib/oh-story/skills/story-import/references/import-report.md +64 -0
- package/lib/oh-story/skills/story-import/references/import-special-cases.md +34 -0
- package/lib/oh-story/skills/story-import/references/import-tracking.md +95 -0
- package/lib/oh-story/skills/story-import/references/length-routing.md +1 -1
- package/lib/oh-story/skills/story-import/references/outline-reverse-rules.md +79 -0
- package/lib/oh-story/skills/story-import/references/outline-reverse.md +24 -0
- package/lib/oh-story/skills/story-import/references/state-tracking.md +4 -69
- package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +98 -92
- package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +24 -12
- package/lib/oh-story/skills/story-import/references/tracking-initialization.md +1 -1
- package/lib/oh-story/skills/story-import/references/tracking-transaction.md +15 -11
- package/lib/oh-story/skills/story-import/scripts/build_outline_brief.py +332 -0
- package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +132 -27
- package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +478 -15
- package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +182 -23
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +81 -270
- package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +252 -0
- package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +3 -3
- package/lib/oh-story/skills/story-long-analyze/references/final-checks.md +19 -0
- package/lib/oh-story/skills/story-long-analyze/references/index-rebuild.md +11 -0
- package/lib/oh-story/skills/story-long-analyze/references/inspiration-library.md +105 -0
- package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +138 -64
- package/lib/oh-story/skills/story-long-analyze/references/semantic-acceptance-fixtures.md +84 -0
- package/lib/oh-story/skills/story-long-analyze/references/stage1-golden-chapters.md +128 -0
- package/lib/oh-story/skills/story-long-analyze/references/stage2-extraction.md +229 -0
- package/lib/oh-story/skills/story-long-analyze/references/stage3-plot-rhythm.md +480 -0
- package/lib/oh-story/skills/story-long-analyze/references/stage4-characters-settings.md +195 -0
- package/lib/oh-story/skills/story-long-analyze/references/stage5-report.md +33 -0
- package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +55 -128
- package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +6 -6
- package/lib/oh-story/skills/story-long-analyze/references/synthesis-inputs.md +39 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/build_chapter_index.py +466 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/inspect_existing_assets.py +702 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/inspiration_index.py +864 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +1145 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/render_relation_chart.py +425 -0
- package/lib/oh-story/skills/story-long-scan/SKILL.md +45 -194
- package/lib/oh-story/skills/story-long-scan/references/platform-ciweimao.md +19 -0
- package/lib/oh-story/skills/story-long-scan/references/platform-fanqie.md +45 -0
- package/lib/oh-story/skills/story-long-scan/references/platform-jjwxc.md +28 -0
- package/lib/oh-story/skills/story-long-scan/references/platform-qidian.md +35 -0
- package/lib/oh-story/skills/story-long-scan/references/platform-qimao.md +27 -0
- package/lib/oh-story/skills/story-long-scan/references/topic-decision.md +6 -2
- package/lib/oh-story/skills/story-long-write/SKILL.md +49 -81
- package/lib/oh-story/skills/story-long-write/references/agent-calls.md +47 -0
- package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +12 -11
- package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +24 -202
- package/lib/oh-story/skills/story-long-write/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story-long-write/references/author-memory.md +46 -94
- package/lib/oh-story/skills/story-long-write/references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +7 -11
- package/lib/oh-story/skills/story-long-write/references/character-relations.md +49 -1
- package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +26 -0
- package/lib/oh-story/skills/story-long-write/references/deslop-gates.md +171 -0
- package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +4 -0
- package/lib/oh-story/skills/story-long-write/references/emotion-on-page.md +116 -0
- package/lib/oh-story/skills/story-long-write/references/emotional-arc-design.md +1 -114
- package/lib/oh-story/skills/story-long-write/references/emotional-methods.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +7 -15
- package/lib/oh-story/skills/story-long-write/references/long-format.md +0 -8
- package/lib/oh-story/skills/story-long-write/references/long-genre-catalog.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/long-genre-mechanics.md +3 -3
- package/lib/oh-story/skills/story-long-write/references/opening-design.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/outline-conflict.md +4 -4
- package/lib/oh-story/skills/story-long-write/references/outline-methods.md +7 -7
- package/lib/oh-story/skills/story-long-write/references/outline-rhythm.md +3 -3
- package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +14 -69
- package/lib/oh-story/skills/story-long-write/references/plot-frameworks.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/plot-special-topics.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/project-files.md +19 -13
- package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +13 -5
- package/lib/oh-story/skills/story-long-write/references/reference-index.md +5 -6
- package/lib/oh-story/skills/story-long-write/references/stop-and-fix.md +9 -0
- package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -58
- package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/style-resolution.md +4 -4
- package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +15 -11
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +81 -121
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +41 -139
- package/lib/oh-story/skills/story-long-write/references/workflow-outline.md +157 -0
- package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +57 -67
- package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +112 -253
- package/lib/oh-story/skills/story-long-write/references/workflow-volume.md +142 -0
- package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +6 -4
- package/lib/oh-story/skills/story-long-write/references/writing-craft.md +19 -65
- package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-long-write/scripts/build_architect_brief.py +260 -0
- package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +226 -25
- package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +155 -8
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +132 -27
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +7 -2
- package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +7 -4
- package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +304 -46
- package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +478 -15
- package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +182 -23
- package/lib/oh-story/skills/story-review/SKILL.md +31 -302
- package/lib/oh-story/skills/story-review/references/agent-prompts.md +184 -0
- package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +12 -11
- package/lib/oh-story/skills/story-review/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story-review/references/author-memory.md +46 -94
- package/lib/oh-story/skills/story-review/references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-review/references/batch-review.md +19 -0
- package/lib/oh-story/skills/story-review/references/character-relations.md +49 -1
- package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +4 -0
- package/lib/oh-story/skills/story-review/references/review-quality.md +6 -11
- package/lib/oh-story/skills/story-review/references/review-tracking.md +10 -0
- package/lib/oh-story/skills/story-review/references/solo.md +52 -0
- package/lib/oh-story/skills/story-review/references/style-resolution.md +4 -4
- package/lib/oh-story/skills/story-review/references/tracking-initialization.md +1 -1
- package/lib/oh-story/skills/story-review/references/tracking-transaction.md +15 -11
- package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +155 -8
- package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +478 -15
- package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +182 -23
- package/lib/oh-story/skills/story-setup/SKILL.md +71 -452
- package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +4 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +12 -11
- package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/benchmark-style-load.md +107 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/character-relations.md +49 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +22 -5
- package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +4 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/emotion-on-page.md +116 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/emotional-arc-design.md +1 -114
- package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-catalog.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-mechanics.md +3 -3
- package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +7 -5
- package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/outline-conflict.md +4 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/outline-methods.md +7 -7
- package/lib/oh-story/skills/story-setup/references/agent-references/outline-rhythm.md +3 -3
- package/lib/oh-story/skills/story-setup/references/agent-references/short-emotional-methods.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +0 -15
- package/lib/oh-story/skills/story-setup/references/agent-references/short-reversal.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -58
- package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-resolution.md +4 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +19 -65
- package/lib/oh-story/skills/story-setup/references/diagnostics.md +4 -4
- package/lib/oh-story/skills/story-short-analyze/SKILL.md +56 -110
- package/lib/oh-story/skills/story-short-analyze/references/analysis-character-relations.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/analysis-method.md +42 -0
- package/lib/oh-story/skills/story-short-analyze/references/optional-modules.md +97 -0
- package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +9 -47
- package/lib/oh-story/skills/story-short-analyze/references/quality-checklist.md +80 -0
- package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +4 -4
- package/lib/oh-story/skills/story-short-analyze/references/stage2-3-structure-emotion.md +308 -0
- package/lib/oh-story/skills/story-short-analyze/references/stage4-6-reversal-summary.md +611 -0
- package/lib/oh-story/skills/story-short-scan/SKILL.md +35 -65
- package/lib/oh-story/skills/story-short-scan/references/manual-rank-input.md +36 -0
- package/lib/oh-story/skills/story-short-scan/references/platform-dianzhong.md +24 -0
- package/lib/oh-story/skills/story-short-scan/references/platform-heiyan.md +21 -0
- package/lib/oh-story/skills/story-short-scan/references/topic-match.md +38 -0
- package/lib/oh-story/skills/story-short-write/SKILL.md +23 -175
- package/lib/oh-story/skills/story-short-write/references/agent-calls.md +13 -0
- package/lib/oh-story/skills/story-short-write/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story-short-write/references/author-memory.md +46 -94
- package/lib/oh-story/skills/story-short-write/references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +1 -21
- package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +4 -0
- package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +0 -15
- package/lib/oh-story/skills/story-short-write/references/output-contract.md +9 -47
- package/lib/oh-story/skills/story-short-write/references/reference-index.md +17 -47
- package/lib/oh-story/skills/story-short-write/references/scene-craft.md +40 -0
- package/lib/oh-story/skills/story-short-write/references/short-craft.md +7 -43
- package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -1
- package/lib/oh-story/skills/story-short-write/references/short-reversal.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/style-resolution.md +4 -4
- package/lib/oh-story/skills/story-short-write/references/submission-craft.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/villain-and-reveal.md +5 -10
- package/lib/oh-story/skills/story-short-write/references/workflow-design.md +21 -13
- package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +116 -0
- package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +99 -0
- package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +7 -119
- package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +155 -8
- package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +7 -2
- package/lib/types/client/drama-production-view.d.ts.map +1 -1
- package/lib/types/client/file-activity.d.ts +19 -1
- package/lib/types/client/file-activity.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/production-prompts.d.ts +7 -0
- package/lib/types/client/production-prompts.d.ts.map +1 -1
- package/lib/types/client/production-runtime.d.ts +16 -0
- package/lib/types/client/production-runtime.d.ts.map +1 -1
- package/lib/types/client/video-studio.d.ts.map +1 -1
- package/lib/types/drama-adapters.d.ts +5 -3
- package/lib/types/drama-adapters.d.ts.map +1 -1
- package/lib/types/native-hooks.d.ts +53 -1
- package/lib/types/native-hooks.d.ts.map +1 -1
- package/lib/types/role-identity.d.ts +17 -0
- package/lib/types/role-identity.d.ts.map +1 -0
- package/lib/types/role-tool.d.ts.map +1 -1
- package/lib/types/skill-provider.d.ts.map +1 -1
- package/lib/types/video-project.d.ts.map +1 -1
- package/lib/types/workspace-route.d.ts.map +1 -1
- package/lib/video-recap/CHANGELOG.md +60 -1
- package/lib/video-recap/README.en.md +348 -137
- package/lib/video-recap/README.md +340 -136
- package/lib/video-recap/manifest.json +406 -251
- package/lib/video-recap/skills/video-assemble/SKILL.md +30 -15
- package/lib/video-recap/skills/video-assemble/references/audio-modes.md +63 -0
- package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +75 -0
- package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +118 -0
- package/lib/video-recap/skills/video-assemble/references/narration-adoption.md +103 -0
- package/lib/video-recap/skills/video-assemble/references/packaging.md +45 -0
- package/lib/video-recap/skills/video-assemble/references/pair-media.md +74 -0
- package/lib/video-recap/skills/video-assemble/references/source-score.md +147 -0
- package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +202 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/__init__.py +1 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +279 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/frozen_audio.py +187 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/narration_binding.py +323 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_inputs.py +109 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +130 -0
- package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +10 -46
- package/lib/video-recap/skills/video-assemble/scripts/assemble.py +378 -119
- package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +29 -12
- package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +95 -44
- package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +75 -32
- package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +11 -11
- package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +293 -0
- package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +71 -78
- package/lib/video-recap/skills/video-assemble/scripts/jianying/__init__.py +1 -0
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_builders.py → jianying/builders.py} +39 -67
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_model.py → jianying/model.py} +3 -11
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_optional.py → jianying/optional.py} +1 -1
- package/lib/video-recap/skills/video-assemble/scripts/jianying/schema.py +53 -0
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_templates.py → jianying/templates.py} +1 -1
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_timeline_contract.py → jianying/timeline_contract.py} +34 -13
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_writer.py → jianying/writer.py} +7 -17
- package/lib/video-recap/skills/video-assemble/scripts/lib.py +37 -4
- package/lib/video-recap/skills/video-assemble/scripts/media.py +12 -21
- package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +64 -34
- package/lib/video-recap/skills/video-assemble/scripts/packaging.py +122 -0
- package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +216 -0
- package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +1 -3
- package/lib/video-recap/skills/video-assemble/scripts/source_score.py +469 -0
- package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +25 -9
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/__init__.py +1 -0
- package/lib/video-recap/skills/video-assemble/scripts/{subtitle_core.py → subtitles/core.py} +13 -6
- package/lib/video-recap/skills/video-assemble/scripts/{subtitle_render.py → subtitles/render.py} +5 -4
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +324 -0
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +200 -0
- package/lib/video-recap/skills/video-assemble/scripts/timeline.py +30 -46
- package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +91 -7
- package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +22 -8
- package/lib/video-recap/skills/video-cut/SKILL.md +34 -16
- package/lib/video-recap/skills/video-cut/references/shot-review.md +50 -0
- package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -28
- package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +58 -62
- package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +21 -106
- package/lib/video-recap/skills/video-cut/scripts/cut_render.py +14 -9
- package/lib/video-recap/skills/video-cut/scripts/lib.py +39 -0
- package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +2 -155
- package/lib/video-recap/skills/video-cut/scripts/narrative_selection.py +244 -0
- package/lib/video-recap/skills/video-cut/scripts/shot_review.py +357 -0
- package/lib/video-recap/skills/video-recap/SKILL.md +86 -42
- package/lib/video-recap/skills/video-recap/references/audio-routing.md +126 -0
- package/lib/video-recap/skills/video-recap/references/config-playbook.md +12 -8
- package/lib/video-recap/skills/video-recap/references/data-schema.md +44 -33
- package/lib/video-recap/skills/video-recap/references/resource-library.md +151 -0
- package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +1 -1
- package/lib/video-recap/skills/video-recap/scripts/doctor.py +123 -30
- package/lib/video-recap/skills/video-recap/scripts/final_qc.py +22 -75
- package/lib/video-recap/skills/video-recap/scripts/lib.py +3 -3
- package/lib/video-recap/skills/video-recap/scripts/library.py +509 -0
- package/lib/video-recap/skills/video-recap/scripts/materials.py +63 -91
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +8 -70
- package/lib/video-recap/skills/video-recap/scripts/project_binding.py +286 -0
- package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +1 -0
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_client.py → qc/mimo_client.py} +1 -1
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_evidence.py → qc/mimo_evidence.py} +33 -66
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_observations.py → qc/mimo_observations.py} +3 -11
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_payload.py → qc/mimo_payload.py} +4 -7
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_report.py → qc/mimo_report.py} +17 -28
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_runner.py → qc/mimo_runner.py} +2 -2
- package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -19
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +119 -50
- package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +32 -114
- package/lib/video-recap/skills/video-recap/scripts/recap_review.py +9 -23
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +387 -334
- package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +23 -23
- package/lib/video-recap/skills/video-recap/scripts/recap_source.py +278 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +25 -12
- package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +59 -52
- package/lib/video-recap/skills/video-recap/scripts/resource_lock.py +162 -0
- package/lib/video-recap/skills/video-script/SKILL.md +16 -13
- package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +9 -5
- package/lib/video-recap/skills/video-script/references/promotional-copy.md +68 -0
- package/lib/video-recap/skills/video-script/references/research-guide.md +1 -1
- package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +24 -76
- package/lib/video-recap/skills/video-script/scripts/lib.py +10 -87
- package/lib/video-recap/skills/video-script/scripts/narration_lint.py +63 -39
- package/lib/video-recap/skills/video-script/scripts/review.py +0 -2
- package/lib/video-recap/skills/video-script/scripts/review_grounding.py +35 -59
- package/lib/video-recap/skills/video-script/scripts/review_response.py +66 -128
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +12 -20
- package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +4 -4
- package/lib/video-recap/skills/video-script/scripts/validate.py +53 -42
- package/lib/video-recap/skills/video-understanding/SKILL.md +6 -4
- package/lib/video-recap/skills/video-understanding/references/data-schema.md +50 -156
- package/lib/video-recap/skills/video-understanding/references/research-guide.md +4 -4
- package/lib/video-recap/skills/video-understanding/scripts/asr.py +85 -65
- package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +270 -0
- package/lib/video-recap/skills/video-understanding/scripts/brief.py +2 -5
- package/lib/video-recap/skills/video-understanding/scripts/briefing/__init__.py +1 -0
- package/lib/video-recap/skills/{video-script/scripts/agent_brief.py → video-understanding/scripts/briefing/builder.py} +38 -35
- package/lib/video-recap/skills/video-understanding/scripts/{brief_context.py → briefing/context.py} +4 -35
- package/lib/video-recap/skills/{video-script/scripts/brief_inputs.py → video-understanding/scripts/briefing/inputs.py} +9 -31
- package/lib/video-recap/skills/{video-script/scripts/brief_timeline.py → video-understanding/scripts/briefing/timeline.py} +5 -6
- package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +65 -116
- package/lib/video-recap/skills/video-understanding/scripts/detect.py +31 -40
- package/lib/video-recap/skills/video-understanding/scripts/lib.py +30 -86
- package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +63 -39
- package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +4 -4
- package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +17 -79
- package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +14 -40
- package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +64 -46
- package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +31 -11
- package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +19 -14
- package/lib/video-recap/skills/video-understanding/scripts/vlm.py +80 -108
- package/lib/video-recap/skills/video-voiceover/SKILL.md +29 -19
- package/lib/video-recap/skills/video-voiceover/references/index-tts.md +31 -0
- package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +123 -0
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +51 -59
- package/lib/video-recap/skills/video-voiceover/scripts/lib.py +8 -44
- package/lib/video-recap/skills/video-voiceover/scripts/providers/__init__.py +1 -0
- package/lib/video-recap/skills/video-voiceover/scripts/providers/index_tts.py +184 -0
- package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +212 -134
- package/package.json +45 -45
- package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +0 -58
- package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +0 -53
- package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +0 -38
- package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +0 -530
- package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +0 -559
- package/lib/oh-story/skills/story-long-scan/references/scan-output-format.md +0 -207
- package/lib/oh-story/skills/story-long-write/references/state-tracking.md +0 -108
- package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +0 -678
- package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +0 -561
- package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +0 -98
- package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +0 -225
- package/lib/video-recap/skills/video-recap/references/research-guide.md +0 -74
- package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +0 -250
- package/lib/video-recap/skills/video-script/scripts/brief_context.py +0 -348
- package/lib/video-recap/skills/video-script/scripts/narration.py +0 -12
- package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +0 -399
- package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +0 -290
- package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +0 -327
- /package/lib/video-recap/skills/video-assemble/scripts/{jianying_tracks.py → jianying/tracks.py} +0 -0
- /package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_contract.py → qc/mimo_contract.py} +0 -0
- /package/lib/video-recap/skills/video-voiceover/scripts/{fish_audio.py → providers/fish_audio.py} +0 -0
|
@@ -58,8 +58,31 @@ CONTEXT_HEADINGS = (
|
|
|
58
58
|
FORESHADOW_STATUSES = ("已埋", "已回收", "已过期", "放弃")
|
|
59
59
|
FORESHADOW_IMPORTANCE = ("高", "中", "低")
|
|
60
60
|
REVEAL_STATUSES = ("未揭示", "部分揭示", "已揭示")
|
|
61
|
+
# 伏笔与时间线条目里模型常用的同义写法:含义明确的直接改成正式名,不让一次提交为措辞失败。
|
|
62
|
+
ACTION_ALIASES = {
|
|
63
|
+
"add": "upsert", "new": "upsert", "create": "upsert", "update": "upsert", "plant": "upsert",
|
|
64
|
+
"advance": "upsert", "resolve": "upsert", "insert": "upsert",
|
|
65
|
+
"新增": "upsert", "埋设": "upsert", "更新": "upsert", "推进": "upsert", "回收": "upsert",
|
|
66
|
+
"remove": "delete", "删除": "delete",
|
|
67
|
+
}
|
|
68
|
+
# 带状态含义的伏笔动作词:它们也映射成 upsert,但动作词本身说了伏笔走到哪一步。
|
|
69
|
+
# status 缺省时按动作词补上(意图只有这一个来源,不会猜错);status 与动作词矛盾时退回,
|
|
70
|
+
# 因为无法判断是动作词用错还是状态写错——静默改任何一边都可能把没回收的伏笔记成已回收。
|
|
71
|
+
FORESHADOW_ACTION_STATUS = {
|
|
72
|
+
"resolve": "已回收", "回收": "已回收",
|
|
73
|
+
"advance": "已埋", "推进": "已埋",
|
|
74
|
+
}
|
|
75
|
+
FORESHADOW_KEY_ALIASES = {
|
|
76
|
+
"planned_chapter": "planned_resolution_chapter", "planned_payoff_chapter": "planned_resolution_chapter",
|
|
77
|
+
"payoff_chapter": "planned_resolution_chapter", "resolution_chapter": "planned_resolution_chapter",
|
|
78
|
+
"resolve_chapter": "planned_resolution_chapter", "plant_chapter": "planted_chapter",
|
|
79
|
+
"planted": "planted_chapter", "description": "summary", "content": "summary",
|
|
80
|
+
}
|
|
81
|
+
TIMELINE_KEY_ALIASES = {"time": "story_time", "fact": "objective_fact", "reveal": "reveal_status"}
|
|
61
82
|
INVALID_FILE_CHARS = re.compile(r"[<>:\"/\\|?*\x00-\x1f]")
|
|
62
83
|
FORESHADOW_ID = re.compile(r"^F\d{3,}$")
|
|
84
|
+
SNAPSHOT_TEXT_FIELDS = ("identity", "location", "goal", "state")
|
|
85
|
+
SNAPSHOT_LIST_FIELDS = ("abilities_resources", "relationships", "knowledge", "open_threads")
|
|
63
86
|
EVENT_ID = re.compile(r"^E\d{3,}$")
|
|
64
87
|
WINDOWS_RESERVED_NAMES = {
|
|
65
88
|
"CON",
|
|
@@ -112,16 +135,56 @@ def as_int(value: object, label: str, *, minimum: int = 0) -> int:
|
|
|
112
135
|
return value
|
|
113
136
|
|
|
114
137
|
|
|
138
|
+
def as_chapter(value: object, label: str, *, minimum: int = 1) -> int:
|
|
139
|
+
"""章号:模型常写成 "22"。纯数字字符串按整数收,语义不变;其余照旧报错。"""
|
|
140
|
+
if isinstance(value, str) and value.strip().isdecimal():
|
|
141
|
+
value = int(value.strip())
|
|
142
|
+
return as_int(value, label, minimum=minimum)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def with_aliases(row: dict[str, Any], aliases: dict[str, str]) -> dict[str, Any]:
|
|
146
|
+
"""把含义明确的同义字段名改成正式字段名;正式字段已在时不动,留给未知字段报错。"""
|
|
147
|
+
row = dict(row)
|
|
148
|
+
for alias, canonical in aliases.items():
|
|
149
|
+
if alias in row and canonical not in row:
|
|
150
|
+
row[canonical] = row.pop(alias)
|
|
151
|
+
return row
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def normalize_action(value: object, label: str, *, allow_delete: bool) -> str:
|
|
155
|
+
action = clean_text(value, label, max_bytes=24)
|
|
156
|
+
action = ACTION_ALIASES.get(action.lower(), action.lower())
|
|
157
|
+
allowed = ("upsert", "delete") if allow_delete else ("upsert",)
|
|
158
|
+
require(action in allowed, f"{label} is invalid: use {' or '.join(allowed)}")
|
|
159
|
+
return action
|
|
160
|
+
|
|
161
|
+
|
|
115
162
|
def require_known_keys(mapping: dict[str, Any], allowed: set[str], label: str) -> None:
|
|
116
163
|
unknown = set(mapping) - allowed
|
|
117
|
-
require(not unknown, f"{label} contains unsupported fields: {', '.join(sorted(unknown))}"
|
|
164
|
+
require(not unknown, f"{label} contains unsupported fields: {', '.join(sorted(unknown))}"
|
|
165
|
+
f"(只收 {', '.join(sorted(allowed))})")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# 事务校验期间收集全部超长字段,一次报完;None 表示逐条立即报错(初始化、状态读取等路径)。
|
|
169
|
+
_LENGTH_ISSUES: list[str] | None = None
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def length_hint(label: str, text: str, max_bytes: int) -> str:
|
|
173
|
+
"""把字节上限换算成字数:模型写的是中文,只看得懂「要删几个字」。"""
|
|
174
|
+
size = len(text.encode("utf-8"))
|
|
175
|
+
allowed = max(1, max_bytes * len(text) // size)
|
|
176
|
+
return (f"{label} exceeds {max_bytes} bytes(现 {len(text)} 字,上限约 {allowed} 字,"
|
|
177
|
+
f"至少删 {len(text) - allowed} 字)")
|
|
118
178
|
|
|
119
179
|
|
|
120
180
|
def clean_text(value: object, label: str, *, allow_empty: bool = False, max_bytes: int = 768) -> str:
|
|
121
181
|
require(isinstance(value, str), f"{label} must be a string")
|
|
122
182
|
cleaned = " ".join(value.replace("|", "|").split())
|
|
123
183
|
require(allow_empty or bool(cleaned), f"{label} must not be empty")
|
|
124
|
-
|
|
184
|
+
if len(cleaned.encode("utf-8")) > max_bytes:
|
|
185
|
+
if _LENGTH_ISSUES is None:
|
|
186
|
+
raise TrackingError(length_hint(label, cleaned, max_bytes))
|
|
187
|
+
_LENGTH_ISSUES.append(length_hint(label, cleaned, max_bytes))
|
|
125
188
|
return cleaned
|
|
126
189
|
|
|
127
190
|
|
|
@@ -277,9 +340,15 @@ def archive_retired_tracking_paths(tracking: Path) -> list[str]:
|
|
|
277
340
|
return retired
|
|
278
341
|
|
|
279
342
|
|
|
343
|
+
def _both_blank(position: dict[str, Any]) -> bool:
|
|
344
|
+
return all(isinstance(position.get(key), str) and not position[key].strip() for key in ("story_time", "scene"))
|
|
345
|
+
|
|
346
|
+
|
|
280
347
|
def validate_position(value: object, label: str = "context.position") -> dict[str, Any]:
|
|
281
348
|
position = as_mapping(value, label)
|
|
282
349
|
require_known_keys(position, {"volume", "volume_start_chapter", "story_time", "scene"}, label)
|
|
350
|
+
require(not _both_blank(position),
|
|
351
|
+
f"{label}.story_time and {label}.scene must not be empty: fill where this chapter ends")
|
|
283
352
|
return {
|
|
284
353
|
"volume": safe_file_component(position.get("volume"), f"{label}.volume"),
|
|
285
354
|
"volume_start_chapter": as_int(
|
|
@@ -290,8 +359,22 @@ def validate_position(value: object, label: str = "context.position") -> dict[st
|
|
|
290
359
|
}
|
|
291
360
|
|
|
292
361
|
|
|
362
|
+
def joined_text(value: object) -> object:
|
|
363
|
+
"""单句字段写成了字符串列表时按「;」连成一句,内容不丢。"""
|
|
364
|
+
if isinstance(value, list) and value and all(isinstance(item, str) for item in value):
|
|
365
|
+
return ";".join(value)
|
|
366
|
+
return value
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def as_text_list(value: object) -> object:
|
|
370
|
+
"""列表字段只写了一句时当作一项。"""
|
|
371
|
+
return [value] if isinstance(value, str) and value.strip() else value
|
|
372
|
+
|
|
373
|
+
|
|
293
374
|
def normalize_snapshot(value: object, label: str) -> dict[str, Any]:
|
|
294
375
|
snapshot = as_mapping(value, label)
|
|
376
|
+
snapshot = {key: joined_text(item) if key in SNAPSHOT_TEXT_FIELDS else as_text_list(item)
|
|
377
|
+
if key in SNAPSHOT_LIST_FIELDS else item for key, item in snapshot.items()}
|
|
295
378
|
require_known_keys(
|
|
296
379
|
snapshot,
|
|
297
380
|
{"identity", "location", "goal", "state", "abilities_resources", "relationships", "knowledge", "open_threads"},
|
|
@@ -358,23 +441,33 @@ def normalize_foreshadow_change(
|
|
|
358
441
|
allow_delete: bool,
|
|
359
442
|
through_chapter: int,
|
|
360
443
|
) -> dict[str, Any]:
|
|
361
|
-
row = as_mapping(value, label)
|
|
444
|
+
row = with_aliases(as_mapping(value, label), FORESHADOW_KEY_ALIASES)
|
|
362
445
|
require_known_keys(
|
|
363
446
|
row,
|
|
364
447
|
{"action", "id", "summary", "planted_chapter", "planned_resolution_chapter", "status", "importance"},
|
|
365
448
|
label,
|
|
366
449
|
)
|
|
367
|
-
|
|
368
|
-
|
|
450
|
+
raw_action = row.get("action", "upsert")
|
|
451
|
+
action = normalize_action(raw_action, f"{label}.action", allow_delete=allow_delete)
|
|
369
452
|
identifier = clean_text(row.get("id"), f"{label}.id", max_bytes=24)
|
|
370
453
|
require(FORESHADOW_ID.fullmatch(identifier) is not None, f"{label}.id must look like F001")
|
|
371
454
|
if action == "delete":
|
|
372
455
|
return {"action": action, "id": identifier}
|
|
373
|
-
|
|
456
|
+
implied = FORESHADOW_ACTION_STATUS.get(" ".join(str(raw_action).split()).lower())
|
|
457
|
+
if implied is not None:
|
|
458
|
+
if row.get("status") in (None, ""):
|
|
459
|
+
row["status"] = implied
|
|
460
|
+
right = "回收了就写 status=已回收" if implied == "已回收" else "只是推进、还没回收就写 status=已埋"
|
|
461
|
+
require(
|
|
462
|
+
row["status"] == implied,
|
|
463
|
+
f"{label}.action={raw_action} 与 status={row['status']} 矛盾:{right};"
|
|
464
|
+
"不是这个意思就把 action 改成 upsert 并写实际状态",
|
|
465
|
+
)
|
|
466
|
+
planted_chapter = as_chapter(row.get("planted_chapter"), f"{label}.planted_chapter")
|
|
374
467
|
require(planted_chapter <= through_chapter, f"{label}.planted_chapter cannot be in the future")
|
|
375
468
|
planned_raw = row.get("planned_resolution_chapter")
|
|
376
469
|
planned_chapter = (
|
|
377
|
-
None if planned_raw
|
|
470
|
+
None if planned_raw in (None, "") else as_chapter(planned_raw, f"{label}.planned_resolution_chapter")
|
|
378
471
|
)
|
|
379
472
|
require(
|
|
380
473
|
planned_chapter is None or planned_chapter >= planted_chapter,
|
|
@@ -450,14 +543,13 @@ def normalize_timeline_change(
|
|
|
450
543
|
allow_delete: bool,
|
|
451
544
|
through_chapter: int,
|
|
452
545
|
) -> dict[str, Any]:
|
|
453
|
-
event = as_mapping(value, label)
|
|
546
|
+
event = with_aliases(as_mapping(value, label), TIMELINE_KEY_ALIASES)
|
|
454
547
|
require_known_keys(
|
|
455
548
|
event,
|
|
456
549
|
{"action", "id", "story_time", "objective_fact", "reader_knowledge", "reveal_status", "reveal_chapter", "characters"},
|
|
457
550
|
label,
|
|
458
551
|
)
|
|
459
|
-
action =
|
|
460
|
-
require(action in ({"upsert", "delete"} if allow_delete else {"upsert"}), f"{label}.action is invalid")
|
|
552
|
+
action = normalize_action(event.get("action", "upsert"), f"{label}.action", allow_delete=allow_delete)
|
|
461
553
|
identifier = clean_text(event.get("id"), f"{label}.id", max_bytes=24)
|
|
462
554
|
require(EVENT_ID.fullmatch(identifier) is not None, f"{label}.id must look like E001")
|
|
463
555
|
if action == "delete":
|
|
@@ -465,7 +557,7 @@ def normalize_timeline_change(
|
|
|
465
557
|
reveal_status = clean_text(event.get("reveal_status"), f"{label}.reveal_status", max_bytes=24)
|
|
466
558
|
require(reveal_status in REVEAL_STATUSES, f"{label}.reveal_status must be one of {REVEAL_STATUSES}")
|
|
467
559
|
reveal_raw = event.get("reveal_chapter")
|
|
468
|
-
reveal_chapter = None if reveal_raw
|
|
560
|
+
reveal_chapter = None if reveal_raw in (None, "") else as_chapter(reveal_raw, f"{label}.reveal_chapter")
|
|
469
561
|
if reveal_status == "未揭示":
|
|
470
562
|
require(reveal_chapter is None, f"{label} must not put a future reveal chapter in established timeline facts")
|
|
471
563
|
else:
|
|
@@ -687,7 +779,8 @@ def normalize_delta(
|
|
|
687
779
|
# 本章退役的角色记录最后一次变化即可,不必再交一份马上要删的快照。
|
|
688
780
|
require(
|
|
689
781
|
not is_core or name in snapshots or portable_name_key(name) in retiring,
|
|
690
|
-
f"core character {name} changed but has no current snapshot"
|
|
782
|
+
f"core character {name} changed but has no current snapshot: 把 draft 输出 current_snapshots 里"
|
|
783
|
+
f"「{name}」的快照整份改成本章结束时的状态,放进 character_snapshots",
|
|
691
784
|
)
|
|
692
785
|
character_changes.append(
|
|
693
786
|
{"name": name, "change": clean_text(change.get("change"), f"delta.character_changes[{index}].change", max_bytes=360)}
|
|
@@ -734,11 +827,18 @@ def normalize_delta(
|
|
|
734
827
|
}
|
|
735
828
|
|
|
736
829
|
|
|
830
|
+
def render_commitments(commitments: list[str]) -> list[str]:
|
|
831
|
+
# 常见情形一行用「;」连写;有承诺原文自带「;」时连写就拆不回来,改成逐条缩进子项。
|
|
832
|
+
if any(";" in item for item in commitments):
|
|
833
|
+
return ["- 下一章承诺:"] + [f" - {item}" for item in commitments]
|
|
834
|
+
return ["- 下一章承诺:" + (";".join(commitments) or "无")]
|
|
835
|
+
|
|
836
|
+
|
|
737
837
|
def render_delta(chapter: int, title: str, delta: dict[str, Any], core_names: set[str]) -> str:
|
|
738
838
|
lines = [
|
|
739
839
|
f"# 第{chapter:03d}章 · {title}",
|
|
740
840
|
f"- 结果:{delta['result']}",
|
|
741
|
-
|
|
841
|
+
*render_commitments(delta["next_chapter_commitments"]),
|
|
742
842
|
"",
|
|
743
843
|
"## 角色变化",
|
|
744
844
|
]
|
|
@@ -781,7 +881,13 @@ def render_delta(chapter: int, title: str, delta: dict[str, Any], core_names: se
|
|
|
781
881
|
lines.extend(f"- {item}" for item in retired)
|
|
782
882
|
payload = "\n".join(lines) + "\n"
|
|
783
883
|
size = byte_size(payload)
|
|
784
|
-
|
|
884
|
+
if size > DELTA_MAX_BYTES:
|
|
885
|
+
longest = sorted((line for line in lines if line.startswith("- ")), key=len, reverse=True)[:3]
|
|
886
|
+
allowed = DELTA_MAX_BYTES * len(payload) // size
|
|
887
|
+
raise TrackingError(
|
|
888
|
+
f"chapter delta is {size} bytes; hard cap is {DELTA_MAX_BYTES}(逐章记录现 {len(payload)} 字,"
|
|
889
|
+
f"上限约 {allowed} 字,至少压缩 {len(payload) - allowed} 字;最长几项:"
|
|
890
|
+
+ ";".join(f"{line[2:18]}…({len(line) - 2} 字)" for line in longest) + ")")
|
|
785
891
|
return payload
|
|
786
892
|
|
|
787
893
|
|
|
@@ -901,6 +1007,22 @@ def normalize_initial_document(document: object) -> dict[str, Any]:
|
|
|
901
1007
|
|
|
902
1008
|
|
|
903
1009
|
def normalize_transaction(project: Path, state: dict[str, Any], document: object) -> dict[str, Any]:
|
|
1010
|
+
global _LENGTH_ISSUES
|
|
1011
|
+
_LENGTH_ISSUES = []
|
|
1012
|
+
try:
|
|
1013
|
+
transaction = _normalize_transaction(project, state, document)
|
|
1014
|
+
finally:
|
|
1015
|
+
issues, _LENGTH_ISSUES = _LENGTH_ISSUES, None
|
|
1016
|
+
require(not issues, f"{len(issues)} 个字段超长,一次改完再提交:" + ";".join(issues))
|
|
1017
|
+
return transaction
|
|
1018
|
+
|
|
1019
|
+
|
|
1020
|
+
# 本文件在 story-long-write / story-review / story-import 三份逐字相同;只有 story-long-write 带 storyctl.py。
|
|
1021
|
+
RECOUNT_HINT = ("修订请用 story-long-write 的 storyctl.py chapter commit(带外用 chapter accept-current-length)"
|
|
1022
|
+
"提交同一份事务,它会重新计数并清理本章工作目录")
|
|
1023
|
+
|
|
1024
|
+
|
|
1025
|
+
def _normalize_transaction(project: Path, state: dict[str, Any], document: object) -> dict[str, Any]:
|
|
904
1026
|
root = as_mapping(document, "transaction")
|
|
905
1027
|
require_known_keys(
|
|
906
1028
|
root,
|
|
@@ -940,6 +1062,15 @@ def normalize_transaction(project: Path, state: dict[str, Any], document: object
|
|
|
940
1062
|
wordcount = wordcount_value(
|
|
941
1063
|
wordcount_core.validate_current_wordcount_record, project, chapter, wordcount_input
|
|
942
1064
|
)
|
|
1065
|
+
elif mode == "revision" and str(chapter) in state["wordcount_records"]:
|
|
1066
|
+
# 已提交字数记录的章节:正文或目标改过却绕开 storyctl 提交修订,会留下过期的 actual/body_sha256。
|
|
1067
|
+
# 只核对记录钉住的正文与目标;细纲事后补的「字数范围」不算过期。
|
|
1068
|
+
try:
|
|
1069
|
+
drift = wordcount_core.wordcount_record_drift(project, chapter, state["wordcount_records"][str(chapter)])
|
|
1070
|
+
except wordcount_core.WordcountError as exc:
|
|
1071
|
+
raise TrackingError(f"第{chapter}章的字数记录无法核对({exc});{RECOUNT_HINT}") from exc
|
|
1072
|
+
if drift:
|
|
1073
|
+
raise TrackingError(f"第{chapter}章{'和'.join(drift)}已改;{RECOUNT_HINT}")
|
|
943
1074
|
return {
|
|
944
1075
|
"mode": mode,
|
|
945
1076
|
"chapter": chapter,
|
|
@@ -999,6 +1130,13 @@ def merge_transaction(state: dict[str, Any], transaction: dict[str, Any]) -> dic
|
|
|
999
1130
|
"a revision must resubmit every current context item; retire them in an append transaction instead: "
|
|
1000
1131
|
+ ";".join(sorted(dropped)),
|
|
1001
1132
|
)
|
|
1133
|
+
kept = set(next_context["long_term_constraints"]) | set(next_context["continuity_risks"])
|
|
1134
|
+
still_listed = sorted(set(transaction["delta"]["retired_context_items"]) & kept)
|
|
1135
|
+
require(
|
|
1136
|
+
not still_listed,
|
|
1137
|
+
"delta.retired_context_items lists items that are still in context; remove them from context: "
|
|
1138
|
+
+ ";".join(still_listed),
|
|
1139
|
+
)
|
|
1002
1140
|
undeclared = sorted(dropped - set(transaction["delta"]["retired_context_items"]))
|
|
1003
1141
|
require(
|
|
1004
1142
|
not undeclared,
|
|
@@ -1126,6 +1264,11 @@ def _apply_transaction_locked(project: Path, document: object) -> dict[str, Any]
|
|
|
1126
1264
|
state = load_state(project)
|
|
1127
1265
|
transaction = normalize_transaction(project, state, document)
|
|
1128
1266
|
next_state = merge_transaction(state, transaction)
|
|
1267
|
+
path = delta_path(tracking, transaction["chapter"])
|
|
1268
|
+
previous_record = parse_chapter_record(path) if transaction["mode"] == "revision" else None
|
|
1269
|
+
if previous_record is not None:
|
|
1270
|
+
# 退役只在 append 发生;修订重写旧章记录时把当初的退役登记原样带过来,不让它随修订消失。
|
|
1271
|
+
transaction["delta"]["retired_context_items"] = previous_record["retired"]
|
|
1129
1272
|
|
|
1130
1273
|
delta_payload = render_delta(
|
|
1131
1274
|
transaction["chapter"],
|
|
@@ -1136,7 +1279,6 @@ def _apply_transaction_locked(project: Path, document: object) -> dict[str, Any]
|
|
|
1136
1279
|
)
|
|
1137
1280
|
views = render_views(next_state)
|
|
1138
1281
|
next_state_payload = json_payload(next_state)
|
|
1139
|
-
path = delta_path(tracking, transaction["chapter"])
|
|
1140
1282
|
if transaction["mode"] == "append" and path.exists():
|
|
1141
1283
|
require(
|
|
1142
1284
|
path.read_text(encoding="utf-8") == delta_payload,
|
|
@@ -1148,9 +1290,21 @@ def _apply_transaction_locked(project: Path, document: object) -> dict[str, Any]
|
|
|
1148
1290
|
# 唯一权威文件最后落盘;在此之前失败可用同一事务直接重跑。
|
|
1149
1291
|
atomic_write_text(state_path(project), next_state_payload)
|
|
1150
1292
|
warn_sizes(views, delta_payload)
|
|
1293
|
+
if previous_record is not None:
|
|
1294
|
+
warn_emptied_record(transaction["chapter"], previous_record, transaction["delta"])
|
|
1151
1295
|
return next_state
|
|
1152
1296
|
|
|
1153
1297
|
|
|
1298
|
+
def warn_emptied_record(chapter: int, previous: dict[str, Any], delta: dict[str, Any]) -> None:
|
|
1299
|
+
labels = {"character_changes": "角色变化", "foreshadow_changes": "伏笔变化", "timeline_events": "时间与揭示",
|
|
1300
|
+
"constraints": "连贯性约束", "next_chapter_commitments": "下一章承诺"}
|
|
1301
|
+
emptied = [f"{label}(原有 {len(previous[key])} 项)" for key, label in labels.items()
|
|
1302
|
+
if previous[key] and not delta[key]]
|
|
1303
|
+
if emptied:
|
|
1304
|
+
emit(f"WARNING: 第{chapter}章修订把逐章记录的 " + "、".join(emptied)
|
|
1305
|
+
+ " 清空了;修订的 delta 是本章完整记录,不是只写改动。不是有意删除就用 draft 重新预填后再提交。", error=True)
|
|
1306
|
+
|
|
1307
|
+
|
|
1154
1308
|
def apply_transaction(project: Path, document: object) -> dict[str, Any]:
|
|
1155
1309
|
with project_write_lock(project):
|
|
1156
1310
|
return _apply_transaction_locked(project, document)
|
|
@@ -1188,6 +1342,252 @@ def check_project(project: Path) -> dict[str, Any]:
|
|
|
1188
1342
|
return state
|
|
1189
1343
|
|
|
1190
1344
|
|
|
1345
|
+
# 事务草稿里各文本字段的字数上限(按全中文估:UTF-8 每字 3 字节),写进提示,免得模型反复试错。
|
|
1346
|
+
DRAFT_LIMITS = {
|
|
1347
|
+
"delta.result": 160, "character_changes[].change": 120, "foreshadow_changes[].summary": 120,
|
|
1348
|
+
"timeline_events[].objective_fact / reader_knowledge": 160, "context.position.story_time / scene": 80,
|
|
1349
|
+
"character_snapshots.*.goal / state": 100, "逐章记录总量": 1024,
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
|
|
1353
|
+
RECORD_SECTIONS = {"角色变化": "character_changes", "伏笔变化": "foreshadow_changes",
|
|
1354
|
+
"时间与揭示": "timeline_events", "连贯性约束": "constraints", "本章退役登记": "retired"}
|
|
1355
|
+
|
|
1356
|
+
|
|
1357
|
+
def parse_chapter_record(path: Path) -> dict[str, Any] | None:
|
|
1358
|
+
"""读回本工具自己渲染的逐章记录(render_delta 的固定格式),供修订草稿预填与修订提交核对。
|
|
1359
|
+
这是对自家确定性输出的逆运算,不是解析手写 Markdown;读不到返回 None。"""
|
|
1360
|
+
try:
|
|
1361
|
+
text = path.read_text(encoding="utf-8")
|
|
1362
|
+
except (OSError, UnicodeError):
|
|
1363
|
+
return None
|
|
1364
|
+
record: dict[str, Any] = {"title": "", "result": "", "next_chapter_commitments": [],
|
|
1365
|
+
**{key: [] for key in RECORD_SECTIONS.values()}}
|
|
1366
|
+
section = None
|
|
1367
|
+
in_commitments = False
|
|
1368
|
+
for line in text.splitlines():
|
|
1369
|
+
heading = re.match(r"^# 第\d+章 · (.+)$", line)
|
|
1370
|
+
if heading and not record["title"]:
|
|
1371
|
+
record["title"] = heading.group(1).strip()
|
|
1372
|
+
continue
|
|
1373
|
+
if line.startswith("## "):
|
|
1374
|
+
section = RECORD_SECTIONS.get(line[3:].strip())
|
|
1375
|
+
in_commitments = False
|
|
1376
|
+
continue
|
|
1377
|
+
if in_commitments and line.startswith(" - "):
|
|
1378
|
+
record["next_chapter_commitments"].append(line[4:])
|
|
1379
|
+
continue
|
|
1380
|
+
in_commitments = False
|
|
1381
|
+
if not line.startswith("- "):
|
|
1382
|
+
continue
|
|
1383
|
+
item = line[2:]
|
|
1384
|
+
if section is None:
|
|
1385
|
+
if item.startswith("结果:"):
|
|
1386
|
+
record["result"] = item[len("结果:"):]
|
|
1387
|
+
elif item.startswith("下一章承诺:"):
|
|
1388
|
+
value = item[len("下一章承诺:"):]
|
|
1389
|
+
# 空值 = 逐条子项格式(render_commitments),后面的缩进行逐条读回。
|
|
1390
|
+
in_commitments = value == ""
|
|
1391
|
+
record["next_chapter_commitments"] = [] if value in ("无", "") else value.split(";")
|
|
1392
|
+
continue
|
|
1393
|
+
if item == "无" and section != "retired":
|
|
1394
|
+
continue
|
|
1395
|
+
record[section].append(item)
|
|
1396
|
+
return record
|
|
1397
|
+
|
|
1398
|
+
|
|
1399
|
+
def record_prefill(state: dict[str, Any], record: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any], list[str]]:
|
|
1400
|
+
"""把已提交的逐章记录还原成修订 delta:结果、承诺、约束、角色变化按记录原文;伏笔与时间线
|
|
1401
|
+
取受影响 ID 的当前值(修订提交的就是截至最新章的当前值)。另返回记录里核心角色的当前快照。"""
|
|
1402
|
+
notes: list[str] = []
|
|
1403
|
+
changes = []
|
|
1404
|
+
for line in record["character_changes"]:
|
|
1405
|
+
parts = line.split("|", 2)
|
|
1406
|
+
if len(parts) == 3:
|
|
1407
|
+
changes.append({"name": parts[0], "change": parts[2]})
|
|
1408
|
+
snapshots = {item["name"]: copy.deepcopy(state["characters"][item["name"]])
|
|
1409
|
+
for item in changes if item["name"] in state["characters"]}
|
|
1410
|
+
|
|
1411
|
+
def rows(lines: list[str], current: dict[str, Any], label: str) -> list[dict[str, Any]]:
|
|
1412
|
+
prefilled = []
|
|
1413
|
+
for line in lines:
|
|
1414
|
+
parts = line.split("|")
|
|
1415
|
+
identifier = parts[0]
|
|
1416
|
+
if len(parts) > 1 and parts[1] == "删除当前登记":
|
|
1417
|
+
if identifier in current:
|
|
1418
|
+
notes.append(f"{label} {identifier} 本章删过、后来又登记了,没有预填删除,确需删除自行加回")
|
|
1419
|
+
else:
|
|
1420
|
+
prefilled.append({"action": "delete", "id": identifier})
|
|
1421
|
+
elif identifier in current:
|
|
1422
|
+
prefilled.append({"action": "upsert", **{key: copy.deepcopy(value) for key, value in current[identifier].items()
|
|
1423
|
+
if key not in ("updated_chapter", "first_recorded_chapter")}})
|
|
1424
|
+
else:
|
|
1425
|
+
notes.append(f"{label} {identifier} 已在后续章节删除,没有预填")
|
|
1426
|
+
return prefilled
|
|
1427
|
+
|
|
1428
|
+
delta = {
|
|
1429
|
+
"result": record["result"],
|
|
1430
|
+
"character_changes": changes,
|
|
1431
|
+
"foreshadow_changes": rows(record["foreshadow_changes"], state["foreshadow"], "伏笔"),
|
|
1432
|
+
"timeline_events": rows(record["timeline_events"], state["timeline"], "时间线事件"),
|
|
1433
|
+
"constraints": list(record["constraints"]),
|
|
1434
|
+
"next_chapter_commitments": list(record["next_chapter_commitments"]),
|
|
1435
|
+
}
|
|
1436
|
+
return delta, snapshots, notes
|
|
1437
|
+
|
|
1438
|
+
|
|
1439
|
+
def entry_shapes(chapter: int) -> dict[str, Any]:
|
|
1440
|
+
"""draft 当场给出每类条目的形状:模型填 delta 时手里只有空数组,字段名、整数、枚举都靠猜,
|
|
1441
|
+
首次提交几乎都因此被退回。取值从校验用的同一组常量生成,不另立一套。"""
|
|
1442
|
+
return {
|
|
1443
|
+
"delta.character_changes[]": {"name": "角色名", "change": "一句话:本章起了什么变化"},
|
|
1444
|
+
"delta.foreshadow_changes[]": {
|
|
1445
|
+
"action": "upsert(撤掉整条写 delete,只带 id)", "id": "F007", "summary": "伏笔一句话",
|
|
1446
|
+
"planted_chapter": chapter, "planned_resolution_chapter": "计划回收章的整数,未定写 null",
|
|
1447
|
+
"status": "|".join(FORESHADOW_STATUSES), "importance": "|".join(FORESHADOW_IMPORTANCE),
|
|
1448
|
+
},
|
|
1449
|
+
"delta.timeline_events[]": {
|
|
1450
|
+
"action": "upsert(撤掉整条写 delete,只带 id)", "id": "E012", "story_time": "故事内时间",
|
|
1451
|
+
"objective_fact": "客观发生了什么", "reader_knowledge": "读者此刻知道/以为什么",
|
|
1452
|
+
"reveal_status": "|".join(REVEAL_STATUSES),
|
|
1453
|
+
"reveal_chapter": f"已揭示或部分揭示时写揭示章的整数(不晚于 {chapter}),未揭示写 null",
|
|
1454
|
+
"characters": ["角色名"],
|
|
1455
|
+
},
|
|
1456
|
+
"character_snapshots.{角色名}": {
|
|
1457
|
+
**{key: "一句话" for key in SNAPSHOT_TEXT_FIELDS},
|
|
1458
|
+
**{key: ["一条一句"] for key in SNAPSHOT_LIST_FIELDS},
|
|
1459
|
+
},
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
|
|
1463
|
+
def draft_transaction(project: Path, chapter: int) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]:
|
|
1464
|
+
"""按当前 state 预填一份逐章事务:修订号、模式、章名和整份提交的上下文当前值都填好。
|
|
1465
|
+
新章(append)调用方只写本章变化;另返回在场核心角色的当前快照,供有变化时整份改写后放进事务。
|
|
1466
|
+
新章的故事时间与场景留空:它们必须是本章结束时的位置,沿用上一章的值能静默提交,
|
|
1467
|
+
所以不预填,上一章的值另行返回作参考。
|
|
1468
|
+
修订(revision)的 delta 是修订后本章的完整记录,所以按已提交的逐章记录预填,调用方在上面改;
|
|
1469
|
+
第三项返回 {"notes": [...], "prefilled": bool}:没能预填的条目,以及逐章记录是否读到。"""
|
|
1470
|
+
state = load_state(project)
|
|
1471
|
+
last = state["last_committed_chapter"]
|
|
1472
|
+
require(1 <= chapter <= last + 1, f"draft chapter must be between 1 and {last + 1}")
|
|
1473
|
+
mode = "append" if chapter == last + 1 else "revision"
|
|
1474
|
+
title = ""
|
|
1475
|
+
for outline in sorted((project / "大纲").glob(f"细纲_第{chapter:03d}章*.md")):
|
|
1476
|
+
match = re.search(rf"^#{{1,4}}\s*第\s*0*{chapter}\s*章\s*[::]\s*(.+?)\s*$",
|
|
1477
|
+
outline.read_text(encoding="utf-8"), re.M)
|
|
1478
|
+
if match:
|
|
1479
|
+
title = match.group(1).strip()
|
|
1480
|
+
break
|
|
1481
|
+
context = json.loads(json.dumps(state["context"], ensure_ascii=False))
|
|
1482
|
+
previous_position = {key: context["position"][key] for key in ("story_time", "scene")}
|
|
1483
|
+
delta: dict[str, Any] = {
|
|
1484
|
+
"result": "", "character_changes": [], "foreshadow_changes": [], "timeline_events": [],
|
|
1485
|
+
"constraints": [], "next_chapter_commitments": [],
|
|
1486
|
+
}
|
|
1487
|
+
snapshots_prefill: dict[str, Any] = {}
|
|
1488
|
+
notes: list[str] = []
|
|
1489
|
+
prefilled = False
|
|
1490
|
+
if mode == "append":
|
|
1491
|
+
delta.update({"retired_context_items": [], "retired_characters": []})
|
|
1492
|
+
context["position"].update({"story_time": "", "scene": ""})
|
|
1493
|
+
else:
|
|
1494
|
+
record = parse_chapter_record(delta_path(tracking_root(project), chapter))
|
|
1495
|
+
if record is not None:
|
|
1496
|
+
prefilled = True
|
|
1497
|
+
delta, snapshots_prefill, notes = record_prefill(state, record)
|
|
1498
|
+
title = title or record["title"]
|
|
1499
|
+
if chapter == last:
|
|
1500
|
+
# 最新章的承诺原样在 state 里,比从记录行按「;」拆回更准。
|
|
1501
|
+
delta["next_chapter_commitments"] = list(state["context"]["next_chapter_commitments"])
|
|
1502
|
+
document = {
|
|
1503
|
+
"schema_version": INPUT_SCHEMA_VERSION,
|
|
1504
|
+
"mode": mode,
|
|
1505
|
+
"chapter": chapter,
|
|
1506
|
+
"chapter_title": title,
|
|
1507
|
+
"expected_state_revision": state["state_revision"],
|
|
1508
|
+
"delta": delta,
|
|
1509
|
+
"context": {key: context[key] for key in
|
|
1510
|
+
("position", "long_term_constraints", "active_character_names", "continuity_risks")},
|
|
1511
|
+
"character_snapshots": snapshots_prefill,
|
|
1512
|
+
}
|
|
1513
|
+
snapshots = {name: state["characters"][name] for name in context["active_character_names"]
|
|
1514
|
+
if name in state["characters"]}
|
|
1515
|
+
if mode == "revision":
|
|
1516
|
+
return document, snapshots, {"notes": notes, "prefilled": prefilled}
|
|
1517
|
+
return document, snapshots, previous_position
|
|
1518
|
+
|
|
1519
|
+
|
|
1520
|
+
def rebuild_stale_draft(document: dict[str, Any], existing: dict[str, Any], append: bool,
|
|
1521
|
+
current_characters: Any = ()) -> str:
|
|
1522
|
+
"""修订号变了:中间提交过别的事务。以当前状态为底,只自动合并确定安全的部分,其余列给调用方核对。
|
|
1523
|
+
|
|
1524
|
+
新章草稿之间只可能插进修订事务,修订不能删约束与风险条目,所以当前约束与风险覆盖旧底稿:
|
|
1525
|
+
旧草稿多出来的就是本章新增,delta 里声明退役的就是本章删除,可以精确合并。
|
|
1526
|
+
修订草稿之间可能插进新章(新章能退役条目),在场角色名修订也能删,都无法判断谁新:
|
|
1527
|
+
取当前状态,差异列出来;只有本章带了快照的新角色名自动加回。"""
|
|
1528
|
+
def items(value: object) -> list[str]:
|
|
1529
|
+
return [item for item in value if isinstance(item, str)] if isinstance(value, list) else []
|
|
1530
|
+
|
|
1531
|
+
def norm(item: str) -> str:
|
|
1532
|
+
return " ".join(item.replace("|", "|").split())
|
|
1533
|
+
|
|
1534
|
+
notes = ["修订号已变:context 按当前状态重建,本章已填的 delta" + ("、新增与退役的约束、结尾位置" if append else "")
|
|
1535
|
+
+ "已合并"]
|
|
1536
|
+
for key in ("delta", "character_snapshots"):
|
|
1537
|
+
if isinstance(existing.get(key), dict):
|
|
1538
|
+
document[key] = existing[key]
|
|
1539
|
+
if existing.get("chapter_title"):
|
|
1540
|
+
document["chapter_title"] = existing["chapter_title"]
|
|
1541
|
+
old_context = existing.get("context") if isinstance(existing.get("context"), dict) else {}
|
|
1542
|
+
delta = document["delta"] if isinstance(document["delta"], dict) else {}
|
|
1543
|
+
context = document["context"]
|
|
1544
|
+
retired = {norm(item) for item in items(delta.get("retired_context_items"))}
|
|
1545
|
+
left_out: list[str] = []
|
|
1546
|
+
for key in ("long_term_constraints", "continuity_risks"):
|
|
1547
|
+
current = {norm(item) for item in context[key]}
|
|
1548
|
+
extra = []
|
|
1549
|
+
for item in items(old_context.get(key)):
|
|
1550
|
+
if norm(item) not in current:
|
|
1551
|
+
current.add(norm(item))
|
|
1552
|
+
extra.append(item)
|
|
1553
|
+
if append:
|
|
1554
|
+
context[key] = [item for item in context[key] if norm(item) not in retired] + extra
|
|
1555
|
+
else:
|
|
1556
|
+
left_out += extra
|
|
1557
|
+
snapshots = document["character_snapshots"] if isinstance(document["character_snapshots"], dict) else {}
|
|
1558
|
+
names = context["active_character_names"]
|
|
1559
|
+
for name in items(old_context.get("active_character_names")):
|
|
1560
|
+
if name in names:
|
|
1561
|
+
continue
|
|
1562
|
+
if append and name in snapshots:
|
|
1563
|
+
names.append(name)
|
|
1564
|
+
else:
|
|
1565
|
+
left_out.append(f"在场角色 {name}")
|
|
1566
|
+
names[:] = [name for name in names if name not in set(items(delta.get("retired_characters")))]
|
|
1567
|
+
known = set(current_characters)
|
|
1568
|
+
for name in list(snapshots):
|
|
1569
|
+
if name not in known and name not in names:
|
|
1570
|
+
snapshots.pop(name)
|
|
1571
|
+
left_out.append(f"角色快照 {name}")
|
|
1572
|
+
if left_out:
|
|
1573
|
+
notes.append("草稿里有、当前状态没有的条目没有自动带回(可能已被别的提交退役),本章确实需要就加回:"
|
|
1574
|
+
+ ";".join(left_out))
|
|
1575
|
+
old_position = old_context.get("position") if isinstance(old_context.get("position"), dict) else {}
|
|
1576
|
+
position = context["position"]
|
|
1577
|
+
if append:
|
|
1578
|
+
for key in ("story_time", "scene"):
|
|
1579
|
+
if isinstance(old_position.get(key), str) and old_position[key].strip():
|
|
1580
|
+
position[key] = old_position[key]
|
|
1581
|
+
differs = [f"{key}:草稿写的是「{old_position[key]}」,当前状态是「{position[key]}」"
|
|
1582
|
+
for key in ("volume", "volume_start_chapter")
|
|
1583
|
+
if key in old_position and old_position[key] != position[key]]
|
|
1584
|
+
if differs:
|
|
1585
|
+
notes.append("卷信息与当前状态不同,已取当前状态,本章确实换卷就改回(" + ";".join(differs) + ")")
|
|
1586
|
+
if snapshots:
|
|
1587
|
+
notes.append("带过来的角色快照(" + "、".join(snapshots) + ")可能被中间的提交改过,逐个对照 current_snapshots 重核")
|
|
1588
|
+
return ";".join(notes)
|
|
1589
|
+
|
|
1590
|
+
|
|
1191
1591
|
def build_parser() -> argparse.ArgumentParser:
|
|
1192
1592
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
1193
1593
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
@@ -1197,12 +1597,75 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1197
1597
|
subparser.add_argument("--input", type=Path, required=True, help="UTF-8 JSON input document")
|
|
1198
1598
|
check_parser = subparsers.add_parser("check")
|
|
1199
1599
|
check_parser.add_argument("--project", type=Path, required=True, help="book project root containing 追踪/")
|
|
1600
|
+
draft_parser = subparsers.add_parser("draft", help="write a pre-filled chapter transaction to fill in")
|
|
1601
|
+
draft_parser.add_argument("--project", type=Path, required=True, help="book project root containing 追踪/")
|
|
1602
|
+
draft_parser.add_argument("--chapter", type=int, required=True)
|
|
1603
|
+
draft_parser.add_argument("--out", type=Path, help="default: <project>/.story/work/第NNN章/tracking.json")
|
|
1604
|
+
draft_parser.add_argument("--force", action="store_true",
|
|
1605
|
+
help="discard an existing draft instead of only refreshing its revision")
|
|
1200
1606
|
return parser
|
|
1201
1607
|
|
|
1202
1608
|
|
|
1203
1609
|
def main() -> int:
|
|
1204
1610
|
args = build_parser().parse_args()
|
|
1205
1611
|
try:
|
|
1612
|
+
if args.command == "draft":
|
|
1613
|
+
document, snapshots, extra = draft_transaction(args.project, args.chapter)
|
|
1614
|
+
previous_position = extra if document["mode"] == "append" else {}
|
|
1615
|
+
prefill_notes = extra.get("notes", []) if document["mode"] == "revision" else []
|
|
1616
|
+
record_prefilled = bool(extra.get("prefilled")) if document["mode"] == "revision" else False
|
|
1617
|
+
out = args.out or args.project / ".story" / "work" / f"第{args.chapter:03d}章" / "tracking.json"
|
|
1618
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
1619
|
+
refreshed = ""
|
|
1620
|
+
if out.exists() and not args.force:
|
|
1621
|
+
# 重跑 draft 不冲掉已经填好的变化。修订号变了说明中间提交过别的事务:context 按当前
|
|
1622
|
+
# 状态重建(沿用旧 context 会把那次变更整份覆盖回去),只带过本章自己填的部分。
|
|
1623
|
+
existing = read_json(out)
|
|
1624
|
+
require(isinstance(existing, dict) and existing.get("chapter") == document["chapter"]
|
|
1625
|
+
and existing.get("mode") == document["mode"],
|
|
1626
|
+
f"{out} already holds a draft for a different chapter or mode; pass --force to replace it")
|
|
1627
|
+
if existing.get("expected_state_revision") == document["expected_state_revision"]:
|
|
1628
|
+
document, refreshed = existing, "已有草稿且修订号未变,原样保留"
|
|
1629
|
+
else:
|
|
1630
|
+
refreshed = rebuild_stale_draft(document, existing, bool(previous_position),
|
|
1631
|
+
load_state(args.project)["characters"])
|
|
1632
|
+
out.write_text(json.dumps(document, ensure_ascii=False, indent=1) + "\n", encoding="utf-8")
|
|
1633
|
+
if document["mode"] == "revision" and not record_prefilled:
|
|
1634
|
+
fill = (f"这是修订,但本章逐章记录缺失或读不了({delta_path(Path('追踪'), args.chapter).as_posix()}),delta 没有预填:"
|
|
1635
|
+
"按修订后正文把本章完整记录整份写上(结果、承诺、角色变化、伏笔、时间线、约束),没写的项不会进记录。"
|
|
1636
|
+
"角色快照、伏笔、时间线写截至最新已写章的当前值;context 原样保留全部当前条目(修订不能退役)。"
|
|
1637
|
+
"正文改过就用 story-long-write 的 storyctl.py chapter commit 提交(重新计数并清理本章工作目录)。"
|
|
1638
|
+
"不要从脚本源码或 state 文件里另找格式。")
|
|
1639
|
+
elif document["mode"] == "revision":
|
|
1640
|
+
fill = ("这是修订:delta 是修订后本章的完整记录,已按本章现有逐章记录预填(伏笔与时间线取当前值,"
|
|
1641
|
+
"记录里的核心角色快照已放进 character_snapshots)。对照修订后正文逐项改:仍成立的原样保留,"
|
|
1642
|
+
"被推翻的删掉或改写,新增的补上;不要清空后只写改动,没写的项会从本章记录里消失。"
|
|
1643
|
+
"角色快照、伏笔、时间线改成截至最新已写章的当前值;context 原样保留全部当前条目(修订不能退役)。"
|
|
1644
|
+
"正文改过就用 story-long-write 的 storyctl.py chapter commit 提交(重新计数并清理本章工作目录)。"
|
|
1645
|
+
"不要从脚本源码或 state 文件里另找格式。")
|
|
1646
|
+
if prefill_notes:
|
|
1647
|
+
fill += "没能预填:" + ";".join(prefill_notes) + "。"
|
|
1648
|
+
else:
|
|
1649
|
+
fill = ("只填 delta 里本章的变化;context 其余字段已是当前值,要撤下的长期约束或连贯性风险从 context 删掉并把原文放进 "
|
|
1650
|
+
"delta.retired_context_items(仅 append);本章有变化的核心角色把下面的当前快照整份改好放进 character_snapshots,"
|
|
1651
|
+
"并在 character_changes 写一句变化。每类条目照 shapes 的形状写,不要从脚本源码或 state 文件里另找格式。")
|
|
1652
|
+
blank = not (document["context"]["position"].get("story_time") or document["context"]["position"].get("scene"))
|
|
1653
|
+
if previous_position and blank:
|
|
1654
|
+
fill = ("context.position 的 story_time 与 scene 留空,填本章结束时的故事时间与场景(上一章结束时见 "
|
|
1655
|
+
"previous_position;换卷时连同 volume 与 volume_start_chapter 一起改)。") + fill
|
|
1656
|
+
payload = {
|
|
1657
|
+
"draft": str(out),
|
|
1658
|
+
"mode": document["mode"],
|
|
1659
|
+
"expected_state_revision": document["expected_state_revision"],
|
|
1660
|
+
"fill": (f"{refreshed};要从头生成加 --force。" if refreshed else "") + fill,
|
|
1661
|
+
"limits_chars": DRAFT_LIMITS,
|
|
1662
|
+
"shapes": entry_shapes(args.chapter),
|
|
1663
|
+
"current_snapshots": snapshots,
|
|
1664
|
+
}
|
|
1665
|
+
if previous_position:
|
|
1666
|
+
payload["previous_position"] = previous_position
|
|
1667
|
+
emit(json.dumps(payload, ensure_ascii=False))
|
|
1668
|
+
return 0
|
|
1206
1669
|
if args.command == "init":
|
|
1207
1670
|
result = initialize(args.project, read_json(args.input))
|
|
1208
1671
|
elif args.command == "commit":
|