@oh-story/dsh 0.1.9 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -12
- package/lib/client.js +204 -72
- package/lib/client.js.map +3 -3
- package/lib/drama/manifest.json +84 -74
- package/lib/drama/skills/short-drama/SKILL.md +2 -2
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +14 -0
- package/lib/drama/skills/short-drama-assets/references/character-and-look.md +10 -0
- package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +6 -9
- package/lib/drama/skills/short-drama-assets/references/voice-direction.md +28 -0
- package/lib/drama/skills/short-drama-edit/SKILL.md +27 -29
- package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +2 -2
- package/lib/drama/skills/short-drama-edit/references/cut-craft.md +24 -67
- package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +27 -56
- package/lib/drama/skills/short-drama-edit/references/generated-footage.md +37 -79
- package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +39 -92
- package/lib/drama/skills/short-drama-edit/references/stage-contract.md +12 -16
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +126 -11
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +64 -0
- package/lib/drama/skills/short-drama-image-prompts/SKILL.md +2 -0
- package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +11 -0
- package/lib/drama/skills/short-drama-produce/SKILL.md +5 -1
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +14 -20
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +8 -26
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-speech.md +41 -0
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +142 -1
- package/lib/drama/skills/short-drama-review/SKILL.md +2 -3
- package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +6 -19
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +9 -35
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +10 -6
- package/lib/drama/skills/short-drama-storyboard/references/lighting-craft.md +39 -0
- package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +58 -21
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +1 -1
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +10 -9
- package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +17 -0
- package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +12 -38
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +4 -7
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +8 -22
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +6 -19
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +2 -3
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +34 -9
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +2 -2
- package/lib/drama/skills/short-drama-write/SKILL.md +2 -4
- package/lib/index.js +289 -91
- package/lib/index.js.map +4 -4
- package/lib/novel-to-game/README.md +299 -59
- package/lib/novel-to-game/README_ZH.md +299 -56
- package/lib/novel-to-game/VERSION +1 -1
- package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +3 -1
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +11 -3
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +397 -8
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +0 -31
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +71 -73
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +78 -89
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +37 -14
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +5 -5
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -1
- package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +11 -16
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +11 -11
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +23 -0
- package/lib/novel-to-game/manifest.json +77 -87
- package/lib/novel-to-game/skills/game-art-direction/SKILL.md +7 -12
- package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +27 -106
- package/lib/novel-to-game/skills/game-build/SKILL.md +13 -14
- package/lib/novel-to-game/skills/game-build/references/blender-asset-workflow.md +24 -0
- package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +45 -82
- package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +14 -45
- package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +30 -65
- package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +13 -27
- package/lib/novel-to-game/skills/game-concept/SKILL.md +13 -15
- package/lib/novel-to-game/skills/game-concept/references/concept-method.md +28 -75
- package/lib/novel-to-game/skills/game-qa/SKILL.md +6 -7
- package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +9 -5
- package/lib/novel-to-game/skills/game-world-design/SKILL.md +12 -16
- package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +23 -72
- package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +14 -48
- package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +36 -103
- package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +4 -10
- package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +4 -4
- package/lib/novel-to-game/skills/novel-to-game/SKILL.md +9 -21
- package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +5 -4
- package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +5 -8
- package/lib/oh-story/manifest.json +230 -185
- package/lib/oh-story/roles/chapter-extractor.md +177 -237
- package/lib/oh-story/roles/consistency-checker.md +4 -4
- package/lib/oh-story/roles/narrative-writer.md +24 -25
- package/lib/oh-story/roles/story-architect.md +5 -5
- package/lib/oh-story/roles/story-explorer.md +11 -11
- package/lib/oh-story/skills/story/SKILL.md +13 -7
- package/lib/oh-story/skills/story/VERSION +1 -1
- package/lib/oh-story/skills/story/references/author-memory.md +54 -24
- package/lib/oh-story/skills/story/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-cover/SKILL.md +10 -1
- package/lib/oh-story/skills/story-deslop/SKILL.md +40 -53
- package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +11 -10
- package/lib/oh-story/skills/story-deslop/references/author-memory.md +54 -24
- package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +1 -1
- package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-import/SKILL.md +77 -81
- package/lib/oh-story/skills/story-import/references/character-state-reverse.md +1 -1
- package/lib/oh-story/skills/story-import/references/state-tracking.md +4 -69
- package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +10 -8
- package/lib/oh-story/skills/story-import/references/tracking-transaction.md +8 -6
- package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +34 -16
- package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +210 -2
- package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +3 -0
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +91 -258
- package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +245 -0
- package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +2 -2
- package/lib/oh-story/skills/story-long-analyze/references/inspiration-library.md +105 -0
- package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +113 -105
- package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +169 -125
- package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +172 -64
- package/lib/oh-story/skills/story-long-analyze/references/semantic-acceptance-fixtures.md +84 -0
- package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +42 -132
- package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +5 -5
- package/lib/oh-story/skills/story-long-analyze/scripts/build_chapter_index.py +466 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/inspect_existing_assets.py +702 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/inspiration_index.py +864 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +1106 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/render_relation_chart.py +425 -0
- package/lib/oh-story/skills/story-long-scan/SKILL.md +4 -1
- package/lib/oh-story/skills/story-long-write/SKILL.md +38 -29
- package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +11 -10
- package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +25 -20
- package/lib/oh-story/skills/story-long-write/references/author-memory.md +54 -24
- package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +3 -3
- package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +26 -0
- package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +4 -0
- package/lib/oh-story/skills/story-long-write/references/emotional-methods.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +4 -13
- package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/project-files.md +9 -7
- package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +12 -4
- package/lib/oh-story/skills/story-long-write/references/reference-index.md +1 -2
- package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +8 -6
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +86 -115
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +55 -120
- package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +15 -11
- package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +66 -68
- package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +3 -3
- package/lib/oh-story/skills/story-long-write/references/writing-craft.md +4 -12
- package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +139 -18
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +34 -16
- package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +5 -3
- package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +58 -0
- package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +210 -2
- package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +3 -0
- package/lib/oh-story/skills/story-review/SKILL.md +68 -85
- package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +11 -10
- package/lib/oh-story/skills/story-review/references/author-memory.md +54 -24
- package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +4 -0
- package/lib/oh-story/skills/story-review/references/review-quality.md +4 -10
- package/lib/oh-story/skills/story-review/references/tracking-transaction.md +8 -6
- package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +210 -2
- package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +3 -0
- package/lib/oh-story/skills/story-setup/SKILL.md +50 -42
- package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +11 -10
- package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +4 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +4 -12
- package/lib/oh-story/skills/story-setup/references/diagnostics.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/SKILL.md +22 -3
- package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +5 -1
- package/lib/oh-story/skills/story-short-scan/SKILL.md +5 -1
- package/lib/oh-story/skills/story-short-write/SKILL.md +6 -147
- package/lib/oh-story/skills/story-short-write/references/author-memory.md +54 -24
- package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +4 -0
- package/lib/oh-story/skills/story-short-write/references/reference-index.md +2 -0
- package/lib/oh-story/skills/story-short-write/references/workflow-design.md +3 -2
- package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +126 -0
- package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +32 -0
- package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +934 -170
- package/lib/types/client/drama-production-view.d.ts.map +1 -1
- package/lib/types/client/file-activity.d.ts +19 -1
- package/lib/types/client/file-activity.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/production-prompts.d.ts +7 -0
- package/lib/types/client/production-prompts.d.ts.map +1 -1
- package/lib/types/client/production-runtime.d.ts +16 -0
- package/lib/types/client/production-runtime.d.ts.map +1 -1
- package/lib/types/drama-adapters.d.ts +5 -3
- package/lib/types/drama-adapters.d.ts.map +1 -1
- package/lib/types/native-hooks.d.ts +52 -0
- package/lib/types/native-hooks.d.ts.map +1 -1
- package/lib/types/role-identity.d.ts +17 -0
- package/lib/types/role-identity.d.ts.map +1 -0
- package/lib/types/role-tool.d.ts.map +1 -1
- package/lib/types/skill-provider.d.ts.map +1 -1
- package/package.json +45 -45
- package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +0 -58
- package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +0 -53
- package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +0 -38
- package/lib/oh-story/skills/story-long-write/references/state-tracking.md +0 -108
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
"""build_writer_prompt.py — 确定性组装 narrative-writer 的 spawn prompt 骨架。
|
|
3
3
|
|
|
4
4
|
用法:
|
|
5
|
-
python build_writer_prompt.py --project <书目录> --chapter N [--out <文件>]
|
|
5
|
+
python build_writer_prompt.py --project <书目录> --chapter N [--out [<文件>]]
|
|
6
|
+
|
|
7
|
+
`--out` 不带路径时留档到本章工作目录 `<书目录>/.story/work/第NNN章/writer_prompt.md`。
|
|
8
|
+
分组 segment、prompt 留档和逐章事务 JSON 都只放这个书内工作目录——不写系统 /tmp
|
|
9
|
+
(多本书/多会话同章号会互相覆盖,Windows 也没有 /tmp),也不写进 正文/(会被当成章节)。
|
|
10
|
+
`storyctl.py chapter commit` 成功后自动删除该章工作目录。
|
|
6
11
|
|
|
7
12
|
跑在「写前准备」第一步。stdout 分两区,`===` 分隔线以上是 prompt 正文
|
|
8
13
|
(主会话照抄,空槽以外一字不改),以下是核对报告(不进 prompt)。
|
|
@@ -10,8 +15,9 @@
|
|
|
10
15
|
职责边界:
|
|
11
16
|
- 脚本做确定性部分:固定首行、定位、标题行字面量、细纲指针、文风全文路径与裁决、
|
|
12
17
|
上一章结尾、降档判定与情绪/节奏槽、固定块指针。
|
|
18
|
+
- 脚本代查作者记忆(prose_style + story_design)并填好 author_preferences。
|
|
13
19
|
- 主会话填八槽:执行安排 / 本章意图 / 参考技法 / 本节速记 / 涉及角色 / genre_prose_card /
|
|
14
|
-
必读设定 /
|
|
20
|
+
必读设定 / style_resolution。降档不成立时情绪与节奏槽也归主会话。
|
|
15
21
|
材料槽对应原流程步骤 3「写前准备」的四项输出(本节速记 / 情绪目标 / 涉及角色 /
|
|
16
22
|
参考技法)加上题材卡、设定补漏与作者偏好——都是判断,脚本做不了。
|
|
17
23
|
- 续写状态卡校验后由主会话筛选,在「本节速记」槽内写入本章需要的状态。
|
|
@@ -23,7 +29,9 @@ Exit: 0 = 骨架已输出;2 = 输入缺失或无效。
|
|
|
23
29
|
|
|
24
30
|
import argparse
|
|
25
31
|
import io
|
|
32
|
+
import json
|
|
26
33
|
import re
|
|
34
|
+
import subprocess
|
|
27
35
|
import sys
|
|
28
36
|
from pathlib import Path
|
|
29
37
|
from outline_view import parse as parse_volume
|
|
@@ -31,6 +39,13 @@ from outline_view import parse as parse_volume
|
|
|
31
39
|
TAIL_CHARS = 400 # 上一章结尾注入的目标字符数(按整行回退,不切半句)
|
|
32
40
|
STATE_SECTIONS = ("当前位置", "长期约束", "核心角色状态", "活跃伏笔", "近三章速记", "下一章承诺", "连贯性风险")
|
|
33
41
|
SLOT_MARK = "[主会话填]"
|
|
42
|
+
WORK_ROOT = (".story", "work")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def chapter_work_dir(project: Path, chapter: int) -> Path:
|
|
46
|
+
"""本章临时文件的唯一落点;与 storyctl.py 提交后清理的目录同一口径。"""
|
|
47
|
+
width = max(3, len(str(chapter)))
|
|
48
|
+
return project.joinpath(*WORK_ROOT, f"第{chapter:0{width}d}章")
|
|
34
49
|
|
|
35
50
|
|
|
36
51
|
def read_text(path: Path):
|
|
@@ -58,7 +73,7 @@ def find_chapter_file(directory: Path, chapter: int, prefix: str):
|
|
|
58
73
|
return None
|
|
59
74
|
pattern = re.compile(rf"^{prefix}第0*{chapter}章.*\.md$")
|
|
60
75
|
for entry in sorted(directory.iterdir()):
|
|
61
|
-
if entry.is_file() and pattern.match(entry.name):
|
|
76
|
+
if entry.is_file() and pattern.match(entry.name) and "_原稿_" not in entry.name:
|
|
62
77
|
return entry
|
|
63
78
|
return None
|
|
64
79
|
|
|
@@ -125,6 +140,83 @@ def previous_chapter_tail(project: Path, chapter: int):
|
|
|
125
140
|
return prev, "\n".join(reversed(picked))
|
|
126
141
|
|
|
127
142
|
|
|
143
|
+
MEMORY_KINDS = ("prose_style", "story_design")
|
|
144
|
+
# 限定「流程」的作者记忆里,这些取值指的就是长篇写正文。
|
|
145
|
+
LONG_WRITE_WORKFLOWS = {"长篇", "长篇写作", "写长篇", "长篇连载", "长篇网文", "长篇正文", "story-long-write"}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def scoped_memory_values(workspace: Path):
|
|
149
|
+
"""项目级 store 里限定题材/流程的 active 条目取值:{"genre": {...}, "workflow": {...}}。"""
|
|
150
|
+
values = {"genre": set(), "workflow": set()}
|
|
151
|
+
state = read_text(workspace / ".story" / "作者记忆" / "_author-memory-state.json")
|
|
152
|
+
try:
|
|
153
|
+
items = (json.loads(state) if state else {}).get("items") or {}
|
|
154
|
+
items = list(items.values())
|
|
155
|
+
except (ValueError, AttributeError, TypeError):
|
|
156
|
+
return values
|
|
157
|
+
for item in items:
|
|
158
|
+
if not isinstance(item, dict):
|
|
159
|
+
continue
|
|
160
|
+
scope = item.get("scope") if isinstance(item.get("scope"), dict) else {}
|
|
161
|
+
if (item.get("status") == "active" and item.get("kind") in MEMORY_KINDS
|
|
162
|
+
and scope.get("level") in values and scope.get("value")):
|
|
163
|
+
values[scope["level"]].add(scope["value"])
|
|
164
|
+
return values
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def book_genres(project: Path):
|
|
168
|
+
"""「题材类型」行按分隔符切成词;未填的模板占位({…})不算。"""
|
|
169
|
+
text = read_text(project / "设定" / "题材定位.md") or ""
|
|
170
|
+
match = re.search(r"^[ \t]*[-*+]?[ \t]*\**题材(?:类型)?\**[ \t]*[::](.*)$", text, re.M)
|
|
171
|
+
if not match or "{" in match.group(1):
|
|
172
|
+
return set()
|
|
173
|
+
return {word.casefold() for word in re.split(r"[\W_丨×]+", match.group(1)) if word}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def query_author_memory(project: Path):
|
|
177
|
+
"""代主会话做写正文那一次作者记忆查询(prose_style + story_design,每次 ≤2KB)。
|
|
178
|
+
|
|
179
|
+
工作区取书目录及其祖先里第一个带 `.story-deployed` 的目录;找不到就把书目录当工作区。
|
|
180
|
+
限定题材的条目只在取值出现在本书「题材类型」里时代查,限定流程的只认长篇写作的取值;
|
|
181
|
+
其余限定条目不猜,报给主会话自己判断。
|
|
182
|
+
返回 (条目列表, 超编 ID 列表, 未代查的限定取值, 错误);错误时由主会话按 author-memory.md 手动查询。
|
|
183
|
+
"""
|
|
184
|
+
workspace = next((d for d in (project, *project.parents) if (d / ".story-deployed").is_file()), project)
|
|
185
|
+
script = Path(__file__).with_name("author_memory_commit.py")
|
|
186
|
+
if not script.is_file():
|
|
187
|
+
return None, [], {}, "author_memory_commit.py 缺失"
|
|
188
|
+
scoped = scoped_memory_values(workspace)
|
|
189
|
+
book = book_genres(project)
|
|
190
|
+
genres = sorted(v for v in scoped["genre"] if v.casefold() in book) or [None]
|
|
191
|
+
workflows = sorted(v for v in scoped["workflow"] if v.casefold() in {w.casefold() for w in LONG_WRITE_WORKFLOWS})
|
|
192
|
+
skipped = {"genre": sorted(v for v in scoped["genre"] if v not in genres),
|
|
193
|
+
"workflow": sorted(v for v in scoped["workflow"] if v not in workflows)}
|
|
194
|
+
items, omitted, seen = [], [], set()
|
|
195
|
+
for genre in genres:
|
|
196
|
+
for workflow in workflows or [None]:
|
|
197
|
+
command = [sys.executable, str(script), "query", "--workspace", str(workspace), "--book-root", str(project)]
|
|
198
|
+
for kind in MEMORY_KINDS:
|
|
199
|
+
command += ["--kind", kind]
|
|
200
|
+
if genre:
|
|
201
|
+
command += ["--genre", genre]
|
|
202
|
+
if workflow:
|
|
203
|
+
command += ["--workflow", workflow]
|
|
204
|
+
completed = subprocess.run(command, capture_output=True, text=True, encoding="utf-8", check=False)
|
|
205
|
+
try:
|
|
206
|
+
result = json.loads(completed.stdout or "{}")
|
|
207
|
+
except ValueError:
|
|
208
|
+
result = {}
|
|
209
|
+
if not result.get("ok"):
|
|
210
|
+
return None, [], {}, result.get("error") or completed.stderr.strip() or "query 失败"
|
|
211
|
+
for item in result.get("items") or []:
|
|
212
|
+
if item.get("id") not in seen:
|
|
213
|
+
seen.add(item.get("id"))
|
|
214
|
+
items.append(item)
|
|
215
|
+
omitted += [i for i in result.get("omitted_ids") or [] if i not in omitted and i not in seen]
|
|
216
|
+
omitted = [i for i in omitted if i not in seen]
|
|
217
|
+
return items, omitted, {k: v for k, v in skipped.items() if v}, None
|
|
218
|
+
|
|
219
|
+
|
|
128
220
|
def learn_heading_form(project: Path, chapter: int, title: str):
|
|
129
221
|
"""从已有正文学标题行形态(层级/章号填充位数/分隔符),不按通用规则推导。
|
|
130
222
|
|
|
@@ -233,7 +325,7 @@ def build(project: Path, chapter: int, report: list):
|
|
|
233
325
|
parts.append(
|
|
234
326
|
"——— 文风 ———\n"
|
|
235
327
|
"(本书无可用的 设定/文风.md,未进入自定义文风模式;"
|
|
236
|
-
"按
|
|
328
|
+
"按 benchmark-recall.md 走对标文风召回,由主会话补路径与召回指令)")
|
|
237
329
|
report.append("文风:custom_style=false,文风召回归主会话(未跳过,留标题)")
|
|
238
330
|
|
|
239
331
|
# ---- 上一章结尾(不给路径,避免写手回头读整章)----
|
|
@@ -296,15 +388,18 @@ def build(project: Path, chapter: int, report: list):
|
|
|
296
388
|
else:
|
|
297
389
|
slot_recall = ("——— 情绪与节奏召回 ———\n"
|
|
298
390
|
f"{SLOT_MARK} 降档不成立(" + "、".join(why) +
|
|
299
|
-
"),按
|
|
391
|
+
"),按 benchmark-recall.md 走全量召回后填此槽")
|
|
300
392
|
report.append("召回降档:不成立(" + "、".join(why) + ")—— 全量召回归主会话")
|
|
301
393
|
|
|
302
394
|
# ---- 需要主会话判断的槽位 ----
|
|
395
|
+
work_dir = chapter_work_dir(project, chapter)
|
|
303
396
|
parts.append(
|
|
304
397
|
"——— 执行安排 ———\n"
|
|
305
398
|
f"{SLOT_MARK} 全章细纲用于整体编排。默认按自然转场或因果停顿分前后两组,"
|
|
306
|
-
"
|
|
307
|
-
"
|
|
399
|
+
"填写当前组的情节点/片段;先只写前组,父流程测一次 checkpoint 后"
|
|
400
|
+
"再给后组和机器剩余区间。只有用户明确要求一次成文时才填「全章,直接写最终路径」。\n"
|
|
401
|
+
f"分组临时文件:前组 {work_dir / '前组.md'},后组 {work_dir / '后组.md'}"
|
|
402
|
+
"(只写这里,不写 /tmp 或 正文/)。")
|
|
308
403
|
parts.append(f"——— 本章意图(一句话)———\n{SLOT_MARK}")
|
|
309
404
|
parts.append(slot_recall)
|
|
310
405
|
# 伏笔与卷级禁忌走「主会话筛选后写进速记」这条原设计路线(步骤 3 状态筛选),
|
|
@@ -312,7 +407,7 @@ def build(project: Path, chapter: int, report: list):
|
|
|
312
407
|
# 代价是它依赖主会话逐章想起来,所以这里把提示语写成写死的三问清单。
|
|
313
408
|
parts.append(
|
|
314
409
|
"——— 参考技法 ———\n"
|
|
315
|
-
f"{SLOT_MARK}
|
|
410
|
+
f"{SLOT_MARK} 借鉴哪个参考文件的哪个技法、用在哪些段落;没有就写「无」。"
|
|
316
411
|
"按 reference 表的任务条件读取;本书文风只覆盖冲突表达条款,不停读整份文件。")
|
|
317
412
|
parts.append(
|
|
318
413
|
"——— 本节速记 ———\n"
|
|
@@ -328,15 +423,33 @@ def build(project: Path, chapter: int, report: list):
|
|
|
328
423
|
parts.append("——— 题材正文提示卡(genre_prose_card,只含本章相关条目)———\n"
|
|
329
424
|
f"{SLOT_MARK} 主题材抽 3-5 条、辅题材 1-2 条;只作内部校准,不进正文")
|
|
330
425
|
parts.append(slot_setting)
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
426
|
+
items, omitted, skipped, memory_error = query_author_memory(project)
|
|
427
|
+
if memory_error:
|
|
428
|
+
memory_block = (f"{SLOT_MARK} 组装脚本查询作者记忆失败({memory_error}),"
|
|
429
|
+
"按 author-memory.md 手动 query prose_style + story_design 后填入;无则写「无」")
|
|
430
|
+
report.append(f"作者记忆:脚本查询失败——{memory_error},归主会话手动查询")
|
|
431
|
+
elif items:
|
|
432
|
+
memory_block = "\n".join(f"- {item.get('assertion', '').strip()}({item.get('id', '')})" for item in items)
|
|
433
|
+
report.append(f"作者记忆:已注入 {len(items)} 条" + (
|
|
434
|
+
f";超编未装下 {len(omitted)} 条({'、'.join(omitted)}),转告作者建议「整理作者记忆」" if omitted else ""))
|
|
435
|
+
else:
|
|
436
|
+
memory_block = "无"
|
|
437
|
+
report.append("作者记忆:无相关 active 条目")
|
|
438
|
+
if skipped and not memory_error:
|
|
439
|
+
named = ";".join(f"{'题材' if k == 'genre' else '流程'}:{'、'.join(v)}" for k, v in skipped.items())
|
|
440
|
+
memory_block += (f"\n{SLOT_MARK} 另有限定范围的作者记忆未代查({named}):本书适用的,"
|
|
441
|
+
"按 author-memory.md 带 --genre/--workflow 查询后补进本块")
|
|
442
|
+
report.append(f"作者记忆:限定范围未代查({named}),归主会话判断是否适用")
|
|
443
|
+
parts.append("——— author_preferences(作者记忆,低优先级倾向,不逐条追求命中)———\n" + memory_block)
|
|
444
|
+
parts.append("——— style_resolution ———\n"
|
|
445
|
+
f"{SLOT_MARK} 本轮请求里作者对表达的明确要求及其覆盖的默认条款(没有写「无」);"
|
|
446
|
+
"本书文风与作者记忆的裁决见上方各块,同一裁决传去味与审稿。")
|
|
447
|
+
|
|
448
|
+
parts.append("检查分工:写手负责编排、内容覆盖和格式自检;父流程质量阶段负责语义去味及最终文件扫描。写手不提前重复整轮去味或相同检查链,保留情节点落点、新增申报与原定交付。")
|
|
336
449
|
|
|
337
450
|
# ---- 固定块:压成指针,不重述 agent 定义 ----
|
|
338
451
|
parts.append(
|
|
339
|
-
"本次照你的铁律 1-8
|
|
452
|
+
"本次照你的铁律 1-8 与被调用协议执行(细纲优先边界、正文形状、新增物三级、"
|
|
340
453
|
"阅读体验字段、交付三附件均以你的定义为准,此处不重述)。")
|
|
341
454
|
parts.append(
|
|
342
455
|
"字数目标按细纲执行,字数口径 visible_chars_v1;按执行安排交付,"
|
|
@@ -360,7 +473,8 @@ def main(argv=None):
|
|
|
360
473
|
parser = argparse.ArgumentParser()
|
|
361
474
|
parser.add_argument("--project", required=True)
|
|
362
475
|
parser.add_argument("--chapter", required=True, type=int)
|
|
363
|
-
parser.add_argument("--out", default=None
|
|
476
|
+
parser.add_argument("--out", nargs="?", const="", default=None,
|
|
477
|
+
help="prompt 留档路径;不带值时留档到本章工作目录 writer_prompt.md")
|
|
364
478
|
args = parser.parse_args(argv)
|
|
365
479
|
|
|
366
480
|
project = Path(args.project).resolve()
|
|
@@ -391,9 +505,16 @@ def main(argv=None):
|
|
|
391
505
|
f"标题预检:《{title}》" + ("与既有章重名 → " + "、".join(clashes)
|
|
392
506
|
if clashes else "无重名"))
|
|
393
507
|
|
|
394
|
-
if args.out:
|
|
395
|
-
|
|
396
|
-
|
|
508
|
+
if args.out is not None:
|
|
509
|
+
out = Path(args.out) if args.out else chapter_work_dir(project, args.chapter) / "writer_prompt.md"
|
|
510
|
+
try:
|
|
511
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
512
|
+
with io.open(out, "w", encoding="utf-8", newline="\n") as handle:
|
|
513
|
+
handle.write(prompt)
|
|
514
|
+
except OSError as exc:
|
|
515
|
+
sys.stderr.write(f"留档写入失败:{out}({exc})\n")
|
|
516
|
+
return 2
|
|
517
|
+
report.append(f"留档:{out}")
|
|
397
518
|
|
|
398
519
|
sys.stdout.write(prompt)
|
|
399
520
|
sys.stdout.write("\n" + "=" * 60 + "\n")
|
|
@@ -18,11 +18,13 @@
|
|
|
18
18
|
const fs = require('fs')
|
|
19
19
|
const path = require('path')
|
|
20
20
|
|
|
21
|
-
// 权威模板:references/workflow-setup.md
|
|
21
|
+
// 权威模板:references/workflow-setup.md「排纲自查与细纲」里的细纲模板
|
|
22
|
+
// 必填只留有证据或事故撑腰的核心字段;阶段位置、章节定位、结构公式、本章标价、闭环状态、
|
|
23
|
+
// 写手自由区、契约风险、镜头准入、信息差触发点与「分辨率」列是可选的阅读体验字段——
|
|
24
|
+
// 写了照样消费,没写不拦(#383 隔离实验:只补目标情绪与主角目标就复现了补满全部字段的收益)。
|
|
22
25
|
const FIELDS = [
|
|
23
|
-
'核心事件', '字数目标', '字数口径', '
|
|
24
|
-
'主角目标/关键选择', '
|
|
25
|
-
'本章标价', '闭环状态', '本章禁止提前释放', '写手自由区', '契约风险',
|
|
26
|
+
'核心事件', '字数目标', '字数口径', '单元ID/位置', '目标情绪',
|
|
27
|
+
'主角目标/关键选择', '章首钩子', '爽点', '本章禁止提前释放',
|
|
26
28
|
]
|
|
27
29
|
const SUBSECTIONS = ['内容概括', '情节安排', '人物关系和出场顺序', '情节细化']
|
|
28
30
|
const FIVE_ACT = ['起因', '发展', '转折', '高潮', '结尾']
|
|
@@ -101,7 +103,7 @@ function checkSettingRefs(text, name, file, projectRoot) {
|
|
|
101
103
|
name,
|
|
102
104
|
missing.length ? `引用的路径不是可用文件:${missing.join('、')}` : `${refs.size} 处项目内引用全部存在`,
|
|
103
105
|
'细纲引用的每个项目内 .md 文件都真实存在——不许把槽位指向查无实据的权威文件',
|
|
104
|
-
'
|
|
106
|
+
'修正引用路径;或按新增物三级先把缺的设定落档(「写了要报」直接补并登记、「先问作者」进排纲底稿的供给自查提案),再在细纲引用。'
|
|
105
107
|
)
|
|
106
108
|
}
|
|
107
109
|
|
|
@@ -173,8 +175,8 @@ function verify(file, projectRoot = null) {
|
|
|
173
175
|
let header = null
|
|
174
176
|
for (const line of lines) {
|
|
175
177
|
const cells = parseTableRow(line)
|
|
176
|
-
//
|
|
177
|
-
// 两种都收:功能标签固定在第 3
|
|
178
|
+
// 现行模板默认四列(# / 情节点 / 功能标签 / 执行边界(禁+放)),需要时在功能标签后加「分辨率」列成五列。
|
|
179
|
+
// 两种都收:功能标签固定在第 3 列,执行边界一律取最后一列。
|
|
178
180
|
if (cells && (cells.length === 4 || cells.length === 5) && PLOT_HEADER_FIRST.test(cells[0])) {
|
|
179
181
|
header = cells
|
|
180
182
|
break
|
|
@@ -189,21 +191,21 @@ function verify(file, projectRoot = null) {
|
|
|
189
191
|
headerOk,
|
|
190
192
|
name,
|
|
191
193
|
header ? `表头:${header.join(' | ')}` : '未找到 | # | 情节点 | 功能标签 | [分辨率] | 执行边界 | 表头',
|
|
192
|
-
'
|
|
193
|
-
'
|
|
194
|
+
'情节细化使用表格:# / 情节点(谁做了什么) / 功能标签 / 执行边界(禁+放);需要时可在功能标签后加「分辨率」列',
|
|
195
|
+
'只把情节点序列改成这张表,逐点补功能标签与执行边界;不增删情节点本身。'
|
|
194
196
|
))
|
|
195
197
|
|
|
196
|
-
//
|
|
197
|
-
// 全是禁令的情节点表实测会把整章压成同一个温度(015
|
|
198
|
-
if (headerOk && header.length === 5) {
|
|
198
|
+
// 「放」半边:五列表或表头写明「禁+放」的四列表,执行边界必须至少一个点解除限制,
|
|
199
|
+
// 全是禁令的情节点表实测会把整章压成同一个温度(015 章十一个点全「不许」)。表头没写「禁+放」的四列表不追溯。
|
|
200
|
+
if (headerOk && (header.length === 5 || /放/.test(header[header.length - 1]))) {
|
|
199
201
|
const rows = []
|
|
200
202
|
for (const line of lines) {
|
|
201
203
|
const cells = parseTableRow(line)
|
|
202
|
-
if (cells && cells.length ===
|
|
204
|
+
if (cells && cells.length === header.length && /^\d+$/.test(cells[0])) rows.push(cells)
|
|
203
205
|
}
|
|
204
206
|
if (rows.length) {
|
|
205
207
|
const released = rows.filter((cells) => {
|
|
206
|
-
const match = cells[
|
|
208
|
+
const match = cells[cells.length - 1].match(/(?:^|[。;;\s])放\s*[::]([^]*?)(?=(?:禁|放)\s*[::]|$)/)
|
|
207
209
|
if (!match) return false
|
|
208
210
|
const value = match[1].replace(/[\s。;;,,、\[\]【】]/g, '')
|
|
209
211
|
return value.length > 0 && !/^(?:无|暂无|待补充|待定|无缺口)$/.test(value)
|
|
@@ -303,8 +305,24 @@ function resolveChapter(project, chapter) {
|
|
|
303
305
|
return { file: path.join(dir, hit) }
|
|
304
306
|
}
|
|
305
307
|
|
|
306
|
-
// --supply
|
|
308
|
+
// --supply:批末验证供给自查已落盘。新书写在卷纲旁的 排纲底稿_{单元ID}.md;老卷纲写在单元卡块内。
|
|
309
|
+
function hasSupplyHeading(text) {
|
|
310
|
+
return text.split(/\r?\n/).some((line) => {
|
|
311
|
+
const heading = line.replace(/\*\*/g, '').match(/^#{1,6}\s+(.+)$/)
|
|
312
|
+
return Boolean(heading) && /^供给自查(?:\s|[((]|$)/.test(heading[1])
|
|
313
|
+
})
|
|
314
|
+
}
|
|
315
|
+
|
|
307
316
|
function verifySupply(volumeFile, unitId) {
|
|
317
|
+
const draftFile = path.join(path.dirname(volumeFile), `排纲底稿_${unitId}.md`)
|
|
318
|
+
const draft = readUtf8(draftFile)
|
|
319
|
+
if (draft.ok) {
|
|
320
|
+
const ok = hasSupplyHeading(draft.text)
|
|
321
|
+
return {
|
|
322
|
+
schema_version: 1, verifier: 'story-long-write.outline-supply', file: path.resolve(draftFile), unit: unitId, ok,
|
|
323
|
+
evidence: ok ? '排纲底稿含「供给自查」小节' : `${path.basename(draftFile)} 里没有「供给自查」小节——每批出细纲前须做供给自查(含「无缺口」情形),见 workflow-setup.md「排纲自查与细纲」步骤 3`,
|
|
324
|
+
}
|
|
325
|
+
}
|
|
308
326
|
const read = readUtf8(volumeFile)
|
|
309
327
|
if (!read.ok) {
|
|
310
328
|
return { schema_version: 1, verifier: 'story-long-write.outline-supply', file: path.resolve(volumeFile), unit: unitId, ok: false, evidence: read.error || '卷纲文件为空' }
|
|
@@ -345,7 +363,7 @@ function verifySupply(volumeFile, unitId) {
|
|
|
345
363
|
file: path.resolve(volumeFile),
|
|
346
364
|
unit: unitId,
|
|
347
365
|
ok,
|
|
348
|
-
evidence: ok ? '单元卡含「供给自查」小节' :
|
|
366
|
+
evidence: ok ? '单元卡含「供给自查」小节' : `没有 排纲底稿_${unitId}.md,剧情单元 ${unitId} 的卡内也没有「供给自查」小节——每批出细纲前须做供给自查(含「无缺口」情形),见 workflow-setup.md「排纲自查与细纲」步骤 3`,
|
|
349
367
|
}
|
|
350
368
|
}
|
|
351
369
|
|
|
@@ -217,9 +217,11 @@ def main():
|
|
|
217
217
|
parser.add_argument("--check", action="store_true")
|
|
218
218
|
parser.add_argument("--history", action="store_true")
|
|
219
219
|
parser.add_argument("--strict", action="store_true", help="新建卷纲校验:缺作用域也视为错误")
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
220
|
+
# 排纲底稿现在放在卷纲外的 大纲/排纲底稿_{单元ID}.md,默认输出就是写作要的闭包。
|
|
221
|
+
# --stage outline 只为老卷纲保留:它们的供给自查等底稿还写在卷纲里(作用域:批次底稿)。
|
|
222
|
+
parser.add_argument("--stage", choices=["outline", "write"], default="write",
|
|
223
|
+
help="write(默认)=卷级常任+单元级;"
|
|
224
|
+
"outline=另带老卷纲里仍在用的批次底稿段")
|
|
223
225
|
args = parser.parse_args()
|
|
224
226
|
|
|
225
227
|
if hasattr(sys.stdout, "reconfigure"):
|
|
@@ -30,6 +30,9 @@ for _name in dir(core):
|
|
|
30
30
|
|
|
31
31
|
CHAPTER_CHECK_SCHEMA = "story-chapter-check/v1"
|
|
32
32
|
CHAPTER_ERROR_SCHEMA = "story-chapter-error/v1"
|
|
33
|
+
# 书内工作目录:分组 segment、writer prompt 留档、逐章事务 JSON 的唯一落点。
|
|
34
|
+
# 与 build_writer_prompt.py 同一口径;提交成功后整目录删除,失败时原样保留供重跑。
|
|
35
|
+
WORK_ROOT = (".story", "work")
|
|
33
36
|
|
|
34
37
|
|
|
35
38
|
class CliArgumentError(ValueError):
|
|
@@ -72,6 +75,32 @@ def _read_json_object(path: Path, label: str) -> dict[str, Any]:
|
|
|
72
75
|
return value
|
|
73
76
|
|
|
74
77
|
|
|
78
|
+
def chapter_work_dir(project: Path, chapter: int) -> Path:
|
|
79
|
+
width = max(3, len(str(chapter)))
|
|
80
|
+
return project.resolve().joinpath(*WORK_ROOT, f"第{chapter:0{width}d}章")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _remove_chapter_work_dir(project: Path, chapter: int) -> str | None:
|
|
84
|
+
"""Delete this chapter's scratch directory after a successful commit.
|
|
85
|
+
|
|
86
|
+
Only the per-chapter directory is removed; `.story/work` and `.story` are
|
|
87
|
+
pruned only when empty, so book-level author memory under `.story/` stays.
|
|
88
|
+
"""
|
|
89
|
+
path = chapter_work_dir(project, chapter)
|
|
90
|
+
if path.is_symlink():
|
|
91
|
+
path.unlink()
|
|
92
|
+
elif path.is_dir():
|
|
93
|
+
shutil.rmtree(path)
|
|
94
|
+
else:
|
|
95
|
+
return None
|
|
96
|
+
for parent in (path.parent, path.parent.parent):
|
|
97
|
+
try:
|
|
98
|
+
parent.rmdir()
|
|
99
|
+
except OSError:
|
|
100
|
+
break
|
|
101
|
+
return "/".join((*WORK_ROOT, path.name))
|
|
102
|
+
|
|
103
|
+
|
|
75
104
|
def _project_files(project: Path, chapter: int) -> tuple[Path, Path, int]:
|
|
76
105
|
root = project.resolve()
|
|
77
106
|
outline = find_chapter_file(root / "大纲", chapter, outline=True)
|
|
@@ -200,6 +229,23 @@ def chapter_check(project: Path, chapter: int) -> dict[str, Any]:
|
|
|
200
229
|
}
|
|
201
230
|
|
|
202
231
|
|
|
232
|
+
def fix_punctuation(project: Path, chapter: int) -> bool:
|
|
233
|
+
"""Run the deterministic punctuation normalizer on the chapter body in place.
|
|
234
|
+
|
|
235
|
+
Folding it into `chapter check --fix-punctuation` lets the parent flow close a
|
|
236
|
+
chapter with one call instead of running each cleanup script separately.
|
|
237
|
+
"""
|
|
238
|
+
_, body, _ = _project_files(project, chapter)
|
|
239
|
+
node = shutil.which("node")
|
|
240
|
+
script = Path(__file__).with_name("normalize-punctuation.js")
|
|
241
|
+
if node is None or not script.is_file():
|
|
242
|
+
return False # chapter check reports TOOL_UNAVAILABLE as a blocking finding
|
|
243
|
+
before = body.read_bytes()
|
|
244
|
+
subprocess.run([node, str(script), str(body)], text=True, encoding="utf-8",
|
|
245
|
+
capture_output=True, check=False)
|
|
246
|
+
return body.read_bytes() != before
|
|
247
|
+
|
|
248
|
+
|
|
203
249
|
def chapter_commit(project: Path, chapter: int, input_path: Path, *, accept_current_length: bool) -> dict[str, Any]:
|
|
204
250
|
checked = chapter_check(project, chapter)
|
|
205
251
|
require(checked["quality"]["status"] == "pass", "blocking quality findings must be fixed before commit")
|
|
@@ -220,6 +266,12 @@ def chapter_commit(project: Path, chapter: int, input_path: Path, *, accept_curr
|
|
|
220
266
|
checked["tracking_committed"] = state["last_committed_chapter"] >= chapter
|
|
221
267
|
checked["next_chapter_started"] = state["last_committed_chapter"] > chapter
|
|
222
268
|
checked["wordcount"] = state["wordcount_records"].get(str(chapter))
|
|
269
|
+
# The commit is already durable; a cleanup failure is reported, never turned into a failed commit.
|
|
270
|
+
try:
|
|
271
|
+
checked["work_dir_removed"] = _remove_chapter_work_dir(project, chapter)
|
|
272
|
+
except OSError as exc:
|
|
273
|
+
checked["work_dir_removed"] = None
|
|
274
|
+
checked["work_dir_cleanup_error"] = str(exc)
|
|
223
275
|
return checked
|
|
224
276
|
|
|
225
277
|
|
|
@@ -243,6 +295,9 @@ def _build_parser() -> StructuredArgumentParser:
|
|
|
243
295
|
subparser.add_argument("--chapter", type=int, required=True)
|
|
244
296
|
if command != "check":
|
|
245
297
|
subparser.add_argument("--input", type=Path, required=True)
|
|
298
|
+
else:
|
|
299
|
+
subparser.add_argument("--fix-punctuation", action="store_true",
|
|
300
|
+
help="先就地整理正文标点,再做检查")
|
|
246
301
|
return parser
|
|
247
302
|
|
|
248
303
|
|
|
@@ -286,7 +341,10 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
286
341
|
return _wordcount_command(args)
|
|
287
342
|
try:
|
|
288
343
|
if args.chapter_command == "check":
|
|
344
|
+
fixed = fix_punctuation(args.project, args.chapter) if args.fix_punctuation else None
|
|
289
345
|
result = chapter_check(args.project, args.chapter)
|
|
346
|
+
if fixed is not None:
|
|
347
|
+
result["punctuation_fixed"] = fixed
|
|
290
348
|
else:
|
|
291
349
|
result = chapter_commit(
|
|
292
350
|
args.project, args.chapter, args.input,
|