@oh-story/dsh 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +38 -0
- package/cordis.patch.yml +3 -0
- package/lib/client.js +1201 -0
- package/lib/client.js.map +7 -0
- package/lib/drama/LICENSE.upstream +21 -0
- package/lib/drama/manifest.json +1012 -0
- package/lib/drama/skills/short-drama/SKILL.md +153 -0
- package/lib/drama/skills/short-drama/agents/openai.yaml +4 -0
- package/lib/drama/skills/short-drama/assets/creator-decision.example.jsonl +4 -0
- package/lib/drama/skills/short-drama/assets/production-observation.example.jsonl +2 -0
- package/lib/drama/skills/short-drama/assets/project-template/short-drama.json +36 -0
- package/lib/drama/skills/short-drama/assets/reference-observation.example.jsonl +2 -0
- package/lib/drama/skills/short-drama/references/audience-reveal.md +74 -0
- package/lib/drama/skills/short-drama/references/contract-and-ownership.md +94 -0
- package/lib/drama/skills/short-drama/references/creator-workflow.md +82 -0
- package/lib/drama/skills/short-drama/references/form-cards/Q/347/211/210/350/241/250/350/276/276.md +73 -0
- package/lib/drama/skills/short-drama/references/form-cards//344/272/214/347/273/264/345/212/250/346/200/201/346/274/253.md +74 -0
- package/lib/drama/skills/short-drama/references/form-cards//345/233/275/346/274/253/344/272/214/346/254/241/345/205/203.md +79 -0
- package/lib/drama/skills/short-drama/references/form-cards//345/256/236/346/213/215.md +75 -0
- package/lib/drama/skills/short-drama/references/form-cards//346/260/264/345/242/250/347/254/224/350/247/246.md +85 -0
- package/lib/drama/skills/short-drama/references/form-cards//351/243/216/346/240/274/345/214/226/344/270/211/347/273/264.md +72 -0
- package/lib/drama/skills/short-drama/references/knowhow-index.md +80 -0
- package/lib/drama/skills/short-drama/references/lifecycle-commands.md +130 -0
- package/lib/drama/skills/short-drama/references/look-development.md +52 -0
- package/lib/drama/skills/short-drama/references/pickup-and-alternate.md +58 -0
- package/lib/drama/skills/short-drama/references/production-form-profiles.md +126 -0
- package/lib/drama/skills/short-drama/references/reference-roles.md +142 -0
- package/lib/drama/skills/short-drama/references/runtime-preflight.md +27 -0
- package/lib/drama/skills/short-drama/scripts/project_tool.py +1963 -0
- package/lib/drama/skills/short-drama/scripts/selftest.py +287 -0
- package/lib/drama/skills/short-drama-assets/SKILL.md +187 -0
- package/lib/drama/skills/short-drama-assets/agents/openai.yaml +4 -0
- package/lib/drama/skills/short-drama-assets/assets/character-look.example.jsonl +3 -0
- package/lib/drama/skills/short-drama-assets/assets/continuity.example.jsonl +4 -0
- package/lib/drama/skills/short-drama-assets/assets/decisions.example.jsonl +5 -0
- package/lib/drama/skills/short-drama-assets/assets/location-view.example.jsonl +3 -0
- package/lib/drama/skills/short-drama-assets/assets/occurrences.example.jsonl +5 -0
- package/lib/drama/skills/short-drama-assets/assets/prop-state.example.jsonl +3 -0
- package/lib/drama/skills/short-drama-assets/examples/minimal/characters.jsonl +2 -0
- package/lib/drama/skills/short-drama-assets/examples/minimal/looks.jsonl +2 -0
- package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +89 -0
- package/lib/drama/skills/short-drama-assets/references/character-and-look.md +164 -0
- package/lib/drama/skills/short-drama-assets/references/continuity-delta.md +123 -0
- package/lib/drama/skills/short-drama-assets/references/identity-vs-variant.md +146 -0
- package/lib/drama/skills/short-drama-assets/references/location-and-view.md +84 -0
- package/lib/drama/skills/short-drama-assets/references/occurrence-extraction.md +111 -0
- package/lib/drama/skills/short-drama-assets/references/prop-and-state.md +107 -0
- package/lib/drama/skills/short-drama-assets/references/stage-contract.md +136 -0
- package/lib/drama/skills/short-drama-assets/references/voice-direction.md +171 -0
- package/lib/drama/skills/short-drama-assets/scripts/asset_check.py +267 -0
- package/lib/drama/skills/short-drama-assets/scripts/selftest.py +89 -0
- package/lib/drama/skills/short-drama-develop/SKILL.md +157 -0
- package/lib/drama/skills/short-drama-develop/agents/openai.yaml +4 -0
- package/lib/drama/skills/short-drama-develop/assets/adaptation-map.example.jsonl +1 -0
- package/lib/drama/skills/short-drama-develop/assets/creative-brief.md +120 -0
- package/lib/drama/skills/short-drama-develop/assets/episode-map.jsonl +1 -0
- package/lib/drama/skills/short-drama-develop/assets/story-engine.md +181 -0
- package/lib/drama/skills/short-drama-develop/references/adaptation-craft.md +219 -0
- package/lib/drama/skills/short-drama-develop/references/creative-reference-intake.md +57 -0
- package/lib/drama/skills/short-drama-develop/references/director-brief-craft.md +113 -0
- package/lib/drama/skills/short-drama-develop/references/episode-design.md +509 -0
- package/lib/drama/skills/short-drama-develop/references/genre-and-hook-playbook.md +240 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards//344/272/262/345/255/220/351/232/220/347/247/230.md +112 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards//344/273/231/344/276/240/344/277/256/347/234/237.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards//345/212/250/344/275/234/344/273/273/345/212/241.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards//345/217/244/350/243/205/346/235/203/350/260/213.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards//345/244/215/344/273/207/346/211/223/350/204/270.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards//345/256/266/345/272/255/345/205/263/347/263/273.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards//346/202/254/347/226/221/350/247/204/345/210/231.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards//347/224/237/346/264/273/346/265/201.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards//347/240/264/351/225/234/351/207/215/345/234/206.md +109 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards//350/201/214/345/234/272/345/226/234/345/211/247.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards//350/261/252/351/227/250/345/251/232/346/201/213.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards//350/272/253/344/273/275/351/224/231/344/275/215.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards.md +92 -0
- package/lib/drama/skills/short-drama-develop/references/mechanism-loop.md +282 -0
- package/lib/drama/skills/short-drama-develop/references/multi-episode-intake.md +128 -0
- package/lib/drama/skills/short-drama-develop/references/premise-devices.md +211 -0
- package/lib/drama/skills/short-drama-develop/references/reveal-reversal-payoff.md +99 -0
- package/lib/drama/skills/short-drama-develop/references/serial-character-and-memory.md +293 -0
- package/lib/drama/skills/short-drama-develop/references/stage-contract.md +88 -0
- package/lib/drama/skills/short-drama-develop/references/story-craft.md +359 -0
- package/lib/drama/skills/short-drama-develop/scripts/episode_intake.py +699 -0
- package/lib/drama/skills/short-drama-develop/scripts/selftest.py +47 -0
- package/lib/drama/skills/short-drama-image-prompts/SKILL.md +181 -0
- package/lib/drama/skills/short-drama-image-prompts/agents/openai.yaml +4 -0
- package/lib/drama/skills/short-drama-image-prompts/assets/image-prompt-spec.jsonl.md +187 -0
- package/lib/drama/skills/short-drama-image-prompts/assets/image-prompts.md +37 -0
- package/lib/drama/skills/short-drama-image-prompts/assets/lookdev-frame-spec.jsonl.md +128 -0
- package/lib/drama/skills/short-drama-image-prompts/assets/lookdev-prompts.md +31 -0
- package/lib/drama/skills/short-drama-image-prompts/examples/minimal-image-prompt-specs.jsonl +2 -0
- package/lib/drama/skills/short-drama-image-prompts/references/character-and-look.md +154 -0
- package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +231 -0
- package/lib/drama/skills/short-drama-image-prompts/references/edit-and-revision.md +122 -0
- package/lib/drama/skills/short-drama-image-prompts/references/location-plate.md +152 -0
- package/lib/drama/skills/short-drama-image-prompts/references/look-and-state-variant.md +96 -0
- package/lib/drama/skills/short-drama-image-prompts/references/lookdev-frame.md +38 -0
- package/lib/drama/skills/short-drama-image-prompts/references/production-sheet-recipes.md +177 -0
- package/lib/drama/skills/short-drama-image-prompts/references/prop-plate.md +140 -0
- package/lib/drama/skills/short-drama-image-prompts/references/review-and-fixtures.md +92 -0
- package/lib/drama/skills/short-drama-image-prompts/references/stage-contract.md +89 -0
- package/lib/drama/skills/short-drama-image-prompts/scripts/image_prompt_check.py +443 -0
- package/lib/drama/skills/short-drama-image-prompts/scripts/selftest.py +93 -0
- package/lib/drama/skills/short-drama-novel-analyze/SKILL.md +245 -0
- package/lib/drama/skills/short-drama-novel-analyze/agents/openai.yaml +4 -0
- package/lib/drama/skills/short-drama-novel-analyze/assets/episode-candidate.example.jsonl +3 -0
- package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-triage.md +116 -0
- package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-value.md +124 -0
- package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +155 -0
- package/lib/drama/skills/short-drama-novel-analyze/references/chapter-extraction.md +141 -0
- package/lib/drama/skills/short-drama-novel-analyze/references/stage-contract.md +67 -0
- package/lib/drama/skills/short-drama-novel-analyze/scripts/novel_index.py +651 -0
- package/lib/drama/skills/short-drama-novel-analyze/scripts/selftest.py +51 -0
- package/lib/drama/skills/short-drama-produce/SKILL.md +103 -0
- package/lib/drama/skills/short-drama-produce/agents/openai.yaml +4 -0
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +140 -0
- package/lib/drama/skills/short-drama-produce/references/providers/gpt-image-2.md +24 -0
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-music.md +25 -0
- package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +40 -0
- package/lib/drama/skills/short-drama-produce/scripts/fixture_adapter.py +60 -0
- package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +1635 -0
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +964 -0
- package/lib/drama/skills/short-drama-produce/scripts/selftest.py +134 -0
- package/lib/drama/skills/short-drama-review/SKILL.md +178 -0
- package/lib/drama/skills/short-drama-review/agents/openai.yaml +4 -0
- package/lib/drama/skills/short-drama-review/assets/finding-template.jsonl +2 -0
- package/lib/drama/skills/short-drama-review/assets/supersession-decision.example.json +42 -0
- package/lib/drama/skills/short-drama-review/assets/verdict-template.json +32 -0
- package/lib/drama/skills/short-drama-review/examples/minimal-findings.jsonl +2 -0
- package/lib/drama/skills/short-drama-review/examples/minimal-verdict.json +34 -0
- package/lib/drama/skills/short-drama-review/references/anti-template-repair.md +114 -0
- package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +270 -0
- package/lib/drama/skills/short-drama-review/references/project-calibration.md +85 -0
- package/lib/drama/skills/short-drama-review/references/review-method.md +82 -0
- package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +115 -0
- package/lib/drama/skills/short-drama-review/references/rubric-source-analysis.md +108 -0
- package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +164 -0
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +166 -0
- package/lib/drama/skills/short-drama-review/references/stage-contract.md +87 -0
- package/lib/drama/skills/short-drama-review/scripts/review_check.py +325 -0
- package/lib/drama/skills/short-drama-review/scripts/selftest.py +87 -0
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +264 -0
- package/lib/drama/skills/short-drama-storyboard/agents/openai.yaml +4 -0
- package/lib/drama/skills/short-drama-storyboard/assets/coverage-audition.example.jsonl +2 -0
- package/lib/drama/skills/short-drama-storyboard/assets/coverage-template.json +49 -0
- package/lib/drama/skills/short-drama-storyboard/assets/keyframe-prompts.md +32 -0
- package/lib/drama/skills/short-drama-storyboard/assets/keyframe-template.jsonl +2 -0
- package/lib/drama/skills/short-drama-storyboard/assets/revision-lineage.fragment.json +12 -0
- package/lib/drama/skills/short-drama-storyboard/assets/scene-visual-plan.example.jsonl +2 -0
- package/lib/drama/skills/short-drama-storyboard/assets/shot-template.jsonl +2 -0
- package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +241 -0
- package/lib/drama/skills/short-drama-storyboard/references/coverage-audition.md +39 -0
- package/lib/drama/skills/short-drama-storyboard/references/keyframe-craft.md +169 -0
- package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +264 -0
- package/lib/drama/skills/short-drama-storyboard/references/review-and-fixtures.md +70 -0
- package/lib/drama/skills/short-drama-storyboard/references/scene-visual-plan.md +39 -0
- package/lib/drama/skills/short-drama-storyboard/references/screenplay-to-keyframe-example.md +157 -0
- package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +192 -0
- package/lib/drama/skills/short-drama-storyboard/references/shot-revision-identity.md +29 -0
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +110 -0
- package/lib/drama/skills/short-drama-storyboard/scripts/selftest.py +191 -0
- package/lib/drama/skills/short-drama-storyboard/scripts/storyboard_check.py +824 -0
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +245 -0
- package/lib/drama/skills/short-drama-video-prompts/agents/openai.yaml +4 -0
- package/lib/drama/skills/short-drama-video-prompts/assets/coverage-scope.fragment.json +21 -0
- package/lib/drama/skills/short-drama-video-prompts/assets/delivery-container.jsonl.md +101 -0
- package/lib/drama/skills/short-drama-video-prompts/assets/motion-spec.jsonl.md +212 -0
- package/lib/drama/skills/short-drama-video-prompts/assets/motion-terminal.example.jsonl +2 -0
- package/lib/drama/skills/short-drama-video-prompts/assets/music-spec.jsonl.md +31 -0
- package/lib/drama/skills/short-drama-video-prompts/assets/performance.fragment.json +38 -0
- package/lib/drama/skills/short-drama-video-prompts/assets/video-prompts.md +46 -0
- package/lib/drama/skills/short-drama-video-prompts/examples/minimal-music-specs.jsonl +2 -0
- package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +240 -0
- package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +255 -0
- package/lib/drama/skills/short-drama-video-prompts/references/generability.md +93 -0
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +304 -0
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +191 -0
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +312 -0
- package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +168 -0
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +110 -0
- package/lib/drama/skills/short-drama-video-prompts/scripts/container_check.py +329 -0
- package/lib/drama/skills/short-drama-video-prompts/scripts/motion_timing_check.py +450 -0
- package/lib/drama/skills/short-drama-video-prompts/scripts/music_spec_check.py +327 -0
- package/lib/drama/skills/short-drama-video-prompts/scripts/selftest.py +82 -0
- package/lib/drama/skills/short-drama-write/SKILL.md +284 -0
- package/lib/drama/skills/short-drama-write/agents/openai.yaml +4 -0
- package/lib/drama/skills/short-drama-write/assets/beats.jsonl +2 -0
- package/lib/drama/skills/short-drama-write/assets/episode-card-standalone.json +120 -0
- package/lib/drama/skills/short-drama-write/assets/episode-card.json +55 -0
- package/lib/drama/skills/short-drama-write/assets/screenplay.md +36 -0
- package/lib/drama/skills/short-drama-write/assets/voice-record-sheet.jsonl.md +89 -0
- package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +519 -0
- package/lib/drama/skills/short-drama-write/references/production-format-dialect.md +273 -0
- package/lib/drama/skills/short-drama-write/references/scene-handoff-capsule.md +51 -0
- package/lib/drama/skills/short-drama-write/references/scene-sound-dramaturgy.md +43 -0
- package/lib/drama/skills/short-drama-write/references/screenplay-format.md +260 -0
- package/lib/drama/skills/short-drama-write/references/script-craft.md +558 -0
- package/lib/drama/skills/short-drama-write/references/stage-contract.md +94 -0
- package/lib/drama/skills/short-drama-write/references/substitutable-realization.md +107 -0
- package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +309 -0
- package/lib/drama/skills/short-drama-write/scripts/screenplay_index.py +1091 -0
- package/lib/drama/skills/short-drama-write/scripts/selftest.py +229 -0
- package/lib/drama/skills/short-drama-write/scripts/voice_sheet_check.py +416 -0
- package/lib/index.js +756 -0
- package/lib/index.js.map +7 -0
- package/lib/oh-story/LICENSE.upstream +21 -0
- package/lib/oh-story/manifest.json +1351 -0
- package/lib/oh-story/roles/chapter-extractor.md +314 -0
- package/lib/oh-story/roles/character-designer.md +169 -0
- package/lib/oh-story/roles/consistency-checker.md +190 -0
- package/lib/oh-story/roles/narrative-writer.md +294 -0
- package/lib/oh-story/roles/story-architect.md +195 -0
- package/lib/oh-story/roles/story-explorer.md +364 -0
- package/lib/oh-story/roles/story-researcher.md +304 -0
- package/lib/oh-story/skills/browser-cdp/SKILL.md +175 -0
- package/lib/oh-story/skills/story/SKILL.md +116 -0
- package/lib/oh-story/skills/story/VERSION +1 -0
- package/lib/oh-story/skills/story-cover/SKILL.md +327 -0
- package/lib/oh-story/skills/story-cover/references/cover-styles.md +210 -0
- package/lib/oh-story/skills/story-deslop/SKILL.md +445 -0
- package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +582 -0
- package/lib/oh-story/skills/story-deslop/references/banned-words.md +147 -0
- package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +1371 -0
- package/lib/oh-story/skills/story-deslop/scripts/check-degeneration.js +330 -0
- package/lib/oh-story/skills/story-deslop/scripts/check-outline-copy.js +241 -0
- package/lib/oh-story/skills/story-deslop/scripts/normalize-punctuation.js +410 -0
- package/lib/oh-story/skills/story-import/SKILL.md +634 -0
- package/lib/oh-story/skills/story-import/references/character-state-reverse.md +82 -0
- package/lib/oh-story/skills/story-import/references/format-and-structure.md +169 -0
- package/lib/oh-story/skills/story-import/references/length-routing.md +134 -0
- package/lib/oh-story/skills/story-import/references/state-tracking.md +108 -0
- package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +442 -0
- package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +175 -0
- package/lib/oh-story/skills/story-import/references/tracking-transaction.md +161 -0
- package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +1139 -0
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +329 -0
- package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +270 -0
- package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +530 -0
- package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +559 -0
- package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +80 -0
- package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +149 -0
- package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +133 -0
- package/lib/oh-story/skills/story-long-scan/SKILL.md +342 -0
- package/lib/oh-story/skills/story-long-scan/references/genre-trends.md +197 -0
- package/lib/oh-story/skills/story-long-scan/references/publishing-guide.md +114 -0
- package/lib/oh-story/skills/story-long-scan/references/reader-profiling.md +105 -0
- package/lib/oh-story/skills/story-long-scan/references/scan-output-format.md +207 -0
- package/lib/oh-story/skills/story-long-scan/references/topic-decision.md +78 -0
- package/lib/oh-story/skills/story-long-write/SKILL.md +327 -0
- package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +582 -0
- package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +302 -0
- package/lib/oh-story/skills/story-long-write/references/banned-words.md +147 -0
- package/lib/oh-story/skills/story-long-write/references/character-basics.md +445 -0
- package/lib/oh-story/skills/story-long-write/references/character-design-methods.md +428 -0
- package/lib/oh-story/skills/story-long-write/references/character-relations.md +385 -0
- package/lib/oh-story/skills/story-long-write/references/commercial-core-methods.md +502 -0
- package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +62 -0
- package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +282 -0
- package/lib/oh-story/skills/story-long-write/references/emotional-arc-design.md +426 -0
- package/lib/oh-story/skills/story-long-write/references/emotional-methods.md +209 -0
- package/lib/oh-story/skills/story-long-write/references/female-audience-writing.md +188 -0
- package/lib/oh-story/skills/story-long-write/references/format-and-structure.md +169 -0
- package/lib/oh-story/skills/story-long-write/references/genre-catalog.md +480 -0
- package/lib/oh-story/skills/story-long-write/references/genre-core-mechanics.md +415 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//344/270/234/346/226/271/344/273/231/344/276/240.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//344/274/240/347/273/237/347/216/204/345/271/273.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//345/216/206/345/217/262/345/217/244/344/273/243.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//345/216/206/345/217/262/350/204/221/346/264/236.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//345/217/214/347/224/267/344/270/273.md +53 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//345/217/244/350/250/200/350/204/221/346/264/236.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//345/217/244/351/243/216/344/270/226/346/203/205.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//345/245/263/351/242/221/346/202/254/347/226/221.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//345/245/263/351/242/221/347/247/215/347/224/260.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//345/256/253/346/226/227/345/256/205/346/226/227.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//345/271/264/344/273/243.md +53 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//345/277/253/347/251/277.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//346/202/254/347/226/221/347/201/265/345/274/202.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//346/202/254/347/226/221/350/204/221/346/264/236.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//346/210/230/347/245/236/350/265/230/345/251/277.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//346/212/227/346/210/230/350/260/215/346/210/230.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//346/230/237/345/205/211/347/222/200/347/222/250.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//346/260/221/345/233/275/350/250/200/346/203/205.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//346/270/270/346/210/217/344/275/223/350/202/262.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//347/216/204/345/271/273/350/204/221/346/264/236.md +53 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//347/216/204/345/271/273/350/250/200/346/203/205.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//347/216/260/350/250/200/350/204/221/346/264/236.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//347/247/221/345/271/273/346/234/253/344/270/226.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//350/201/214/345/234/272/345/251/232/346/201/213.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//350/245/277/346/226/271/345/245/207/345/271/273.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//350/261/252/351/227/250/346/200/273/350/243/201.md +53 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//351/203/275/345/270/202/344/277/256/347/234/237.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//351/203/275/345/270/202/346/227/245/345/270/270.md +53 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//351/203/275/345/270/202/347/247/215/347/224/260.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//351/203/275/345/270/202/350/204/221/346/264/236.md +53 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//351/203/275/345/270/202/351/253/230/346/255/246.md +53 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//351/235/222/346/230/245/347/224/234/345/256/240.md +51 -0
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +91 -0
- package/lib/oh-story/skills/story-long-write/references/genre-readers.md +235 -0
- package/lib/oh-story/skills/story-long-write/references/genre-writing-formulas.md +444 -0
- package/lib/oh-story/skills/story-long-write/references/hooks-chapter.md +225 -0
- package/lib/oh-story/skills/story-long-write/references/hooks-paragraph.md +146 -0
- package/lib/oh-story/skills/story-long-write/references/hooks-suspense.md +352 -0
- package/lib/oh-story/skills/story-long-write/references/opening-design.md +349 -0
- package/lib/oh-story/skills/story-long-write/references/outline-conflict.md +457 -0
- package/lib/oh-story/skills/story-long-write/references/outline-methods.md +287 -0
- package/lib/oh-story/skills/story-long-write/references/outline-rhythm.md +389 -0
- package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +326 -0
- package/lib/oh-story/skills/story-long-write/references/plot-core-methods.md +522 -0
- package/lib/oh-story/skills/story-long-write/references/plot-emotion-system.md +354 -0
- package/lib/oh-story/skills/story-long-write/references/plot-frameworks.md +711 -0
- package/lib/oh-story/skills/story-long-write/references/plot-special-topics.md +622 -0
- package/lib/oh-story/skills/story-long-write/references/quality-checklist.md +184 -0
- package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +109 -0
- package/lib/oh-story/skills/story-long-write/references/reversal-toolkit.md +349 -0
- package/lib/oh-story/skills/story-long-write/references/state-tracking.md +108 -0
- package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +409 -0
- package/lib/oh-story/skills/story-long-write/references/style-craft.md +347 -0
- package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +510 -0
- package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +161 -0
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +140 -0
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +171 -0
- package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +99 -0
- package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +284 -0
- package/lib/oh-story/skills/story-long-write/references/writing-craft.md +421 -0
- package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +1371 -0
- package/lib/oh-story/skills/story-long-write/scripts/check-degeneration.js +330 -0
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +241 -0
- package/lib/oh-story/skills/story-long-write/scripts/normalize-punctuation.js +410 -0
- package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +1139 -0
- package/lib/oh-story/skills/story-review/SKILL.md +483 -0
- package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +582 -0
- package/lib/oh-story/skills/story-review/references/banned-words.md +147 -0
- package/lib/oh-story/skills/story-review/references/character-relations.md +385 -0
- package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +282 -0
- package/lib/oh-story/skills/story-review/references/plot-core-methods.md +522 -0
- package/lib/oh-story/skills/story-review/references/quality-checklist.md +250 -0
- package/lib/oh-story/skills/story-review/references/quality-rubric.md +55 -0
- package/lib/oh-story/skills/story-review/references/rubrics/fanqie.md +18 -0
- package/lib/oh-story/skills/story-review/references/rubrics/qidian.md +19 -0
- package/lib/oh-story/skills/story-review/references/rubrics/zhihu.md +20 -0
- package/lib/oh-story/skills/story-review/references/tracking-transaction.md +161 -0
- package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +1371 -0
- package/lib/oh-story/skills/story-review/scripts/check-degeneration.js +330 -0
- package/lib/oh-story/skills/story-review/scripts/normalize-punctuation.js +410 -0
- package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +1139 -0
- package/lib/oh-story/skills/story-setup/SKILL.md +495 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +582 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +147 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/character-basics.md +445 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/character-design-methods.md +428 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/character-relations.md +385 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +282 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/emotional-arc-design.md +426 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/emotional-methods.md +179 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +169 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-catalog.md +480 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-core-mechanics.md +415 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//344/270/234/346/226/271/344/273/231/344/276/240.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//344/274/240/347/273/237/347/216/204/345/271/273.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//345/216/206/345/217/262/345/217/244/344/273/243.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//345/216/206/345/217/262/350/204/221/346/264/236.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//345/217/214/347/224/267/344/270/273.md +53 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//345/217/244/350/250/200/350/204/221/346/264/236.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//345/217/244/351/243/216/344/270/226/346/203/205.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//345/245/263/351/242/221/346/202/254/347/226/221.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//345/245/263/351/242/221/347/247/215/347/224/260.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//345/256/253/346/226/227/345/256/205/346/226/227.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//345/271/264/344/273/243.md +53 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//345/277/253/347/251/277.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//346/202/254/347/226/221/347/201/265/345/274/202.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//346/202/254/347/226/221/350/204/221/346/264/236.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//346/210/230/347/245/236/350/265/230/345/251/277.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//346/212/227/346/210/230/350/260/215/346/210/230.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//346/230/237/345/205/211/347/222/200/347/222/250.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//346/260/221/345/233/275/350/250/200/346/203/205.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//346/270/270/346/210/217/344/275/223/350/202/262.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//347/216/204/345/271/273/350/204/221/346/264/236.md +53 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//347/216/204/345/271/273/350/250/200/346/203/205.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//347/216/260/350/250/200/350/204/221/346/264/236.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//347/247/221/345/271/273/346/234/253/344/270/226.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//350/201/214/345/234/272/345/251/232/346/201/213.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//350/245/277/346/226/271/345/245/207/345/271/273.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//350/261/252/351/227/250/346/200/273/350/243/201.md +53 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//351/203/275/345/270/202/344/277/256/347/234/237.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//351/203/275/345/270/202/346/227/245/345/270/270.md +53 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//351/203/275/345/270/202/347/247/215/347/224/260.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//351/203/275/345/270/202/350/204/221/346/264/236.md +53 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//351/203/275/345/270/202/351/253/230/346/255/246.md +53 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//351/235/222/346/230/245/347/224/234/345/256/240.md +51 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +91 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-readers.md +235 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-writing-formulas.md +444 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/hooks-chapter.md +225 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/hooks-suspense.md +352 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +349 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/outline-conflict.md +457 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/outline-methods.md +287 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/outline-rhythm.md +389 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/plot-core-methods.md +522 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/quality-checklist.md +247 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/reversal-toolkit.md +349 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +409 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +510 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +421 -0
- package/lib/oh-story/skills/story-short-analyze/SKILL.md +278 -0
- package/lib/oh-story/skills/story-short-analyze/references/anti-ai-writing.md +582 -0
- package/lib/oh-story/skills/story-short-analyze/references/banned-words.md +147 -0
- package/lib/oh-story/skills/story-short-analyze/references/character-basics.md +453 -0
- package/lib/oh-story/skills/story-short-analyze/references/character-design-methods.md +438 -0
- package/lib/oh-story/skills/story-short-analyze/references/character-relations.md +395 -0
- package/lib/oh-story/skills/story-short-analyze/references/deconstruction-examples.md +79 -0
- package/lib/oh-story/skills/story-short-analyze/references/genre-catalog.md +490 -0
- package/lib/oh-story/skills/story-short-analyze/references/genre-core-mechanics.md +425 -0
- package/lib/oh-story/skills/story-short-analyze/references/genre-readers.md +245 -0
- package/lib/oh-story/skills/story-short-analyze/references/genre-writing-formulas.md +453 -0
- package/lib/oh-story/skills/story-short-analyze/references/genre-writing-techniques.md +475 -0
- package/lib/oh-story/skills/story-short-analyze/references/hooks-chapter.md +225 -0
- package/lib/oh-story/skills/story-short-analyze/references/hooks-paragraph.md +146 -0
- package/lib/oh-story/skills/story-short-analyze/references/hooks-suspense.md +352 -0
- package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +685 -0
- package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +165 -0
- package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +561 -0
- package/lib/oh-story/skills/story-short-analyze/references/quality-checklist.md +242 -0
- package/lib/oh-story/skills/story-short-analyze/references/real-market-data.md +90 -0
- package/lib/oh-story/skills/story-short-analyze/references/zhihu-style.md +54 -0
- package/lib/oh-story/skills/story-short-scan/SKILL.md +212 -0
- package/lib/oh-story/skills/story-short-scan/references/real-market-data.md +90 -0
- package/lib/oh-story/skills/story-short-write/SKILL.md +433 -0
- package/lib/oh-story/skills/story-short-write/references/banned-words.md +147 -0
- package/lib/oh-story/skills/story-short-write/references/cross-book-recall.md +62 -0
- package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +282 -0
- package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +179 -0
- package/lib/oh-story/skills/story-short-write/references/genre-styles//344/270/226/346/203/205/346/211/223/350/204/270.md +159 -0
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +156 -0
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/244/215/344/273/207/346/211/223/350/204/270.md +163 -0
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/256/205/346/226/227/345/256/253/346/226/227.md +167 -0
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/200/273/350/243/201/350/261/252/351/227/250.md +178 -0
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +166 -0
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +159 -0
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/262/231/351/233/225/350/204/221/346/264/236.md +151 -0
- package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +153 -0
- 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 +210 -0
- package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +444 -0
- package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +488 -0
- package/lib/oh-story/skills/story-short-write/references/hooks-chapter.md +225 -0
- package/lib/oh-story/skills/story-short-write/references/hooks-paragraph.md +146 -0
- package/lib/oh-story/skills/story-short-write/references/hooks-suspense.md +352 -0
- package/lib/oh-story/skills/story-short-write/references/output-contract.md +165 -0
- package/lib/oh-story/skills/story-short-write/references/quality-checklist.md +222 -0
- package/lib/oh-story/skills/story-short-write/references/reversal-toolkit.md +349 -0
- package/lib/oh-story/skills/story-short-write/references/short-craft.md +185 -0
- package/lib/oh-story/skills/story-short-write/references/short-deslop.md +133 -0
- package/lib/oh-story/skills/story-short-write/references/short-format.md +167 -0
- package/lib/oh-story/skills/story-short-write/references/submission-craft.md +35 -0
- package/lib/oh-story/skills/story-short-write/references/villain-and-reveal.md +158 -0
- package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +153 -0
- package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +1371 -0
- package/lib/oh-story/skills/story-short-write/scripts/check-degeneration.js +330 -0
- package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +241 -0
- package/lib/oh-story/skills/story-short-write/scripts/normalize-punctuation.js +410 -0
- package/lib/types/client/file-activity.d.ts +30 -0
- package/lib/types/client/file-activity.d.ts.map +1 -0
- package/lib/types/client/file-tree.d.ts +20 -0
- package/lib/types/client/file-tree.d.ts.map +1 -0
- package/lib/types/client/index.d.ts +12 -0
- package/lib/types/client/index.d.ts.map +1 -0
- package/lib/types/client/jsonl-preview.d.ts +15 -0
- package/lib/types/client/jsonl-preview.d.ts.map +1 -0
- package/lib/types/client/markdown-preview.d.ts +38 -0
- package/lib/types/client/markdown-preview.d.ts.map +1 -0
- package/lib/types/index.d.ts +25 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/native-hooks.d.ts +16 -0
- package/lib/types/native-hooks.d.ts.map +1 -0
- package/lib/types/role-provider.d.ts +6 -0
- package/lib/types/role-provider.d.ts.map +1 -0
- package/lib/types/role-tool.d.ts +10 -0
- package/lib/types/role-tool.d.ts.map +1 -0
- package/lib/types/skill-provider.d.ts +15 -0
- package/lib/types/skill-provider.d.ts.map +1 -0
- package/lib/types/workspace-request-trust.d.ts +13 -0
- package/lib/types/workspace-request-trust.d.ts.map +1 -0
- package/lib/types/workspace-route.d.ts +10 -0
- package/lib/types/workspace-route.d.ts.map +1 -0
- package/lib/types/workspace-security.d.ts +9 -0
- package/lib/types/workspace-security.d.ts.map +1 -0
- package/package.json +107 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Offline self-test for screenplay indexing and voice-sheet projection."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
import tempfile
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from duration_estimate import StaleIndex, estimate, index_blocks
|
|
13
|
+
from screenplay_index import build_index
|
|
14
|
+
from voice_sheet_check import check
|
|
15
|
+
|
|
16
|
+
MINIMUM_PYTHON = (3, 9)
|
|
17
|
+
if sys.version_info < MINIMUM_PYTHON:
|
|
18
|
+
raise SystemExit("selftest.py requires Python 3.9 or newer")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def require(condition: bool, message: str) -> None:
|
|
22
|
+
if not condition:
|
|
23
|
+
raise AssertionError(message)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def codes(result: dict[str, object]) -> set[str]:
|
|
27
|
+
findings = result["findings"]
|
|
28
|
+
assert isinstance(findings, list)
|
|
29
|
+
return {finding["code"] for finding in findings}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def measure_text(body: str, project: dict[str, object] | None = None) -> dict[str, object]:
|
|
33
|
+
"""Index a screenplay the documented way, then estimate from that index.
|
|
34
|
+
|
|
35
|
+
The estimate has no reader of its own. Going through ``build_index`` here is
|
|
36
|
+
the point of the test, not overhead: it is what stops this script and the
|
|
37
|
+
index from holding two opinions about the same line.
|
|
38
|
+
"""
|
|
39
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
40
|
+
root = Path(directory)
|
|
41
|
+
screenplay = root / "screenplay.md"
|
|
42
|
+
index_path = root / "screenplay-index.jsonl"
|
|
43
|
+
screenplay.write_text(
|
|
44
|
+
"# EP001\n\n## EP001-SC001 内 · 房间 · 日\n\n" + body, encoding="utf-8"
|
|
45
|
+
)
|
|
46
|
+
build_index(
|
|
47
|
+
screenplay,
|
|
48
|
+
index_path,
|
|
49
|
+
source_ref="剧集/EP001/screenplay.md",
|
|
50
|
+
speakers=["甲"],
|
|
51
|
+
)
|
|
52
|
+
records = [
|
|
53
|
+
json.loads(line)
|
|
54
|
+
for line in index_path.read_text(encoding="utf-8").splitlines()
|
|
55
|
+
if line.strip()
|
|
56
|
+
]
|
|
57
|
+
source = screenplay.read_bytes()
|
|
58
|
+
blocks, review = index_blocks(records, source)
|
|
59
|
+
return estimate(source, blocks, project, review)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_duration_reports_counts_without_rates() -> None:
|
|
63
|
+
"""No declared rates must yield no seconds -- never a default guess."""
|
|
64
|
+
report = measure_text("甲:一二三四五。\n\n他把门关上。\n")
|
|
65
|
+
require(report["seconds"] is None, "seconds must stay unknown without rates")
|
|
66
|
+
require(report["counts"]["dialogue_characters"] == 6, "spoken characters counted")
|
|
67
|
+
require(report["counts"]["action_paragraphs"] == 1, "action paragraph counted")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_duration_uses_the_projects_own_rates() -> None:
|
|
71
|
+
project = {
|
|
72
|
+
"format": {
|
|
73
|
+
"target_seconds_per_episode": 10,
|
|
74
|
+
"pacing": {
|
|
75
|
+
"spoken_characters_per_second": 3.0,
|
|
76
|
+
"seconds_per_action_paragraph": 2.0,
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
report = measure_text("甲:一二三四五六\n\n他把门关上。\n", project)
|
|
81
|
+
# Punctuation counts as spoken time because it stands for the pause it
|
|
82
|
+
# creates; here the line is six bare characters: 6 / 3.0 + 1 * 2.0 == 4.0.
|
|
83
|
+
require(report["seconds"] == 4.0, f"expected 4.0s, got {report['seconds']}")
|
|
84
|
+
require(report["delta_seconds"] == -6.0, "delta is reported against the target")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_production_tags_are_not_performed_time() -> None:
|
|
88
|
+
report = measure_text("[画面文字] 账号后台:粉丝 2\n\n他把门关上。\n")
|
|
89
|
+
require(report["counts"]["production_tag_lines"] == 1, "tag counted separately")
|
|
90
|
+
require(report["counts"]["action_paragraphs"] == 1, "tag is not an action")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_voice_over_is_performed_time() -> None:
|
|
94
|
+
"""[VO] is a line delivered off-camera, not an instruction to a later stage."""
|
|
95
|
+
report = measure_text("[VO] 甲:一二三四五。\n")
|
|
96
|
+
require(report["counts"]["dialogue_lines"] == 1, "[VO] is a spoken line")
|
|
97
|
+
require(report["counts"]["dialogue_characters"] == 6, "[VO] characters are timed")
|
|
98
|
+
require(report["counts"]["production_tag_lines"] == 0, "[VO] is not a mute tag")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_one_action_paragraph_is_one_paragraph_however_many_lines() -> None:
|
|
102
|
+
"""The format contract lets an action paragraph run several lines."""
|
|
103
|
+
report = measure_text("他站起来,\n走到窗边,\n把窗帘拉开。\n")
|
|
104
|
+
require(
|
|
105
|
+
report["counts"]["action_paragraphs"] == 1,
|
|
106
|
+
f"expected 1 paragraph, got {report['counts']['action_paragraphs']}",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_comments_are_not_production_content() -> None:
|
|
111
|
+
"""A Markdown comment is a note to the writer, not a line to perform."""
|
|
112
|
+
report = measure_text("<!-- 待确认:这一段的转场是否保留。 -->\n")
|
|
113
|
+
require(report["counts"]["dialogue_lines"] == 0, "a comment is not dialogue")
|
|
114
|
+
require(report["counts"]["dialogue_characters"] == 0, "a comment is not spoken")
|
|
115
|
+
require(report["counts"]["action_paragraphs"] == 0, "a comment is not an action")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_an_index_from_other_bytes_is_refused() -> None:
|
|
119
|
+
"""Spans from a stale index land on text it never classified."""
|
|
120
|
+
stale = [
|
|
121
|
+
{
|
|
122
|
+
"record_type": "screenplay_index_meta",
|
|
123
|
+
"source_byte_length": 999_999,
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
try:
|
|
127
|
+
index_blocks(stale, b"short")
|
|
128
|
+
except StaleIndex:
|
|
129
|
+
return
|
|
130
|
+
raise AssertionError("a stale index must be refused, not measured")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def main() -> int:
|
|
134
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
135
|
+
root = Path(directory)
|
|
136
|
+
screenplay = root / "screenplay.md"
|
|
137
|
+
index_path = root / "screenplay-index.jsonl"
|
|
138
|
+
screenplay.write_text(
|
|
139
|
+
"# EP001\n\n## EP001-SC001 内 · 客厅 · 夜\n\n陈予安推开门。\n\n陈予安:我回来了。\n",
|
|
140
|
+
encoding="utf-8",
|
|
141
|
+
)
|
|
142
|
+
summary = build_index(
|
|
143
|
+
screenplay,
|
|
144
|
+
index_path,
|
|
145
|
+
source_ref="剧集/EP001/screenplay.md",
|
|
146
|
+
speakers=["陈予安"],
|
|
147
|
+
)
|
|
148
|
+
require(summary["review_status"] == "clean", "valid screenplay index")
|
|
149
|
+
index_bytes = index_path.read_bytes()
|
|
150
|
+
records = [
|
|
151
|
+
json.loads(line) for line in index_bytes.decode("utf-8").splitlines()
|
|
152
|
+
]
|
|
153
|
+
dialogue = next(record for record in records if record.get("kind") == "dialogue")
|
|
154
|
+
|
|
155
|
+
header = {
|
|
156
|
+
"record_type": "sources",
|
|
157
|
+
"schema_version": "1.0.0",
|
|
158
|
+
"sources": {
|
|
159
|
+
"screenplay-index": {
|
|
160
|
+
"owner": "short-drama-write",
|
|
161
|
+
"artifact": "剧集/EP001/screenplay-index.jsonl",
|
|
162
|
+
"hash": hashlib.sha256(index_bytes).hexdigest(),
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
line = {
|
|
167
|
+
"line_id": "LINE-001",
|
|
168
|
+
"channel": "sync",
|
|
169
|
+
"speaker_display": "陈予安",
|
|
170
|
+
"line_text": "我回来了。",
|
|
171
|
+
"source_ref": {"src": "screenplay-index", "record_id": dialogue["block_id"]},
|
|
172
|
+
}
|
|
173
|
+
sheet = [header, line]
|
|
174
|
+
result = check(sheet, records, screenplay.read_bytes())
|
|
175
|
+
require(result["status"] == "pass", "faithful voice sheet")
|
|
176
|
+
require(result["lines"] == 1, "the sources header is not a voice line")
|
|
177
|
+
|
|
178
|
+
# A sheet written before the compact form still resolves; real projects
|
|
179
|
+
# hold both spellings and neither is rewritten.
|
|
180
|
+
expanded = [
|
|
181
|
+
dict(
|
|
182
|
+
line,
|
|
183
|
+
source_ref={
|
|
184
|
+
"owner": "short-drama-write",
|
|
185
|
+
"artifact": "剧集/EP001/screenplay-index.jsonl",
|
|
186
|
+
"hash": hashlib.sha256(index_bytes).hexdigest(),
|
|
187
|
+
"record_id": dialogue["block_id"],
|
|
188
|
+
},
|
|
189
|
+
)
|
|
190
|
+
]
|
|
191
|
+
require(
|
|
192
|
+
check(expanded, records, screenplay.read_bytes())["status"] == "pass",
|
|
193
|
+
"expanded voice sheet",
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
undeclared = [header, dict(line, source_ref={"src": "screenplay", "record_id": "X"})]
|
|
197
|
+
require(
|
|
198
|
+
"VOICE_SOURCE_REF_UNDECLARED"
|
|
199
|
+
in codes(check(undeclared, records, screenplay.read_bytes())),
|
|
200
|
+
"src without a sources entry was not detected",
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
unbound = [header, dict(line, source_ref={"record_id": dialogue["block_id"]})]
|
|
204
|
+
require(
|
|
205
|
+
"VOICE_SOURCE_REF_MISSING"
|
|
206
|
+
in codes(check(unbound, records, screenplay.read_bytes())),
|
|
207
|
+
"source_ref naming no upstream snapshot was not detected",
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
changed = [header, dict(line, line_text="我走了。")]
|
|
211
|
+
require(
|
|
212
|
+
check(changed, records, screenplay.read_bytes())["status"] == "fail",
|
|
213
|
+
"changed voice line was not detected",
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
test_duration_reports_counts_without_rates()
|
|
217
|
+
test_duration_uses_the_projects_own_rates()
|
|
218
|
+
test_production_tags_are_not_performed_time()
|
|
219
|
+
test_voice_over_is_performed_time()
|
|
220
|
+
test_one_action_paragraph_is_one_paragraph_however_many_lines()
|
|
221
|
+
test_comments_are_not_production_content()
|
|
222
|
+
test_an_index_from_other_bytes_is_refused()
|
|
223
|
+
|
|
224
|
+
print("14 self-tests passed")
|
|
225
|
+
return 0
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
if __name__ == "__main__":
|
|
229
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Prove a voice record sheet is still a projection of the screenplay.
|
|
3
|
+
|
|
4
|
+
The sheet exists to be carried into a recording session, which is exactly the
|
|
5
|
+
moment nobody can check it against the script. A line edited in the sheet, or a
|
|
6
|
+
script revised after the sheet was built, both read as a perfectly ordinary
|
|
7
|
+
sheet — so the comparison has to be mechanical: resolve each line's block in
|
|
8
|
+
the derived index, slice those exact bytes out of the screenplay, and compare.
|
|
9
|
+
|
|
10
|
+
The script reads accepted creator files and writes nothing.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import re
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from collections.abc import Mapping
|
|
21
|
+
from typing import Any, NamedTuple
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Creators run these scripts on whatever interpreter their machine provides, so
|
|
25
|
+
# an unsupported version must say so instead of failing inside an import.
|
|
26
|
+
MINIMUM_PYTHON = (3, 9)
|
|
27
|
+
if sys.version_info < MINIMUM_PYTHON:
|
|
28
|
+
raise SystemExit(
|
|
29
|
+
"short-drama needs Python {}.{} or newer; this interpreter is {}.{}".format(
|
|
30
|
+
*MINIMUM_PYTHON, sys.version_info.major, sys.version_info.minor
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# REFERENCE RESOLVER -- reference implementation.
|
|
36
|
+
#
|
|
37
|
+
# Each skill checker carries its own copy of this block. The suite has no shared
|
|
38
|
+
# library on purpose: a skill must stay runnable after copying only its own
|
|
39
|
+
# directory, so duplicating these few lines across skills is the correct shape.
|
|
40
|
+
# Copy the block verbatim; do not import it.
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
SOURCES_RECORD_TYPE = "sources"
|
|
44
|
+
SOURCES_SCHEMA_VERSION = "1.0.0"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ResolvedRef(NamedTuple):
|
|
48
|
+
"""An upstream reference with its snapshot resolved, whichever form it used."""
|
|
49
|
+
|
|
50
|
+
owner: str
|
|
51
|
+
artifact: str
|
|
52
|
+
record_id: str | None
|
|
53
|
+
field: str | None
|
|
54
|
+
authority: str | None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class RefFinding(NamedTuple):
|
|
58
|
+
"""A structural defect in a reference object."""
|
|
59
|
+
|
|
60
|
+
code: str
|
|
61
|
+
location: str
|
|
62
|
+
detail: str
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def load_sources(document: Any) -> dict[str, dict[str, Any]]:
|
|
66
|
+
"""Return the ``sources`` declaration of a parsed file, or ``{}`` if absent.
|
|
67
|
+
|
|
68
|
+
Accepts a parsed ``.json`` document (a dict) or the parsed record list of a
|
|
69
|
+
``.jsonl`` file, whose declaration lives on the first record.
|
|
70
|
+
"""
|
|
71
|
+
if isinstance(document, list):
|
|
72
|
+
document = document[0] if document else None
|
|
73
|
+
if not isinstance(document, dict):
|
|
74
|
+
return {}
|
|
75
|
+
declared = document.get("sources")
|
|
76
|
+
if not isinstance(declared, dict):
|
|
77
|
+
return {}
|
|
78
|
+
return {key: value for key, value in declared.items() if isinstance(value, dict)}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def resolve_ref(
|
|
82
|
+
ref: Any, sources: dict[str, dict[str, Any]], location: str
|
|
83
|
+
) -> tuple[ResolvedRef | None, RefFinding | None]:
|
|
84
|
+
"""Resolve a reference object written in either the compact or expanded form."""
|
|
85
|
+
if not isinstance(ref, dict):
|
|
86
|
+
return None, RefFinding("REF_IS_NOT_AN_OBJECT", location, f"got {type(ref).__name__}")
|
|
87
|
+
src = ref.get("src")
|
|
88
|
+
if isinstance(src, str):
|
|
89
|
+
entry = sources.get(src)
|
|
90
|
+
if entry is None:
|
|
91
|
+
return None, RefFinding(
|
|
92
|
+
"REF_SRC_IS_NOT_DECLARED", location, f"src {src!r} has no sources entry"
|
|
93
|
+
)
|
|
94
|
+
owner, artifact = entry.get("owner"), entry.get("artifact")
|
|
95
|
+
if not (isinstance(owner, str) and isinstance(artifact, str)):
|
|
96
|
+
return None, RefFinding(
|
|
97
|
+
"SOURCE_ENTRY_IS_INCOMPLETE",
|
|
98
|
+
location,
|
|
99
|
+
f"sources[{src!r}] needs owner/artifact",
|
|
100
|
+
)
|
|
101
|
+
elif all(isinstance(ref.get(key), str) for key in ("owner", "artifact")):
|
|
102
|
+
owner, artifact = ref["owner"], ref["artifact"]
|
|
103
|
+
else:
|
|
104
|
+
return None, RefFinding(
|
|
105
|
+
"REF_HAS_NO_UPSTREAM_BINDING", location, "needs src, or owner+artifact"
|
|
106
|
+
)
|
|
107
|
+
optional = {
|
|
108
|
+
key: ref[key]
|
|
109
|
+
for key in ("record_id", "field", "authority")
|
|
110
|
+
if isinstance(ref.get(key), str)
|
|
111
|
+
}
|
|
112
|
+
return (
|
|
113
|
+
ResolvedRef(
|
|
114
|
+
owner,
|
|
115
|
+
artifact,
|
|
116
|
+
optional.get("record_id"),
|
|
117
|
+
optional.get("field"),
|
|
118
|
+
optional.get("authority"),
|
|
119
|
+
),
|
|
120
|
+
None,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# ---------------------------------------------------------------------------
|
|
125
|
+
# END REFERENCE RESOLVER
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
SCHEMA_VERSION = "1.0.0"
|
|
129
|
+
CHANNELS = {"sync", "dubbed", "VO", "OS"}
|
|
130
|
+
# `[VO]` and `[OS]` are spoken lines that happen to be delivered off-camera, and
|
|
131
|
+
# the index already records them with a `tag` and a `speaker`. Projecting only
|
|
132
|
+
# `dialogue` blocks made the VO and OS channels above unreachable, so an episode
|
|
133
|
+
# that carries its interiority in voice-over reported a clean sheet while the
|
|
134
|
+
# recording list was missing every one of those lines.
|
|
135
|
+
VOICED_TAGS = {"VO", "OS"}
|
|
136
|
+
# `[VO] 角色:台词` — the tag is stripped before the line grammar is applied.
|
|
137
|
+
VOICE_TAG_PREFIX = re.compile(r"^\[(?:VO|OS)\]\s*")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _is_voiced(block: Mapping[str, Any]) -> bool:
|
|
141
|
+
kind = block.get("kind")
|
|
142
|
+
if kind == "dialogue":
|
|
143
|
+
return True
|
|
144
|
+
return kind == "production_tag" and block.get("tag") in VOICED_TAGS
|
|
145
|
+
# The resolver speaks the suite-wide reference vocabulary; this checker reports
|
|
146
|
+
# in its own.
|
|
147
|
+
REF_FINDING_CODES = {
|
|
148
|
+
"REF_IS_NOT_AN_OBJECT": "VOICE_SOURCE_REF_MISSING",
|
|
149
|
+
"REF_HAS_NO_UPSTREAM_BINDING": "VOICE_SOURCE_REF_MISSING",
|
|
150
|
+
"REF_SRC_IS_NOT_DECLARED": "VOICE_SOURCE_REF_UNDECLARED",
|
|
151
|
+
"SOURCE_ENTRY_IS_INCOMPLETE": "VOICE_SOURCE_REF_UNDECLARED",
|
|
152
|
+
}
|
|
153
|
+
# `角色(可表演提示):台词` — the cue is optional and never part of the line.
|
|
154
|
+
DIALOGUE = re.compile(
|
|
155
|
+
r"^(?P<speaker>[^((::]+)(?:[((](?P<cue>[^))]*)[))])?\s*[::]\s*(?P<line>.*)$",
|
|
156
|
+
re.DOTALL,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class CheckError(ValueError):
|
|
161
|
+
"""The inputs cannot be checked at all, as opposed to failing a check."""
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _load_jsonl(path: Path) -> list[dict[str, Any]]:
|
|
165
|
+
records: list[dict[str, Any]] = []
|
|
166
|
+
try:
|
|
167
|
+
text = path.read_text(encoding="utf-8")
|
|
168
|
+
except (OSError, UnicodeError) as error:
|
|
169
|
+
raise CheckError(f"unreadable JSONL: {path}") from error
|
|
170
|
+
for number, line in enumerate(text.splitlines(), start=1):
|
|
171
|
+
if not line.strip():
|
|
172
|
+
continue
|
|
173
|
+
try:
|
|
174
|
+
record = json.loads(line)
|
|
175
|
+
except json.JSONDecodeError as error:
|
|
176
|
+
raise CheckError(f"invalid JSONL at {path.name}:{number}") from error
|
|
177
|
+
if not isinstance(record, dict):
|
|
178
|
+
raise CheckError(f"JSONL needs one object per line: {path.name}:{number}")
|
|
179
|
+
records.append(record)
|
|
180
|
+
return records
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _finding(code: str, message: str, **detail: Any) -> dict[str, Any]:
|
|
184
|
+
return {"code": code, "message": message, **detail}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _blocks_by_id(index: list[dict[str, Any]]) -> dict[str, dict[str, Any]]:
|
|
188
|
+
return {
|
|
189
|
+
record["block_id"]: record
|
|
190
|
+
for record in index
|
|
191
|
+
if record.get("record_type") == "block"
|
|
192
|
+
and isinstance(record.get("block_id"), str)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def check(
|
|
197
|
+
sheet: list[dict[str, Any]],
|
|
198
|
+
index: list[dict[str, Any]],
|
|
199
|
+
screenplay: bytes,
|
|
200
|
+
) -> dict[str, Any]:
|
|
201
|
+
findings: list[dict[str, Any]] = []
|
|
202
|
+
blocks = _blocks_by_id(index)
|
|
203
|
+
seen: set[str] = set()
|
|
204
|
+
# The first record declares the upstream snapshots this sheet references; the
|
|
205
|
+
# rest are voice lines.
|
|
206
|
+
sources = load_sources(sheet)
|
|
207
|
+
lines = [record for record in sheet if record.get("record_type") != SOURCES_RECORD_TYPE]
|
|
208
|
+
|
|
209
|
+
for record in lines:
|
|
210
|
+
line_id = record.get("line_id")
|
|
211
|
+
if not isinstance(line_id, str):
|
|
212
|
+
findings.append(_finding("VOICE_LINE_HAS_NO_ID", "a line record has no id"))
|
|
213
|
+
continue
|
|
214
|
+
if line_id in seen:
|
|
215
|
+
findings.append(
|
|
216
|
+
_finding("VOICE_LINE_ID_REPEATS", "line_id must be unique", line_id=line_id)
|
|
217
|
+
)
|
|
218
|
+
continue
|
|
219
|
+
seen.add(line_id)
|
|
220
|
+
|
|
221
|
+
channel = record.get("channel")
|
|
222
|
+
if channel not in CHANNELS:
|
|
223
|
+
findings.append(
|
|
224
|
+
_finding(
|
|
225
|
+
"VOICE_CHANNEL_INVALID",
|
|
226
|
+
"channel must be sync, dubbed, VO, or OS",
|
|
227
|
+
line_id=line_id,
|
|
228
|
+
channel=channel,
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
resolved, defect = resolve_ref(record.get("source_ref"), sources, line_id)
|
|
233
|
+
if defect is not None:
|
|
234
|
+
findings.append(
|
|
235
|
+
_finding(
|
|
236
|
+
REF_FINDING_CODES[defect.code],
|
|
237
|
+
f"source_ref does not name an upstream snapshot: {defect.detail}",
|
|
238
|
+
line_id=line_id,
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
continue
|
|
242
|
+
if resolved is None or resolved.record_id is None:
|
|
243
|
+
findings.append(
|
|
244
|
+
_finding(
|
|
245
|
+
"VOICE_SOURCE_REF_MISSING",
|
|
246
|
+
"a line must bind the screenplay block it projects",
|
|
247
|
+
line_id=line_id,
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
continue
|
|
251
|
+
record_id = resolved.record_id
|
|
252
|
+
block = blocks.get(record_id)
|
|
253
|
+
if block is None:
|
|
254
|
+
findings.append(
|
|
255
|
+
_finding(
|
|
256
|
+
"VOICE_SOURCE_REF_UNRESOLVABLE",
|
|
257
|
+
"source_ref names a block that is not in the index",
|
|
258
|
+
line_id=line_id,
|
|
259
|
+
record_id=record_id,
|
|
260
|
+
)
|
|
261
|
+
)
|
|
262
|
+
continue
|
|
263
|
+
if not _is_voiced(block):
|
|
264
|
+
findings.append(
|
|
265
|
+
_finding(
|
|
266
|
+
"VOICE_SOURCE_IS_NOT_DIALOGUE",
|
|
267
|
+
"a voice line must project a spoken block: dialogue, [VO] or [OS]",
|
|
268
|
+
line_id=line_id,
|
|
269
|
+
record_id=record_id,
|
|
270
|
+
kind=block.get("kind"),
|
|
271
|
+
)
|
|
272
|
+
)
|
|
273
|
+
continue
|
|
274
|
+
|
|
275
|
+
start, end = block.get("byte_start"), block.get("byte_end")
|
|
276
|
+
if (
|
|
277
|
+
not isinstance(start, int)
|
|
278
|
+
or not isinstance(end, int)
|
|
279
|
+
or not 0 <= start < end <= len(screenplay)
|
|
280
|
+
):
|
|
281
|
+
findings.append(
|
|
282
|
+
_finding(
|
|
283
|
+
"VOICE_BLOCK_SPAN_INVALID",
|
|
284
|
+
"the indexed block span does not fit this screenplay",
|
|
285
|
+
line_id=line_id,
|
|
286
|
+
record_id=record_id,
|
|
287
|
+
)
|
|
288
|
+
)
|
|
289
|
+
continue
|
|
290
|
+
# The channel decides how the line is booked, and off-camera and
|
|
291
|
+
# on-camera carry completely different room for change. It was checked
|
|
292
|
+
# against the enum and never against the block it projects, so a [VO]
|
|
293
|
+
# line could be booked `sync` and go to the booth under lip-sync
|
|
294
|
+
# constraints. `_is_voiced` already reads the block's tag; this compares.
|
|
295
|
+
expected = block.get("tag") if block.get("kind") == "production_tag" else "on_camera"
|
|
296
|
+
if expected in VOICED_TAGS and channel not in {expected, "dubbed"}:
|
|
297
|
+
findings.append(
|
|
298
|
+
_finding(
|
|
299
|
+
"VOICE_CHANNEL_DISAGREES_WITH_BLOCK",
|
|
300
|
+
"an off-camera block must be booked on its own channel",
|
|
301
|
+
line_id=line_id,
|
|
302
|
+
record_id=record_id,
|
|
303
|
+
channel=channel,
|
|
304
|
+
tag=expected,
|
|
305
|
+
)
|
|
306
|
+
)
|
|
307
|
+
elif expected == "on_camera" and channel in VOICED_TAGS:
|
|
308
|
+
findings.append(
|
|
309
|
+
_finding(
|
|
310
|
+
"VOICE_CHANNEL_DISAGREES_WITH_BLOCK",
|
|
311
|
+
"an on-camera dialogue block must not be booked as VO or OS",
|
|
312
|
+
line_id=line_id,
|
|
313
|
+
record_id=record_id,
|
|
314
|
+
channel=channel,
|
|
315
|
+
)
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
raw = screenplay[start:end]
|
|
319
|
+
try:
|
|
320
|
+
decoded = raw.decode("utf-8")
|
|
321
|
+
except UnicodeDecodeError:
|
|
322
|
+
# A span that begins or ends mid-character is a stale offset. It was
|
|
323
|
+
# crashing the whole check instead of reporting the one line.
|
|
324
|
+
findings.append(
|
|
325
|
+
_finding(
|
|
326
|
+
"VOICE_BLOCK_SPAN_INVALID",
|
|
327
|
+
"the indexed block span does not start and end on characters",
|
|
328
|
+
line_id=line_id,
|
|
329
|
+
record_id=record_id,
|
|
330
|
+
)
|
|
331
|
+
)
|
|
332
|
+
continue
|
|
333
|
+
body = VOICE_TAG_PREFIX.sub("", decoded.strip())
|
|
334
|
+
match = DIALOGUE.match(body)
|
|
335
|
+
if match is None:
|
|
336
|
+
findings.append(
|
|
337
|
+
_finding(
|
|
338
|
+
"VOICE_BLOCK_IS_UNPARSEABLE",
|
|
339
|
+
"the dialogue block does not use the documented line grammar",
|
|
340
|
+
line_id=line_id,
|
|
341
|
+
record_id=record_id,
|
|
342
|
+
)
|
|
343
|
+
)
|
|
344
|
+
continue
|
|
345
|
+
if record.get("line_text") != match.group("line"):
|
|
346
|
+
findings.append(
|
|
347
|
+
_finding(
|
|
348
|
+
"VOICE_LINE_TEXT_DIVERGED",
|
|
349
|
+
"line_text is not the screenplay wording; change the screenplay",
|
|
350
|
+
line_id=line_id,
|
|
351
|
+
record_id=record_id,
|
|
352
|
+
)
|
|
353
|
+
)
|
|
354
|
+
indexed_speaker = block.get("speaker")
|
|
355
|
+
if isinstance(indexed_speaker, str) and record.get("speaker_display") not in (
|
|
356
|
+
None,
|
|
357
|
+
indexed_speaker,
|
|
358
|
+
):
|
|
359
|
+
findings.append(
|
|
360
|
+
_finding(
|
|
361
|
+
"VOICE_SPEAKER_DIVERGED",
|
|
362
|
+
"speaker_display disagrees with the indexed speaker",
|
|
363
|
+
line_id=line_id,
|
|
364
|
+
record_id=record_id,
|
|
365
|
+
)
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
dialogue_blocks = {
|
|
369
|
+
block_id for block_id, block in blocks.items() if _is_voiced(block)
|
|
370
|
+
}
|
|
371
|
+
projected: set[str] = set()
|
|
372
|
+
for record in lines:
|
|
373
|
+
covered, _defect = resolve_ref(record.get("source_ref"), sources, "")
|
|
374
|
+
if covered is not None and covered.record_id is not None:
|
|
375
|
+
projected.add(covered.record_id)
|
|
376
|
+
# Reported, never a finding: a sheet may legitimately cover one actor or one
|
|
377
|
+
# scene, so an incomplete sheet is a scope decision, not a defect.
|
|
378
|
+
uncovered = sorted(dialogue_blocks - projected)
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
"schema_version": SCHEMA_VERSION,
|
|
382
|
+
"lines": len(lines),
|
|
383
|
+
"dialogue_blocks": len(dialogue_blocks),
|
|
384
|
+
"uncovered_dialogue_blocks": uncovered,
|
|
385
|
+
"findings": findings,
|
|
386
|
+
"status": "pass" if not findings else "fail",
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
391
|
+
parser = argparse.ArgumentParser(
|
|
392
|
+
description="Check a voice record sheet against its screenplay and index."
|
|
393
|
+
)
|
|
394
|
+
parser.add_argument("sheet", type=Path, help="the voice record sheet JSONL")
|
|
395
|
+
parser.add_argument("--index", type=Path, required=True)
|
|
396
|
+
parser.add_argument("--screenplay", type=Path, required=True)
|
|
397
|
+
return parser
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def main(argv: list[str] | None = None) -> int:
|
|
401
|
+
args = build_parser().parse_args(argv)
|
|
402
|
+
try:
|
|
403
|
+
result = check(
|
|
404
|
+
_load_jsonl(args.sheet),
|
|
405
|
+
_load_jsonl(args.index),
|
|
406
|
+
args.screenplay.read_bytes(),
|
|
407
|
+
)
|
|
408
|
+
except (CheckError, OSError) as error:
|
|
409
|
+
print(f"{type(error).__name__}: {error}", file=sys.stderr)
|
|
410
|
+
return 2
|
|
411
|
+
print(json.dumps(result, ensure_ascii=False, sort_keys=True))
|
|
412
|
+
return 0 if result["status"] == "pass" else 1
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
if __name__ == "__main__":
|
|
416
|
+
raise SystemExit(main())
|