@oh-story/dsh 0.1.7 → 0.1.9
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 +30 -15
- package/lib/client.js +93 -16
- package/lib/client.js.map +2 -2
- package/lib/drama/manifest.json +165 -69
- package/lib/drama/skills/short-drama/SKILL.md +5 -1
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +126 -9
- package/lib/drama/skills/short-drama/scripts/project_tool.py +11 -0
- package/lib/drama/skills/short-drama-assets/references/character-and-look.md +18 -0
- package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +10 -1
- package/lib/drama/skills/short-drama-edit/SKILL.md +148 -0
- package/lib/drama/skills/short-drama-edit/agents/openai.yaml +4 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +60 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +15 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/remotion.config.ts +6 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +26 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +88 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +46 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/index.ts +4 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +32 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +13 -0
- package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +43 -0
- package/lib/drama/skills/short-drama-edit/references/cut-craft.md +81 -0
- package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +58 -0
- package/lib/drama/skills/short-drama-edit/references/generated-footage.md +96 -0
- package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +113 -0
- package/lib/drama/skills/short-drama-edit/references/stage-contract.md +55 -0
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1041 -0
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +456 -0
- package/lib/drama/skills/short-drama-image-prompts/assets/image-prompts.md +5 -0
- package/lib/drama/skills/short-drama-image-prompts/assets/lookdev-prompts.md +3 -0
- package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +2 -2
- package/lib/drama/skills/short-drama-novel-analyze/references/chapter-extraction.md +4 -1
- package/lib/drama/skills/short-drama-produce/SKILL.md +24 -3
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +28 -0
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +24 -5
- package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +7 -0
- package/lib/drama/skills/short-drama-produce/scripts/fixture_adapter.py +18 -1
- package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +181 -2
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +57 -2
- package/lib/drama/skills/short-drama-review/SKILL.md +3 -0
- package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +20 -0
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +36 -0
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +7 -0
- package/lib/drama/skills/short-drama-storyboard/assets/keyframe-prompts.md +4 -0
- package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +39 -0
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +2 -0
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +7 -1
- package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +101 -6
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +7 -1
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +35 -0
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +3 -1
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +13 -3
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +35 -4
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -1
- package/lib/drama/skills/short-drama-write/SKILL.md +13 -2
- package/lib/drama/skills/short-drama-write/references/screenplay-format.md +6 -0
- package/lib/drama/skills/short-drama-write/references/stage-contract.md +1 -0
- package/lib/index.js +4 -2
- package/lib/index.js.map +2 -2
- package/lib/novel-to-game/VERSION +1 -1
- package/lib/novel-to-game/manifest.json +28 -28
- package/lib/novel-to-game/skills/game-art-direction/SKILL.md +6 -27
- package/lib/novel-to-game/skills/game-build/SKILL.md +12 -29
- package/lib/novel-to-game/skills/game-concept/SKILL.md +10 -26
- package/lib/novel-to-game/skills/game-qa/SKILL.md +5 -15
- package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +1 -1
- package/lib/novel-to-game/skills/game-world-design/SKILL.md +19 -28
- package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +1 -2
- package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +6 -9
- package/lib/novel-to-game/skills/novel-to-game/SKILL.md +6 -11
- package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +0 -4
- package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +1 -3
- package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +3 -5
- package/lib/oh-story/manifest.json +292 -172
- package/lib/oh-story/roles/character-designer.md +1 -1
- package/lib/oh-story/roles/narrative-writer.md +56 -242
- package/lib/oh-story/roles/story-architect.md +1 -1
- package/lib/oh-story/skills/story/SKILL.md +1 -1
- package/lib/oh-story/skills/story/VERSION +1 -1
- package/lib/oh-story/skills/story/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-deslop/SKILL.md +20 -182
- package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-deslop/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-deslop/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +154 -0
- package/lib/oh-story/skills/story-deslop/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-deslop/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-deslop/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-deslop/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-import/SKILL.md +2 -2
- package/lib/oh-story/skills/story-import/references/format-and-structure.md +2 -2
- package/lib/oh-story/skills/story-import/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-import/references/tracking-transaction.md +2 -29
- package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +176 -12
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +1 -1
- package/lib/oh-story/skills/story-long-write/SKILL.md +20 -193
- package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +62 -7
- package/lib/oh-story/skills/story-long-write/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-long-write/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +20 -0
- package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/long-format.md +2 -0
- package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +2 -0
- package/lib/oh-story/skills/story-long-write/references/project-files.md +108 -0
- package/lib/oh-story/skills/story-long-write/references/reference-index.md +87 -0
- package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/style-craft.md +9 -29
- package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +2 -29
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +51 -52
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +8 -7
- package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +36 -15
- package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +22 -0
- package/lib/oh-story/skills/story-long-write/references/writing-craft.md +69 -2
- package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +410 -0
- package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-long-write/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +176 -12
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +90 -23
- package/lib/oh-story/skills/story-long-write/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +283 -0
- package/lib/oh-story/skills/story-long-write/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-review/SKILL.md +13 -3
- package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-review/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-review/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-review/references/review-quality.md +2 -2
- package/lib/oh-story/skills/story-review/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-review/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-review/references/tracking-transaction.md +2 -29
- package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-review/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-review/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-review/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-setup/SKILL.md +17 -10
- package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +154 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +69 -2
- package/lib/oh-story/skills/story-setup/references/diagnostics.md +38 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-writing-techniques.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +2 -2
- package/lib/oh-story/skills/story-short-write/SKILL.md +18 -186
- package/lib/oh-story/skills/story-short-write/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-short-write/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +60 -0
- package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//344/270/226/346/203/205/346/211/223/350/204/270.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/244/215/344/273/207/346/211/223/350/204/270.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/256/205/346/226/227/345/256/253/346/226/227.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/200/273/350/243/201/350/261/252/351/227/250.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +5 -5
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +4 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +5 -5
- package/lib/oh-story/skills/story-short-write/references/genre-styles//350/277/275/345/246/273/347/201/253/350/221/254/345/234/272.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/reference-index.md +53 -0
- package/lib/oh-story/skills/story-short-write/references/short-craft.md +12 -14
- package/lib/oh-story/skills/story-short-write/references/short-deslop.md +10 -10
- package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -1
- package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-short-write/references/workflow-design.md +77 -0
- package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +5 -5
- package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-short-write/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-short-write/scripts/check-delivery-contract.js +34 -5
- package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +90 -23
- package/lib/oh-story/skills/story-short-write/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-short-write/scripts/style-whitelist.js +39 -0
- package/lib/types/client/drama-production-view.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/production-prompts.d.ts.map +1 -1
- package/lib/types/client/production-runtime.d.ts +17 -0
- package/lib/types/client/production-runtime.d.ts.map +1 -1
- package/lib/types/skill-provider.d.ts.map +1 -1
- package/lib/video-recap/CHANGELOG.md +31 -1
- package/lib/video-recap/manifest.json +162 -162
- package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +4 -9
- package/lib/video-recap/skills/video-assemble/scripts/assemble.py +47 -60
- package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +77 -115
- package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +41 -40
- package/lib/video-recap/skills/video-assemble/scripts/audio_automation.py +22 -32
- package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +102 -192
- package/lib/video-recap/skills/video-assemble/scripts/jianying_builders.py +14 -35
- package/lib/video-recap/skills/video-assemble/scripts/jianying_model.py +14 -12
- package/lib/video-recap/skills/video-assemble/scripts/jianying_optional.py +8 -9
- package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +3 -14
- package/lib/video-recap/skills/video-assemble/scripts/jianying_timeline_contract.py +23 -12
- package/lib/video-recap/skills/video-assemble/scripts/jianying_tracks.py +1 -4
- package/lib/video-recap/skills/video-assemble/scripts/jianying_writer.py +24 -25
- package/lib/video-recap/skills/video-assemble/scripts/lib.py +31 -85
- package/lib/video-recap/skills/video-assemble/scripts/media.py +100 -144
- package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +101 -195
- package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +10 -21
- package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +90 -169
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_core.py +69 -122
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_render.py +10 -17
- package/lib/video-recap/skills/video-assemble/scripts/timeline.py +33 -48
- package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +14 -20
- package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +135 -213
- package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +49 -57
- package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +38 -74
- package/lib/video-recap/skills/video-cut/scripts/cut_render.py +46 -95
- package/lib/video-recap/skills/video-cut/scripts/lib.py +23 -25
- package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +70 -173
- package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +28 -53
- package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +162 -322
- package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +6 -7
- package/lib/video-recap/skills/video-recap/scripts/doctor.py +104 -124
- package/lib/video-recap/skills/video-recap/scripts/final_qc.py +255 -412
- package/lib/video-recap/skills/video-recap/scripts/lib.py +30 -52
- package/lib/video-recap/skills/video-recap/scripts/materials.py +97 -120
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_client.py +7 -12
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_evidence.py +46 -97
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_observations.py +18 -36
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_payload.py +29 -47
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_report.py +43 -76
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_runner.py +2 -1
- package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +2 -1
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +2 -2
- package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +129 -145
- package/lib/video-recap/skills/video-recap/scripts/recap_review.py +27 -20
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +20 -31
- package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +31 -106
- package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +43 -113
- package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +196 -377
- package/lib/video-recap/skills/video-script/scripts/agent_brief.py +1 -1
- package/lib/video-recap/skills/video-script/scripts/agent_text.py +63 -172
- package/lib/video-recap/skills/video-script/scripts/brief_context.py +80 -135
- package/lib/video-recap/skills/video-script/scripts/brief_inputs.py +125 -167
- package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +93 -208
- package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +20 -46
- package/lib/video-recap/skills/video-script/scripts/lib.py +30 -26
- package/lib/video-recap/skills/video-script/scripts/narration_lint.py +145 -140
- package/lib/video-recap/skills/video-script/scripts/review_response.py +12 -42
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +0 -6
- package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +39 -100
- package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +33 -124
- package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +1 -1
- package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +63 -172
- package/lib/video-recap/skills/video-understanding/scripts/asr.py +1 -2
- package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +80 -135
- package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +125 -167
- package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +93 -208
- package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +1 -1
- package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +20 -46
- package/lib/video-recap/skills/video-understanding/scripts/lib.py +31 -26
- package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +145 -140
- package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +39 -100
- package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +33 -124
- package/lib/video-recap/skills/video-understanding/scripts/vlm.py +1 -4
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +7 -13
- package/lib/video-recap/skills/video-voiceover/scripts/fish_audio.py +12 -30
- package/lib/video-recap/skills/video-voiceover/scripts/lib.py +98 -131
- package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +17 -28
- package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +151 -240
- package/package.json +41 -41
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""build_writer_prompt.py — 确定性组装 narrative-writer 的 spawn prompt 骨架。
|
|
3
|
+
|
|
4
|
+
用法:
|
|
5
|
+
python build_writer_prompt.py --project <书目录> --chapter N [--out <文件>]
|
|
6
|
+
|
|
7
|
+
跑在「写前准备」第一步。stdout 分两区,`===` 分隔线以上是 prompt 正文
|
|
8
|
+
(主会话照抄,空槽以外一字不改),以下是核对报告(不进 prompt)。
|
|
9
|
+
|
|
10
|
+
职责边界:
|
|
11
|
+
- 脚本做确定性部分:固定首行、定位、标题行字面量、细纲指针、文风全文路径与裁决、
|
|
12
|
+
上一章结尾、降档判定与情绪/节奏槽、固定块指针。
|
|
13
|
+
- 主会话填八槽:执行安排 / 本章意图 / 参考技法 / 本节速记 / 涉及角色 / genre_prose_card /
|
|
14
|
+
必读设定 / author_preferences。降档不成立时情绪与节奏槽也归主会话。
|
|
15
|
+
材料槽对应原流程步骤 3「写前准备」的四项输出(本节速记 / 情绪目标 / 涉及角色 /
|
|
16
|
+
参考技法)加上题材卡、设定补漏与作者偏好——都是判断,脚本做不了。
|
|
17
|
+
- 续写状态卡校验后由主会话筛选,在「本节速记」槽内写入本章需要的状态。
|
|
18
|
+
- 可选块缺失留标题并写明原因(「没有」与「漏了」在产物上必须长得不一样)。
|
|
19
|
+
必在块缺失退出码 2 —— 那是数据问题,要修数据后重跑,不是回落手拼的理由。
|
|
20
|
+
|
|
21
|
+
Exit: 0 = 骨架已输出;2 = 输入缺失或无效。
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import argparse
|
|
25
|
+
import io
|
|
26
|
+
import re
|
|
27
|
+
import sys
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from outline_view import parse as parse_volume
|
|
30
|
+
|
|
31
|
+
TAIL_CHARS = 400 # 上一章结尾注入的目标字符数(按整行回退,不切半句)
|
|
32
|
+
STATE_SECTIONS = ("当前位置", "长期约束", "核心角色状态", "活跃伏笔", "近三章速记", "下一章承诺", "连贯性风险")
|
|
33
|
+
SLOT_MARK = "[主会话填]"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def read_text(path: Path):
|
|
37
|
+
try:
|
|
38
|
+
return io.open(path, encoding="utf-8").read().lstrip("")
|
|
39
|
+
except (OSError, UnicodeError):
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def substantive(text):
|
|
44
|
+
if not text:
|
|
45
|
+
return False
|
|
46
|
+
body = re.sub(r"^#{1,6}[^\n]*", "", text, flags=re.M)
|
|
47
|
+
body = re.sub(r"待补充|待办|待定|暂无|TODO|TBD", "", body, flags=re.I)
|
|
48
|
+
return bool(re.sub(r"[\s_#*\[\]【】{}。,;::-]", "", body))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def has_custom_style(text):
|
|
52
|
+
# A single substantive sentence can express the author's choice.
|
|
53
|
+
return substantive(re.sub(r"<!--[\s\S]*?-->", "", text or ""))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def find_chapter_file(directory: Path, chapter: int, prefix: str):
|
|
57
|
+
if not directory.is_dir():
|
|
58
|
+
return None
|
|
59
|
+
pattern = re.compile(rf"^{prefix}第0*{chapter}章.*\.md$")
|
|
60
|
+
for entry in sorted(directory.iterdir()):
|
|
61
|
+
if entry.is_file() and pattern.match(entry.name):
|
|
62
|
+
return entry
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def extract_field(outline_text: str, field: str):
|
|
67
|
+
lines = outline_text.splitlines()
|
|
68
|
+
pattern = re.compile(rf"^([ \t]*)[-*+]\s+\*{{0,2}}{re.escape(field)}\*{{0,2}}[ \t]*[::][ \t]*(.*)$")
|
|
69
|
+
for index, line in enumerate(lines):
|
|
70
|
+
match = pattern.match(line)
|
|
71
|
+
if not match:
|
|
72
|
+
continue
|
|
73
|
+
values = [match.group(2)]
|
|
74
|
+
for following in lines[index + 1:]:
|
|
75
|
+
bullet = re.match(r"^([ \t]*)[-*+]\s", following)
|
|
76
|
+
if re.match(r"^#{1,6}\s", following) or (bullet and len(bullet.group(1)) <= len(match.group(1))):
|
|
77
|
+
break
|
|
78
|
+
values.append(following)
|
|
79
|
+
return "\n".join(values).strip()
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def extract_title(outline_text: str, chapter: int):
|
|
84
|
+
match = re.search(rf"^#{{1,4}}\s*第\s*0*{chapter}\s*章\s*[::]\s*(.+?)\s*$",
|
|
85
|
+
outline_text, re.M)
|
|
86
|
+
return match.group(1).strip() if match else None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def extract_unit_block(volume_text: str, unit_id: str):
|
|
90
|
+
lines, sections = parse_volume(volume_text)
|
|
91
|
+
for section in sections:
|
|
92
|
+
if section.unit != unit_id or "剧情单元" not in section.title or section.status == "已退役":
|
|
93
|
+
continue
|
|
94
|
+
end = next((other.start for other in sections
|
|
95
|
+
if other.start > section.start and other.level <= section.level), len(lines))
|
|
96
|
+
return "\n".join(lines[section.start:end]).strip()
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def extract_state_sections(state_text: str):
|
|
101
|
+
found = {}
|
|
102
|
+
for section in STATE_SECTIONS:
|
|
103
|
+
match = re.search(rf"^##\s*{section}\s*$(.*?)(?=^##\s|\Z)",
|
|
104
|
+
state_text, re.M | re.S)
|
|
105
|
+
if match and match.group(1).strip():
|
|
106
|
+
found[section] = match.group(1).strip()
|
|
107
|
+
return found
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def previous_chapter_tail(project: Path, chapter: int):
|
|
111
|
+
"""按整行从尾部回退,凑够 TAIL_CHARS 即停 —— 不切半句。"""
|
|
112
|
+
prev = find_chapter_file(project / "正文", chapter - 1, "")
|
|
113
|
+
if prev is None:
|
|
114
|
+
return None, None
|
|
115
|
+
text = read_text(prev)
|
|
116
|
+
if not text:
|
|
117
|
+
return prev, None
|
|
118
|
+
lines = [line for line in text.rstrip().splitlines() if line.strip()]
|
|
119
|
+
picked, total = [], 0
|
|
120
|
+
for line in reversed(lines):
|
|
121
|
+
picked.append(line)
|
|
122
|
+
total += len(line)
|
|
123
|
+
if total >= TAIL_CHARS:
|
|
124
|
+
break
|
|
125
|
+
return prev, "\n".join(reversed(picked))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def learn_heading_form(project: Path, chapter: int, title: str):
|
|
129
|
+
"""从已有正文学标题行形态(层级/章号填充位数/分隔符),不按通用规则推导。
|
|
130
|
+
|
|
131
|
+
通用规则写的是 `## 第N章 章名`,而各书实际形态不一(本仓库某书用 `# 第015章 …`),
|
|
132
|
+
两处都对不上时写手只能自己去翻既有章——每章翻一次。这里替它翻。
|
|
133
|
+
"""
|
|
134
|
+
body = project / "正文"
|
|
135
|
+
if not body.is_dir():
|
|
136
|
+
return None, "正文/ 目录不存在"
|
|
137
|
+
pattern = re.compile(r"^(#+)([ \t ]*)第(0*\d+)章([ \t ]*)(.*)$")
|
|
138
|
+
best = None
|
|
139
|
+
for entry in sorted(body.glob("*.md")):
|
|
140
|
+
num = re.match(r"^第(\d+)章", entry.name)
|
|
141
|
+
if not num or int(num.group(1)) == chapter:
|
|
142
|
+
continue
|
|
143
|
+
head = (read_text(entry) or "").lstrip().splitlines()
|
|
144
|
+
if not head:
|
|
145
|
+
continue
|
|
146
|
+
hit = pattern.match(head[0].strip())
|
|
147
|
+
if hit:
|
|
148
|
+
number = int(num.group(1))
|
|
149
|
+
if number < chapter and (best is None or number > best[0]):
|
|
150
|
+
best = (number, hit)
|
|
151
|
+
if best is None:
|
|
152
|
+
return None, "正文/ 下没有可解析标题行的既有章,写手按自身规则处理"
|
|
153
|
+
_, hit = best
|
|
154
|
+
level, gap1, digits, gap2 = hit.group(1), hit.group(2), hit.group(3), hit.group(4)
|
|
155
|
+
# 有前导零才算补零形态,且按它的位数补;`第15章` 这种不补——
|
|
156
|
+
# 否则会从两位数章学出「补到 2 位」,把第 9 章写成 `第09章`。
|
|
157
|
+
if digits.startswith("0"):
|
|
158
|
+
number, how = f"{chapter:0{len(digits)}d}", f"{len(digits)} 位补零"
|
|
159
|
+
else:
|
|
160
|
+
number, how = str(chapter), "不补零"
|
|
161
|
+
return (f"{level}{gap1}第{number}章{gap2 or ' '}{title}",
|
|
162
|
+
f"照既有章形态({level} + 章号{how})")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def build(project: Path, chapter: int, report: list):
|
|
166
|
+
errors = []
|
|
167
|
+
|
|
168
|
+
outline_file = find_chapter_file(project / "大纲", chapter, "细纲_")
|
|
169
|
+
if outline_file is None:
|
|
170
|
+
errors.append(f"必在块缺失:{project / '大纲'} 下没有第 {chapter} 章细纲")
|
|
171
|
+
return None, errors
|
|
172
|
+
outline_text = read_text(outline_file)
|
|
173
|
+
if not outline_text or not outline_text.strip():
|
|
174
|
+
errors.append(f"必在块缺失:{outline_file} 为空")
|
|
175
|
+
return None, errors
|
|
176
|
+
|
|
177
|
+
parts = []
|
|
178
|
+
title = extract_title(outline_text, chapter)
|
|
179
|
+
|
|
180
|
+
# ---- 固定首行(指令,不是栏目;漏传即写手不读参考直接开写)----
|
|
181
|
+
parts.append(
|
|
182
|
+
f"本次任务:写第 {chapter} 章,执行范围见「执行安排」。开始前先按「参考文件体系」逐行独立判定命中并读取,"
|
|
183
|
+
"命中即必读,未命中不预加载;交付摘要里报出读了哪几个。")
|
|
184
|
+
|
|
185
|
+
parts.append(f"项目目录:{project}")
|
|
186
|
+
parts.append(f"章节:第 {chapter} 章")
|
|
187
|
+
if title:
|
|
188
|
+
out_path = project / "正文" / f"第{chapter:03d}章_{title}.md"
|
|
189
|
+
else:
|
|
190
|
+
return None, [f"细纲缺章名:{outline_file},需要 ### 第 N 章:章名"]
|
|
191
|
+
if any(char in title for char in '/\\'):
|
|
192
|
+
return None, ["章名不能包含路径分隔符"]
|
|
193
|
+
parts.append(f"最终输出路径:{out_path}")
|
|
194
|
+
if title:
|
|
195
|
+
heading_line, how = learn_heading_form(project, chapter, title)
|
|
196
|
+
if heading_line:
|
|
197
|
+
parts.append(
|
|
198
|
+
f"标题行(逐字照抄,勿按通用规则推导):{heading_line}")
|
|
199
|
+
report.append(f"标题行:{heading_line} {how}")
|
|
200
|
+
else:
|
|
201
|
+
report.append(f"标题行:未注入——{how}")
|
|
202
|
+
|
|
203
|
+
# ---- 细纲:只给路径,不注入全文 ----
|
|
204
|
+
parts.append(f"细纲文件(动笔前完整读到 EOF):{outline_file}")
|
|
205
|
+
|
|
206
|
+
# ---- 续写状态卡:校验结构;主会话筛选相关状态 ----
|
|
207
|
+
state_file = project / "追踪" / "上下文.md"
|
|
208
|
+
state_text = read_text(state_file)
|
|
209
|
+
if not state_text:
|
|
210
|
+
errors.append(f"必在块缺失:读不到 {state_file}")
|
|
211
|
+
else:
|
|
212
|
+
found = extract_state_sections(state_text)
|
|
213
|
+
missing = [s for s in STATE_SECTIONS if s not in found]
|
|
214
|
+
if missing:
|
|
215
|
+
errors.append(
|
|
216
|
+
f"必在块缺失:{state_file} 缺栏目 " + "、".join(missing) +
|
|
217
|
+
"(续写状态卡应为固定 7 栏,先用 tracking_commit.py 修派生视图)")
|
|
218
|
+
else:
|
|
219
|
+
report.append("续写状态:七栏已校验,主会话按本章需要筛选到「本节速记」")
|
|
220
|
+
|
|
221
|
+
# ---- 文风(本书自定义文风时由脚本全包)----
|
|
222
|
+
style_file = project / "设定" / "文风.md"
|
|
223
|
+
style_text = read_text(style_file)
|
|
224
|
+
custom_style = has_custom_style(style_text)
|
|
225
|
+
if custom_style:
|
|
226
|
+
parts.append(f"文风路径:{style_file}(书级权威文风,写作与去味均读全文;摘要只作索引)")
|
|
227
|
+
parts.append(
|
|
228
|
+
"文风优先裁决:`设定/文风.md` 对句段/句法/对话落法/标点形态与删改取向的规定"
|
|
229
|
+
"按 style-resolution.md 裁决:当前请求 > 本书文风 > active 作者记忆 > 对标 > 通用参考;不覆盖细纲事实、信息边界、调用方所选 Gate 范围及文件结构。"
|
|
230
|
+
"风格冲突按文风写,交付摘要列出「因文风优先而未执行的通用条款」。")
|
|
231
|
+
report.append("文风:custom_style=true,表达冲突按维度裁决;reference 读取仍按任务条件")
|
|
232
|
+
else:
|
|
233
|
+
parts.append(
|
|
234
|
+
"——— 文风 ———\n"
|
|
235
|
+
"(本书无可用的 设定/文风.md,未进入自定义文风模式;"
|
|
236
|
+
"按 workflow-chapter 3(d) 走对标文风召回,由主会话补路径与召回指令)")
|
|
237
|
+
report.append("文风:custom_style=false,文风召回归主会话(未跳过,留标题)")
|
|
238
|
+
|
|
239
|
+
# ---- 上一章结尾(不给路径,避免写手回头读整章)----
|
|
240
|
+
if chapter > 1:
|
|
241
|
+
prev_file, tail = previous_chapter_tail(project, chapter)
|
|
242
|
+
if prev_file is None or not tail:
|
|
243
|
+
errors.append(f"必在块缺失:找不到或读不到第 {chapter - 1} 章正文")
|
|
244
|
+
else:
|
|
245
|
+
parts.append(
|
|
246
|
+
"——— 上一章结尾(承接用,不重写;全章不需要,故不给路径)———\n" + tail)
|
|
247
|
+
report.append(f"上一章结尾:{prev_file.name} 末 {len(tail)} 字(按整行回退)")
|
|
248
|
+
|
|
249
|
+
# ---- 必读设定:整槽归主会话 ----
|
|
250
|
+
slot_setting = (
|
|
251
|
+
"——— 必读设定 ———\n"
|
|
252
|
+
f"{SLOT_MARK} 本章要用到的设定,**直接写出那几句**,不要只给路径让写手去查"
|
|
253
|
+
"(Grep 遇超长行会被截断,最关键那句往往正好被吞)。两处来源都要过:"
|
|
254
|
+
"① 细纲里显式引用的 `xxx.md`(连小节一起给);② 细纲没点名、但本章会碰到的"
|
|
255
|
+
"(术语口径、程序、数字锚)。无则写「无」。")
|
|
256
|
+
|
|
257
|
+
# ---- 降档判定与情绪/节奏槽 ----
|
|
258
|
+
unit_field = extract_field(outline_text, "单元ID/位置")
|
|
259
|
+
unit_id = None
|
|
260
|
+
if unit_field:
|
|
261
|
+
match = re.match(r"([A-Za-z0-9\-]+)", unit_field)
|
|
262
|
+
if match:
|
|
263
|
+
unit_id = match.group(1)
|
|
264
|
+
unit_block = None
|
|
265
|
+
if unit_id:
|
|
266
|
+
for volume in sorted((project / "大纲").glob("卷纲_*.md")):
|
|
267
|
+
block = extract_unit_block(read_text(volume) or "", unit_id)
|
|
268
|
+
if block:
|
|
269
|
+
unit_block = block
|
|
270
|
+
break
|
|
271
|
+
|
|
272
|
+
target_emotion = extract_field(outline_text, "目标情绪")
|
|
273
|
+
card_text = read_text(project / "设定" / "题材正文提示卡.md")
|
|
274
|
+
engine = extract_field(unit_block or "", "单元情绪引擎")
|
|
275
|
+
tempo = (extract_field(unit_block or "", "单元节拍/章功能分配")
|
|
276
|
+
or extract_field(unit_block or "", "单元节拍/章功能分配"))
|
|
277
|
+
needs = {"题材正文提示卡": card_text, "目标情绪": target_emotion,
|
|
278
|
+
"单元情绪引擎": engine, "单元节拍/章功能分配": tempo}
|
|
279
|
+
why = [name + "缺有效内容" for name, value in needs.items() if not substantive(value)]
|
|
280
|
+
if not custom_style:
|
|
281
|
+
why.append("custom_style=false")
|
|
282
|
+
downgrade = not why
|
|
283
|
+
|
|
284
|
+
if downgrade:
|
|
285
|
+
lines = [
|
|
286
|
+
"selected_emotion_module:降档(来源:细纲「目标情绪」+单元卡「单元情绪引擎」)",
|
|
287
|
+
f" 细纲目标情绪:{target_emotion}",
|
|
288
|
+
]
|
|
289
|
+
if engine:
|
|
290
|
+
lines.append(f" 单元情绪引擎:{engine}")
|
|
291
|
+
lines.append("rhythm_reference:降档(来源:单元卡「单元节拍/章功能分配」)")
|
|
292
|
+
if tempo:
|
|
293
|
+
lines.append(f" {tempo}")
|
|
294
|
+
slot_recall = "——— 情绪与节奏召回 ———\n" + "\n".join(lines)
|
|
295
|
+
report.append(f"召回降档:成立,文风、题材卡、目标情绪和单元 {unit_id} 情绪/节拍均可用")
|
|
296
|
+
else:
|
|
297
|
+
slot_recall = ("——— 情绪与节奏召回 ———\n"
|
|
298
|
+
f"{SLOT_MARK} 降档不成立(" + "、".join(why) +
|
|
299
|
+
"),按 workflow-chapter 3(a)(b)(e)(f) 走全量召回后填此槽")
|
|
300
|
+
report.append("召回降档:不成立(" + "、".join(why) + ")—— 全量召回归主会话")
|
|
301
|
+
|
|
302
|
+
# ---- 需要主会话判断的槽位 ----
|
|
303
|
+
parts.append(
|
|
304
|
+
"——— 执行安排 ———\n"
|
|
305
|
+
f"{SLOT_MARK} 全章细纲用于整体编排。默认按自然转场或因果停顿分前后两组,"
|
|
306
|
+
"填写当前组的情节点/片段及临时输出路径;先只写前组,父流程测一次 checkpoint 后"
|
|
307
|
+
"再给后组和机器剩余区间。只有用户明确要求一次成文时才填「全章,直接写最终路径」。")
|
|
308
|
+
parts.append(f"——— 本章意图(一句话)———\n{SLOT_MARK}")
|
|
309
|
+
parts.append(slot_recall)
|
|
310
|
+
# 伏笔与卷级禁忌走「主会话筛选后写进速记」这条原设计路线(步骤 3 状态筛选),
|
|
311
|
+
# 不由脚本整栏注入——筛选是判断,而整栏注入还会把伏笔栏里的作者侧真相一并下放。
|
|
312
|
+
# 代价是它依赖主会话逐章想起来,所以这里把提示语写成写死的三问清单。
|
|
313
|
+
parts.append(
|
|
314
|
+
"——— 参考技法 ———\n"
|
|
315
|
+
f"{SLOT_MARK} 步骤 3 三问的第 ②③ 问:借鉴哪个参考文件的哪个技法、用在哪些段落。"
|
|
316
|
+
"按 reference 表的任务条件读取;本书文风只覆盖冲突表达条款,不停读整份文件。")
|
|
317
|
+
parts.append(
|
|
318
|
+
"——— 本节速记 ———\n"
|
|
319
|
+
f"{SLOT_MARK} 按 workflow-chapter 步骤 3「状态筛选」产出(`追踪/上下文.md` 不注入"
|
|
320
|
+
"本 prompt,这一槽是写手唯一的状态来源):核心角色状态里本章在场的/下一章承诺里本章"
|
|
321
|
+
"必须履行的/连贯性风险里本章相关的/活跃伏笔里**要碰**与**要避**的(只下放禁令,"
|
|
322
|
+
"「作者侧:」之后的真相留在主会话)/长期约束里本章相关的卷级常任禁忌(细纲多半只写"
|
|
323
|
+
"「见卷纲,不复读」,而卷纲不进本 prompt)/久别角色是否补读 `追踪/角色状态/{名}.md`。")
|
|
324
|
+
parts.append(
|
|
325
|
+
"——— 涉及角色 ———\n"
|
|
326
|
+
f"{SLOT_MARK} 按细纲「人物出场顺序」与「镜头准入」的台词位/动作位分配,"
|
|
327
|
+
"列出本章要读的角色卡;本节速记已给全状态的不必再列。")
|
|
328
|
+
parts.append("——— 题材正文提示卡(genre_prose_card,只含本章相关条目)———\n"
|
|
329
|
+
f"{SLOT_MARK} 主题材抽 3-5 条、辅题材 1-2 条;只作内部校准,不进正文")
|
|
330
|
+
parts.append(slot_setting)
|
|
331
|
+
parts.append("——— style_resolution / author_preferences ———\n"
|
|
332
|
+
f"{SLOT_MARK} author_memory query 命中本章的 prose_style/story_design 项;"
|
|
333
|
+
"query 显式传本书/题材/流程;偏好是低优先级倾向,无则写「无」。附 style_resolution:生效要求及来源、被覆盖的默认条款和事实边界;同一裁决传去味与审稿,不逐条追求命中。")
|
|
334
|
+
|
|
335
|
+
parts.append("检查分工:写手负责编排、内容覆盖和格式自检;父流程质量阶段负责语义去味及最终文件扫描。写手不提前重复整轮去味或相同检查链,保留时空表、新增申报与原定交付。")
|
|
336
|
+
|
|
337
|
+
# ---- 固定块:压成指针,不重述 agent 定义 ----
|
|
338
|
+
parts.append(
|
|
339
|
+
"本次照你的铁律 1-8 与被调用协议执行(细纲优先边界、正文形状、新增物三档、"
|
|
340
|
+
"阅读体验字段、交付三附件均以你的定义为准,此处不重述)。")
|
|
341
|
+
parts.append(
|
|
342
|
+
"字数目标按细纲执行,字数口径 visible_chars_v1;按执行安排交付,"
|
|
343
|
+
"目标按整章分量刻度使用,疏密自行分配,不拆逐点配额;不自测字数。")
|
|
344
|
+
|
|
345
|
+
if errors:
|
|
346
|
+
return None, errors
|
|
347
|
+
|
|
348
|
+
prompt = "\n\n".join(parts) + "\n"
|
|
349
|
+
|
|
350
|
+
return prompt, []
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def main(argv=None):
|
|
354
|
+
for stream in (sys.stdout, sys.stderr):
|
|
355
|
+
try:
|
|
356
|
+
stream.reconfigure(encoding="utf-8")
|
|
357
|
+
except AttributeError:
|
|
358
|
+
pass
|
|
359
|
+
|
|
360
|
+
parser = argparse.ArgumentParser()
|
|
361
|
+
parser.add_argument("--project", required=True)
|
|
362
|
+
parser.add_argument("--chapter", required=True, type=int)
|
|
363
|
+
parser.add_argument("--out", default=None)
|
|
364
|
+
args = parser.parse_args(argv)
|
|
365
|
+
|
|
366
|
+
project = Path(args.project).resolve()
|
|
367
|
+
if args.chapter < 1:
|
|
368
|
+
parser.error("--chapter 必须大于 0")
|
|
369
|
+
if not project.is_dir():
|
|
370
|
+
sys.stderr.write(f"项目目录不存在:{project}\n")
|
|
371
|
+
return 2
|
|
372
|
+
|
|
373
|
+
report = []
|
|
374
|
+
prompt, errors = build(project, args.chapter, report)
|
|
375
|
+
if prompt is None:
|
|
376
|
+
sys.stderr.write("组装中止,先修数据再重跑(这不是回落手拼的理由):\n")
|
|
377
|
+
for item in errors:
|
|
378
|
+
sys.stderr.write(f" - {item}\n")
|
|
379
|
+
return 2
|
|
380
|
+
|
|
381
|
+
# 标题预检:正文目录里有没有同名章
|
|
382
|
+
title = None
|
|
383
|
+
outline_file = find_chapter_file(project / "大纲", args.chapter, "细纲_")
|
|
384
|
+
if outline_file:
|
|
385
|
+
title = extract_title(read_text(outline_file) or "", args.chapter)
|
|
386
|
+
if title:
|
|
387
|
+
clashes = [p.name for p in sorted((project / "正文").glob("*.md"))
|
|
388
|
+
if p.name.endswith(f"_{title}.md")
|
|
389
|
+
and not p.name.startswith(f"第{args.chapter:03d}章")]
|
|
390
|
+
report.append(
|
|
391
|
+
f"标题预检:《{title}》" + ("与既有章重名 → " + "、".join(clashes)
|
|
392
|
+
if clashes else "无重名"))
|
|
393
|
+
|
|
394
|
+
if args.out:
|
|
395
|
+
io.open(args.out, "w", encoding="utf-8", newline="\n").write(prompt)
|
|
396
|
+
report.append(f"留档:{args.out}")
|
|
397
|
+
|
|
398
|
+
sys.stdout.write(prompt)
|
|
399
|
+
sys.stdout.write("\n" + "=" * 60 + "\n")
|
|
400
|
+
sys.stdout.write("以上是 prompt 正文(空槽以外一字不改,照抄)。以下不进 prompt:\n\n")
|
|
401
|
+
for item in report:
|
|
402
|
+
sys.stdout.write(f"- {item}\n")
|
|
403
|
+
sys.stdout.write(
|
|
404
|
+
f"- 待填槽位:{prompt.count(SLOT_MARK)} 个(搜 {SLOT_MARK})\n")
|
|
405
|
+
sys.stdout.write(f"- 骨架长度:{len(prompt)} 字符\n")
|
|
406
|
+
return 0
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
if __name__ == "__main__":
|
|
410
|
+
sys.exit(main())
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
5
|
+
const { loadStyleWhitelist, maskStyleText } = require('./style-whitelist.js');
|
|
5
6
|
const path = require('path');
|
|
6
7
|
|
|
7
8
|
const USAGE = `Usage: node check-ai-patterns.js [--check] [--json] [--fail-on=blocking|all] <file...>
|
|
@@ -30,6 +31,7 @@ Detect high-risk AI-flavor prose patterns that need human rewrite:
|
|
|
30
31
|
- 章尾状态总结体 (文末窗口 这一夜注定/这一切都结束了/新的人生才刚刚开始/命运的齿轮)
|
|
31
32
|
- 引号强调滥用 (叙述里 1-4 字短词加引号强调,密度型)
|
|
32
33
|
|
|
34
|
+
Book-local .deslop-whitelist literal spans are excluded from style scanning (no regex or ancestor inheritance).
|
|
33
35
|
Each finding carries severity: blocking by default for generation/deslop cleanup (not-is-comparison / em-dash / voice-contrast / negation-parade / reverse-not-is / trailer-ending / trailer-summary). This is a local style/readability gate, not an AIGC detector score; functional human text can be marked for review instead of hard-edited for a detector.
|
|
34
36
|
或 advisory (period-stutter / long-paragraph / micro-action-tic / stock-reaction-tic / action-list-tic / abstract-summary-tic / cliche-density-tic / metaphor-density-tic / reasoning-chain-tic / system-notice-formality-tic / overcompressed-prose-tic / low-connective-density-tic / quote-emphasis-tic / formulaic-parallelism,是提示,justified 的长推理/氛围段可保留)。
|
|
35
37
|
--fail-on=blocking 只在出现 blocking finding 时退出 1;默认 --fail-on=all 有任何 finding 即退出 1。
|
|
@@ -308,7 +310,10 @@ for (const file of options.files) {
|
|
|
308
310
|
continue;
|
|
309
311
|
}
|
|
310
312
|
|
|
311
|
-
|
|
313
|
+
let whitelist;
|
|
314
|
+
try { whitelist = loadStyleWhitelist(fullPath); }
|
|
315
|
+
catch (error) { die(`${file}: unable to read .deslop-whitelist (${error.message})`); }
|
|
316
|
+
const findings = scanDocument(maskStyleText(input, whitelist)).map((finding) => ({ file, ...finding }));
|
|
312
317
|
allFindings.push(...findings);
|
|
313
318
|
}
|
|
314
319
|
|
|
@@ -43,6 +43,17 @@ const PLACEHOLDER_PATTERNS = [
|
|
|
43
43
|
{ re: /我(无法|不能)(继续(写|创作|生成|下去)|生成(内容|文本|正文)?|创作|续写|完成(这个|本)?(章|篇|创作|请求))/, label: '元信息泄漏(生成拒绝语)', hard: false },
|
|
44
44
|
];
|
|
45
45
|
|
|
46
|
+
const QUOTED_SPAN_PATTERNS = [
|
|
47
|
+
/「[^」]*」/g,
|
|
48
|
+
/『[^』]*』/g,
|
|
49
|
+
/【[^】]*】/g,
|
|
50
|
+
/“[^”]*”/g,
|
|
51
|
+
// 单引号须成对;词内撇号(don't、O’Connor)不作为开闭引号。
|
|
52
|
+
/(?<![A-Za-z0-9_])‘(?:[^’]|(?<=[A-Za-z0-9_])’(?=[A-Za-z0-9_]))*(?!(?<=[A-Za-z0-9_])’[A-Za-z0-9_])’/g,
|
|
53
|
+
/"[^"]*"/g,
|
|
54
|
+
/(?<![A-Za-z0-9_])'(?:[^']|(?<=[A-Za-z0-9_])'(?=[A-Za-z0-9_]))*(?!(?<=[A-Za-z0-9_])'[A-Za-z0-9_])'/g,
|
|
55
|
+
];
|
|
56
|
+
|
|
46
57
|
// 工程词泄漏(正文元信息扫描的确定性版):弱模型把写作工程词漏进正文,破坏代入感
|
|
47
58
|
// (DeepSeek-v4 这类会在对话里冒「该到下一章了」)。漏词的模型自己发现不了,靠脚本兜。
|
|
48
59
|
// tier1 = 纯写作流水线术语,正文里几乎永不合法;tier2 = 章节结构/歧义词,角色在故事内
|
|
@@ -150,21 +161,23 @@ function isContent(trimmed) {
|
|
|
150
161
|
return trimmed && !trimmed.startsWith('#') && !/^-{3,}$/.test(trimmed);
|
|
151
162
|
}
|
|
152
163
|
|
|
153
|
-
|
|
154
|
-
|
|
164
|
+
// 等长遮住成对引号内的内容以保留列号;未闭合引号不豁免。
|
|
165
|
+
function maskQuotedSpans(text) {
|
|
166
|
+
let masked = text;
|
|
167
|
+
for (const pattern of QUOTED_SPAN_PATTERNS) {
|
|
168
|
+
masked = masked.replace(pattern, (match) => ' '.repeat(match.length));
|
|
169
|
+
}
|
|
170
|
+
return masked;
|
|
155
171
|
}
|
|
156
172
|
|
|
157
173
|
// 去掉成对引号内的片段(台词/系统词/引用物件),只留引号外叙述。复读判定用:重复台词是体裁
|
|
158
174
|
// 手法(豁免),但「叙述 + 引号内物件/短台词」混合行里引号外叙述的复读仍是退化,不能整行豁免。
|
|
159
175
|
function stripQuoted(text) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
.replace(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
.replace(/‘[^’]*’/g, '')
|
|
166
|
-
.replace(/"[^"]*"/g, '')
|
|
167
|
-
.replace(/'[^']*'/g, '');
|
|
176
|
+
let stripped = text;
|
|
177
|
+
for (const pattern of QUOTED_SPAN_PATTERNS) {
|
|
178
|
+
stripped = stripped.replace(pattern, '');
|
|
179
|
+
}
|
|
180
|
+
return stripped;
|
|
168
181
|
}
|
|
169
182
|
|
|
170
183
|
function visibleLength(text) {
|
|
@@ -253,18 +266,19 @@ function findPlaceholders(content) {
|
|
|
253
266
|
const findings = [];
|
|
254
267
|
for (const { trimmed, lineNo } of content) {
|
|
255
268
|
if (!isContent(trimmed)) continue;
|
|
256
|
-
const
|
|
269
|
+
const outsideQuotes = maskQuotedSpans(trimmed);
|
|
257
270
|
for (const { re, label, hard } of PLACEHOLDER_PATTERNS) {
|
|
258
|
-
|
|
259
|
-
const m = re.exec(trimmed);
|
|
271
|
+
// hard 信号仍扫描整行;soft 自指/拒绝语只扫描成对引号外,且遮罩等长以保留列号。
|
|
272
|
+
const m = re.exec(hard ? trimmed : outsideQuotes);
|
|
260
273
|
if (m) {
|
|
274
|
+
const index = m.index || 0;
|
|
261
275
|
findings.push({
|
|
262
276
|
line: lineNo,
|
|
263
|
-
column:
|
|
277
|
+
column: index + 1,
|
|
264
278
|
type: 'placeholder-leak',
|
|
265
279
|
severity: 'blocking',
|
|
266
280
|
message: `${label}:正文混入元信息/拒绝语/占位符,重写本段干净落地。`,
|
|
267
|
-
excerpt: compact(trimmed.slice(Math.max(0,
|
|
281
|
+
excerpt: compact(trimmed.slice(Math.max(0, index - 4), index + 20)),
|
|
268
282
|
});
|
|
269
283
|
break; // one finding per line is enough
|
|
270
284
|
}
|
|
@@ -283,17 +297,22 @@ function findMetaLeak(content) {
|
|
|
283
297
|
// 标题行(第N章 章名,无 ## 前缀时也算)属「标题行以外的正文」之外,排除
|
|
284
298
|
if (/^第[一二三四五六七八九十百千万两0-9]+章/.test(trimmed)) continue;
|
|
285
299
|
}
|
|
286
|
-
const
|
|
287
|
-
let m = META_TIER1_RE.exec(
|
|
300
|
+
const outsideQuotes = maskQuotedSpans(trimmed);
|
|
301
|
+
let m = META_TIER1_RE.exec(outsideQuotes);
|
|
302
|
+
let quotedTier1 = false;
|
|
303
|
+
if (!m) {
|
|
304
|
+
m = META_TIER1_RE.exec(trimmed);
|
|
305
|
+
quotedTier1 = Boolean(m);
|
|
306
|
+
}
|
|
288
307
|
if (m) {
|
|
289
308
|
// tier1 纯工程词正文里几乎永不合法→blocking;但写手/编剧题材里角色在故事内真讨论创作,
|
|
290
|
-
//
|
|
309
|
+
// 且命中实际位于成对引号内时可能合法,降级为 advisory。同一行别处出现引号不影响判定。
|
|
291
310
|
findings.push({
|
|
292
311
|
line: lineNo,
|
|
293
312
|
column: m.index + 1,
|
|
294
313
|
type: 'meta-leak',
|
|
295
|
-
severity:
|
|
296
|
-
message: `工程词泄漏:「${m[0]}」是写作流水线术语,正文里不该出现;改成角色/场景内表达。${
|
|
314
|
+
severity: quotedTier1 ? 'advisory' : 'blocking',
|
|
315
|
+
message: `工程词泄漏:「${m[0]}」是写作流水线术语,正文里不该出现;改成角色/场景内表达。${quotedTier1 ? '例外:角色为作者/编剧、在故事内真实讨论创作时,台词里可能合法。' : ''}`,
|
|
297
316
|
excerpt: compact(trimmed.slice(Math.max(0, m.index - 6), m.index + 18)),
|
|
298
317
|
});
|
|
299
318
|
continue; // tier1 命中即可,不再叠 tier2
|