@oh-story/dsh 0.1.10 → 0.1.12
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 +11 -11
- package/lib/client.js +7 -3
- package/lib/client.js.map +2 -2
- package/lib/drama/manifest.json +212 -142
- package/lib/drama/skills/short-drama/SKILL.md +14 -2
- package/lib/drama/skills/short-drama/assets/project-template/short-drama.json +3 -0
- package/lib/drama/skills/short-drama/references/creator-documents.md +3 -0
- package/lib/drama/skills/short-drama/references/knowhow-index.md +15 -0
- package/lib/drama/skills/short-drama/scripts/project_tool.py +131 -0
- package/lib/drama/skills/short-drama-assets/SKILL.md +2 -1
- package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +2 -0
- package/lib/drama/skills/short-drama-assets/references/era-anchors.md +37 -0
- package/lib/drama/skills/short-drama-assets/references/stage-contract.md +2 -0
- package/lib/drama/skills/short-drama-develop/SKILL.md +10 -4
- package/lib/drama/skills/short-drama-develop/assets/adaptation-map.example.jsonl +1 -1
- package/lib/drama/skills/short-drama-develop/assets/creative-brief.md +8 -0
- package/lib/drama/skills/short-drama-develop/references/adaptation-craft.md +80 -13
- package/lib/drama/skills/short-drama-develop/references/episode-design.md +95 -12
- package/lib/drama/skills/short-drama-develop/references/genre-and-hook-playbook.md +9 -5
- package/lib/drama/skills/short-drama-develop/references/genre-cards//345/244/215/344/273/207/346/211/223/350/204/270.md +1 -1
- package/lib/drama/skills/short-drama-develop/references/genre-cards//347/263/273/347/273/237/346/265/201.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards//351/200/206/350/242/255.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards//351/207/215/347/224/237/347/251/277/350/266/212.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards.md +10 -7
- package/lib/drama/skills/short-drama-develop/references/premise-devices.md +15 -9
- package/lib/drama/skills/short-drama-develop/references/serial-character-and-memory.md +3 -2
- package/lib/drama/skills/short-drama-develop/references/stage-contract.md +14 -7
- package/lib/drama/skills/short-drama-develop/references/story-craft.md +7 -3
- package/lib/drama/skills/short-drama-edit/SKILL.md +43 -20
- package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +49 -13
- package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +4 -2
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Overlay.tsx +31 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +5 -5
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +68 -69
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +39 -29
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/rules.mjs +188 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +26 -13
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/Card.tsx +72 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/CornerChip.tsx +39 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/ScreenText.tsx +36 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/SystemPanel.tsx +44 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/TaskPanel.tsx +100 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/chrome.tsx +197 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/tokens.ts +62 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +1 -0
- package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +4 -2
- package/lib/drama/skills/short-drama-edit/references/cut-craft.md +3 -1
- package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +6 -2
- package/lib/drama/skills/short-drama-edit/references/generated-footage.md +12 -6
- package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +13 -8
- package/lib/drama/skills/short-drama-edit/references/stage-contract.md +2 -0
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1153 -121
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +49 -9
- package/lib/drama/skills/short-drama-novel-analyze/SKILL.md +3 -3
- package/lib/drama/skills/short-drama-novel-analyze/assets/episode-candidate.example.jsonl +2 -2
- package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-triage.md +8 -4
- package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-value.md +10 -3
- package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +25 -1
- package/lib/drama/skills/short-drama-novel-analyze/references/stage-contract.md +1 -0
- package/lib/drama/skills/short-drama-review/SKILL.md +2 -2
- package/lib/drama/skills/short-drama-review/references/review-method.md +6 -1
- package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +11 -0
- package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +77 -3
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +15 -0
- package/lib/drama/skills/short-drama-review/references/stage-contract.md +3 -0
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +13 -5
- package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +1 -1
- package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +2 -2
- package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +82 -14
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +4 -2
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +5 -3
- package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +12 -0
- package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +4 -5
- package/lib/drama/skills/short-drama-video-prompts/references/generability.md +4 -2
- package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +40 -1
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +20 -14
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +1 -1
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +8 -7
- package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +1 -1
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -0
- package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +8 -1
- package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +103 -0
- package/lib/drama/skills/short-drama-video-prompts/scripts/motion_timing_check.py +8 -7
- package/lib/drama/skills/short-drama-write/SKILL.md +8 -3
- package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +1 -1
- package/lib/drama/skills/short-drama-write/references/script-craft.md +56 -3
- package/lib/drama/skills/short-drama-write/references/stage-contract.md +4 -0
- package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +48 -2
- package/lib/index.js +71 -21
- package/lib/index.js.map +3 -3
- package/lib/oh-story/manifest.json +545 -310
- package/lib/oh-story/roles/chapter-extractor.md +11 -11
- package/lib/oh-story/roles/character-designer.md +1 -2
- package/lib/oh-story/roles/consistency-checker.md +11 -9
- package/lib/oh-story/roles/narrative-writer.md +24 -25
- package/lib/oh-story/roles/story-architect.md +9 -61
- package/lib/oh-story/roles/story-explorer.md +20 -125
- package/lib/oh-story/roles/story-researcher.md +0 -1
- package/lib/oh-story/skills/story/SKILL.md +6 -6
- package/lib/oh-story/skills/story/VERSION +1 -1
- package/lib/oh-story/skills/story/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story/references/author-memory.md +40 -118
- package/lib/oh-story/skills/story/scripts/author_memory_commit.py +3 -3
- package/lib/oh-story/skills/story-deslop/SKILL.md +24 -25
- package/lib/oh-story/skills/story-deslop/references/agent-calls.md +5 -0
- package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +1 -1
- package/lib/oh-story/skills/story-deslop/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story-deslop/references/author-memory.md +40 -118
- package/lib/oh-story/skills/story-deslop/references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +21 -4
- package/lib/oh-story/skills/story-deslop/references/style-resolution.md +4 -4
- package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +3 -3
- package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +155 -8
- package/lib/oh-story/skills/story-import/SKILL.md +63 -579
- package/lib/oh-story/skills/story-import/references/deep-analysis.md +47 -0
- package/lib/oh-story/skills/story-import/references/format-and-structure.md +1 -1
- package/lib/oh-story/skills/story-import/references/import-report.md +64 -0
- package/lib/oh-story/skills/story-import/references/import-special-cases.md +34 -0
- package/lib/oh-story/skills/story-import/references/import-tracking.md +95 -0
- package/lib/oh-story/skills/story-import/references/length-routing.md +1 -1
- package/lib/oh-story/skills/story-import/references/outline-reverse-rules.md +79 -0
- package/lib/oh-story/skills/story-import/references/outline-reverse.md +24 -0
- package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +95 -91
- package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +24 -12
- package/lib/oh-story/skills/story-import/references/tracking-initialization.md +1 -1
- package/lib/oh-story/skills/story-import/references/tracking-transaction.md +11 -9
- package/lib/oh-story/skills/story-import/scripts/build_outline_brief.py +332 -0
- package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +101 -14
- package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +278 -23
- package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +179 -23
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +28 -50
- package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +12 -5
- package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +1 -1
- package/lib/oh-story/skills/story-long-analyze/references/final-checks.md +19 -0
- package/lib/oh-story/skills/story-long-analyze/references/index-rebuild.md +11 -0
- package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +21 -55
- package/lib/oh-story/skills/story-long-analyze/references/stage1-golden-chapters.md +128 -0
- package/lib/oh-story/skills/story-long-analyze/references/stage2-extraction.md +229 -0
- package/lib/oh-story/skills/story-long-analyze/references/stage3-plot-rhythm.md +480 -0
- package/lib/oh-story/skills/story-long-analyze/references/stage4-characters-settings.md +195 -0
- package/lib/oh-story/skills/story-long-analyze/references/stage5-report.md +33 -0
- package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +17 -0
- package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +1 -1
- package/lib/oh-story/skills/story-long-analyze/references/synthesis-inputs.md +39 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +47 -8
- package/lib/oh-story/skills/story-long-scan/SKILL.md +41 -193
- package/lib/oh-story/skills/story-long-scan/references/platform-ciweimao.md +19 -0
- package/lib/oh-story/skills/story-long-scan/references/platform-fanqie.md +45 -0
- package/lib/oh-story/skills/story-long-scan/references/platform-jjwxc.md +28 -0
- package/lib/oh-story/skills/story-long-scan/references/platform-qidian.md +35 -0
- package/lib/oh-story/skills/story-long-scan/references/platform-qimao.md +27 -0
- package/lib/oh-story/skills/story-long-scan/references/topic-decision.md +6 -2
- package/lib/oh-story/skills/story-long-write/SKILL.md +32 -73
- package/lib/oh-story/skills/story-long-write/references/agent-calls.md +47 -0
- package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +18 -201
- package/lib/oh-story/skills/story-long-write/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story-long-write/references/author-memory.md +40 -118
- package/lib/oh-story/skills/story-long-write/references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +6 -10
- package/lib/oh-story/skills/story-long-write/references/character-relations.md +49 -1
- package/lib/oh-story/skills/story-long-write/references/deslop-gates.md +171 -0
- package/lib/oh-story/skills/story-long-write/references/emotion-on-page.md +116 -0
- package/lib/oh-story/skills/story-long-write/references/emotional-arc-design.md +1 -114
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +3 -2
- package/lib/oh-story/skills/story-long-write/references/long-format.md +0 -8
- package/lib/oh-story/skills/story-long-write/references/long-genre-catalog.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/long-genre-mechanics.md +3 -3
- package/lib/oh-story/skills/story-long-write/references/opening-design.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/outline-conflict.md +4 -4
- package/lib/oh-story/skills/story-long-write/references/outline-methods.md +7 -7
- package/lib/oh-story/skills/story-long-write/references/outline-rhythm.md +3 -3
- package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +14 -69
- package/lib/oh-story/skills/story-long-write/references/plot-frameworks.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/plot-special-topics.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/project-files.md +10 -6
- package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/reference-index.md +4 -4
- package/lib/oh-story/skills/story-long-write/references/stop-and-fix.md +9 -0
- package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -58
- package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/style-resolution.md +4 -4
- package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +11 -9
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +33 -44
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +18 -51
- package/lib/oh-story/skills/story-long-write/references/workflow-outline.md +157 -0
- package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +53 -67
- package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +112 -251
- package/lib/oh-story/skills/story-long-write/references/workflow-volume.md +142 -0
- package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +5 -3
- package/lib/oh-story/skills/story-long-write/references/writing-craft.md +18 -56
- package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +3 -3
- package/lib/oh-story/skills/story-long-write/scripts/build_architect_brief.py +260 -0
- package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +96 -16
- package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +155 -8
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +101 -14
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +7 -2
- package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +2 -1
- package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +250 -50
- package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +278 -23
- package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +179 -23
- package/lib/oh-story/skills/story-review/SKILL.md +19 -273
- package/lib/oh-story/skills/story-review/references/agent-prompts.md +184 -0
- package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +1 -1
- package/lib/oh-story/skills/story-review/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story-review/references/author-memory.md +40 -118
- package/lib/oh-story/skills/story-review/references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-review/references/batch-review.md +19 -0
- package/lib/oh-story/skills/story-review/references/character-relations.md +49 -1
- package/lib/oh-story/skills/story-review/references/review-quality.md +2 -1
- package/lib/oh-story/skills/story-review/references/review-tracking.md +10 -0
- package/lib/oh-story/skills/story-review/references/solo.md +52 -0
- package/lib/oh-story/skills/story-review/references/style-resolution.md +4 -4
- package/lib/oh-story/skills/story-review/references/tracking-initialization.md +1 -1
- package/lib/oh-story/skills/story-review/references/tracking-transaction.md +11 -9
- package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +3 -3
- package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +155 -8
- package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +278 -23
- package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +179 -23
- package/lib/oh-story/skills/story-setup/SKILL.md +69 -458
- package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +4 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/benchmark-style-load.md +107 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/character-relations.md +49 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +21 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/emotion-on-page.md +116 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/emotional-arc-design.md +1 -114
- package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-catalog.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-mechanics.md +3 -3
- package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +7 -5
- package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/outline-conflict.md +4 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/outline-methods.md +7 -7
- package/lib/oh-story/skills/story-setup/references/agent-references/outline-rhythm.md +3 -3
- package/lib/oh-story/skills/story-setup/references/agent-references/short-emotional-methods.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +0 -15
- package/lib/oh-story/skills/story-setup/references/agent-references/short-reversal.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -58
- package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-resolution.md +4 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +18 -56
- package/lib/oh-story/skills/story-setup/references/diagnostics.md +3 -3
- package/lib/oh-story/skills/story-short-analyze/SKILL.md +37 -110
- package/lib/oh-story/skills/story-short-analyze/references/analysis-character-relations.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/analysis-method.md +42 -0
- package/lib/oh-story/skills/story-short-analyze/references/optional-modules.md +97 -0
- package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +9 -47
- package/lib/oh-story/skills/story-short-analyze/references/quality-checklist.md +80 -0
- package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +4 -4
- package/lib/oh-story/skills/story-short-analyze/references/stage2-3-structure-emotion.md +308 -0
- package/lib/oh-story/skills/story-short-analyze/references/{material-decomposition.md → stage4-6-reversal-summary.md} +303 -374
- package/lib/oh-story/skills/story-short-scan/SKILL.md +30 -64
- package/lib/oh-story/skills/story-short-scan/references/manual-rank-input.md +36 -0
- package/lib/oh-story/skills/story-short-scan/references/platform-dianzhong.md +24 -0
- package/lib/oh-story/skills/story-short-scan/references/platform-heiyan.md +21 -0
- package/lib/oh-story/skills/story-short-scan/references/topic-match.md +38 -0
- package/lib/oh-story/skills/story-short-write/SKILL.md +23 -34
- package/lib/oh-story/skills/story-short-write/references/agent-calls.md +13 -0
- package/lib/oh-story/skills/story-short-write/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story-short-write/references/author-memory.md +40 -118
- package/lib/oh-story/skills/story-short-write/references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +1 -21
- package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +0 -15
- package/lib/oh-story/skills/story-short-write/references/output-contract.md +9 -47
- package/lib/oh-story/skills/story-short-write/references/reference-index.md +17 -49
- package/lib/oh-story/skills/story-short-write/references/scene-craft.md +40 -0
- package/lib/oh-story/skills/story-short-write/references/short-craft.md +7 -43
- package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -1
- package/lib/oh-story/skills/story-short-write/references/short-reversal.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/style-resolution.md +4 -4
- package/lib/oh-story/skills/story-short-write/references/submission-craft.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/villain-and-reveal.md +5 -10
- package/lib/oh-story/skills/story-short-write/references/workflow-design.md +19 -12
- package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +22 -32
- package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +84 -17
- package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +7 -119
- package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +3 -3
- package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +155 -8
- package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +7 -2
- package/lib/types/client/video-studio.d.ts.map +1 -1
- package/lib/types/native-hooks.d.ts +2 -2
- package/lib/types/native-hooks.d.ts.map +1 -1
- package/lib/types/role-tool.d.ts.map +1 -1
- package/lib/types/skill-provider.d.ts.map +1 -1
- package/lib/types/video-project.d.ts.map +1 -1
- package/lib/types/workspace-route.d.ts.map +1 -1
- package/lib/video-recap/CHANGELOG.md +60 -1
- package/lib/video-recap/README.en.md +348 -137
- package/lib/video-recap/README.md +340 -136
- package/lib/video-recap/manifest.json +406 -251
- package/lib/video-recap/skills/video-assemble/SKILL.md +30 -15
- package/lib/video-recap/skills/video-assemble/references/audio-modes.md +63 -0
- package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +75 -0
- package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +118 -0
- package/lib/video-recap/skills/video-assemble/references/narration-adoption.md +103 -0
- package/lib/video-recap/skills/video-assemble/references/packaging.md +45 -0
- package/lib/video-recap/skills/video-assemble/references/pair-media.md +74 -0
- package/lib/video-recap/skills/video-assemble/references/source-score.md +147 -0
- package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +202 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/__init__.py +1 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +279 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/frozen_audio.py +187 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/narration_binding.py +323 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_inputs.py +109 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +130 -0
- package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +10 -46
- package/lib/video-recap/skills/video-assemble/scripts/assemble.py +378 -119
- package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +29 -12
- package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +95 -44
- package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +75 -32
- package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +11 -11
- package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +293 -0
- package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +71 -78
- package/lib/video-recap/skills/video-assemble/scripts/jianying/__init__.py +1 -0
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_builders.py → jianying/builders.py} +39 -67
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_model.py → jianying/model.py} +3 -11
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_optional.py → jianying/optional.py} +1 -1
- package/lib/video-recap/skills/video-assemble/scripts/jianying/schema.py +53 -0
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_templates.py → jianying/templates.py} +1 -1
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_timeline_contract.py → jianying/timeline_contract.py} +34 -13
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_writer.py → jianying/writer.py} +7 -17
- package/lib/video-recap/skills/video-assemble/scripts/lib.py +37 -4
- package/lib/video-recap/skills/video-assemble/scripts/media.py +12 -21
- package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +64 -34
- package/lib/video-recap/skills/video-assemble/scripts/packaging.py +122 -0
- package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +216 -0
- package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +1 -3
- package/lib/video-recap/skills/video-assemble/scripts/source_score.py +469 -0
- package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +25 -9
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/__init__.py +1 -0
- package/lib/video-recap/skills/video-assemble/scripts/{subtitle_core.py → subtitles/core.py} +13 -6
- package/lib/video-recap/skills/video-assemble/scripts/{subtitle_render.py → subtitles/render.py} +5 -4
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +324 -0
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +200 -0
- package/lib/video-recap/skills/video-assemble/scripts/timeline.py +30 -46
- package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +91 -7
- package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +22 -8
- package/lib/video-recap/skills/video-cut/SKILL.md +34 -16
- package/lib/video-recap/skills/video-cut/references/shot-review.md +50 -0
- package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -28
- package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +58 -62
- package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +21 -106
- package/lib/video-recap/skills/video-cut/scripts/cut_render.py +14 -9
- package/lib/video-recap/skills/video-cut/scripts/lib.py +39 -0
- package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +2 -155
- package/lib/video-recap/skills/video-cut/scripts/narrative_selection.py +244 -0
- package/lib/video-recap/skills/video-cut/scripts/shot_review.py +357 -0
- package/lib/video-recap/skills/video-recap/SKILL.md +86 -42
- package/lib/video-recap/skills/video-recap/references/audio-routing.md +126 -0
- package/lib/video-recap/skills/video-recap/references/config-playbook.md +12 -8
- package/lib/video-recap/skills/video-recap/references/data-schema.md +44 -33
- package/lib/video-recap/skills/video-recap/references/resource-library.md +151 -0
- package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +1 -1
- package/lib/video-recap/skills/video-recap/scripts/doctor.py +123 -30
- package/lib/video-recap/skills/video-recap/scripts/final_qc.py +22 -75
- package/lib/video-recap/skills/video-recap/scripts/lib.py +3 -3
- package/lib/video-recap/skills/video-recap/scripts/library.py +509 -0
- package/lib/video-recap/skills/video-recap/scripts/materials.py +63 -91
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +8 -70
- package/lib/video-recap/skills/video-recap/scripts/project_binding.py +286 -0
- package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +1 -0
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_client.py → qc/mimo_client.py} +1 -1
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_evidence.py → qc/mimo_evidence.py} +33 -66
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_observations.py → qc/mimo_observations.py} +3 -11
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_payload.py → qc/mimo_payload.py} +4 -7
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_report.py → qc/mimo_report.py} +17 -28
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_runner.py → qc/mimo_runner.py} +2 -2
- package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -19
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +119 -50
- package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +32 -114
- package/lib/video-recap/skills/video-recap/scripts/recap_review.py +9 -23
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +387 -334
- package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +23 -23
- package/lib/video-recap/skills/video-recap/scripts/recap_source.py +278 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +25 -12
- package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +59 -52
- package/lib/video-recap/skills/video-recap/scripts/resource_lock.py +162 -0
- package/lib/video-recap/skills/video-script/SKILL.md +16 -13
- package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +9 -5
- package/lib/video-recap/skills/video-script/references/promotional-copy.md +68 -0
- package/lib/video-recap/skills/video-script/references/research-guide.md +1 -1
- package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +24 -76
- package/lib/video-recap/skills/video-script/scripts/lib.py +10 -87
- package/lib/video-recap/skills/video-script/scripts/narration_lint.py +63 -39
- package/lib/video-recap/skills/video-script/scripts/review.py +0 -2
- package/lib/video-recap/skills/video-script/scripts/review_grounding.py +35 -59
- package/lib/video-recap/skills/video-script/scripts/review_response.py +66 -128
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +12 -20
- package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +4 -4
- package/lib/video-recap/skills/video-script/scripts/validate.py +53 -42
- package/lib/video-recap/skills/video-understanding/SKILL.md +6 -4
- package/lib/video-recap/skills/video-understanding/references/data-schema.md +50 -156
- package/lib/video-recap/skills/video-understanding/references/research-guide.md +4 -4
- package/lib/video-recap/skills/video-understanding/scripts/asr.py +85 -65
- package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +270 -0
- package/lib/video-recap/skills/video-understanding/scripts/brief.py +2 -5
- package/lib/video-recap/skills/video-understanding/scripts/briefing/__init__.py +1 -0
- package/lib/video-recap/skills/video-understanding/scripts/{agent_brief.py → briefing/builder.py} +38 -35
- package/lib/video-recap/skills/{video-script/scripts/brief_context.py → video-understanding/scripts/briefing/context.py} +4 -35
- package/lib/video-recap/skills/{video-script/scripts/brief_inputs.py → video-understanding/scripts/briefing/inputs.py} +9 -31
- package/lib/video-recap/skills/video-understanding/scripts/{brief_timeline.py → briefing/timeline.py} +5 -6
- package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +65 -116
- package/lib/video-recap/skills/video-understanding/scripts/detect.py +31 -40
- package/lib/video-recap/skills/video-understanding/scripts/lib.py +30 -86
- package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +63 -39
- package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +4 -4
- package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +17 -79
- package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +14 -40
- package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +64 -46
- package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +31 -11
- package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +19 -14
- package/lib/video-recap/skills/video-understanding/scripts/vlm.py +80 -108
- package/lib/video-recap/skills/video-voiceover/SKILL.md +29 -19
- package/lib/video-recap/skills/video-voiceover/references/index-tts.md +31 -0
- package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +123 -0
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +51 -59
- package/lib/video-recap/skills/video-voiceover/scripts/lib.py +8 -44
- package/lib/video-recap/skills/video-voiceover/scripts/providers/__init__.py +1 -0
- package/lib/video-recap/skills/video-voiceover/scripts/providers/index_tts.py +184 -0
- package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +212 -134
- package/package.json +41 -41
- package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +0 -538
- package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +0 -603
- package/lib/oh-story/skills/story-long-scan/references/scan-output-format.md +0 -207
- package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +0 -561
- package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +0 -98
- package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +0 -225
- package/lib/video-recap/skills/video-recap/references/research-guide.md +0 -74
- package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +0 -250
- package/lib/video-recap/skills/video-script/scripts/agent_brief.py +0 -399
- package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +0 -327
- package/lib/video-recap/skills/video-script/scripts/narration.py +0 -12
- package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +0 -348
- package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +0 -290
- /package/lib/video-recap/skills/video-assemble/scripts/{jianying_tracks.py → jianying/tracks.py} +0 -0
- /package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_contract.py → qc/mimo_contract.py} +0 -0
- /package/lib/video-recap/skills/video-voiceover/scripts/{fish_audio.py → providers/fish_audio.py} +0 -0
|
@@ -26,8 +26,71 @@ _FRONTMATTER_KEY_RE = re.compile(r"^[A-Za-z_\u3400-\u9FFF][^:\n]{0,80}:[ \t]*(?:
|
|
|
26
26
|
_LEADING_BLANK_RE = re.compile(r"^[\u0009\u0020\u3000]*$")
|
|
27
27
|
_ATX_HEADING_RE = re.compile(r"^[\u0009\u0020]{0,3}#{1,6}[\u0009\u0020]+\S")
|
|
28
28
|
_POSITIVE_INTEGER_RE = re.compile(r"^[1-9]\d*$")
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
# 细纲「字数目标」「字数范围」「字数口径」的唯一写法语法;check-outline-contract.js 逐字镜像同一组正则,
|
|
30
|
+
# scripts/test-storyctl.py 用同一批写法同时跑两边,保证细纲验收过了、章节检查不会再拒。
|
|
31
|
+
# 值里一个数字都没有的目标/范围行(「字数目标:按卷规划」)是说明文字,不参与取值。
|
|
32
|
+
_NUMBER = r"([1-9][0-9]{0,2}(?:[,,][0-9]{3})+|[1-9][0-9]*)"
|
|
33
|
+
_NOTE = r"(?:[((][^()()]*[))])?"
|
|
34
|
+
_TARGET_VALUE_RE = re.compile(rf"^(?:约|大约)?[ \t]*{_NUMBER}[ \t]*字?(?:左右)?[ \t]*{_NOTE}$")
|
|
35
|
+
_RANGE_VALUE_RE = re.compile(
|
|
36
|
+
rf"^{_NUMBER}[ \t]*字?[ \t]*(?:-|~|~|—|–|-|至|到)[ \t]*{_NUMBER}[ \t]*字?[ \t]*{_NOTE}$"
|
|
37
|
+
)
|
|
38
|
+
_METRIC_VALUE_RE = re.compile(rf"^([A-Za-z0-9_-]+)[ \t]*{_NOTE}$")
|
|
39
|
+
_HAS_DIGIT_RE = re.compile(r"[0-90-9]")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _field_values(text: str, label: str) -> list[str]:
|
|
43
|
+
pattern = re.compile(
|
|
44
|
+
rf"^[ \t>]*(?:[-*+][ \t]*)?(?:\*\*)?{label}(?:\*\*)?[ \t]*[::][ \t]*(.*?)[ \t]*$", re.MULTILINE
|
|
45
|
+
)
|
|
46
|
+
return [value.replace("**", "").strip() for value in pattern.findall(text)]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _numeric_field_values(text: str, label: str) -> list[str]:
|
|
50
|
+
return [raw for raw in _field_values(text, label) if _HAS_DIGIT_RE.search(raw)]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _number(raw: str) -> int:
|
|
54
|
+
return int(re.sub(r"[,,]", "", raw))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def parse_target_field(text: str) -> dict[str, Any]:
|
|
58
|
+
"""Return {"status": ok|missing|invalid|conflict, "value": int|None, "raw": [...]}."""
|
|
59
|
+
raws = _numeric_field_values(normalize_newlines(text).lstrip("\ufeff"), "字数目标")
|
|
60
|
+
if not raws:
|
|
61
|
+
return {"status": "missing", "value": None, "raw": raws}
|
|
62
|
+
matches = [_TARGET_VALUE_RE.fullmatch(raw) for raw in raws]
|
|
63
|
+
if not all(matches):
|
|
64
|
+
return {"status": "invalid", "value": None, "raw": raws}
|
|
65
|
+
values = sorted({_number(match.group(1)) for match in matches if match})
|
|
66
|
+
if len(values) != 1:
|
|
67
|
+
return {"status": "conflict", "value": None, "raw": raws}
|
|
68
|
+
return {"status": "ok", "value": values[0], "raw": raws}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def parse_range_field(text: str) -> dict[str, Any]:
|
|
72
|
+
"""Optional author range line 「字数范围:2000-2600」; same status vocabulary as parse_target_field."""
|
|
73
|
+
raws = _numeric_field_values(normalize_newlines(text).lstrip("\ufeff"), "字数范围")
|
|
74
|
+
if not raws:
|
|
75
|
+
return {"status": "missing", "value": None, "raw": raws}
|
|
76
|
+
matches = [_RANGE_VALUE_RE.fullmatch(raw) for raw in raws]
|
|
77
|
+
if not all(matches):
|
|
78
|
+
return {"status": "invalid", "value": None, "raw": raws}
|
|
79
|
+
values = sorted({(_number(match.group(1)), _number(match.group(2))) for match in matches if match})
|
|
80
|
+
if len(values) != 1:
|
|
81
|
+
return {"status": "invalid", "value": None, "raw": raws}
|
|
82
|
+
low, high = values[0]
|
|
83
|
+
if low > high:
|
|
84
|
+
return {"status": "invalid", "value": None, "raw": raws}
|
|
85
|
+
return {"status": "ok", "value": {"min": low, "max": high}, "raw": raws}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def parse_metric_field(text: str) -> dict[str, Any]:
|
|
89
|
+
"""「字数口径:visible_chars_v1(按可见字符)」;每行都得是口径名(可带括号备注),且只能是 METRIC。"""
|
|
90
|
+
raws = _field_values(normalize_newlines(text).lstrip("\ufeff"), "字数口径")
|
|
91
|
+
matches = [_METRIC_VALUE_RE.fullmatch(raw) for raw in raws]
|
|
92
|
+
ok = bool(raws) and all(matches) and {match.group(1) for match in matches if match} == {METRIC}
|
|
93
|
+
return {"ok": ok, "raw": raws}
|
|
31
94
|
|
|
32
95
|
|
|
33
96
|
class WordcountError(ValueError):
|
|
@@ -80,8 +143,26 @@ def parse_target(value: Any) -> int:
|
|
|
80
143
|
return target
|
|
81
144
|
|
|
82
145
|
|
|
83
|
-
def
|
|
146
|
+
def normalize_author_range(value: Any) -> dict[str, int] | None:
|
|
147
|
+
"""作者明确给出的上下限;None 表示沿用默认 ±12% / ±15% 双层带。"""
|
|
148
|
+
if value is None:
|
|
149
|
+
return None
|
|
150
|
+
require(isinstance(value, dict) and set(value) == {"min", "max"}, "author range must be {min, max}")
|
|
151
|
+
low, high = value["min"], value["max"]
|
|
152
|
+
require(
|
|
153
|
+
all(isinstance(item, int) and not isinstance(item, bool) and item >= 1 for item in (low, high)),
|
|
154
|
+
"author range bounds must be positive integers",
|
|
155
|
+
)
|
|
156
|
+
require(low <= high, "author range min must not exceed max")
|
|
157
|
+
return {"min": low, "max": high}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def compute_wordcount_bands(value: Any, author_range: Any = None) -> dict[str, dict[str, int]]:
|
|
84
161
|
target = parse_target(value)
|
|
162
|
+
author = normalize_author_range(author_range)
|
|
163
|
+
if author is not None:
|
|
164
|
+
# 作者给定区间原样优先:内部带与用户带都取它,不再有 borderline。
|
|
165
|
+
return {"internal": dict(author), "user": dict(author)}
|
|
85
166
|
return {
|
|
86
167
|
"internal": {"min": (target * 88 + 99) // 100, "max": target * 112 // 100},
|
|
87
168
|
"user": {"min": (target * 85 + 99) // 100, "max": target * 115 // 100},
|
|
@@ -95,7 +176,7 @@ def invalid_wordcount_result(
|
|
|
95
176
|
return {
|
|
96
177
|
"schema": WORDCOUNT_SCHEMA, "metric": METRIC, "chapter": chapter, "case_id": case_id,
|
|
97
178
|
"target": target, "actual": actual, "internal_band": None, "user_band": None,
|
|
98
|
-
"signed_error_pct": None, "absolute_error_pct": None,
|
|
179
|
+
"signed_error_pct": None, "absolute_error_pct": None, "band_source": None,
|
|
99
180
|
"status": "invalid", "invalid_reason": reason,
|
|
100
181
|
}
|
|
101
182
|
|
|
@@ -115,19 +196,23 @@ def measure_wordcount(value: str, *, chapter: Any = None, case_id: Any = None) -
|
|
|
115
196
|
|
|
116
197
|
|
|
117
198
|
def evaluate_wordcount(
|
|
118
|
-
value: str, target_value: Any, *, chapter: Any = None, case_id: Any = None,
|
|
199
|
+
value: str, target_value: Any, *, chapter: Any = None, case_id: Any = None, author_range: Any = None,
|
|
119
200
|
) -> dict[str, Any]:
|
|
120
201
|
try:
|
|
121
202
|
target = parse_target(target_value)
|
|
122
203
|
except (TypeError, ValueError):
|
|
123
204
|
return invalid_wordcount_result("INVALID_TARGET", chapter=chapter, case_id=case_id)
|
|
205
|
+
try:
|
|
206
|
+
author = normalize_author_range(author_range)
|
|
207
|
+
except WordcountError:
|
|
208
|
+
return invalid_wordcount_result("INVALID_RANGE", chapter=chapter, case_id=case_id, target=target)
|
|
124
209
|
try:
|
|
125
210
|
actual = count_visible_chars(value)
|
|
126
211
|
except (TypeError, ValueError):
|
|
127
212
|
return invalid_wordcount_result("INVALID_BODY", chapter=chapter, case_id=case_id, target=target)
|
|
128
213
|
if actual == 0:
|
|
129
214
|
return invalid_wordcount_result("EMPTY_BODY", chapter=chapter, case_id=case_id, target=target, actual=actual)
|
|
130
|
-
bands = compute_wordcount_bands(target)
|
|
215
|
+
bands = compute_wordcount_bands(target, author)
|
|
131
216
|
internal_pass = bands["internal"]["min"] <= actual <= bands["internal"]["max"]
|
|
132
217
|
user_pass = bands["user"]["min"] <= actual <= bands["user"]["max"]
|
|
133
218
|
status = "internal_pass" if internal_pass else (
|
|
@@ -140,19 +225,22 @@ def evaluate_wordcount(
|
|
|
140
225
|
"internal_band": {**bands["internal"], "status": "pass" if internal_pass else "fail"},
|
|
141
226
|
"user_band": {**bands["user"], "status": "pass" if user_pass else "fail"},
|
|
142
227
|
"signed_error_pct": signed_error, "absolute_error_pct": abs(signed_error),
|
|
228
|
+
"band_source": "author" if author else "default",
|
|
143
229
|
"status": status, "invalid_reason": None,
|
|
144
230
|
}
|
|
145
231
|
|
|
146
232
|
|
|
147
233
|
def checkpoint_wordcount(
|
|
148
|
-
value: str, target_value: Any, *, chapter: Any = None, case_id: Any = None,
|
|
234
|
+
value: str, target_value: Any, *, chapter: Any = None, case_id: Any = None, author_range: Any = None,
|
|
149
235
|
) -> dict[str, Any]:
|
|
150
236
|
target = parse_target(target_value)
|
|
151
237
|
actual = count_visible_chars(value)
|
|
152
|
-
|
|
238
|
+
author = normalize_author_range(author_range)
|
|
239
|
+
user = compute_wordcount_bands(target, author)["user"]
|
|
153
240
|
return {
|
|
154
241
|
"schema": CHECKPOINT_SCHEMA, "metric": METRIC, "chapter": chapter, "case_id": case_id,
|
|
155
242
|
"target": target, "actual": actual, "user_band": user,
|
|
243
|
+
"band_source": "author" if author else "default",
|
|
156
244
|
"remaining_user_range": {
|
|
157
245
|
"min": max(0, user["min"] - actual),
|
|
158
246
|
"max": max(0, user["max"] - actual),
|
|
@@ -160,15 +248,30 @@ def checkpoint_wordcount(
|
|
|
160
248
|
}
|
|
161
249
|
|
|
162
250
|
|
|
251
|
+
_FIELD_ERRORS = {
|
|
252
|
+
"missing": "is missing",
|
|
253
|
+
"invalid": "is not a recognizable number (write e.g. 2300 字 / 约 2300 / 2,300)",
|
|
254
|
+
"conflict": "has conflicting values",
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
|
|
163
258
|
def target_from_outline(value: str) -> int:
|
|
164
259
|
text = normalize_newlines(value)
|
|
165
260
|
if text.startswith("\ufeff"):
|
|
166
261
|
text = text[1:]
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
require(
|
|
170
|
-
|
|
171
|
-
|
|
262
|
+
parsed = parse_target_field(text)
|
|
263
|
+
require(parsed["status"] == "ok", f"字数目标 {_FIELD_ERRORS.get(parsed['status'], '')}: {parsed['raw']}")
|
|
264
|
+
require(parse_metric_field(text)["ok"], f"字数口径 must be {METRIC}")
|
|
265
|
+
return parse_target(parsed["value"])
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def range_from_outline(value: str) -> dict[str, int] | None:
|
|
269
|
+
"""细纲可选行「字数范围:2000-2600」;没写返回 None,写了但写不对就报错而不是静默回落默认带。"""
|
|
270
|
+
parsed = parse_range_field(value)
|
|
271
|
+
if parsed["status"] == "missing":
|
|
272
|
+
return None
|
|
273
|
+
require(parsed["status"] == "ok", f"字数范围 must be one min-max pair with min <= max: {parsed['raw']}")
|
|
274
|
+
return parsed["value"]
|
|
172
275
|
|
|
173
276
|
|
|
174
277
|
def _chapter_number_from_name(name: str, *, outline: bool) -> int | None:
|
|
@@ -192,18 +295,40 @@ def find_chapter_file(directory: Path, chapter: int, *, outline: bool) -> Path:
|
|
|
192
295
|
return matches[0]
|
|
193
296
|
|
|
194
297
|
|
|
195
|
-
def
|
|
298
|
+
def read_chapter_outline(project: Path, chapter: int) -> tuple[Path, str]:
|
|
299
|
+
outline_path = find_chapter_file(project.resolve() / "大纲", chapter, outline=True)
|
|
300
|
+
try:
|
|
301
|
+
return outline_path, outline_path.read_text(encoding="utf-8")
|
|
302
|
+
except (OSError, UnicodeError) as exc:
|
|
303
|
+
raise WordcountError(f"unable to read outline: {exc}") from exc
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def effective_author_range(outline: str, author_range: Any = None) -> dict[str, int] | None:
|
|
307
|
+
"""命令行给了上下限就以它为准(细纲「字数范围」写错也不阻断);否则读细纲。"""
|
|
308
|
+
return normalize_author_range(author_range) if author_range is not None else range_from_outline(outline)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def read_project_chapter(
|
|
312
|
+
project: Path, chapter: int, author_range: Any = None,
|
|
313
|
+
) -> tuple[Path, Path, int, dict[str, int] | None]:
|
|
314
|
+
"""定位本章细纲与正文,读出字数目标和生效的作者字数范围。"""
|
|
315
|
+
outline_path, outline = read_chapter_outline(project, chapter)
|
|
316
|
+
body_path = find_chapter_file(project.resolve() / "正文", chapter, outline=False)
|
|
317
|
+
return outline_path, body_path, target_from_outline(outline), effective_author_range(outline, author_range)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def build_project_wordcount_record(
|
|
321
|
+
project: Path, chapter: int, *, resolution: str, author_range: Any = None,
|
|
322
|
+
) -> dict[str, Any]:
|
|
323
|
+
"""author_range=None 时读细纲「字数范围」;显式传入(命令行 --min-chars/--max-chars)时以它为准。"""
|
|
196
324
|
require(resolution in RESOLUTIONS, f"unsupported wordcount resolution: {resolution}")
|
|
197
|
-
|
|
198
|
-
outline_path = find_chapter_file(root / "大纲", chapter, outline=True)
|
|
199
|
-
body_path = find_chapter_file(root / "正文", chapter, outline=False)
|
|
325
|
+
_, body_path, target, author = read_project_chapter(project, chapter, author_range)
|
|
200
326
|
try:
|
|
201
|
-
target = target_from_outline(outline_path.read_text(encoding="utf-8"))
|
|
202
327
|
body_bytes = body_path.read_bytes()
|
|
203
328
|
body = body_bytes.decode("utf-8")
|
|
204
329
|
except (OSError, UnicodeError) as exc:
|
|
205
330
|
raise WordcountError(f"unable to read chapter files: {exc}") from exc
|
|
206
|
-
result = evaluate_wordcount(body, target, chapter=chapter)
|
|
331
|
+
result = evaluate_wordcount(body, target, chapter=chapter, author_range=author)
|
|
207
332
|
require(result["status"] != "invalid", f"invalid chapter wordcount: {result['invalid_reason']}")
|
|
208
333
|
in_user_band = result["status"] in {"internal_pass", "borderline"}
|
|
209
334
|
require(
|
|
@@ -211,7 +336,7 @@ def build_project_wordcount_record(project: Path, chapter: int, *, resolution: s
|
|
|
211
336
|
or (resolution == "accepted_current_length" and not in_user_band),
|
|
212
337
|
"wordcount resolution does not match the current length",
|
|
213
338
|
)
|
|
214
|
-
|
|
339
|
+
record = {
|
|
215
340
|
"metric": METRIC,
|
|
216
341
|
"target": result["target"],
|
|
217
342
|
"actual": result["actual"],
|
|
@@ -219,19 +344,26 @@ def build_project_wordcount_record(project: Path, chapter: int, *, resolution: s
|
|
|
219
344
|
"resolution": resolution,
|
|
220
345
|
"body_sha256": hashlib.sha256(body_bytes).hexdigest(),
|
|
221
346
|
}
|
|
347
|
+
if author is not None:
|
|
348
|
+
record["author_range"] = dict(author)
|
|
349
|
+
return record
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
_RECORD_FIELDS = {"metric", "target", "actual", "status", "resolution", "body_sha256"}
|
|
222
353
|
|
|
223
354
|
|
|
224
355
|
def normalize_wordcount_record(value: object) -> dict[str, Any]:
|
|
225
356
|
require(isinstance(value, dict), "wordcount record must be an object")
|
|
226
357
|
require(
|
|
227
|
-
set(value)
|
|
358
|
+
set(value) in (_RECORD_FIELDS, _RECORD_FIELDS | {"author_range"}),
|
|
228
359
|
"wordcount record fields are invalid",
|
|
229
360
|
)
|
|
230
361
|
require(value.get("metric") == METRIC, "wordcount metric is unsupported")
|
|
231
362
|
target = parse_target(value.get("target"))
|
|
363
|
+
author = normalize_author_range(value.get("author_range"))
|
|
232
364
|
actual = value.get("actual")
|
|
233
365
|
require(isinstance(actual, int) and not isinstance(actual, bool) and actual >= 1, "wordcount actual is invalid")
|
|
234
|
-
expected = evaluate_wordcount("字" * actual, target)
|
|
366
|
+
expected = evaluate_wordcount("字" * actual, target, author_range=author)
|
|
235
367
|
require(value.get("status") == expected["status"], "wordcount status does not match target and actual")
|
|
236
368
|
resolution = value.get("resolution")
|
|
237
369
|
require(resolution in RESOLUTIONS, "wordcount resolution is unsupported")
|
|
@@ -242,8 +374,32 @@ def normalize_wordcount_record(value: object) -> dict[str, Any]:
|
|
|
242
374
|
return dict(value)
|
|
243
375
|
|
|
244
376
|
|
|
377
|
+
def wordcount_record_drift(project: Path, chapter: int, value: object) -> list[str]:
|
|
378
|
+
"""已提交的字数记录对当前文件过期了哪几项:只核对记录钉住的正文字节与字数目标。
|
|
379
|
+
|
|
380
|
+
作者事后在细纲补/改「字数范围」只影响下次计数的判定带,不让已提交的记录过期。
|
|
381
|
+
"""
|
|
382
|
+
record = normalize_wordcount_record(value)
|
|
383
|
+
root = project.resolve()
|
|
384
|
+
outline_path = find_chapter_file(root / "大纲", chapter, outline=True)
|
|
385
|
+
body_path = find_chapter_file(root / "正文", chapter, outline=False)
|
|
386
|
+
try:
|
|
387
|
+
outline = outline_path.read_text(encoding="utf-8")
|
|
388
|
+
body_bytes = body_path.read_bytes()
|
|
389
|
+
except (OSError, UnicodeError) as exc:
|
|
390
|
+
raise WordcountError(f"unable to read chapter files: {exc}") from exc
|
|
391
|
+
drift = []
|
|
392
|
+
if hashlib.sha256(body_bytes).hexdigest() != record["body_sha256"]:
|
|
393
|
+
drift.append("正文")
|
|
394
|
+
if target_from_outline(outline) != record["target"]:
|
|
395
|
+
drift.append("字数目标")
|
|
396
|
+
return drift
|
|
397
|
+
|
|
398
|
+
|
|
245
399
|
def validate_current_wordcount_record(project: Path, chapter: int, value: object) -> dict[str, Any]:
|
|
246
400
|
record = normalize_wordcount_record(value)
|
|
247
|
-
current = build_project_wordcount_record(
|
|
401
|
+
current = build_project_wordcount_record(
|
|
402
|
+
project, chapter, resolution=record["resolution"], author_range=record.get("author_range"),
|
|
403
|
+
)
|
|
248
404
|
require(record == current, "wordcount record is stale for the current body or target")
|
|
249
405
|
return record
|