@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
|
@@ -18,11 +18,13 @@
|
|
|
18
18
|
const fs = require('fs')
|
|
19
19
|
const path = require('path')
|
|
20
20
|
|
|
21
|
-
// 权威模板:references/workflow-setup.md
|
|
21
|
+
// 权威模板:references/workflow-setup.md「排纲自查与细纲」里的细纲模板
|
|
22
|
+
// 必填只留有证据或事故撑腰的核心字段;阶段位置、章节定位、结构公式、本章标价、闭环状态、
|
|
23
|
+
// 写手自由区、契约风险、镜头准入、信息差触发点与「分辨率」列是可选的阅读体验字段——
|
|
24
|
+
// 写了照样消费,没写不拦(#383 隔离实验:只补目标情绪与主角目标就复现了补满全部字段的收益)。
|
|
22
25
|
const FIELDS = [
|
|
23
|
-
'核心事件', '字数目标', '字数口径', '
|
|
24
|
-
'主角目标/关键选择', '
|
|
25
|
-
'本章禁止提前释放', '契约风险',
|
|
26
|
+
'核心事件', '字数目标', '字数口径', '单元ID/位置', '目标情绪',
|
|
27
|
+
'主角目标/关键选择', '章首钩子', '爽点', '本章禁止提前释放',
|
|
26
28
|
]
|
|
27
29
|
const SUBSECTIONS = ['内容概括', '情节安排', '人物关系和出场顺序', '情节细化']
|
|
28
30
|
const FIVE_ACT = ['起因', '发展', '转折', '高潮', '结尾']
|
|
@@ -46,11 +48,11 @@ function readUtf8(file) {
|
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
function makeCheck(id, ok, file, evidence, expected, repair) {
|
|
51
|
+
function makeCheck(id, ok, file, evidence, expected, repair, severity = 'blocking') {
|
|
50
52
|
return {
|
|
51
53
|
id,
|
|
52
54
|
ok,
|
|
53
|
-
severity
|
|
55
|
+
severity,
|
|
54
56
|
file,
|
|
55
57
|
evidence,
|
|
56
58
|
expected,
|
|
@@ -65,7 +67,47 @@ function parseTableRow(line) {
|
|
|
65
67
|
return trimmed.slice(1, -1).split('|').map((cell) => cell.replace(/\*\*/g, '').replace(/`/g, '').trim())
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
function
|
|
70
|
+
function resolveBookRoot(file, projectRoot) {
|
|
71
|
+
if (projectRoot) return path.resolve(projectRoot)
|
|
72
|
+
const parent = path.dirname(path.resolve(file))
|
|
73
|
+
if (path.basename(parent) === '大纲') return path.dirname(parent)
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 项目引用须写目录路径;裸文件名可能指向 skill reference,不猜测其归属。
|
|
78
|
+
const REF_DIR_PREFIX = /^(?:设定|大纲|追踪|正文|读者笔记|对标)\//
|
|
79
|
+
|
|
80
|
+
function checkSettingRefs(text, name, file, projectRoot) {
|
|
81
|
+
const bookRoot = resolveBookRoot(file, projectRoot)
|
|
82
|
+
if (!bookRoot) {
|
|
83
|
+
return makeCheck('outline.setting-refs-exist', true, name, '未能定位书目录(细纲不在 大纲/ 下且未传 --project),跳过', '细纲引用的项目内文件真实存在', '无需修复。')
|
|
84
|
+
}
|
|
85
|
+
const refs = new Set()
|
|
86
|
+
const pattern = /`([^`\n]+?\.md)[^`\n]*`/g
|
|
87
|
+
let match
|
|
88
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
89
|
+
const token = match[1].trim().replace(/\\/g, '/')
|
|
90
|
+
if (token.includes('{') || token.includes('}')) continue
|
|
91
|
+
if (REF_DIR_PREFIX.test(token) || /^\.{1,2}\//.test(token)) refs.add(token)
|
|
92
|
+
}
|
|
93
|
+
const missing = []
|
|
94
|
+
for (const token of refs) {
|
|
95
|
+
const base = /^\.{1,2}\//.test(token) ? path.dirname(path.resolve(file)) : bookRoot
|
|
96
|
+
try {
|
|
97
|
+
if (!fs.statSync(path.resolve(base, token)).isFile()) missing.push(token)
|
|
98
|
+
} catch { missing.push(token) }
|
|
99
|
+
}
|
|
100
|
+
return makeCheck(
|
|
101
|
+
'outline.setting-refs-exist',
|
|
102
|
+
missing.length === 0,
|
|
103
|
+
name,
|
|
104
|
+
missing.length ? `引用的路径不是可用文件:${missing.join('、')}` : `${refs.size} 处项目内引用全部存在`,
|
|
105
|
+
'细纲引用的每个项目内 .md 文件都真实存在——不许把槽位指向查无实据的权威文件',
|
|
106
|
+
'修正引用路径;或按新增物三级先把缺的设定落档(「写了要报」直接补并登记、「先问作者」进排纲底稿的供给自查提案),再在细纲引用。'
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function verify(file, projectRoot = null) {
|
|
69
111
|
const name = path.basename(file)
|
|
70
112
|
const read = readUtf8(file)
|
|
71
113
|
const checks = []
|
|
@@ -133,21 +175,82 @@ function verify(file) {
|
|
|
133
175
|
let header = null
|
|
134
176
|
for (const line of lines) {
|
|
135
177
|
const cells = parseTableRow(line)
|
|
136
|
-
|
|
178
|
+
// 现行模板默认四列(# / 情节点 / 功能标签 / 执行边界(禁+放)),需要时在功能标签后加「分辨率」列成五列。
|
|
179
|
+
// 两种都收:功能标签固定在第 3 列,执行边界一律取最后一列。
|
|
180
|
+
if (cells && (cells.length === 4 || cells.length === 5) && PLOT_HEADER_FIRST.test(cells[0])) {
|
|
137
181
|
header = cells
|
|
138
182
|
break
|
|
139
183
|
}
|
|
140
184
|
}
|
|
141
|
-
const headerOk = Boolean(header)
|
|
185
|
+
const headerOk = Boolean(header)
|
|
186
|
+
&& header[2].includes('功能标签')
|
|
187
|
+
&& header[header.length - 1].includes('执行边界')
|
|
188
|
+
&& (header.length === 4 || header[3].includes('分辨率'))
|
|
142
189
|
checks.push(makeCheck(
|
|
143
190
|
'outline.plotpoint-table',
|
|
144
191
|
headerOk,
|
|
145
192
|
name,
|
|
146
|
-
header ? `表头:${header.join(' | ')}` : '未找到 | # | 情节点 | 功能标签 | 执行边界 | 表头',
|
|
147
|
-
'
|
|
148
|
-
'
|
|
193
|
+
header ? `表头:${header.join(' | ')}` : '未找到 | # | 情节点 | 功能标签 | [分辨率] | 执行边界 | 表头',
|
|
194
|
+
'情节细化使用表格:# / 情节点(谁做了什么) / 功能标签 / 执行边界(禁+放);需要时可在功能标签后加「分辨率」列',
|
|
195
|
+
'只把情节点序列改成这张表,逐点补功能标签与执行边界;不增删情节点本身。'
|
|
149
196
|
))
|
|
150
197
|
|
|
198
|
+
// 「放」半边:五列表或表头写明「禁+放」的四列表,执行边界必须至少一个点解除限制,
|
|
199
|
+
// 全是禁令的情节点表实测会把整章压成同一个温度(015 章十一个点全「不许」)。表头没写「禁+放」的四列表不追溯。
|
|
200
|
+
if (headerOk && (header.length === 5 || /放/.test(header[header.length - 1]))) {
|
|
201
|
+
const rows = []
|
|
202
|
+
for (const line of lines) {
|
|
203
|
+
const cells = parseTableRow(line)
|
|
204
|
+
if (cells && cells.length === header.length && /^\d+$/.test(cells[0])) rows.push(cells)
|
|
205
|
+
}
|
|
206
|
+
if (rows.length) {
|
|
207
|
+
const released = rows.filter((cells) => {
|
|
208
|
+
const match = cells[cells.length - 1].match(/(?:^|[。;;\s])放\s*[::]([^]*?)(?=(?:禁|放)\s*[::]|$)/)
|
|
209
|
+
if (!match) return false
|
|
210
|
+
const value = match[1].replace(/[\s。;;,,、\[\]【】]/g, '')
|
|
211
|
+
return value.length > 0 && !/^(?:无|暂无|待补充|待定|无缺口)$/.test(value)
|
|
212
|
+
})
|
|
213
|
+
checks.push(makeCheck(
|
|
214
|
+
'outline.plotpoint-release',
|
|
215
|
+
released.length > 0,
|
|
216
|
+
name,
|
|
217
|
+
`${rows.length} 个情节点里 ${released.length} 个写了「放」`,
|
|
218
|
+
'执行边界写成「禁+放」两半,每章至少一个点写了「放」——放行是解除限制,不是义务',
|
|
219
|
+
'给最该出彩的一两个点补「放」半边(谁可以出声、谁可以失态、允不允许当场标价),不改情节点本身。'
|
|
220
|
+
))
|
|
221
|
+
if (released.length > 0 && rows.length >= 3 && released.length * 3 < rows.length) {
|
|
222
|
+
checks.push(makeCheck(
|
|
223
|
+
'outline.plotpoint-release-ratio',
|
|
224
|
+
false,
|
|
225
|
+
name,
|
|
226
|
+
`${rows.length} 个情节点里只有 ${released.length} 个写了「放」(不足三分之一)`,
|
|
227
|
+
'建议至少三分之一的情节点写「放」半边,避免整章只剩禁令',
|
|
228
|
+
'按人工判断酌情补「放」;本项不阻断。',
|
|
229
|
+
'advisory'
|
|
230
|
+
))
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 章级「禁止提前释放」只写本章特有的三五条;条目过多多半是把卷级常任禁忌逐章复读了。
|
|
236
|
+
const releaseMatch = text.match(/^\s*[-*+]\s*\*{0,2}本章禁止提前释放\*{0,2}\s*[::]\s*(.*)$/m)
|
|
237
|
+
if (releaseMatch) {
|
|
238
|
+
const items = releaseMatch[1].split(/[、;;,,\/]/).map((s) => s.trim()).filter(Boolean)
|
|
239
|
+
if (items.length > 5) {
|
|
240
|
+
checks.push(makeCheck(
|
|
241
|
+
'outline.release-brevity',
|
|
242
|
+
false,
|
|
243
|
+
name,
|
|
244
|
+
`本章禁止提前释放列了 ${items.length} 条`,
|
|
245
|
+
'章级只写本章特有的三五条;卷级常任禁忌写在卷纲「本卷禁碰的终局底牌」,不逐章复读',
|
|
246
|
+
'把与卷纲常任重复的条目删掉,只留本章特有;本项不阻断。',
|
|
247
|
+
'advisory'
|
|
248
|
+
))
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
checks.push(checkSettingRefs(text, name, file, projectRoot))
|
|
253
|
+
|
|
151
254
|
const targetMatch = text.match(/字数目标\s*[::]\s*(?:约\s*)?([\d,,]+)/)
|
|
152
255
|
const target = targetMatch ? Number(targetMatch[1].replace(/[,,]/g, '')) : null
|
|
153
256
|
const caliberOk = new RegExp(`字数口径\\s*[::]\\s*${CALIBER}`).test(text)
|
|
@@ -164,7 +267,8 @@ function verify(file) {
|
|
|
164
267
|
}
|
|
165
268
|
|
|
166
269
|
function report(file, checks) {
|
|
167
|
-
const failures = checks.filter((check) => !check.ok)
|
|
270
|
+
const failures = checks.filter((check) => !check.ok && check.severity === 'blocking')
|
|
271
|
+
const advisories = checks.filter((check) => !check.ok && check.severity === 'advisory')
|
|
168
272
|
return {
|
|
169
273
|
schema_version: 1,
|
|
170
274
|
verifier: 'story-long-write.outline-contract',
|
|
@@ -172,6 +276,7 @@ function report(file, checks) {
|
|
|
172
276
|
ok: failures.length === 0,
|
|
173
277
|
checks,
|
|
174
278
|
failures,
|
|
279
|
+
advisories,
|
|
175
280
|
repair_scope: failures.map((failure) => ({
|
|
176
281
|
id: failure.id,
|
|
177
282
|
file: failure.file,
|
|
@@ -200,13 +305,81 @@ function resolveChapter(project, chapter) {
|
|
|
200
305
|
return { file: path.join(dir, hit) }
|
|
201
306
|
}
|
|
202
307
|
|
|
308
|
+
// --supply:批末验证供给自查已落盘。新书写在卷纲旁的 排纲底稿_{单元ID}.md;老卷纲写在单元卡块内。
|
|
309
|
+
function hasSupplyHeading(text) {
|
|
310
|
+
return text.split(/\r?\n/).some((line) => {
|
|
311
|
+
const heading = line.replace(/\*\*/g, '').match(/^#{1,6}\s+(.+)$/)
|
|
312
|
+
return Boolean(heading) && /^供给自查(?:\s|[((]|$)/.test(heading[1])
|
|
313
|
+
})
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function verifySupply(volumeFile, unitId) {
|
|
317
|
+
const draftFile = path.join(path.dirname(volumeFile), `排纲底稿_${unitId}.md`)
|
|
318
|
+
const draft = readUtf8(draftFile)
|
|
319
|
+
if (draft.ok) {
|
|
320
|
+
const ok = hasSupplyHeading(draft.text)
|
|
321
|
+
return {
|
|
322
|
+
schema_version: 1, verifier: 'story-long-write.outline-supply', file: path.resolve(draftFile), unit: unitId, ok,
|
|
323
|
+
evidence: ok ? '排纲底稿含「供给自查」小节' : `${path.basename(draftFile)} 里没有「供给自查」小节——每批出细纲前须做供给自查(含「无缺口」情形),见 workflow-setup.md「排纲自查与细纲」步骤 3`,
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const read = readUtf8(volumeFile)
|
|
327
|
+
if (!read.ok) {
|
|
328
|
+
return { schema_version: 1, verifier: 'story-long-write.outline-supply', file: path.resolve(volumeFile), unit: unitId, ok: false, evidence: read.error || '卷纲文件为空' }
|
|
329
|
+
}
|
|
330
|
+
// 只检查目标单元内的标题,不把正文提及或代码示例当成小节。
|
|
331
|
+
let fence = null
|
|
332
|
+
const lines = read.text.split(/\r?\n/).map((line) => {
|
|
333
|
+
const marker = line.match(/^\s{0,3}(`{3,}|~{3,})/)
|
|
334
|
+
if (marker) {
|
|
335
|
+
if (!fence) fence = marker[1]
|
|
336
|
+
else if (marker[1][0] === fence[0] && marker[1].length >= fence.length) fence = null
|
|
337
|
+
return ''
|
|
338
|
+
}
|
|
339
|
+
return fence ? '' : line.replace(/\*\*/g, '')
|
|
340
|
+
})
|
|
341
|
+
const headings = []
|
|
342
|
+
for (let index = 0; index < lines.length; index++) {
|
|
343
|
+
const heading = lines[index].match(/^(#{1,6})\s+(.+)$/)
|
|
344
|
+
if (heading) headings.push({ index, level: heading[1].length, title: heading[2] })
|
|
345
|
+
}
|
|
346
|
+
const card = headings.find((heading) => {
|
|
347
|
+
const id = heading.title.match(/剧情单元\s+([A-Za-z][\w-]*)/)
|
|
348
|
+
if (id) return id[1] === unitId
|
|
349
|
+
const end = headings.find((next) => next.index > heading.index)?.index ?? lines.length
|
|
350
|
+
return lines.slice(heading.index + 1, end).some((line) => {
|
|
351
|
+
const field = line.match(/^\s*[-*+]\s*单元ID\s*[::]\s*([A-Za-z][\w-]*)\s*$/)
|
|
352
|
+
return field?.[1] === unitId
|
|
353
|
+
})
|
|
354
|
+
})
|
|
355
|
+
const end = card ? (headings.find((next) => next.index > card.index && next.level <= card.level)?.index ?? lines.length) : 0
|
|
356
|
+
const ok = Boolean(card) && headings.some((heading) => heading.index > card.index && heading.index < end && /^供给自查(?:\s|[((]|$)/.test(heading.title))
|
|
357
|
+
if (!card) {
|
|
358
|
+
return { schema_version: 1, verifier: 'story-long-write.outline-supply', file: path.resolve(volumeFile), unit: unitId, ok: false, evidence: `卷纲中未找到剧情单元 ${unitId}` }
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
schema_version: 1,
|
|
362
|
+
verifier: 'story-long-write.outline-supply',
|
|
363
|
+
file: path.resolve(volumeFile),
|
|
364
|
+
unit: unitId,
|
|
365
|
+
ok,
|
|
366
|
+
evidence: ok ? '单元卡含「供给自查」小节' : `没有 排纲底稿_${unitId}.md,剧情单元 ${unitId} 的卡内也没有「供给自查」小节——每批出细纲前须做供给自查(含「无缺口」情形),见 workflow-setup.md「排纲自查与细纲」步骤 3`,
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
203
370
|
function parseArgs(argv) {
|
|
204
371
|
const files = []
|
|
205
372
|
let project = null
|
|
206
373
|
let chapter = null
|
|
374
|
+
let supply = null
|
|
207
375
|
for (let index = 0; index < argv.length; index++) {
|
|
208
376
|
const arg = argv[index]
|
|
209
377
|
if (arg === '--json') continue
|
|
378
|
+
if (arg === '--supply') {
|
|
379
|
+
if (index + 2 >= argv.length) return null
|
|
380
|
+
supply = { volumeFile: argv[++index], unitId: argv[++index] }
|
|
381
|
+
continue
|
|
382
|
+
}
|
|
210
383
|
if (arg === '--project' || arg === '--chapter') {
|
|
211
384
|
if (index + 1 >= argv.length || argv[index + 1].startsWith('--')) return null
|
|
212
385
|
const value = argv[++index]
|
|
@@ -217,6 +390,10 @@ function parseArgs(argv) {
|
|
|
217
390
|
if (arg.startsWith('--')) return null
|
|
218
391
|
files.push(arg)
|
|
219
392
|
}
|
|
393
|
+
if (supply) {
|
|
394
|
+
if (project || chapter || files.length) return null
|
|
395
|
+
return { supply }
|
|
396
|
+
}
|
|
220
397
|
if (project || chapter) {
|
|
221
398
|
if (!project || !chapter || files.length || !/^\d+$/.test(chapter)) return null
|
|
222
399
|
return { project, chapter }
|
|
@@ -228,9 +405,14 @@ function parseArgs(argv) {
|
|
|
228
405
|
function main(argv) {
|
|
229
406
|
const parsed = parseArgs(argv)
|
|
230
407
|
if (!parsed) {
|
|
231
|
-
process.stderr.write('用法: node scripts/check-outline-contract.js --json <细纲路径...> | --json --project <书目录> --chapter N
|
|
408
|
+
process.stderr.write('用法: node scripts/check-outline-contract.js --json <细纲路径...> | --json --project <书目录> --chapter N | --json --supply <卷纲路径> <单元ID>\n')
|
|
232
409
|
return 2
|
|
233
410
|
}
|
|
411
|
+
if (parsed.supply) {
|
|
412
|
+
const supplyReport = verifySupply(parsed.supply.volumeFile, parsed.supply.unitId)
|
|
413
|
+
process.stdout.write(`${JSON.stringify(supplyReport, null, 2)}\n`)
|
|
414
|
+
return supplyReport.ok ? 0 : 1
|
|
415
|
+
}
|
|
234
416
|
let targets = parsed.files
|
|
235
417
|
if (!targets) {
|
|
236
418
|
const resolved = resolveChapter(parsed.project, parsed.chapter)
|
|
@@ -240,7 +422,7 @@ function main(argv) {
|
|
|
240
422
|
}
|
|
241
423
|
targets = [resolved.file]
|
|
242
424
|
}
|
|
243
|
-
const reports = targets.map((file) => verify(file))
|
|
425
|
+
const reports = targets.map((file) => verify(file, parsed.project || null))
|
|
244
426
|
const ok = reports.every((entry) => entry.ok)
|
|
245
427
|
process.stdout.write(`${JSON.stringify(reports.length === 1 ? reports[0] : reports, null, 2)}\n`)
|
|
246
428
|
return ok ? 0 : 1
|
|
@@ -31,8 +31,9 @@
|
|
|
31
31
|
* 收尾复扫用 `正文/第XXX章_*.md` 这类通配传多章时,多出来的正文不能被当成细纲吞掉
|
|
32
32
|
* ——那会让首个文件比错对象、其余文件根本不检,静默退 0 报「干净」。
|
|
33
33
|
*
|
|
34
|
-
* 退出码:0 =
|
|
35
|
-
*
|
|
34
|
+
* 退出码:0 = 干净或未自动发现细纲而跳过;1 = 有重合待复核;
|
|
35
|
+
* 2 = 参数错误、输入不可读(含已发现的细纲)或非预期异常。
|
|
36
|
+
* 干净时静默;自动发现不到细纲时明确报告跳过,避免与「已检查且干净」混淆。
|
|
36
37
|
*/
|
|
37
38
|
|
|
38
39
|
'use strict'
|
|
@@ -42,19 +43,59 @@ const path = require('path')
|
|
|
42
43
|
const MIN_RUN = 16 // 判定阈值:连续重合 >15 字,即 >=16
|
|
43
44
|
const REPORT_TOP = 8 // 最多列出的片段数
|
|
44
45
|
|
|
45
|
-
function
|
|
46
|
+
function readTextFile(p) {
|
|
46
47
|
try {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return
|
|
48
|
+
const stat = fs.statSync(p)
|
|
49
|
+
if (!stat.isFile()) return { ok: false, reason: '不是普通文件' }
|
|
50
|
+
return { ok: true, text: fs.readFileSync(p, 'utf8').replace(/^/, '') }
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (error && error.code === 'ENOENT') return { ok: false, reason: '不存在' }
|
|
53
|
+
if (error && error.code) return { ok: false, reason: `无法读取(${error.code})` }
|
|
54
|
+
return { ok: false, reason: `无法读取:${error instanceof Error ? error.message : String(error)}` }
|
|
50
55
|
}
|
|
51
56
|
}
|
|
52
57
|
|
|
58
|
+
function reportInputError(kind, file, reason) {
|
|
59
|
+
process.stderr.write(`错误: 无法读取${kind} "${file}":${reason}。\n`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function reportUsageError(message) {
|
|
63
|
+
process.stderr.write(`错误: ${message}。\n`)
|
|
64
|
+
process.stderr.write('用法: node check-outline-copy.js [--outline <细纲路径>] <正文路径...>\n')
|
|
65
|
+
return 2
|
|
66
|
+
}
|
|
67
|
+
|
|
53
68
|
/** 只留汉字——剥掉标点/加粗/【】后比对,防止细纲标注造成假阴性 */
|
|
54
69
|
function hanOnly(s) {
|
|
55
70
|
return s.replace(/[^一-鿿]/g, '')
|
|
56
71
|
}
|
|
57
72
|
|
|
73
|
+
// 写后回填的完成记录不是誊抄来源。标题块包含子标题;字段块止于下一字段或标题。
|
|
74
|
+
function stripCompletionRecord(outline) {
|
|
75
|
+
let headingLevel = 0
|
|
76
|
+
let inField = false
|
|
77
|
+
return outline.split('\n').filter((line) => {
|
|
78
|
+
const heading = line.match(/^ {0,3}(#{1,6})[\t ]+(.*)$/)
|
|
79
|
+
if (headingLevel) {
|
|
80
|
+
if (!heading || heading[1].length > headingLevel) return false
|
|
81
|
+
headingLevel = 0
|
|
82
|
+
}
|
|
83
|
+
if (inField) {
|
|
84
|
+
if (!heading && !/^[-*+][\t ]+/.test(line)) return false
|
|
85
|
+
inField = false
|
|
86
|
+
}
|
|
87
|
+
if (heading && heading[2].includes('实际完成情况')) {
|
|
88
|
+
headingLevel = heading[1].length
|
|
89
|
+
return false
|
|
90
|
+
}
|
|
91
|
+
if (/^[-*+][\t ]+(?:\*\*|__)?实际完成情况[^::]*[::]/.test(line)) {
|
|
92
|
+
inField = true
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
return true
|
|
96
|
+
}).join('\n')
|
|
97
|
+
}
|
|
98
|
+
|
|
58
99
|
/**
|
|
59
100
|
* 抽出细纲「复沓锚句」字段下的原话,一行一条。
|
|
60
101
|
* 只认这一个字段,不扫情节点序列——锚句集中在固定区块,情节点保持只写「要发生什么」。
|
|
@@ -129,7 +170,9 @@ function findOutline(proseFile) {
|
|
|
129
170
|
const fm = file.match(/^细纲_第0*(\d+)章.*\.md$/)
|
|
130
171
|
if (fm && fm[1] === chapter) return path.join(dir, file)
|
|
131
172
|
}
|
|
132
|
-
} catch {
|
|
173
|
+
} catch (error) {
|
|
174
|
+
if (error.code !== 'ENOENT') throw error
|
|
175
|
+
}
|
|
133
176
|
return null
|
|
134
177
|
}
|
|
135
178
|
|
|
@@ -137,34 +180,56 @@ function main() {
|
|
|
137
180
|
const proseFiles = []
|
|
138
181
|
let explicitOutline = null
|
|
139
182
|
const argv = process.argv.slice(2)
|
|
183
|
+
let positionalOnly = false
|
|
140
184
|
for (let k = 0; k < argv.length; k++) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
185
|
+
const arg = argv[k]
|
|
186
|
+
if (!positionalOnly && arg === '--') {
|
|
187
|
+
positionalOnly = true
|
|
188
|
+
} else if (!positionalOnly && arg === '--outline') {
|
|
189
|
+
if (explicitOutline !== null) return reportUsageError('--outline 不能重复指定')
|
|
190
|
+
const value = argv[k + 1]
|
|
191
|
+
if (!value || value.startsWith('--')) return reportUsageError('--outline 缺少路径')
|
|
192
|
+
explicitOutline = value
|
|
193
|
+
k++
|
|
194
|
+
} else if (!positionalOnly && arg.startsWith('--')) {
|
|
195
|
+
return reportUsageError(`未知选项: ${arg}`)
|
|
196
|
+
} else {
|
|
197
|
+
proseFiles.push(arg)
|
|
198
|
+
}
|
|
147
199
|
}
|
|
148
|
-
|
|
200
|
+
if (!proseFiles.length) return reportUsageError('缺少正文路径')
|
|
201
|
+
|
|
202
|
+
// 逐个文件独立判定;输入错误优先于重合发现
|
|
149
203
|
let status = 0
|
|
150
204
|
for (const proseFile of proseFiles) {
|
|
151
|
-
|
|
205
|
+
status = Math.max(status, checkOne(proseFile, explicitOutline))
|
|
152
206
|
}
|
|
153
207
|
return status
|
|
154
208
|
}
|
|
155
209
|
|
|
156
210
|
function checkOne(proseFile, explicitOutline) {
|
|
157
|
-
const
|
|
158
|
-
if (
|
|
211
|
+
const proseResult = readTextFile(proseFile)
|
|
212
|
+
if (!proseResult.ok) {
|
|
213
|
+
reportInputError('正文', proseFile, proseResult.reason)
|
|
214
|
+
return 2
|
|
215
|
+
}
|
|
216
|
+
const prose = proseResult.text
|
|
159
217
|
|
|
160
218
|
const outlineFile = explicitOutline || findOutline(proseFile)
|
|
161
|
-
if (!outlineFile)
|
|
162
|
-
|
|
163
|
-
|
|
219
|
+
if (!outlineFile) {
|
|
220
|
+
process.stdout.write(`细纲照搬检测:跳过 "${proseFile}"(未自动发现细纲)。\n`)
|
|
221
|
+
return 0
|
|
222
|
+
}
|
|
223
|
+
const outlineResult = readTextFile(outlineFile)
|
|
224
|
+
if (!outlineResult.ok) {
|
|
225
|
+
reportInputError(explicitOutline ? '显式细纲' : '自动发现的细纲', outlineFile, outlineResult.reason)
|
|
226
|
+
return 2
|
|
227
|
+
}
|
|
228
|
+
const outline = outlineResult.text
|
|
164
229
|
|
|
165
230
|
// 正文去掉标题行后比对
|
|
166
231
|
const P = hanOnly(prose.replace(/^#.*$/gm, ''))
|
|
167
|
-
const O = hanOnly(outline)
|
|
232
|
+
const O = hanOnly(stripCompletionRecord(outline))
|
|
168
233
|
if (P.length < MIN_RUN || O.length < MIN_RUN) return 0
|
|
169
234
|
|
|
170
235
|
// 复沓锚句列出的原话允许逐字落地,命中后计入豁免、不判誊抄
|
|
@@ -236,6 +301,8 @@ function checkOne(proseFile, explicitOutline) {
|
|
|
236
301
|
|
|
237
302
|
try {
|
|
238
303
|
process.exit(main())
|
|
239
|
-
} catch {
|
|
240
|
-
|
|
304
|
+
} catch (error) {
|
|
305
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
306
|
+
process.stderr.write(`错误: 细纲照搬检测异常:${message}。\n`)
|
|
307
|
+
process.exit(2)
|
|
241
308
|
}
|
|
@@ -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;
|