@oh-story/dsh 0.1.10 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- 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 +1 -1
- 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
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
# 结构迁移映射规则(短篇)
|
|
2
2
|
|
|
3
|
-
Phase 3-S
|
|
3
|
+
Phase 3-S 短篇结构迁移只读本文件和 [format-and-structure.md](format-and-structure.md):将 `拆文库/{导入书名}/` 短篇拆文产物转换为 `{短篇标题}/` 短篇工程结构,供 `story-short-write` Phase 3 无缝接手续写。外部对标与作者交代读导入记录(`{书目录}/.story/work/导入记录.md`)。
|
|
4
|
+
|
|
5
|
+
## 迁移步骤
|
|
6
|
+
|
|
7
|
+
目标工程只有 `{短篇标题}/设定.md`(核心框架 + 本书续写基线)、`小节大纲.md`、`正文.md` 三个单文件,外加可选的 `对标/{对标书名}/`(仅外部对标引用视图:拆文报告、情节节点、写作手法)。**短篇工程与长篇完全不同**:正文是单文件、不切章,**不产** `追踪/`、`大纲/`、`正文/` 等长篇目录,迁移时严禁误建。
|
|
8
|
+
|
|
9
|
+
1. **正文迁移**:将 `拆文库/{导入书名}/原文/` 的全文迁移为单文件 `{标题}/正文.md`,按 format-and-structure.md 规范化格式(小节标记 `###1.`、段间仅单换行、对话引号按项目/平台约定统一)。原文已是成稿,不重写内容,只规范格式。
|
|
10
|
+
2. **设定生成**:从 `拆文报告.md`、`写作手法.md` 反推 `{标题}/设定.md`,含核心框架(对齐 story-short-write 核心框架:基本信息、一句话梗概、核心反转、情绪设计、人设速写)与本书续写基线(已写内容的故事结构、情绪节奏、核心反转机制、既有写作手法;这是本书内部上下文,不是对标摘要)两个区块,规则见下方「设定.md 反推规则」。导入记录里作者交代的偏好与红线一并写进续写基线。
|
|
11
|
+
3. **小节大纲生成**:从 `情节节点.md` 的功能分段反推 `{标题}/小节大纲.md`,按开头段/铺垫段/升级段/反转段/结尾段映射,用 story-short-write 的固定 12 列表格,每节一行,填法见下方「小节大纲表格」。相关情节点可由同一动作链或对话同时兑现,不为凑数量拆成多个子事件;原文判断不出的格子写 `[待补充]`,不套用长篇完整章节蓝图。
|
|
12
|
+
4. **外部对标引用视图(可选)**:仅当导入记录已显式绑定外部 `{对标书名}` 时,才把 `拆文库/{对标书名}/` 同步为 `{标题}/对标/{对标书名}/`;没有绑定则跳过。不得把 `拆文库/{导入书名}/` 整体复制进 `对标/`。
|
|
13
|
+
|
|
14
|
+
迁完按文末质量检查清单自检,在导入记录勾「结构迁移」,直接进入 Phase 5 汇报。
|
|
4
15
|
|
|
5
16
|
> 长篇迁移规则见 `structure-mapping-long.md`。
|
|
6
17
|
|
|
@@ -48,7 +59,7 @@ Phase 3-S 短篇结构迁移的详细映射规则。将 `拆文库/{导入书名
|
|
|
48
59
|
- 标题:{导入书名}
|
|
49
60
|
- 目标字数:{原文实际字数} 字
|
|
50
61
|
- 目标平台:{从拆文报告提取,无则填 [待补充]}
|
|
51
|
-
-
|
|
62
|
+
- 目标情绪:{从拆文报告「情感线/爆点分析」提取读者预期感受}
|
|
52
63
|
|
|
53
64
|
### 一句话梗概
|
|
54
65
|
{主角 + 困境 + 反转 + 情绪落点(从拆文报告故事核提取)}
|
|
@@ -108,20 +119,21 @@ Phase 3-S 短篇结构迁移的详细映射规则。将 `拆文库/{导入书名
|
|
|
108
119
|
| 反转段 | 高潮(核心反转引爆) | 通常 1-2 个小节,情绪峰值 |
|
|
109
120
|
| 结尾段 | 结局(收束/情绪落点) | 通常 1-2 个小节 |
|
|
110
121
|
|
|
111
|
-
###
|
|
122
|
+
### 小节大纲表格
|
|
123
|
+
|
|
124
|
+
`小节大纲.md` 用 story-short-write 的固定 12 列表格,每节一行,列名和顺序不改:
|
|
112
125
|
|
|
113
126
|
```markdown
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
- 核心事件:{从情节节点提取该分段的主要情节点}
|
|
118
|
-
- 情绪目标:{对应情感曲线节点的情绪词}
|
|
119
|
-
- 章首钩子:[待补充]
|
|
120
|
-
- 章尾钩子:[待补充]
|
|
121
|
-
- 参考字数:{按原文对应段落字数估算}
|
|
127
|
+
| 结构段/五段功能 | 主事件 | 情节推进 | 情绪 | 人物/关系变化 | 因果/逻辑链 | 读者新获知什么 | 结尾承接/钩子 | 伏笔/物件 | 场景形态 | 对白作用 | 目标字数 |
|
|
128
|
+
|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
129
|
+
| 开头 | 从情节节点提取的主要情节点 | 具体发生了什么{发现} -> 具体发生了什么{冲突} | 前状态→后状态 | 本节前后变化,或 无(本节改变:…) | 原因 → 行动 → 结果 → 后果 | [待补充] | [待补充] | [待补充] | [待补充] | [待补充] | 按原文该节实际字数 |
|
|
122
130
|
```
|
|
123
131
|
|
|
124
|
-
|
|
132
|
+
- `结构段/五段功能` 填 开头/铺垫/升级/反转/结尾。
|
|
133
|
+
- `情节推进` 每个推进单元带 `{对话}` `{冲突}` `{伏笔}` `{回忆}` `{发现}` `{递进}` 之一,有先后用 `->` 连接,按原文实际发生的写,不为凑数拆分。
|
|
134
|
+
- 从原文判断不出的格子写 `[待补充]`,留给作者或续写时填写;不写 `{}` 模板占位。
|
|
135
|
+
- `目标字数` 写原文该节实际字数,各节之和与 `设定.md` 的目标字数对得上。
|
|
136
|
+
- 付费点在哪一节末能从原文认出来时,在该行 `结尾承接/钩子` 标「付费点」;认不出写 `[待补充]`。
|
|
125
137
|
|
|
126
138
|
### 小节数量推算
|
|
127
139
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## 初始化事务
|
|
4
4
|
|
|
5
|
-
新书从第 0
|
|
5
|
+
新书从第 0 章初始化:把下面的 JSON 写到 `{书目录}/.story/work/init.json`,跑 `{PYTHON} {skill 根}/scripts/tracking_commit.py init --project {书目录} --input {书目录}/.story/work/init.json`,成功后删掉临时文件。`story-import` 导入已有小说时把最后完整章写入 `last_chapter=N`;第 1..N 章不伪造日更记录,常规续写从 N+1 章开始。
|
|
6
6
|
|
|
7
7
|
```json
|
|
8
8
|
{
|
|
@@ -21,22 +21,23 @@ Markdown 只负责给作者和 Agent 阅读,工具不再反向解析 Markdown
|
|
|
21
21
|
{PYTHON} {当前 skill 根}/scripts/tracking_commit.py init --project {书项目根} --input {书项目根}/.story/work/init.json
|
|
22
22
|
{PYTHON} {当前 skill 根}/scripts/tracking_commit.py check --project {书项目根}
|
|
23
23
|
{PYTHON} {当前 skill 根}/scripts/tracking_commit.py draft --project {书项目根} --chapter {N}
|
|
24
|
-
{PYTHON} {story-long-write skill 根}/scripts/storyctl.py chapter check --project {书项目根} --chapter {N}
|
|
24
|
+
{PYTHON} {story-long-write skill 根}/scripts/storyctl.py chapter check --project {书项目根} --chapter {N} [--min-chars {下限} --max-chars {上限}]
|
|
25
25
|
{PYTHON} {story-long-write skill 根}/scripts/storyctl.py chapter commit --project {书项目根} --chapter {N} --input {书项目根}/.story/work/第{NNN}章/tracking.json
|
|
26
26
|
{PYTHON} {story-long-write skill 根}/scripts/storyctl.py chapter accept-current-length --project {书项目根} --chapter {N} --input {书项目根}/.story/work/第{NNN}章/tracking.json
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
- `init`:只在 `_tracking-state.json` 不存在时执行,绝不覆盖已初始化项目。
|
|
30
|
-
- `draft`:按当前 state 把逐章事务预填到 `.story/work/第NNN章/tracking.json`(修订号、模式、章名、`context`
|
|
31
|
-
- `wordcount
|
|
32
|
-
- `
|
|
33
|
-
- `chapter
|
|
34
|
-
- `chapter
|
|
30
|
+
- `draft`:按当前 state 把逐章事务预填到 `.story/work/第NNN章/tracking.json`(修订号、模式、章名、`context` 四项当前值),并输出各文本字段的字数上限与在场核心角色的当前快照。新章(append)调用方只填 `delta` 与有变化角色的快照;已提交章(revision)的 `delta` 与相关角色快照按该章现有逐章记录预填(伏笔、时间线取当前值),调用方在上面改成修订后的完整记录,不清空重写。
|
|
31
|
+
- 作者字数范围:细纲可写一行 `字数范围:2000-2600`(作者明确给了上下限时);本轮临时给的范围用 `--min-chars` / `--max-chars`(两者同给,优先于细纲)。`wordcount check` / `checkpoint` 与 `chapter check` / `commit` / `accept-current-length` 都认,给了就同时替代默认 ±12% 内部带与 ±15% 用户带,结果里 `band_source` 为 `author`,提交的字数记录多一项 `author_range`。
|
|
32
|
+
- `wordcount measure` / `wordcount checkpoint`:纯测量入口;不写正文、不写 tracking、不做语义判断。长篇正文只在前组写完后由主会话带 `--project` 调用一次 `checkpoint`(目标与字数范围读细纲,见 workflow-chapter 步骤 6),整章长度由 `chapter check` 收口;`remaining_user_range` 按生效区间(作者区间或默认用户带)计算。
|
|
33
|
+
- `chapter check`:重新读取当前正文与细纲目标,返回确定性长度状态、现有 blocking quality、`state_revision` 和当前可执行动作,不保存 approval。顶层 `status`:`ready`(可提交,exit 0)/ `needs_decision`(长度带外,按 `available_actions` 交作者,exit 0)/ `blocked`(有 blocking 正文问题,exit 1)/ `invalid`(正文为空等,exit 1)/ `tool_unavailable`(找不到 node 或检测脚本,见 `quality.tool_errors`,不是正文问题,exit 3)。参数错误或文件缺失输出 `story-chapter-error/v1`(`status: error`,exit 2)。`under` 不提供自动补写;`over` 额外返回一次净删型 `compress-once` 及回到生效区间所需的删除字数。`quality.semantic_advisories` 是语义类 advisory 条数(检测器标 `review: mechanical` 的不计)。
|
|
34
|
+
- `chapter commit`:再次读取当前文件、重新计数并重跑 blocking quality;只接受生效区间内的章节,把简短字数记录与逐章事务一起原子提交。新章与修订都走它。
|
|
35
|
+
- `chapter accept-current-length`:只接受带外但 quality pass 的章节;接受动作发生时重新读取、重新计数并立即原子提交,不保存可陈旧的历史决议。低于字数目标一半(`BELOW_ACCEPT_FLOOR`),或超长却还没做过那一次压缩(`COMPRESSION_REQUIRED`),都拒绝;作者明确拍板时加 `--force`。
|
|
35
36
|
- `check`:严格验证 state schema、逐章记录连续性/规范名/体积、固定 7 栏、角色快照硬上限、派生文件集合,以及所有派生视图与 state 的逐字一致性。
|
|
36
37
|
|
|
37
38
|
每本书由 `追踪/.tracking-commit.lock` 串行写事务,`expected_state_revision` 再拒绝基于旧状态构造的 stale transaction。两个不同事务并发时至多一个修订成功。字数记录也在锁内对当前正文和目标重新验证,正文或目标变化会让预先构造的记录直接失败。
|
|
38
39
|
|
|
39
|
-
事务 JSON 是临时输入,不是项目产物:只写在书目录 `.story/work/` 下(逐章事务放 `.story/work/第NNN章/tracking.json`),不写系统 `/tmp`、书根、`大纲/` 或 `正文/`;成功前必须保留,提交成功后即可删除(`storyctl.py chapter commit` / `accept-current-length` 成功时自动删除该章目录;直接调用 `tracking_commit.py commit`
|
|
40
|
+
事务 JSON 是临时输入,不是项目产物:只写在书目录 `.story/work/` 下(逐章事务放 `.story/work/第NNN章/tracking.json`),不写系统 `/tmp`、书根、`大纲/` 或 `正文/`;成功前必须保留,提交成功后即可删除(`storyctl.py chapter commit` / `accept-current-length` 成功时自动删除该章目录;直接调用 `tracking_commit.py commit` 时由调用方删除。已有字数记录的章节正文或目标改过后,`tracking_commit.py commit` 拒绝修订事务,须走 `storyctl.py chapter commit`)——之后 `check` 失败按下文提交 `mode=revision` 新事务修复,不需要原事务。若文件写入失败,`_tracking-state.json` 尚未推进;修正环境后直接重跑**同一份** `commit`。append 重跑只接受内容完全相同的既有逐章记录,不维护 `dirty/pending/repair` 状态机。
|
|
40
41
|
|
|
41
42
|
校验失败与写入失败处理方式不同:校验失败(字段非法、退役结构、容量超限)要按报错改事务本身,重跑同一份结果不变。派生视图被手改或外部改动导致 `check` 报 `derived view differs from _tracking-state.json` 时,重新提交**该章**的 `mode=revision` 事务让工具整份重建,`expected_state_revision` 取 `追踪/_tracking-state.json` 的 `state_revision` 字段——`check` 失败时只往 stderr 打 ERROR,不输出 JSON;不手改派生文件,也不删 `_tracking-state.json` 重来。手写出的逐章记录会让同章 `append` 永久报 `chapter delta N already exists with different content`——删掉那个手写文件后重跑原事务即可。
|
|
42
43
|
|
|
@@ -46,7 +47,7 @@ Markdown 只负责给作者和 Agent 阅读,工具不再反向解析 Markdown
|
|
|
46
47
|
|
|
47
48
|
仅新书或导入初始化时,执行 `init` 前必须完整读取 [tracking-initialization.md](tracking-initialization.md),按其中原始 JSON 与导入边界构造事务。已有项目续写直接使用下方逐章事务,不重复读初始化示例。
|
|
48
49
|
|
|
49
|
-
调用方的逐章 JSON 不写 `wordcount`;正式入口 `chapter commit` 或 `chapter accept-current-length` 在提交当下生成并注入。最终 state 只为已提交章节保留 `metric / target / actual / status / resolution / body_sha256
|
|
50
|
+
调用方的逐章 JSON 不写 `wordcount`;正式入口 `chapter commit` 或 `chapter accept-current-length` 在提交当下生成并注入。最终 state 只为已提交章节保留 `metric / target / actual / status / resolution / body_sha256`(生效了作者字数范围时另加 `author_range`),不保存 MEASURE/RESOLVE 事件、ID 链、policy fingerprint 或独立 chapter state。
|
|
50
51
|
|
|
51
52
|
## 逐章事务
|
|
52
53
|
|
|
@@ -121,12 +122,13 @@ Markdown 只负责给作者和 Agent 阅读,工具不再反向解析 Markdown
|
|
|
121
122
|
- `character_snapshots` 中出现的角色视为核心复用角色,必须同时出现在 `character_changes`;已经建立快照的核心角色再次变化时必须提交新快照。
|
|
122
123
|
- 角色快照的四个列表不限制条数,只限制单项长度和最终文件总字节:目标 ≤4096 字节,超过警告;硬上限 8192 字节,超过则在任何写入前拒绝。
|
|
123
124
|
- 没有快照的角色变化视为临时角色,不建立状态文件;`context.active_character_names` 最多 6 人且必须已有当前快照。
|
|
125
|
+
- `context.long_term_constraints` 最多 6 条:要加第 7 条先合并语义重叠项或请作者取舍,不自动删旧约束;永久生效的设定裁定进它,短期强承诺进 `next_chapter_commitments`,过程决策不进续写状态卡。
|
|
124
126
|
- `context.long_term_constraints` 和 `context.continuity_risks` 是整份提交的当前值。凡是上一版有、本次没有的条目,必须逐条列进 `delta.retired_context_items`,否则工具在任何写入前拒绝——漏写不会被当成删除。实际退役的条目由工具写进本章逐章记录的 `## 本章退役登记`,随后仍可回查。
|
|
125
127
|
- 不再复用的核心角色写进 `delta.retired_characters`:工具删除其当前快照与 `角色状态/{角色名}.md`,并在逐章记录留档。同一事务里不能既退役又提交快照,也不能退役仍列在 `context.active_character_names` 的角色。角色阵亡/退场这一章,把变化照写进 `character_changes` 即可,本章退役的角色不必再交一份马上要删的快照,逐章记录仍按核心角色标注。退役只表示不再进入热上下文,正文与逐章记录不受影响。
|
|
126
128
|
- 两类退役都只能在 `mode=append` 提交。退役表示「从此刻起离开当前状态」,而修订事务的逐章记录属于被改写的旧章,落在那里会谎报退役发生的章节;`mode=revision` 必须原样重交当前全部上下文条目,需要退役就放到下一次 append。
|
|
127
129
|
- `伏笔.md` 只呈现已经埋设过的当前状态。未来规划仍留在大纲。
|
|
128
130
|
- `timeline_events.action` 可为 `upsert/delete`。`未揭示` 的 `reveal_chapter` 必须为 `null`;部分/完全揭示只能填写已经发生的实际章节。
|
|
129
|
-
- `mode=revision`
|
|
131
|
+
- `mode=revision` 时,逐章记录必须重算为修订后该章仍然成立的完整连续性记录(用 `draft` 预填的草稿改,漏写的项会从记录里消失,工具在 stderr 警告被清空的栏目;原有的退役登记原样保留);当前角色、伏笔、时间线和上下文则提交受影响对象截至最新已写章的当前值。
|
|
130
132
|
- 修订导入截止章内的正文时,会新增或覆盖该章的逐章记录;`imported_through_chapter` 不变。
|
|
131
133
|
|
|
132
134
|
## 续写状态卡固定格式
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""build_outline_brief.py — 拼出导入长篇「逐批反推细纲」的任务包。
|
|
3
|
+
|
|
4
|
+
用法:
|
|
5
|
+
python build_outline_brief.py --project <书目录> --chapters A-B [--analysis <拆文库/导入书名>]
|
|
6
|
+
|
|
7
|
+
导入按作者时间线分时刻(见 SKILL.md「时刻表与交接」)。细纲按批反推,负载按批计、不随全书章数增长。
|
|
8
|
+
有 story-architect 时主会话只跑本脚本、把任务包路径交给它(它没有执行命令的权限),没有时主会话
|
|
9
|
+
自己读包写本批。任务包写在书内 `.story/work/排纲/`,是这次任务的输入数据,不是 reference。
|
|
10
|
+
|
|
11
|
+
包里只有本批需要的东西,执行者不再整读每章约 4K 字的摘要:
|
|
12
|
+
- 反推规则与细纲模板:references/outline-reverse-rules.md 全文;
|
|
13
|
+
- 本批每章:正文路径、按 visible_chars_v1 测好的历史长度(与写作时章节检查同一口径,
|
|
14
|
+
story-architect 不能自己跑测量),以及从拆文库摘要里抽出的、写细纲真正要用的字段
|
|
15
|
+
(关键事件、因果、局面结果、涉及、信息变化、状态变化、章尾钩子,情节点只取「标题|类型|基调」);
|
|
16
|
+
拆文库没有摘要时改用 `.story/work/简化摘要/第NNN章.md`(超过 200 章的书,未深拆章节的简化摘要);
|
|
17
|
+
- 卷纲「剧情单元(反推)」表里与本批章节重叠的行(只按表头「章节范围」那一列判断重叠)。
|
|
18
|
+
本书设定与作者已定的事不进包,执行者按规则定点读项目文件与 `.story/work/导入记录.md`。
|
|
19
|
+
|
|
20
|
+
批大小按总量定:请求 10–20 章(全书最后一批可以更少);执行者要读的总量(任务包 + 没有摘要、
|
|
21
|
+
只能读正文的章)超过约 25K 字时,从请求范围末尾往前少排几章(可少于 10 章,至少 1 章)。
|
|
22
|
+
|
|
23
|
+
stdout 输出一行 JSON:任务包路径、本批实际章节范围与下一批起点、执行者要读的总量 chars(去空白)、
|
|
24
|
+
本批每章长度、没找到摘要的章。
|
|
25
|
+
Exit: 0 = 已写出;2 = 参数错误、正文或卷纲缺失、长度测不出。
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import argparse
|
|
31
|
+
import json
|
|
32
|
+
import re
|
|
33
|
+
import sys
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
|
|
36
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
37
|
+
from wordcount_core import METRIC, WordcountError, find_chapter_file, measure_wordcount # noqa: E402
|
|
38
|
+
|
|
39
|
+
RULES = Path(__file__).resolve().parent.parent / "references" / "outline-reverse-rules.md"
|
|
40
|
+
WORK_DIR = (".story", "work", "排纲")
|
|
41
|
+
BRIEF_SUMMARY_DIR = (".story", "work", "简化摘要")
|
|
42
|
+
MIN_BATCH, MAX_BATCH = 10, 20
|
|
43
|
+
LOAD_LIMIT = 25000 # 执行者一次要读的量(去空白字数);再加上 agent 模板或 SKILL 入口仍在 35K 内
|
|
44
|
+
# 摘要里写细纲要用的字段(与 story-long-analyze 摘要投影同名);旧版摘要的同义标签放在别名里。
|
|
45
|
+
SUMMARY_FIELDS = (
|
|
46
|
+
("关键事件", ()),
|
|
47
|
+
("因果", ()),
|
|
48
|
+
("局面结果", ()),
|
|
49
|
+
("涉及", ("涉及人物", "出场人物")),
|
|
50
|
+
("信息变化", ()),
|
|
51
|
+
("状态变化", ()),
|
|
52
|
+
("章尾钩子", ("卡点与伏笔",)),
|
|
53
|
+
)
|
|
54
|
+
FIELD_NAMES = "|".join(name for field in SUMMARY_FIELDS for name in (field[0], *field[1]))
|
|
55
|
+
# `**标签**:`(当前投影与旧版加粗写法)或不加粗的 `- 关键事件:`(只认上面这些字段名)。
|
|
56
|
+
LABEL_RE = re.compile(r"^\s*(?:[-*+]\s*)?(?:\*\*([^*]+?)\*\*|(%s))\s*[::((]" % FIELD_NAMES)
|
|
57
|
+
POINT_HEADER_RE = re.compile(r"^P(\d+)\s+\*\*(.+?)\*\*\s*[::]\s*(.+)$")
|
|
58
|
+
POINT_TAG_RE = re.compile(r"^主题标签\s*[::]?\s*[^||]*?\s*[||]\s*基调\s*[::]?\s*(.*?)\s*$")
|
|
59
|
+
# 「章节范围」单元格的一段:第X章—第Y章 / 第X-Y章 / 第X章 / 章X-Y / X-Y。
|
|
60
|
+
SPAN_CELL_RE = re.compile(
|
|
61
|
+
r"^\s*(?:第|章)?\s*(\d+)\s*章?\s*(?:(?:-|~|~|—|–|-|至|到)+\s*第?\s*(\d+)\s*章?)?\s*$"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def emit(text: str, stream) -> None:
|
|
66
|
+
"""统一按 UTF-8 写出:Windows 控制台默认 GBK/ASCII 时直接 print 中文会崩。"""
|
|
67
|
+
stream.buffer.write((text + "\n").encode("utf-8"))
|
|
68
|
+
stream.flush()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def weight(text: str) -> int:
|
|
72
|
+
return len(re.sub(r"\s", "", text))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def parse_range(value: str) -> tuple[int, int]:
|
|
76
|
+
match = re.fullmatch(r"\s*(\d+)\s*(?:-\s*(\d+))?\s*", value or "")
|
|
77
|
+
if not match:
|
|
78
|
+
raise SystemExit("ERROR: --chapters 写成 A-B,如 1-20")
|
|
79
|
+
first = int(match.group(1))
|
|
80
|
+
last = int(match.group(2) or first)
|
|
81
|
+
if first < 1 or last < first:
|
|
82
|
+
raise SystemExit("ERROR: --chapters 范围无效")
|
|
83
|
+
return first, last
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def last_body_chapter(project: Path) -> int:
|
|
87
|
+
numbers = [int(m.group(1)) for p in (project / "正文").glob("第*章*.md")
|
|
88
|
+
if "_原稿_" not in p.name and (m := re.match(r"^第0*(\d+)章", p.name))]
|
|
89
|
+
return max(numbers, default=0)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def check_batch_size(first: int, last: int, final: int) -> None:
|
|
93
|
+
size = last - first + 1
|
|
94
|
+
if size > MAX_BATCH:
|
|
95
|
+
raise SystemExit(f"ERROR: 一批细纲最多 {MAX_BATCH} 章,拆成两批")
|
|
96
|
+
# 只有全书最后一批可以不足 10 章(包太大时脚本自己少排几章不受此限)。
|
|
97
|
+
if size < MIN_BATCH and last < final:
|
|
98
|
+
raise SystemExit(f"ERROR: 一批细纲至少 {MIN_BATCH} 章(全书最后一批除外),把范围扩到第{first}-{min(final, first + MIN_BATCH - 1)}章")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def shown_path(path: Path, base: Path) -> str:
|
|
102
|
+
"""项目根下的路径写相对路径(story-architect 从项目根解析),在外面的写绝对路径。"""
|
|
103
|
+
try:
|
|
104
|
+
return path.resolve().relative_to(base.resolve()).as_posix()
|
|
105
|
+
except ValueError:
|
|
106
|
+
return str(path)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def measure(project: Path, chapter: int) -> tuple[Path, str, int]:
|
|
110
|
+
try:
|
|
111
|
+
body = find_chapter_file(project / "正文", chapter, outline=False)
|
|
112
|
+
text = body.read_bytes().decode("utf-8")
|
|
113
|
+
except (WordcountError, OSError, UnicodeError) as exc:
|
|
114
|
+
raise SystemExit(f"ERROR: 第{chapter}章正文读不到({exc}),先完成正文迁移")
|
|
115
|
+
result = measure_wordcount(text, chapter=chapter)
|
|
116
|
+
if result["status"] != "measured":
|
|
117
|
+
raise SystemExit(f"ERROR: 第{chapter}章长度测不出({result['invalid_reason']})")
|
|
118
|
+
return body, text, result["actual"]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ---------- 摘要字段抽取 ----------
|
|
122
|
+
|
|
123
|
+
def label_blocks(text: str) -> dict[str, str]:
|
|
124
|
+
"""把摘要按 `**标签**:` 切块;情节点之后的内容另行解析,不进字段块。"""
|
|
125
|
+
blocks: dict[str, list[str]] = {}
|
|
126
|
+
current = None
|
|
127
|
+
for line in text.replace("\r\n", "\n").split("\n"):
|
|
128
|
+
stripped = line.strip()
|
|
129
|
+
if POINT_HEADER_RE.match(stripped) or stripped.startswith("#"):
|
|
130
|
+
current = None
|
|
131
|
+
continue
|
|
132
|
+
match = LABEL_RE.match(line)
|
|
133
|
+
if match:
|
|
134
|
+
current = (match.group(1) or match.group(2)).strip()
|
|
135
|
+
rest = re.split(r"[::]", line, maxsplit=1)
|
|
136
|
+
blocks.setdefault(current, []).append(rest[1] if len(rest) > 1 else "")
|
|
137
|
+
continue
|
|
138
|
+
if current is not None:
|
|
139
|
+
blocks[current].append(line)
|
|
140
|
+
return {name: "\n".join(lines).strip() for name, lines in blocks.items()}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def flatten(value: str) -> str:
|
|
144
|
+
"""列表/表格压成一行:表格只取第一列(人物名),跳过表头与分隔行。"""
|
|
145
|
+
rows = [line.strip() for line in value.split("\n") if line.strip()]
|
|
146
|
+
if rows and all(row.startswith("|") for row in rows):
|
|
147
|
+
names = [row.strip("|").split("|")[0].strip() for row in rows[2:]]
|
|
148
|
+
return "、".join(name for name in names if name)
|
|
149
|
+
return " ".join(re.sub(r"^(?:\d+[.、]|[-*+])\s*", "", row) for row in rows)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def summary_points(text: str) -> list[str]:
|
|
153
|
+
lines = text.replace("\r\n", "\n").split("\n")
|
|
154
|
+
points = []
|
|
155
|
+
for index, line in enumerate(lines):
|
|
156
|
+
header = POINT_HEADER_RE.match(line.strip())
|
|
157
|
+
if not header:
|
|
158
|
+
continue
|
|
159
|
+
kind = re.split(r"[||]", header.group(3))[0].strip()
|
|
160
|
+
kind = kind[2:].lstrip(" ::") if kind.startswith("类型") else kind
|
|
161
|
+
tone = "—"
|
|
162
|
+
for follow in lines[index + 1:index + 12]:
|
|
163
|
+
if POINT_HEADER_RE.match(follow.strip()):
|
|
164
|
+
break
|
|
165
|
+
tag = POINT_TAG_RE.match(follow.strip())
|
|
166
|
+
if tag:
|
|
167
|
+
tone = tag.group(1) or "—"
|
|
168
|
+
break
|
|
169
|
+
points.append(f"P{header.group(1)} {header.group(2).strip()}|{kind}|{tone}")
|
|
170
|
+
return points
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def summary_lines(text: str) -> list[str]:
|
|
174
|
+
blocks = label_blocks(text)
|
|
175
|
+
out = []
|
|
176
|
+
for name, aliases in SUMMARY_FIELDS:
|
|
177
|
+
raw = next((blocks[key] for key in (name, *aliases) if blocks.get(key)), "")
|
|
178
|
+
out.append(f"- {name}:{flatten(raw) or '(摘要未写)'}")
|
|
179
|
+
points = summary_points(text)
|
|
180
|
+
out.append("- 情节点(标题|类型|基调):" + (";".join(points) if points else "(摘要里没有可读的情节点)"))
|
|
181
|
+
return out
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def chapter_block(project: Path, analysis: Path, chapter: int) -> tuple[str, int, int, bool]:
|
|
185
|
+
"""返回(本章块、历史长度、执行者还需另读的正文量、是否缺摘要)。"""
|
|
186
|
+
body, body_text, actual = measure(project, chapter)
|
|
187
|
+
head = [f"### 第{chapter}章", "",
|
|
188
|
+
f"- 正文:`{shown_path(body, project.parent)}`;字数目标 {actual}({METRIC})"]
|
|
189
|
+
summary = analysis / "章节" / f"第{chapter}章_摘要.md"
|
|
190
|
+
brief = project.joinpath(*BRIEF_SUMMARY_DIR) / f"第{chapter:03d}章.md"
|
|
191
|
+
if summary.is_file():
|
|
192
|
+
lines = [f"- 摘要要点(抽自 `{shown_path(summary, project.parent)}`):"] + [
|
|
193
|
+
" " + line for line in summary_lines(summary.read_text(encoding="utf-8").lstrip(""))]
|
|
194
|
+
return "\n".join(head + lines), actual, 0, False
|
|
195
|
+
if brief.is_file():
|
|
196
|
+
text = brief.read_text(encoding="utf-8").lstrip("").strip()
|
|
197
|
+
return "\n".join(head + ["- 简化摘要(未深拆章节):", "", text]), actual, 0, False
|
|
198
|
+
note = "- 摘要:未找到,读该章正文反推,证据不足处写 [待补充]"
|
|
199
|
+
return "\n".join(head + [note]), actual, weight(body_text), True
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# ---------- 卷纲剧情单元 ----------
|
|
203
|
+
|
|
204
|
+
def parse_span_cell(cell: str) -> list[tuple[int, int]]:
|
|
205
|
+
"""只解析「章节范围」这一格;认不出的写法返回空,不去别的列找数字。"""
|
|
206
|
+
spans = []
|
|
207
|
+
for part in re.split(r"[、,,;;/]", cell):
|
|
208
|
+
match = SPAN_CELL_RE.match(part)
|
|
209
|
+
if not match:
|
|
210
|
+
continue
|
|
211
|
+
start = int(match.group(1))
|
|
212
|
+
end = int(match.group(2) or start)
|
|
213
|
+
if start <= end:
|
|
214
|
+
spans.append((start, end))
|
|
215
|
+
return spans
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def split_row(row: str) -> list[str]:
|
|
219
|
+
return [cell.strip() for cell in row.strip().strip("|").split("|")]
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def unit_rows(project: Path, first: int, last: int) -> list[str]:
|
|
223
|
+
"""从各卷纲的「剧情单元」表里取与本批章节重叠的行,连同表头。"""
|
|
224
|
+
volumes = sorted((project / "大纲").glob("卷纲_第*卷.md"))
|
|
225
|
+
if not volumes:
|
|
226
|
+
raise SystemExit("ERROR: 大纲/ 下没有卷纲,先在结构迁移时刻写定卷纲")
|
|
227
|
+
out: list[str] = []
|
|
228
|
+
for volume in volumes:
|
|
229
|
+
lines = volume.read_text(encoding="utf-8").splitlines()
|
|
230
|
+
start = next((i for i, line in enumerate(lines) if re.match(r"^#+\s*剧情单元", line)), None)
|
|
231
|
+
if start is None:
|
|
232
|
+
continue
|
|
233
|
+
table: list[str] = []
|
|
234
|
+
for line in lines[start + 1:]:
|
|
235
|
+
if line.lstrip().startswith("|"):
|
|
236
|
+
table.append(line)
|
|
237
|
+
elif table or line.strip():
|
|
238
|
+
break # 表格结束,或标题下先出现了非表格内容
|
|
239
|
+
if len(table) < 3:
|
|
240
|
+
continue
|
|
241
|
+
column = next((i for i, cell in enumerate(split_row(table[0])) if "章节范围" in cell), None)
|
|
242
|
+
if column is None:
|
|
243
|
+
continue
|
|
244
|
+
hits = []
|
|
245
|
+
for row in table[2:]:
|
|
246
|
+
cells = split_row(row)
|
|
247
|
+
spans = parse_span_cell(cells[column]) if column < len(cells) else []
|
|
248
|
+
if any(a <= last and b >= first for a, b in spans):
|
|
249
|
+
hits.append(row)
|
|
250
|
+
if hits:
|
|
251
|
+
out += [f"来源:`{project.name}/大纲/{volume.name}`", "", *table[:2], *hits, ""]
|
|
252
|
+
if not out:
|
|
253
|
+
raise SystemExit(f"ERROR: 卷纲「剧情单元」表里找不到覆盖第{first}-{last}章的行(表头要有「章节范围」列,写成「第X-Y章」),先补卷纲")
|
|
254
|
+
return out
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
# ---------- 拼包 ----------
|
|
258
|
+
|
|
259
|
+
def render(project: Path, first: int, last: int, blocks: list[str]) -> str:
|
|
260
|
+
head = (f"# 任务包:反推第{first}-{last}章细纲(导入)\n\n"
|
|
261
|
+
f"只交付 `{project.name}/大纲/细纲_第{first:03d}章.md` 到 `细纲_第{last:03d}章.md`,每章一个文件(已有的同名文件按本包重写覆盖);"
|
|
262
|
+
f"不改正文、卷纲和 `设定/`,不建 `追踪/`。证据用下方每章的摘要要点,不整读拆文库摘要;某个字段要点里没有、"
|
|
263
|
+
f"又必须写实际内容时,只回看该章正文的相关段落。作者已定的事在 `{project.name}/.story/work/导入记录.md`;"
|
|
264
|
+
"不要另读本包以外的写作技法文件。交付后只回:写了哪些文件、每章一句核心事件、"
|
|
265
|
+
"证据不足标了 [待补充] 的要点(没有写「无」)。\n\n"
|
|
266
|
+
"包里提到的检查由主会话在你交付后跑,你不执行命令;需要的测量结果已放进包里。")
|
|
267
|
+
parts = [head, f"\n## 本批章节\n\n路径相对项目根(`{project.parent}`)。\n\n" + "\n\n".join(blocks),
|
|
268
|
+
"\n## 卷纲剧情单元(覆盖本批)\n\n" + "\n".join(unit_rows(project, first, last)).rstrip(),
|
|
269
|
+
"\n---\n\n<!-- 资料:outline-reverse-rules.md -->\n\n" + RULES.read_text(encoding="utf-8").lstrip("").strip()]
|
|
270
|
+
return "\n".join(parts) + "\n"
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def build(project: Path, analysis: Path, first: int, last: int) -> dict:
|
|
274
|
+
if not RULES.is_file():
|
|
275
|
+
raise SystemExit(f"ERROR: 缺少 reference:{RULES}")
|
|
276
|
+
fixed = weight(render(project, first, last, [])) # 包头、剧情单元行与规则:章数减少只会更少
|
|
277
|
+
blocks: list[str] = []
|
|
278
|
+
lengths: dict[int, int] = {}
|
|
279
|
+
missing: list[int] = []
|
|
280
|
+
load, extra_reading = fixed, 0
|
|
281
|
+
for chapter in range(first, last + 1):
|
|
282
|
+
block, actual, extra, absent = chapter_block(project, analysis, chapter)
|
|
283
|
+
cost = weight(block) + extra
|
|
284
|
+
if blocks and load + cost > LOAD_LIMIT:
|
|
285
|
+
break # 包已够大:本章留给下一批
|
|
286
|
+
blocks.append(block)
|
|
287
|
+
lengths[chapter] = actual
|
|
288
|
+
load += cost
|
|
289
|
+
extra_reading += extra
|
|
290
|
+
if absent:
|
|
291
|
+
missing.append(chapter)
|
|
292
|
+
end = first + len(blocks) - 1
|
|
293
|
+
text = render(project, first, end, blocks)
|
|
294
|
+
return {"text": text, "end": end, "lengths": lengths, "missing": missing,
|
|
295
|
+
"chars": weight(text) + extra_reading}
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def main(argv=None) -> int:
|
|
299
|
+
parser = argparse.ArgumentParser(description="拼出导入长篇逐批反推细纲的任务包")
|
|
300
|
+
parser.add_argument("--project", required=True, type=Path, help="书目录({导入书名}/)")
|
|
301
|
+
parser.add_argument("--chapters", required=True, help="章节范围 A-B,请求 10–20 章;包过大时自动少排")
|
|
302
|
+
parser.add_argument("--analysis", type=Path, help="拆文库目录,默认 书目录的上一级/拆文库/{书目录名}")
|
|
303
|
+
args = parser.parse_args(argv)
|
|
304
|
+
try:
|
|
305
|
+
project = args.project.resolve()
|
|
306
|
+
if not project.is_dir():
|
|
307
|
+
raise SystemExit(f"ERROR: 书目录不存在:{args.project}")
|
|
308
|
+
first, last = parse_range(args.chapters)
|
|
309
|
+
final = last_body_chapter(project)
|
|
310
|
+
if last > final:
|
|
311
|
+
raise SystemExit(f"ERROR: 正文只迁到第{final}章,范围超出")
|
|
312
|
+
check_batch_size(first, last, final)
|
|
313
|
+
analysis = (args.analysis or project.parent / "拆文库" / project.name).resolve()
|
|
314
|
+
result = build(project, analysis, first, last)
|
|
315
|
+
except SystemExit as exc:
|
|
316
|
+
emit(str(exc), sys.stderr)
|
|
317
|
+
return 2
|
|
318
|
+
end = result["end"]
|
|
319
|
+
out_dir = project.joinpath(*WORK_DIR)
|
|
320
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
321
|
+
out = out_dir / f"导入细纲_第{first:03d}-{end:03d}章.md"
|
|
322
|
+
out.write_text(result["text"], encoding="utf-8")
|
|
323
|
+
emit(json.dumps({"brief": str(out), "chapters": f"{first}-{end}",
|
|
324
|
+
"next": end + 1 if end < final else None, "chars": result["chars"], "limit": LOAD_LIMIT,
|
|
325
|
+
"metric": METRIC, "lengths": {str(k): v for k, v in result["lengths"].items()},
|
|
326
|
+
"missing_summaries": result["missing"]},
|
|
327
|
+
ensure_ascii=False), sys.stdout)
|
|
328
|
+
return 0
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
if __name__ == "__main__":
|
|
332
|
+
sys.exit(main())
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
const fs = require('fs')
|
|
19
19
|
const path = require('path')
|
|
20
20
|
|
|
21
|
-
// 权威模板:references/workflow-
|
|
21
|
+
// 权威模板:references/workflow-outline.md 的细纲模板
|
|
22
22
|
// 必填只留有证据或事故撑腰的核心字段;阶段位置、章节定位、结构公式、本章标价、闭环状态、
|
|
23
23
|
// 写手自由区、契约风险、镜头准入、信息差触发点与「分辨率」列是可选的阅读体验字段——
|
|
24
24
|
// 写了照样消费,没写不拦(#383 隔离实验:只补目标情绪与主角目标就复现了补满全部字段的收益)。
|
|
@@ -32,6 +32,69 @@ const PLOT_HEADER_FIRST = /^(?:#|序号)$/
|
|
|
32
32
|
// 这两个字段实测直接影响正文质量,必须有实际内容
|
|
33
33
|
const INTENT_FIELDS = ['目标情绪', '主角目标/关键选择']
|
|
34
34
|
const CALIBER = 'visible_chars_v1'
|
|
35
|
+
// 可选字段「契约风险」只收三档;值未知可写 [待补充]。旧稿里的「风险等级」按同一字段认(旧名)。
|
|
36
|
+
const CONTRACT_RISK_LEVELS = ['契约安全', '需补强', '契约破坏']
|
|
37
|
+
|
|
38
|
+
// 「字数目标」「字数范围」「字数口径」的写法语法与 wordcount_core.py 逐字同构(scripts/test-storyctl.py 双跑同一批写法),
|
|
39
|
+
// 细纲验收通过的写法,storyctl chapter check 不能再拒。值里一个数字都没有的目标/范围行是说明文字,不参与取值。
|
|
40
|
+
const NUMBER = '([1-9][0-9]{0,2}(?:[,,][0-9]{3})+|[1-9][0-9]*)'
|
|
41
|
+
const NOTE = '(?:[((][^()()]*[))])?'
|
|
42
|
+
const TARGET_VALUE = new RegExp(`^(?:约|大约)?[ \\t]*${NUMBER}[ \\t]*字?(?:左右)?[ \\t]*${NOTE}$`)
|
|
43
|
+
const RANGE_VALUE = new RegExp(`^${NUMBER}[ \\t]*字?[ \\t]*(?:-|~|~|—|–|-|至|到)[ \\t]*${NUMBER}[ \\t]*字?[ \\t]*${NOTE}$`)
|
|
44
|
+
const METRIC_VALUE = new RegExp(`^([A-Za-z0-9_-]+)[ \\t]*${NOTE}$`)
|
|
45
|
+
const HAS_DIGIT = /[0-90-9]/
|
|
46
|
+
|
|
47
|
+
function fieldValues(text, label) {
|
|
48
|
+
const pattern = new RegExp(`^[ \\t>]*(?:[-*+][ \\t]*)?(?:\\*\\*)?${label}(?:\\*\\*)?[ \\t]*[::][ \\t]*(.*?)[ \\t]*$`, 'gm')
|
|
49
|
+
const values = []
|
|
50
|
+
let match
|
|
51
|
+
const normalized = text.replace(/\r\n?/g, '\n').replace(/^\uFEFF/, '')
|
|
52
|
+
while ((match = pattern.exec(normalized)) !== null) values.push(match[1].replace(/\*\*/g, '').trim())
|
|
53
|
+
return values
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const numericFieldValues = (text, label) => fieldValues(text, label).filter((value) => HAS_DIGIT.test(value))
|
|
57
|
+
const toNumber = (raw) => Number(raw.replace(/[,,]/g, ''))
|
|
58
|
+
|
|
59
|
+
function parseWordcountTarget(text) {
|
|
60
|
+
const raw = numericFieldValues(text, '字数目标')
|
|
61
|
+
if (!raw.length) return { status: 'missing', value: null, raw }
|
|
62
|
+
const matches = raw.map((value) => value.match(TARGET_VALUE))
|
|
63
|
+
if (!matches.every(Boolean)) return { status: 'invalid', value: null, raw }
|
|
64
|
+
const values = [...new Set(matches.map((match) => toNumber(match[1])))]
|
|
65
|
+
if (values.length !== 1) return { status: 'conflict', value: null, raw }
|
|
66
|
+
return { status: 'ok', value: values[0], raw }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function parseWordcountRange(text) {
|
|
70
|
+
const raw = numericFieldValues(text, '字数范围')
|
|
71
|
+
if (!raw.length) return { status: 'missing', value: null, raw }
|
|
72
|
+
const matches = raw.map((value) => value.match(RANGE_VALUE))
|
|
73
|
+
if (!matches.every(Boolean)) return { status: 'invalid', value: null, raw }
|
|
74
|
+
const pairs = [...new Set(matches.map((match) => `${toNumber(match[1])}-${toNumber(match[2])}`))]
|
|
75
|
+
if (pairs.length !== 1) return { status: 'invalid', value: null, raw }
|
|
76
|
+
const [min, max] = pairs[0].split('-').map(Number)
|
|
77
|
+
if (min > max) return { status: 'invalid', value: null, raw }
|
|
78
|
+
return { status: 'ok', value: { min, max }, raw }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parseWordcountCaliber(text) {
|
|
82
|
+
const raw = fieldValues(text, '字数口径')
|
|
83
|
+
const matches = raw.map((value) => value.match(METRIC_VALUE))
|
|
84
|
+
return raw.length > 0 && matches.every(Boolean) && new Set(matches.map((match) => match[1])).size === 1 && matches[0][1] === CALIBER
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function parseContractRisk(text) {
|
|
88
|
+
let raw = fieldValues(text, '契约风险')
|
|
89
|
+
if (!raw.length) raw = fieldValues(text, '风险等级')
|
|
90
|
+
if (!raw.length) return null
|
|
91
|
+
const value = raw[raw.length - 1].replace(/`/g, '')
|
|
92
|
+
if (/^\[待补充\]$/.test(value)) return { status: 'unknown', level: null, detail: '', raw: value }
|
|
93
|
+
const level = CONTRACT_RISK_LEVELS.find((name) => value.startsWith(name))
|
|
94
|
+
if (!level) return { status: 'invalid', level: null, detail: '', raw: value }
|
|
95
|
+
const detail = value.slice(level.length).replace(/^[\s;;,,。::—\-((]+/, '').replace(/[))]$/, '').trim()
|
|
96
|
+
return { status: 'ok', level, detail, raw: value }
|
|
97
|
+
}
|
|
35
98
|
|
|
36
99
|
function fieldPattern(name) {
|
|
37
100
|
// 允许 -/*/+ 项目符号、可选 ** 加粗、全角或半角冒号
|
|
@@ -56,7 +119,7 @@ function makeCheck(id, ok, file, evidence, expected, repair, severity = 'blockin
|
|
|
56
119
|
file,
|
|
57
120
|
evidence,
|
|
58
121
|
expected,
|
|
59
|
-
references: ['references/workflow-
|
|
122
|
+
references: ['references/workflow-outline.md'],
|
|
60
123
|
repair,
|
|
61
124
|
}
|
|
62
125
|
}
|
|
@@ -251,22 +314,45 @@ function verify(file, projectRoot = null) {
|
|
|
251
314
|
|
|
252
315
|
checks.push(checkSettingRefs(text, name, file, projectRoot))
|
|
253
316
|
|
|
254
|
-
const
|
|
255
|
-
const
|
|
256
|
-
const
|
|
317
|
+
const targetParse = parseWordcountTarget(text)
|
|
318
|
+
const rangeParse = parseWordcountRange(text)
|
|
319
|
+
const target = targetParse.value
|
|
320
|
+
const caliberOk = parseWordcountCaliber(text)
|
|
321
|
+
const targetLabel = { ok: target, missing: '未识别', invalid: `写法认不出(${targetParse.raw.join('、')})`, conflict: `多个不同的值(${targetParse.raw.join('、')})` }[targetParse.status]
|
|
322
|
+
const rangeLabel = rangeParse.status === 'invalid' ? `;字数范围写法不对(${rangeParse.raw.join('、')})` : (rangeParse.value ? `;字数范围:${rangeParse.value.min}-${rangeParse.value.max}` : '')
|
|
257
323
|
checks.push(makeCheck(
|
|
258
324
|
'outline.wordcount-target',
|
|
259
|
-
|
|
325
|
+
targetParse.status === 'ok' && target >= 500 && target <= 20000 && caliberOk && rangeParse.status !== 'invalid',
|
|
260
326
|
name,
|
|
261
|
-
`字数目标:${
|
|
262
|
-
`字数目标为 500-20000
|
|
263
|
-
'
|
|
327
|
+
`字数目标:${targetLabel};字数口径 ${CALIBER}:${caliberOk}${rangeLabel}`,
|
|
328
|
+
`字数目标为 500-20000 的正整数(可写「2300 字」「约 2300」「2,300」,可带括号备注),并声明 字数口径:${CALIBER};作者给了上下限时另写一行「字数范围:2000-2600」`,
|
|
329
|
+
'只补字数目标、字数范围或字数口径行,不调整情节安排。'
|
|
264
330
|
))
|
|
265
331
|
|
|
266
|
-
|
|
332
|
+
const risk = parseContractRisk(text)
|
|
333
|
+
if (risk) {
|
|
334
|
+
checks.push(makeCheck(
|
|
335
|
+
'outline.contract-risk',
|
|
336
|
+
risk.status !== 'invalid',
|
|
337
|
+
name,
|
|
338
|
+
risk.status === 'invalid' ? `契约风险写的是「${risk.raw}」` : `契约风险:${risk.level || '[待补充]'}`,
|
|
339
|
+
`契约风险只写 ${CONTRACT_RISK_LEVELS.join(' / ')} 三档之一(需补强时后面写补强方式),未定写 [待补充]`,
|
|
340
|
+
'只把契约风险改成三档之一,不改其他字段。'
|
|
341
|
+
))
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return report(file, checks, {
|
|
345
|
+
wordcount: {
|
|
346
|
+
target,
|
|
347
|
+
target_status: targetParse.status,
|
|
348
|
+
range: rangeParse.value,
|
|
349
|
+
range_status: rangeParse.status,
|
|
350
|
+
},
|
|
351
|
+
contract_risk: risk ? { level: risk.level, detail: risk.detail, status: risk.status } : null,
|
|
352
|
+
})
|
|
267
353
|
}
|
|
268
354
|
|
|
269
|
-
function report(file, checks) {
|
|
355
|
+
function report(file, checks, extra = {}) {
|
|
270
356
|
const failures = checks.filter((check) => !check.ok && check.severity === 'blocking')
|
|
271
357
|
const advisories = checks.filter((check) => !check.ok && check.severity === 'advisory')
|
|
272
358
|
return {
|
|
@@ -274,6 +360,7 @@ function report(file, checks) {
|
|
|
274
360
|
verifier: 'story-long-write.outline-contract',
|
|
275
361
|
file: path.resolve(file),
|
|
276
362
|
ok: failures.length === 0,
|
|
363
|
+
...extra,
|
|
277
364
|
checks,
|
|
278
365
|
failures,
|
|
279
366
|
advisories,
|
|
@@ -320,7 +407,7 @@ function verifySupply(volumeFile, unitId) {
|
|
|
320
407
|
const ok = hasSupplyHeading(draft.text)
|
|
321
408
|
return {
|
|
322
409
|
schema_version: 1, verifier: 'story-long-write.outline-supply', file: path.resolve(draftFile), unit: unitId, ok,
|
|
323
|
-
evidence: ok ? '排纲底稿含「供给自查」小节' : `${path.basename(draftFile)} 里没有「供给自查」小节——每批出细纲前须做供给自查(含「无缺口」情形),见 workflow-
|
|
410
|
+
evidence: ok ? '排纲底稿含「供给自查」小节' : `${path.basename(draftFile)} 里没有「供给自查」小节——每批出细纲前须做供给自查(含「无缺口」情形),见 workflow-outline.md 步骤 3`,
|
|
324
411
|
}
|
|
325
412
|
}
|
|
326
413
|
const read = readUtf8(volumeFile)
|
|
@@ -363,7 +450,7 @@ function verifySupply(volumeFile, unitId) {
|
|
|
363
450
|
file: path.resolve(volumeFile),
|
|
364
451
|
unit: unitId,
|
|
365
452
|
ok,
|
|
366
|
-
evidence: ok ? '单元卡含「供给自查」小节' : `没有 排纲底稿_${unitId}.md,剧情单元 ${unitId} 的卡内也没有「供给自查」小节——每批出细纲前须做供给自查(含「无缺口」情形),见 workflow-
|
|
453
|
+
evidence: ok ? '单元卡含「供给自查」小节' : `没有 排纲底稿_${unitId}.md,剧情单元 ${unitId} 的卡内也没有「供给自查」小节——每批出细纲前须做供给自查(含「无缺口」情形),见 workflow-outline.md 步骤 3`,
|
|
367
454
|
}
|
|
368
455
|
}
|
|
369
456
|
|
|
@@ -430,4 +517,4 @@ function main(argv) {
|
|
|
430
517
|
|
|
431
518
|
if (require.main === module) process.exitCode = main(process.argv.slice(2))
|
|
432
519
|
|
|
433
|
-
module.exports = { verify, FIELDS, SUBSECTIONS, FIVE_ACT }
|
|
520
|
+
module.exports = { verify, parseWordcountTarget, parseWordcountRange, parseContractRisk, FIELDS, SUBSECTIONS, FIVE_ACT }
|