@oh-story/dsh 0.1.8 → 0.1.10
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 +237 -84
- package/lib/client.js.map +3 -3
- package/lib/drama/manifest.json +183 -77
- package/lib/drama/skills/short-drama/SKILL.md +5 -1
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +140 -9
- package/lib/drama/skills/short-drama/scripts/project_tool.py +11 -0
- package/lib/drama/skills/short-drama-assets/references/character-and-look.md +28 -0
- package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +8 -2
- package/lib/drama/skills/short-drama-assets/references/voice-direction.md +28 -0
- package/lib/drama/skills/short-drama-edit/SKILL.md +146 -0
- package/lib/drama/skills/short-drama-edit/agents/openai.yaml +4 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +60 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +15 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/remotion.config.ts +6 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +26 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +88 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +46 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/index.ts +4 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +32 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +13 -0
- package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +43 -0
- package/lib/drama/skills/short-drama-edit/references/cut-craft.md +38 -0
- package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +29 -0
- package/lib/drama/skills/short-drama-edit/references/generated-footage.md +54 -0
- package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +60 -0
- package/lib/drama/skills/short-drama-edit/references/stage-contract.md +51 -0
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1156 -0
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +520 -0
- package/lib/drama/skills/short-drama-image-prompts/SKILL.md +2 -0
- package/lib/drama/skills/short-drama-image-prompts/assets/image-prompts.md +5 -0
- package/lib/drama/skills/short-drama-image-prompts/assets/lookdev-prompts.md +3 -0
- package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +11 -0
- package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +2 -2
- package/lib/drama/skills/short-drama-novel-analyze/references/chapter-extraction.md +4 -1
- package/lib/drama/skills/short-drama-produce/SKILL.md +29 -4
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +22 -0
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +13 -12
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-speech.md +41 -0
- package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +7 -0
- package/lib/drama/skills/short-drama-produce/scripts/fixture_adapter.py +18 -1
- package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +181 -2
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +199 -3
- package/lib/drama/skills/short-drama-review/SKILL.md +2 -0
- package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +7 -0
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +10 -0
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +12 -1
- package/lib/drama/skills/short-drama-storyboard/assets/keyframe-prompts.md +4 -0
- package/lib/drama/skills/short-drama-storyboard/references/lighting-craft.md +39 -0
- package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +78 -2
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +2 -0
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +12 -5
- package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +17 -0
- package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +76 -7
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +5 -2
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +21 -0
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +6 -17
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +12 -3
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +61 -5
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +3 -3
- package/lib/drama/skills/short-drama-write/SKILL.md +11 -2
- package/lib/drama/skills/short-drama-write/references/screenplay-format.md +6 -0
- package/lib/drama/skills/short-drama-write/references/stage-contract.md +1 -0
- package/lib/index.js +290 -90
- 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 +9 -35
- 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 +17 -35
- 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 +16 -34
- package/lib/novel-to-game/skills/game-concept/references/concept-method.md +28 -75
- package/lib/novel-to-game/skills/game-qa/SKILL.md +9 -20
- package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +10 -6
- package/lib/novel-to-game/skills/game-world-design/SKILL.md +19 -32
- 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 +9 -18
- 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 +10 -27
- package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +5 -6
- package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +6 -11
- package/lib/oh-story/manifest.json +419 -254
- package/lib/oh-story/roles/chapter-extractor.md +177 -237
- package/lib/oh-story/roles/character-designer.md +1 -1
- package/lib/oh-story/roles/consistency-checker.md +4 -4
- package/lib/oh-story/roles/narrative-writer.md +56 -243
- package/lib/oh-story/roles/story-architect.md +6 -6
- package/lib/oh-story/roles/story-explorer.md +11 -11
- package/lib/oh-story/skills/story/SKILL.md +13 -7
- package/lib/oh-story/skills/story/VERSION +1 -1
- package/lib/oh-story/skills/story/references/author-memory.md +61 -29
- 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 +54 -229
- package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +29 -56
- package/lib/oh-story/skills/story-deslop/references/author-memory.md +61 -29
- package/lib/oh-story/skills/story-deslop/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +154 -0
- package/lib/oh-story/skills/story-deslop/references/style-resolution.md +27 -0
- 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 +6 -1
- package/lib/oh-story/skills/story-deslop/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-deslop/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-deslop/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-import/SKILL.md +78 -82
- package/lib/oh-story/skills/story-import/references/character-state-reverse.md +1 -1
- package/lib/oh-story/skills/story-import/references/format-and-structure.md +2 -2
- 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 +10 -8
- package/lib/oh-story/skills/story-import/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-import/references/tracking-transaction.md +9 -34
- package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +197 -15
- package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +210 -2
- package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +3 -0
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +91 -258
- package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +245 -0
- package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +2 -2
- package/lib/oh-story/skills/story-long-analyze/references/inspiration-library.md +105 -0
- package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +113 -105
- package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +169 -125
- package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +172 -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/style-profile-generator.md +42 -132
- package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +5 -5
- 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 +1106 -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 +4 -1
- package/lib/oh-story/skills/story-long-write/SKILL.md +52 -216
- package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +29 -56
- package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +72 -12
- package/lib/oh-story/skills/story-long-write/references/author-memory.md +61 -29
- package/lib/oh-story/skills/story-long-write/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +20 -0
- package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +26 -0
- package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +10 -4
- package/lib/oh-story/skills/story-long-write/references/emotional-methods.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +6 -15
- package/lib/oh-story/skills/story-long-write/references/long-format.md +2 -0
- package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +2 -0
- package/lib/oh-story/skills/story-long-write/references/project-files.md +110 -0
- package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +12 -4
- package/lib/oh-story/skills/story-long-write/references/reference-index.md +86 -0
- package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/style-craft.md +9 -29
- 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 +27 -0
- package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +9 -34
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +88 -118
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +55 -119
- package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +15 -11
- package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +73 -54
- package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +22 -0
- package/lib/oh-story/skills/story-long-write/references/writing-craft.md +63 -4
- 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_writer_prompt.py +531 -0
- package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-long-write/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +197 -15
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +90 -23
- package/lib/oh-story/skills/story-long-write/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +285 -0
- package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +58 -0
- package/lib/oh-story/skills/story-long-write/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +210 -2
- package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +3 -0
- package/lib/oh-story/skills/story-review/SKILL.md +80 -87
- package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +29 -56
- package/lib/oh-story/skills/story-review/references/author-memory.md +61 -29
- package/lib/oh-story/skills/story-review/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +10 -4
- package/lib/oh-story/skills/story-review/references/review-quality.md +6 -12
- package/lib/oh-story/skills/story-review/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-review/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-review/references/tracking-transaction.md +9 -34
- 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 +6 -1
- package/lib/oh-story/skills/story-review/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-review/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-review/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +210 -2
- package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +3 -0
- package/lib/oh-story/skills/story-setup/SKILL.md +58 -43
- package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +29 -56
- package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +154 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +10 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +2 -2
- 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-quality.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -1
- 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 +27 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +63 -4
- package/lib/oh-story/skills/story-setup/references/diagnostics.md +38 -0
- package/lib/oh-story/skills/story-short-analyze/SKILL.md +22 -3
- package/lib/oh-story/skills/story-short-analyze/references/analysis-writing-techniques.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +5 -1
- package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +2 -2
- package/lib/oh-story/skills/story-short-scan/SKILL.md +5 -1
- package/lib/oh-story/skills/story-short-write/SKILL.md +15 -324
- package/lib/oh-story/skills/story-short-write/references/author-memory.md +61 -29
- package/lib/oh-story/skills/story-short-write/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +60 -0
- package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +10 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//344/270/226/346/203/205/346/211/223/350/204/270.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/244/215/344/273/207/346/211/223/350/204/270.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/256/205/346/226/227/345/256/253/346/226/227.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/200/273/350/243/201/350/261/252/351/227/250.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +5 -5
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +4 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +5 -5
- package/lib/oh-story/skills/story-short-write/references/genre-styles//350/277/275/345/246/273/347/201/253/350/221/254/345/234/272.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/reference-index.md +55 -0
- package/lib/oh-story/skills/story-short-write/references/short-craft.md +12 -14
- package/lib/oh-story/skills/story-short-write/references/short-deslop.md +10 -10
- package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -1
- package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-short-write/references/workflow-design.md +78 -0
- package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +126 -0
- package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +32 -0
- package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +5 -5
- 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 +6 -1
- package/lib/oh-story/skills/story-short-write/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-short-write/scripts/check-delivery-contract.js +34 -5
- package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +90 -23
- package/lib/oh-story/skills/story-short-write/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-short-write/scripts/style-whitelist.js +39 -0
- 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 +33 -0
- package/lib/types/client/production-runtime.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 +52 -0
- 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/video-recap/CHANGELOG.md +31 -1
- package/lib/video-recap/manifest.json +162 -162
- package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +4 -9
- package/lib/video-recap/skills/video-assemble/scripts/assemble.py +47 -60
- package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +77 -115
- package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +41 -40
- package/lib/video-recap/skills/video-assemble/scripts/audio_automation.py +22 -32
- package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +102 -192
- package/lib/video-recap/skills/video-assemble/scripts/jianying_builders.py +14 -35
- package/lib/video-recap/skills/video-assemble/scripts/jianying_model.py +14 -12
- package/lib/video-recap/skills/video-assemble/scripts/jianying_optional.py +8 -9
- package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +3 -14
- package/lib/video-recap/skills/video-assemble/scripts/jianying_timeline_contract.py +23 -12
- package/lib/video-recap/skills/video-assemble/scripts/jianying_tracks.py +1 -4
- package/lib/video-recap/skills/video-assemble/scripts/jianying_writer.py +24 -25
- package/lib/video-recap/skills/video-assemble/scripts/lib.py +31 -85
- package/lib/video-recap/skills/video-assemble/scripts/media.py +100 -144
- package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +101 -195
- package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +10 -21
- package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +90 -169
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_core.py +69 -122
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_render.py +10 -17
- package/lib/video-recap/skills/video-assemble/scripts/timeline.py +33 -48
- package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +14 -20
- package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +135 -213
- package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +49 -57
- package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +38 -74
- package/lib/video-recap/skills/video-cut/scripts/cut_render.py +46 -95
- package/lib/video-recap/skills/video-cut/scripts/lib.py +23 -25
- package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +70 -173
- package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +28 -53
- package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +162 -322
- package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +6 -7
- package/lib/video-recap/skills/video-recap/scripts/doctor.py +104 -124
- package/lib/video-recap/skills/video-recap/scripts/final_qc.py +255 -412
- package/lib/video-recap/skills/video-recap/scripts/lib.py +30 -52
- package/lib/video-recap/skills/video-recap/scripts/materials.py +97 -120
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_client.py +7 -12
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_evidence.py +46 -97
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_observations.py +18 -36
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_payload.py +29 -47
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_report.py +43 -76
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_runner.py +2 -1
- package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +2 -1
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +2 -2
- package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +129 -145
- package/lib/video-recap/skills/video-recap/scripts/recap_review.py +27 -20
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +20 -31
- package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +31 -106
- package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +43 -113
- package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +196 -377
- package/lib/video-recap/skills/video-script/scripts/agent_brief.py +1 -1
- package/lib/video-recap/skills/video-script/scripts/agent_text.py +63 -172
- package/lib/video-recap/skills/video-script/scripts/brief_context.py +80 -135
- package/lib/video-recap/skills/video-script/scripts/brief_inputs.py +125 -167
- package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +93 -208
- package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +20 -46
- package/lib/video-recap/skills/video-script/scripts/lib.py +30 -26
- package/lib/video-recap/skills/video-script/scripts/narration_lint.py +145 -140
- package/lib/video-recap/skills/video-script/scripts/review_response.py +12 -42
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +0 -6
- package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +39 -100
- package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +33 -124
- package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +1 -1
- package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +63 -172
- package/lib/video-recap/skills/video-understanding/scripts/asr.py +1 -2
- package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +80 -135
- package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +125 -167
- package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +93 -208
- package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +1 -1
- package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +20 -46
- package/lib/video-recap/skills/video-understanding/scripts/lib.py +31 -26
- package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +145 -140
- package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +39 -100
- package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +33 -124
- package/lib/video-recap/skills/video-understanding/scripts/vlm.py +1 -4
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +7 -13
- package/lib/video-recap/skills/video-voiceover/scripts/fish_audio.py +12 -30
- package/lib/video-recap/skills/video-voiceover/scripts/lib.py +98 -131
- package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +17 -28
- package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +151 -240
- package/package.json +45 -45
- package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +0 -59
- 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 -42
- package/lib/oh-story/skills/story-long-write/references/state-tracking.md +0 -108
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
5
|
+
const { loadStyleWhitelist, maskStyleText } = require('./style-whitelist.js');
|
|
5
6
|
const path = require('path');
|
|
6
7
|
|
|
7
8
|
const USAGE = `Usage: node check-ai-patterns.js [--check] [--json] [--fail-on=blocking|all] <file...>
|
|
@@ -30,6 +31,7 @@ Detect high-risk AI-flavor prose patterns that need human rewrite:
|
|
|
30
31
|
- 章尾状态总结体 (文末窗口 这一夜注定/这一切都结束了/新的人生才刚刚开始/命运的齿轮)
|
|
31
32
|
- 引号强调滥用 (叙述里 1-4 字短词加引号强调,密度型)
|
|
32
33
|
|
|
34
|
+
Book-local .deslop-whitelist literal spans are excluded from style scanning (no regex or ancestor inheritance).
|
|
33
35
|
Each finding carries severity: blocking by default for generation/deslop cleanup (not-is-comparison / em-dash / voice-contrast / negation-parade / reverse-not-is / trailer-ending / trailer-summary). This is a local style/readability gate, not an AIGC detector score; functional human text can be marked for review instead of hard-edited for a detector.
|
|
34
36
|
或 advisory (period-stutter / long-paragraph / micro-action-tic / stock-reaction-tic / action-list-tic / abstract-summary-tic / cliche-density-tic / metaphor-density-tic / reasoning-chain-tic / system-notice-formality-tic / overcompressed-prose-tic / low-connective-density-tic / quote-emphasis-tic / formulaic-parallelism,是提示,justified 的长推理/氛围段可保留)。
|
|
35
37
|
--fail-on=blocking 只在出现 blocking finding 时退出 1;默认 --fail-on=all 有任何 finding 即退出 1。
|
|
@@ -308,7 +310,10 @@ for (const file of options.files) {
|
|
|
308
310
|
continue;
|
|
309
311
|
}
|
|
310
312
|
|
|
311
|
-
|
|
313
|
+
let whitelist;
|
|
314
|
+
try { whitelist = loadStyleWhitelist(fullPath); }
|
|
315
|
+
catch (error) { die(`${file}: unable to read .deslop-whitelist (${error.message})`); }
|
|
316
|
+
const findings = scanDocument(maskStyleText(input, whitelist)).map((finding) => ({ file, ...finding }));
|
|
312
317
|
allFindings.push(...findings);
|
|
313
318
|
}
|
|
314
319
|
|
|
@@ -43,6 +43,17 @@ const PLACEHOLDER_PATTERNS = [
|
|
|
43
43
|
{ re: /我(无法|不能)(继续(写|创作|生成|下去)|生成(内容|文本|正文)?|创作|续写|完成(这个|本)?(章|篇|创作|请求))/, label: '元信息泄漏(生成拒绝语)', hard: false },
|
|
44
44
|
];
|
|
45
45
|
|
|
46
|
+
const QUOTED_SPAN_PATTERNS = [
|
|
47
|
+
/「[^」]*」/g,
|
|
48
|
+
/『[^』]*』/g,
|
|
49
|
+
/【[^】]*】/g,
|
|
50
|
+
/“[^”]*”/g,
|
|
51
|
+
// 单引号须成对;词内撇号(don't、O’Connor)不作为开闭引号。
|
|
52
|
+
/(?<![A-Za-z0-9_])‘(?:[^’]|(?<=[A-Za-z0-9_])’(?=[A-Za-z0-9_]))*(?!(?<=[A-Za-z0-9_])’[A-Za-z0-9_])’/g,
|
|
53
|
+
/"[^"]*"/g,
|
|
54
|
+
/(?<![A-Za-z0-9_])'(?:[^']|(?<=[A-Za-z0-9_])'(?=[A-Za-z0-9_]))*(?!(?<=[A-Za-z0-9_])'[A-Za-z0-9_])'/g,
|
|
55
|
+
];
|
|
56
|
+
|
|
46
57
|
// 工程词泄漏(正文元信息扫描的确定性版):弱模型把写作工程词漏进正文,破坏代入感
|
|
47
58
|
// (DeepSeek-v4 这类会在对话里冒「该到下一章了」)。漏词的模型自己发现不了,靠脚本兜。
|
|
48
59
|
// tier1 = 纯写作流水线术语,正文里几乎永不合法;tier2 = 章节结构/歧义词,角色在故事内
|
|
@@ -150,21 +161,23 @@ function isContent(trimmed) {
|
|
|
150
161
|
return trimmed && !trimmed.startsWith('#') && !/^-{3,}$/.test(trimmed);
|
|
151
162
|
}
|
|
152
163
|
|
|
153
|
-
|
|
154
|
-
|
|
164
|
+
// 等长遮住成对引号内的内容以保留列号;未闭合引号不豁免。
|
|
165
|
+
function maskQuotedSpans(text) {
|
|
166
|
+
let masked = text;
|
|
167
|
+
for (const pattern of QUOTED_SPAN_PATTERNS) {
|
|
168
|
+
masked = masked.replace(pattern, (match) => ' '.repeat(match.length));
|
|
169
|
+
}
|
|
170
|
+
return masked;
|
|
155
171
|
}
|
|
156
172
|
|
|
157
173
|
// 去掉成对引号内的片段(台词/系统词/引用物件),只留引号外叙述。复读判定用:重复台词是体裁
|
|
158
174
|
// 手法(豁免),但「叙述 + 引号内物件/短台词」混合行里引号外叙述的复读仍是退化,不能整行豁免。
|
|
159
175
|
function stripQuoted(text) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
.replace(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
.replace(/‘[^’]*’/g, '')
|
|
166
|
-
.replace(/"[^"]*"/g, '')
|
|
167
|
-
.replace(/'[^']*'/g, '');
|
|
176
|
+
let stripped = text;
|
|
177
|
+
for (const pattern of QUOTED_SPAN_PATTERNS) {
|
|
178
|
+
stripped = stripped.replace(pattern, '');
|
|
179
|
+
}
|
|
180
|
+
return stripped;
|
|
168
181
|
}
|
|
169
182
|
|
|
170
183
|
function visibleLength(text) {
|
|
@@ -253,18 +266,19 @@ function findPlaceholders(content) {
|
|
|
253
266
|
const findings = [];
|
|
254
267
|
for (const { trimmed, lineNo } of content) {
|
|
255
268
|
if (!isContent(trimmed)) continue;
|
|
256
|
-
const
|
|
269
|
+
const outsideQuotes = maskQuotedSpans(trimmed);
|
|
257
270
|
for (const { re, label, hard } of PLACEHOLDER_PATTERNS) {
|
|
258
|
-
|
|
259
|
-
const m = re.exec(trimmed);
|
|
271
|
+
// hard 信号仍扫描整行;soft 自指/拒绝语只扫描成对引号外,且遮罩等长以保留列号。
|
|
272
|
+
const m = re.exec(hard ? trimmed : outsideQuotes);
|
|
260
273
|
if (m) {
|
|
274
|
+
const index = m.index || 0;
|
|
261
275
|
findings.push({
|
|
262
276
|
line: lineNo,
|
|
263
|
-
column:
|
|
277
|
+
column: index + 1,
|
|
264
278
|
type: 'placeholder-leak',
|
|
265
279
|
severity: 'blocking',
|
|
266
280
|
message: `${label}:正文混入元信息/拒绝语/占位符,重写本段干净落地。`,
|
|
267
|
-
excerpt: compact(trimmed.slice(Math.max(0,
|
|
281
|
+
excerpt: compact(trimmed.slice(Math.max(0, index - 4), index + 20)),
|
|
268
282
|
});
|
|
269
283
|
break; // one finding per line is enough
|
|
270
284
|
}
|
|
@@ -283,17 +297,22 @@ function findMetaLeak(content) {
|
|
|
283
297
|
// 标题行(第N章 章名,无 ## 前缀时也算)属「标题行以外的正文」之外,排除
|
|
284
298
|
if (/^第[一二三四五六七八九十百千万两0-9]+章/.test(trimmed)) continue;
|
|
285
299
|
}
|
|
286
|
-
const
|
|
287
|
-
let m = META_TIER1_RE.exec(
|
|
300
|
+
const outsideQuotes = maskQuotedSpans(trimmed);
|
|
301
|
+
let m = META_TIER1_RE.exec(outsideQuotes);
|
|
302
|
+
let quotedTier1 = false;
|
|
303
|
+
if (!m) {
|
|
304
|
+
m = META_TIER1_RE.exec(trimmed);
|
|
305
|
+
quotedTier1 = Boolean(m);
|
|
306
|
+
}
|
|
288
307
|
if (m) {
|
|
289
308
|
// tier1 纯工程词正文里几乎永不合法→blocking;但写手/编剧题材里角色在故事内真讨论创作,
|
|
290
|
-
//
|
|
309
|
+
// 且命中实际位于成对引号内时可能合法,降级为 advisory。同一行别处出现引号不影响判定。
|
|
291
310
|
findings.push({
|
|
292
311
|
line: lineNo,
|
|
293
312
|
column: m.index + 1,
|
|
294
313
|
type: 'meta-leak',
|
|
295
|
-
severity:
|
|
296
|
-
message: `工程词泄漏:「${m[0]}」是写作流水线术语,正文里不该出现;改成角色/场景内表达。${
|
|
314
|
+
severity: quotedTier1 ? 'advisory' : 'blocking',
|
|
315
|
+
message: `工程词泄漏:「${m[0]}」是写作流水线术语,正文里不该出现;改成角色/场景内表达。${quotedTier1 ? '例外:角色为作者/编剧、在故事内真实讨论创作时,台词里可能合法。' : ''}`,
|
|
297
316
|
excerpt: compact(trimmed.slice(Math.max(0, m.index - 6), m.index + 18)),
|
|
298
317
|
});
|
|
299
318
|
continue; // tier1 命中即可,不再叠 tier2
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
5
|
+
const { loadStyleWhitelist, styleSpans } = require('./style-whitelist.js');
|
|
5
6
|
const path = require('path');
|
|
6
7
|
|
|
7
8
|
const USAGE = `Usage: node normalize-punctuation.js [--check] [--quote-mode keep|ascii|yan] <file...>
|
|
@@ -9,6 +10,7 @@ const USAGE = `Usage: node normalize-punctuation.js [--check] [--quote-mode keep
|
|
|
9
10
|
Normalize正文 punctuation deterministically:
|
|
10
11
|
- replace ellipses, em dashes, and double hyphens with Chinese punctuation
|
|
11
12
|
- remove markdown divider lines (---) from正文
|
|
13
|
+
- preserve pause punctuation covered by book-local .deslop-whitelist literals
|
|
12
14
|
- keep quote style by default; convert quotes only when explicitly requested
|
|
13
15
|
`;
|
|
14
16
|
|
|
@@ -61,7 +63,10 @@ for (const file of options.files) {
|
|
|
61
63
|
continue;
|
|
62
64
|
}
|
|
63
65
|
|
|
64
|
-
|
|
66
|
+
let whitelist;
|
|
67
|
+
try { whitelist = loadStyleWhitelist(fullPath); }
|
|
68
|
+
catch (error) { die(`${file}: unable to read .deslop-whitelist (${error.message})`); }
|
|
69
|
+
const result = normalizeDocument(input, options.quoteMode, whitelist);
|
|
65
70
|
totalFindings += result.findings.length;
|
|
66
71
|
|
|
67
72
|
if (options.check) {
|
|
@@ -94,7 +99,7 @@ function die(message) {
|
|
|
94
99
|
process.exit(2);
|
|
95
100
|
}
|
|
96
101
|
|
|
97
|
-
function normalizeDocument(input, quoteMode) {
|
|
102
|
+
function normalizeDocument(input, quoteMode, whitelist) {
|
|
98
103
|
const { lines, endings } = splitLinesKeepingEndings(input);
|
|
99
104
|
|
|
100
105
|
const findings = [];
|
|
@@ -159,7 +164,7 @@ function normalizeDocument(input, quoteMode) {
|
|
|
159
164
|
}
|
|
160
165
|
|
|
161
166
|
const commentOpenBefore = commentOpen;
|
|
162
|
-
const punctuationResult = normalizePausePunctuation(line, lineNo, commentOpen);
|
|
167
|
+
const punctuationResult = normalizePausePunctuation(line, lineNo, commentOpen, whitelist);
|
|
163
168
|
findings.push(...punctuationResult.findings);
|
|
164
169
|
line = punctuationResult.line;
|
|
165
170
|
commentOpen = punctuationResult.commentOpen;
|
|
@@ -237,7 +242,7 @@ function isClosingFence(line, fence) {
|
|
|
237
242
|
// 一遍归一化留不干净,再跑一遍还会改已定稿的正文;所以反复归一化到不动点。
|
|
238
243
|
// 每遍至少把一个 `…/./—/-` 换成非停顿字符,字符数严格递减,必然收敛。
|
|
239
244
|
// findings 只留第一遍:同一处不重复计数,column 也仍然是原行的偏移。
|
|
240
|
-
function normalizePausePunctuation(line, lineNo, commentOpen) {
|
|
245
|
+
function normalizePausePunctuation(line, lineNo, commentOpen, whitelist) {
|
|
241
246
|
let current = line;
|
|
242
247
|
let findings = null;
|
|
243
248
|
let commentOpenAfter = commentOpen;
|
|
@@ -245,7 +250,7 @@ function normalizePausePunctuation(line, lineNo, commentOpen) {
|
|
|
245
250
|
for (;;) {
|
|
246
251
|
const comments = htmlCommentSpans(current, commentOpen);
|
|
247
252
|
commentOpenAfter = comments.open;
|
|
248
|
-
const pass = normalizePausePunctuationPass(current, lineNo, comments.spans);
|
|
253
|
+
const pass = normalizePausePunctuationPass(current, lineNo, comments.spans.concat(styleSpans(current, whitelist)));
|
|
249
254
|
if (findings === null) findings = pass.findings;
|
|
250
255
|
if (pass.line === current) break;
|
|
251
256
|
current = pass.line;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
// Book-local only: never inherit an unrelated workspace or another book's choices.
|
|
7
|
+
function loadStyleWhitelist(file) {
|
|
8
|
+
const parent = path.dirname(path.resolve(file));
|
|
9
|
+
const book = path.basename(parent) === '正文' ? path.dirname(parent) : parent;
|
|
10
|
+
try {
|
|
11
|
+
return fs.readFileSync(path.join(book, '.deslop-whitelist'), 'utf8')
|
|
12
|
+
.split(/\r?\n/).map(line => line.trim())
|
|
13
|
+
.filter(line => line && !line.startsWith('#'))
|
|
14
|
+
.sort((a, b) => b.length - a.length);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (error.code === 'ENOENT') return [];
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function styleSpans(text, whitelist) {
|
|
22
|
+
const spans = [];
|
|
23
|
+
for (const literal of whitelist) {
|
|
24
|
+
for (let at = text.indexOf(literal); at !== -1; at = text.indexOf(literal, at + 1)) {
|
|
25
|
+
spans.push([at, at + literal.length]);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return spans;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function maskStyleText(text, whitelist) {
|
|
32
|
+
const chars = text.split('');
|
|
33
|
+
for (const [start, end] of styleSpans(text, whitelist)) {
|
|
34
|
+
chars.fill('?', start, end);
|
|
35
|
+
}
|
|
36
|
+
return chars.join('');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = { loadStyleWhitelist, styleSpans, maskStyleText };
|
|
@@ -117,11 +117,26 @@ def require_known_keys(mapping: dict[str, Any], allowed: set[str], label: str) -
|
|
|
117
117
|
require(not unknown, f"{label} contains unsupported fields: {', '.join(sorted(unknown))}")
|
|
118
118
|
|
|
119
119
|
|
|
120
|
+
# 事务校验期间收集全部超长字段,一次报完;None 表示逐条立即报错(初始化、状态读取等路径)。
|
|
121
|
+
_LENGTH_ISSUES: list[str] | None = None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def length_hint(label: str, text: str, max_bytes: int) -> str:
|
|
125
|
+
"""把字节上限换算成字数:模型写的是中文,只看得懂「要删几个字」。"""
|
|
126
|
+
size = len(text.encode("utf-8"))
|
|
127
|
+
allowed = max(1, max_bytes * len(text) // size)
|
|
128
|
+
return (f"{label} exceeds {max_bytes} bytes(现 {len(text)} 字,上限约 {allowed} 字,"
|
|
129
|
+
f"至少删 {len(text) - allowed} 字)")
|
|
130
|
+
|
|
131
|
+
|
|
120
132
|
def clean_text(value: object, label: str, *, allow_empty: bool = False, max_bytes: int = 768) -> str:
|
|
121
133
|
require(isinstance(value, str), f"{label} must be a string")
|
|
122
134
|
cleaned = " ".join(value.replace("|", "|").split())
|
|
123
135
|
require(allow_empty or bool(cleaned), f"{label} must not be empty")
|
|
124
|
-
|
|
136
|
+
if len(cleaned.encode("utf-8")) > max_bytes:
|
|
137
|
+
if _LENGTH_ISSUES is None:
|
|
138
|
+
raise TrackingError(length_hint(label, cleaned, max_bytes))
|
|
139
|
+
_LENGTH_ISSUES.append(length_hint(label, cleaned, max_bytes))
|
|
125
140
|
return cleaned
|
|
126
141
|
|
|
127
142
|
|
|
@@ -277,9 +292,15 @@ def archive_retired_tracking_paths(tracking: Path) -> list[str]:
|
|
|
277
292
|
return retired
|
|
278
293
|
|
|
279
294
|
|
|
295
|
+
def _both_blank(position: dict[str, Any]) -> bool:
|
|
296
|
+
return all(isinstance(position.get(key), str) and not position[key].strip() for key in ("story_time", "scene"))
|
|
297
|
+
|
|
298
|
+
|
|
280
299
|
def validate_position(value: object, label: str = "context.position") -> dict[str, Any]:
|
|
281
300
|
position = as_mapping(value, label)
|
|
282
301
|
require_known_keys(position, {"volume", "volume_start_chapter", "story_time", "scene"}, label)
|
|
302
|
+
require(not _both_blank(position),
|
|
303
|
+
f"{label}.story_time and {label}.scene must not be empty: fill where this chapter ends")
|
|
283
304
|
return {
|
|
284
305
|
"volume": safe_file_component(position.get("volume"), f"{label}.volume"),
|
|
285
306
|
"volume_start_chapter": as_int(
|
|
@@ -781,7 +802,13 @@ def render_delta(chapter: int, title: str, delta: dict[str, Any], core_names: se
|
|
|
781
802
|
lines.extend(f"- {item}" for item in retired)
|
|
782
803
|
payload = "\n".join(lines) + "\n"
|
|
783
804
|
size = byte_size(payload)
|
|
784
|
-
|
|
805
|
+
if size > DELTA_MAX_BYTES:
|
|
806
|
+
longest = sorted((line for line in lines if line.startswith("- ")), key=len, reverse=True)[:3]
|
|
807
|
+
allowed = DELTA_MAX_BYTES * len(payload) // size
|
|
808
|
+
raise TrackingError(
|
|
809
|
+
f"chapter delta is {size} bytes; hard cap is {DELTA_MAX_BYTES}(逐章记录现 {len(payload)} 字,"
|
|
810
|
+
f"上限约 {allowed} 字,至少压缩 {len(payload) - allowed} 字;最长几项:"
|
|
811
|
+
+ ";".join(f"{line[2:18]}…({len(line) - 2} 字)" for line in longest) + ")")
|
|
785
812
|
return payload
|
|
786
813
|
|
|
787
814
|
|
|
@@ -901,6 +928,17 @@ def normalize_initial_document(document: object) -> dict[str, Any]:
|
|
|
901
928
|
|
|
902
929
|
|
|
903
930
|
def normalize_transaction(project: Path, state: dict[str, Any], document: object) -> dict[str, Any]:
|
|
931
|
+
global _LENGTH_ISSUES
|
|
932
|
+
_LENGTH_ISSUES = []
|
|
933
|
+
try:
|
|
934
|
+
transaction = _normalize_transaction(project, state, document)
|
|
935
|
+
finally:
|
|
936
|
+
issues, _LENGTH_ISSUES = _LENGTH_ISSUES, None
|
|
937
|
+
require(not issues, f"{len(issues)} 个字段超长,一次改完再提交:" + ";".join(issues))
|
|
938
|
+
return transaction
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
def _normalize_transaction(project: Path, state: dict[str, Any], document: object) -> dict[str, Any]:
|
|
904
942
|
root = as_mapping(document, "transaction")
|
|
905
943
|
require_known_keys(
|
|
906
944
|
root,
|
|
@@ -999,6 +1037,13 @@ def merge_transaction(state: dict[str, Any], transaction: dict[str, Any]) -> dic
|
|
|
999
1037
|
"a revision must resubmit every current context item; retire them in an append transaction instead: "
|
|
1000
1038
|
+ ";".join(sorted(dropped)),
|
|
1001
1039
|
)
|
|
1040
|
+
kept = set(next_context["long_term_constraints"]) | set(next_context["continuity_risks"])
|
|
1041
|
+
still_listed = sorted(set(transaction["delta"]["retired_context_items"]) & kept)
|
|
1042
|
+
require(
|
|
1043
|
+
not still_listed,
|
|
1044
|
+
"delta.retired_context_items lists items that are still in context; remove them from context: "
|
|
1045
|
+
+ ";".join(still_listed),
|
|
1046
|
+
)
|
|
1002
1047
|
undeclared = sorted(dropped - set(transaction["delta"]["retired_context_items"]))
|
|
1003
1048
|
require(
|
|
1004
1049
|
not undeclared,
|
|
@@ -1188,6 +1233,126 @@ def check_project(project: Path) -> dict[str, Any]:
|
|
|
1188
1233
|
return state
|
|
1189
1234
|
|
|
1190
1235
|
|
|
1236
|
+
# 事务草稿里各文本字段的字数上限(按全中文估:UTF-8 每字 3 字节),写进提示,免得模型反复试错。
|
|
1237
|
+
DRAFT_LIMITS = {
|
|
1238
|
+
"delta.result": 160, "character_changes[].change": 120, "foreshadow_changes[].summary": 120,
|
|
1239
|
+
"timeline_events[].objective_fact / reader_knowledge": 160, "context.position.story_time / scene": 80,
|
|
1240
|
+
"character_snapshots.*.goal / state": 100, "逐章记录总量": 1024,
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
|
|
1244
|
+
def draft_transaction(project: Path, chapter: int) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]:
|
|
1245
|
+
"""按当前 state 预填一份逐章事务:修订号、模式、章名和整份提交的上下文当前值都填好,
|
|
1246
|
+
调用方只写本章变化。另返回在场核心角色的当前快照,供有变化时整份改写后放进事务。
|
|
1247
|
+
新章(append)的故事时间与场景留空:它们必须是本章结束时的位置,沿用上一章的值能静默提交,
|
|
1248
|
+
所以不预填,上一章的值另行返回作参考。"""
|
|
1249
|
+
state = load_state(project)
|
|
1250
|
+
last = state["last_committed_chapter"]
|
|
1251
|
+
require(1 <= chapter <= last + 1, f"draft chapter must be between 1 and {last + 1}")
|
|
1252
|
+
mode = "append" if chapter == last + 1 else "revision"
|
|
1253
|
+
title = ""
|
|
1254
|
+
for outline in sorted((project / "大纲").glob(f"细纲_第{chapter:03d}章*.md")):
|
|
1255
|
+
match = re.search(rf"^#{{1,4}}\s*第\s*0*{chapter}\s*章\s*[::]\s*(.+?)\s*$",
|
|
1256
|
+
outline.read_text(encoding="utf-8"), re.M)
|
|
1257
|
+
if match:
|
|
1258
|
+
title = match.group(1).strip()
|
|
1259
|
+
break
|
|
1260
|
+
context = json.loads(json.dumps(state["context"], ensure_ascii=False))
|
|
1261
|
+
previous_position = {key: context["position"][key] for key in ("story_time", "scene")}
|
|
1262
|
+
delta: dict[str, Any] = {
|
|
1263
|
+
"result": "", "character_changes": [], "foreshadow_changes": [], "timeline_events": [],
|
|
1264
|
+
"constraints": [], "next_chapter_commitments": [],
|
|
1265
|
+
}
|
|
1266
|
+
if mode == "append":
|
|
1267
|
+
delta.update({"retired_context_items": [], "retired_characters": []})
|
|
1268
|
+
context["position"].update({"story_time": "", "scene": ""})
|
|
1269
|
+
document = {
|
|
1270
|
+
"schema_version": INPUT_SCHEMA_VERSION,
|
|
1271
|
+
"mode": mode,
|
|
1272
|
+
"chapter": chapter,
|
|
1273
|
+
"chapter_title": title,
|
|
1274
|
+
"expected_state_revision": state["state_revision"],
|
|
1275
|
+
"delta": delta,
|
|
1276
|
+
"context": {key: context[key] for key in
|
|
1277
|
+
("position", "long_term_constraints", "active_character_names", "continuity_risks")},
|
|
1278
|
+
"character_snapshots": {},
|
|
1279
|
+
}
|
|
1280
|
+
snapshots = {name: state["characters"][name] for name in context["active_character_names"]
|
|
1281
|
+
if name in state["characters"]}
|
|
1282
|
+
return document, snapshots, previous_position if mode == "append" else {}
|
|
1283
|
+
|
|
1284
|
+
|
|
1285
|
+
def rebuild_stale_draft(document: dict[str, Any], existing: dict[str, Any], append: bool,
|
|
1286
|
+
current_characters: Any = ()) -> str:
|
|
1287
|
+
"""修订号变了:中间提交过别的事务。以当前状态为底,只自动合并确定安全的部分,其余列给调用方核对。
|
|
1288
|
+
|
|
1289
|
+
新章草稿之间只可能插进修订事务,修订不能删约束与风险条目,所以当前约束与风险覆盖旧底稿:
|
|
1290
|
+
旧草稿多出来的就是本章新增,delta 里声明退役的就是本章删除,可以精确合并。
|
|
1291
|
+
修订草稿之间可能插进新章(新章能退役条目),在场角色名修订也能删,都无法判断谁新:
|
|
1292
|
+
取当前状态,差异列出来;只有本章带了快照的新角色名自动加回。"""
|
|
1293
|
+
def items(value: object) -> list[str]:
|
|
1294
|
+
return [item for item in value if isinstance(item, str)] if isinstance(value, list) else []
|
|
1295
|
+
|
|
1296
|
+
def norm(item: str) -> str:
|
|
1297
|
+
return " ".join(item.replace("|", "|").split())
|
|
1298
|
+
|
|
1299
|
+
notes = ["修订号已变:context 按当前状态重建,本章已填的 delta" + ("、新增与退役的约束、结尾位置" if append else "")
|
|
1300
|
+
+ "已合并"]
|
|
1301
|
+
for key in ("delta", "character_snapshots"):
|
|
1302
|
+
if isinstance(existing.get(key), dict):
|
|
1303
|
+
document[key] = existing[key]
|
|
1304
|
+
if existing.get("chapter_title"):
|
|
1305
|
+
document["chapter_title"] = existing["chapter_title"]
|
|
1306
|
+
old_context = existing.get("context") if isinstance(existing.get("context"), dict) else {}
|
|
1307
|
+
delta = document["delta"] if isinstance(document["delta"], dict) else {}
|
|
1308
|
+
context = document["context"]
|
|
1309
|
+
retired = {norm(item) for item in items(delta.get("retired_context_items"))}
|
|
1310
|
+
left_out: list[str] = []
|
|
1311
|
+
for key in ("long_term_constraints", "continuity_risks"):
|
|
1312
|
+
current = {norm(item) for item in context[key]}
|
|
1313
|
+
extra = []
|
|
1314
|
+
for item in items(old_context.get(key)):
|
|
1315
|
+
if norm(item) not in current:
|
|
1316
|
+
current.add(norm(item))
|
|
1317
|
+
extra.append(item)
|
|
1318
|
+
if append:
|
|
1319
|
+
context[key] = [item for item in context[key] if norm(item) not in retired] + extra
|
|
1320
|
+
else:
|
|
1321
|
+
left_out += extra
|
|
1322
|
+
snapshots = document["character_snapshots"] if isinstance(document["character_snapshots"], dict) else {}
|
|
1323
|
+
names = context["active_character_names"]
|
|
1324
|
+
for name in items(old_context.get("active_character_names")):
|
|
1325
|
+
if name in names:
|
|
1326
|
+
continue
|
|
1327
|
+
if append and name in snapshots:
|
|
1328
|
+
names.append(name)
|
|
1329
|
+
else:
|
|
1330
|
+
left_out.append(f"在场角色 {name}")
|
|
1331
|
+
names[:] = [name for name in names if name not in set(items(delta.get("retired_characters")))]
|
|
1332
|
+
known = set(current_characters)
|
|
1333
|
+
for name in list(snapshots):
|
|
1334
|
+
if name not in known and name not in names:
|
|
1335
|
+
snapshots.pop(name)
|
|
1336
|
+
left_out.append(f"角色快照 {name}")
|
|
1337
|
+
if left_out:
|
|
1338
|
+
notes.append("草稿里有、当前状态没有的条目没有自动带回(可能已被别的提交退役),本章确实需要就加回:"
|
|
1339
|
+
+ ";".join(left_out))
|
|
1340
|
+
old_position = old_context.get("position") if isinstance(old_context.get("position"), dict) else {}
|
|
1341
|
+
position = context["position"]
|
|
1342
|
+
if append:
|
|
1343
|
+
for key in ("story_time", "scene"):
|
|
1344
|
+
if isinstance(old_position.get(key), str) and old_position[key].strip():
|
|
1345
|
+
position[key] = old_position[key]
|
|
1346
|
+
differs = [f"{key}:草稿写的是「{old_position[key]}」,当前状态是「{position[key]}」"
|
|
1347
|
+
for key in ("volume", "volume_start_chapter")
|
|
1348
|
+
if key in old_position and old_position[key] != position[key]]
|
|
1349
|
+
if differs:
|
|
1350
|
+
notes.append("卷信息与当前状态不同,已取当前状态,本章确实换卷就改回(" + ";".join(differs) + ")")
|
|
1351
|
+
if snapshots:
|
|
1352
|
+
notes.append("带过来的角色快照(" + "、".join(snapshots) + ")可能被中间的提交改过,逐个对照 current_snapshots 重核")
|
|
1353
|
+
return ";".join(notes)
|
|
1354
|
+
|
|
1355
|
+
|
|
1191
1356
|
def build_parser() -> argparse.ArgumentParser:
|
|
1192
1357
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
1193
1358
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
@@ -1197,12 +1362,55 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1197
1362
|
subparser.add_argument("--input", type=Path, required=True, help="UTF-8 JSON input document")
|
|
1198
1363
|
check_parser = subparsers.add_parser("check")
|
|
1199
1364
|
check_parser.add_argument("--project", type=Path, required=True, help="book project root containing 追踪/")
|
|
1365
|
+
draft_parser = subparsers.add_parser("draft", help="write a pre-filled chapter transaction to fill in")
|
|
1366
|
+
draft_parser.add_argument("--project", type=Path, required=True, help="book project root containing 追踪/")
|
|
1367
|
+
draft_parser.add_argument("--chapter", type=int, required=True)
|
|
1368
|
+
draft_parser.add_argument("--out", type=Path, help="default: <project>/.story/work/第NNN章/tracking.json")
|
|
1369
|
+
draft_parser.add_argument("--force", action="store_true",
|
|
1370
|
+
help="discard an existing draft instead of only refreshing its revision")
|
|
1200
1371
|
return parser
|
|
1201
1372
|
|
|
1202
1373
|
|
|
1203
1374
|
def main() -> int:
|
|
1204
1375
|
args = build_parser().parse_args()
|
|
1205
1376
|
try:
|
|
1377
|
+
if args.command == "draft":
|
|
1378
|
+
document, snapshots, previous_position = draft_transaction(args.project, args.chapter)
|
|
1379
|
+
out = args.out or args.project / ".story" / "work" / f"第{args.chapter:03d}章" / "tracking.json"
|
|
1380
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
1381
|
+
refreshed = ""
|
|
1382
|
+
if out.exists() and not args.force:
|
|
1383
|
+
# 重跑 draft 不冲掉已经填好的变化。修订号变了说明中间提交过别的事务:context 按当前
|
|
1384
|
+
# 状态重建(沿用旧 context 会把那次变更整份覆盖回去),只带过本章自己填的部分。
|
|
1385
|
+
existing = read_json(out)
|
|
1386
|
+
require(isinstance(existing, dict) and existing.get("chapter") == document["chapter"]
|
|
1387
|
+
and existing.get("mode") == document["mode"],
|
|
1388
|
+
f"{out} already holds a draft for a different chapter or mode; pass --force to replace it")
|
|
1389
|
+
if existing.get("expected_state_revision") == document["expected_state_revision"]:
|
|
1390
|
+
document, refreshed = existing, "已有草稿且修订号未变,原样保留"
|
|
1391
|
+
else:
|
|
1392
|
+
refreshed = rebuild_stale_draft(document, existing, bool(previous_position),
|
|
1393
|
+
load_state(args.project)["characters"])
|
|
1394
|
+
out.write_text(json.dumps(document, ensure_ascii=False, indent=1) + "\n", encoding="utf-8")
|
|
1395
|
+
fill = ("只填 delta 里本章的变化;context 其余字段已是当前值,要撤下的长期约束或连贯性风险从 context 删掉并把原文放进 "
|
|
1396
|
+
"delta.retired_context_items(仅 append);本章有变化的核心角色把下面的当前快照整份改好放进 character_snapshots,"
|
|
1397
|
+
"并在 character_changes 写一句变化。不要从脚本源码或 state 文件里另找格式。")
|
|
1398
|
+
blank = not (document["context"]["position"].get("story_time") or document["context"]["position"].get("scene"))
|
|
1399
|
+
if previous_position and blank:
|
|
1400
|
+
fill = ("context.position 的 story_time 与 scene 留空,填本章结束时的故事时间与场景(上一章结束时见 "
|
|
1401
|
+
"previous_position;换卷时连同 volume 与 volume_start_chapter 一起改)。") + fill
|
|
1402
|
+
payload = {
|
|
1403
|
+
"draft": str(out),
|
|
1404
|
+
"mode": document["mode"],
|
|
1405
|
+
"expected_state_revision": document["expected_state_revision"],
|
|
1406
|
+
"fill": (f"{refreshed};要从头生成加 --force。" if refreshed else "") + fill,
|
|
1407
|
+
"limits_chars": DRAFT_LIMITS,
|
|
1408
|
+
"current_snapshots": snapshots,
|
|
1409
|
+
}
|
|
1410
|
+
if previous_position:
|
|
1411
|
+
payload["previous_position"] = previous_position
|
|
1412
|
+
emit(json.dumps(payload, ensure_ascii=False))
|
|
1413
|
+
return 0
|
|
1206
1414
|
if args.command == "init":
|
|
1207
1415
|
result = initialize(args.project, read_json(args.input))
|
|
1208
1416
|
elif args.command == "commit":
|
|
@@ -172,6 +172,9 @@ def target_from_outline(value: str) -> int:
|
|
|
172
172
|
|
|
173
173
|
|
|
174
174
|
def _chapter_number_from_name(name: str, *, outline: bool) -> int | None:
|
|
175
|
+
# 改稿流程把原文备份为 第N章_章名_原稿_{日期}.md,与 hooks 一样不把它当本章正文。
|
|
176
|
+
if not outline and "_原稿_" in name:
|
|
177
|
+
return None
|
|
175
178
|
pattern = r"^细纲_第0*(\d+)章.*\.md$" if outline else r"^第0*(\d+)章(?:[_\- ].*)?\.md$"
|
|
176
179
|
match = re.match(pattern, name)
|
|
177
180
|
return int(match.group(1)) if match else None
|