@chengzhao-dev/dsh-quarto-book 0.6.0 → 0.6.2

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.
Files changed (32) hide show
  1. package/README.en.md +6 -5
  2. package/README.md +6 -5
  3. package/locale/en.json +2 -2
  4. package/locale/zh.json +2 -2
  5. package/package.json +4 -4
  6. package/skills/designing-theme/SKILL.md +1 -1
  7. package/skills/designing-theme/assets/theme/palettes/hill-light/tokens.css +1 -1
  8. package/skills/writing-quarto/SKILL.md +6 -4
  9. package/skills/writing-quarto/examples/02-structure-shape.md +28 -0
  10. package/skills/writing-quarto/examples/05-sentence-reference.md +14 -0
  11. package/skills/writing-quarto/examples/06-wording-cases.md +14 -0
  12. package/skills/writing-quarto/examples/{03-layout-list.md → 07-layout-list.md} +14 -0
  13. package/skills/writing-quarto/examples/08-appendix.md +1 -1
  14. package/skills/writing-quarto/quarto/basics.md +1 -1
  15. package/skills/writing-quarto/quarto/prose.md +35 -3
  16. package/skills/writing-quarto/quarto/readability.md +55 -0
  17. package/skills/writing-quarto/quarto/render.md +17 -0
  18. package/skills/writing-quarto/scripts/checking-links.py +4 -1
  19. package/skills/writing-quarto/scripts/checking-prose.py +163 -0
  20. package/skills/writing-quarto/scripts/testing-content.py +3 -1
  21. package/skills/writing-quarto/scripts/verifying-content.py +5 -0
  22. package/skills/writing-quarto/steps/01-task.md +1 -1
  23. package/skills/writing-quarto/steps/02-structure.md +9 -26
  24. package/skills/writing-quarto/steps/03-blocks.md +14 -14
  25. package/skills/writing-quarto/steps/04-paragraphs.md +16 -16
  26. package/skills/writing-quarto/steps/05-sentences.md +24 -14
  27. package/skills/writing-quarto/steps/06-wording.md +1 -0
  28. package/skills/writing-quarto/steps/07-layout.md +11 -12
  29. package/skills/writing-quarto/steps/08-reread.md +8 -4
  30. package/skills/writing-quarto/steps/09-check.md +4 -4
  31. package/templates/quarto-book-project/SKILL.md +2 -0
  32. package/templates/quarto-book-project/writing/chapter-map.md +3 -0
package/README.en.md CHANGED
@@ -4,9 +4,9 @@
4
4
 
5
5
  **English** | [简体中文](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/README.md)
6
6
 
7
- Write Quarto Book chapters, check their structure, and maintain the theme; only inside `type: book` repositories.
7
+ Polish Chinese technical chapters, check book structure and mixed Chinese-English prose, and maintain a consistent, readable theme; only inside `type: book` repositories.
8
8
 
9
- - Write `.qmd`: `writing-quarto` covers the chapter skeleton, structure and prose.
9
+ - Write `.qmd`: `writing-quarto` covers the chapter skeleton, structure and Chinese prose.
10
10
  - Change the theme: `designing-theme` covers CSS, fonts and palettes.
11
11
  - Check rendered HTML: `quarto_render_check` judges publishability once, and `quarto_pass_report` prints the closing receipt.
12
12
 
@@ -26,10 +26,10 @@ Both skills are discovered from the package's `skills/` directory and loaded on
26
26
  ## Install
27
27
 
28
28
  ```bash
29
- dsh plugin --profile web add npm:@chengzhao-dev/dsh-quarto-book@0.6.0
29
+ dsh plugin --profile web add npm:@chengzhao-dev/dsh-quarto-book@0.6.1
30
30
  ```
31
31
 
32
- Replace `--profile` with the profile you actually run. The desktop app always runs the `desktop` profile, and that profile is owned by the app, so `dsh --profile desktop` is rejected; install or upgrade a plugin with `dsh plugin --profile desktop add <package>`, and use the app's Settings → Plugins → Add plugin with `npm:@chengzhao-dev/dsh-quarto-book@0.6.0` only if the app refuses that write. To follow a GitHub tag instead, use `github:chengzhao-dev/dsh-quarto-book#v0.6.0`.
32
+ Replace `--profile` with the profile you actually run. The desktop app always runs the `desktop` profile, and that profile is owned by the app, so `dsh --profile desktop` is rejected; install or upgrade a plugin with `dsh plugin --profile desktop add <package>`, and use the app's Settings → Plugins → Add plugin with `npm:@chengzhao-dev/dsh-quarto-book@0.6.1` only if the app refuses that write. To follow a GitHub tag instead, use `github:chengzhao-dev/dsh-quarto-book#v0.6.1`.
33
33
 
34
34
  Upgrading from the old unscoped package `dsh-quarto-book` (removed from npm with 0.3.4, so this only affects profiles that installed it earlier): remove it from the same profile first with `dsh plugin --profile <profile> remove dsh-quarto-book`, then add the scoped package. Keeping both installed registers `writing-quarto` and `designing-theme` twice at the same rank 550.
35
35
 
@@ -50,10 +50,11 @@ Run the content and link checks from the `writing-quarto` skill directory:
50
50
  ```bash
51
51
  python scripts/verifying-content.py --root <book>
52
52
  python scripts/checking-links.py --root <book>
53
+ python scripts/checking-prose.py --root <book>
53
54
  python scripts/checking-names.py --root <book>
54
55
  ```
55
56
 
56
- Add `checking-names.py` only for a whole-repository refactor; ordinary chapter checks run the first two.
57
+ `checking-prose.py` reports only high-confidence CJK/Latin spacing and punctuation issues. Add `checking-names.py` only for a whole-repository refactor; ordinary chapter checks run the first three.
57
58
 
58
59
  Callouts, HTML output and DOM contracts after a render are checked by `quarto_render_check`, which invokes the packaged scripts; do not run `checking-callouts.py`, `checking-output.py` or `checking-dom.py` by hand. `designing-theme`'s `scripts/checking-layout.py` inspects rendered HTML for overflow, contrast and page structure. `apply()` runs none of these and never runs `quarto render`.
59
60
 
package/README.md CHANGED
@@ -4,9 +4,9 @@
4
4
 
5
5
  [English](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/README.en.md) | **简体中文**
6
6
 
7
- 为 Quarto Book 写章节、检查结构并维护主题;只在 `type: book` 的仓库里出现。
7
+ 润色中文技术章节、检查 Book 结构与中英混排,并维护一套统一、可读的阅读主题;只在 `type: book` 的仓库里出现。
8
8
 
9
- - 写 `.qmd`:`writing-quarto` 管章节骨架、结构与行文。
9
+ - 写 `.qmd`:`writing-quarto` 管章节骨架、结构与中文行文。
10
10
  - 改主题:`designing-theme` 管 CSS、字体与 palette。
11
11
  - 渲染后检查 HTML:`quarto_render_check` 判一次可发布性,`quarto_pass_report` 出收尾回执。
12
12
 
@@ -26,10 +26,10 @@
26
26
  ## 安装
27
27
 
28
28
  ```bash
29
- dsh plugin --profile web add npm:@chengzhao-dev/dsh-quarto-book@0.6.0
29
+ dsh plugin --profile web add npm:@chengzhao-dev/dsh-quarto-book@0.6.1
30
30
  ```
31
31
 
32
- `--profile` 换成你实际运行的那个 profile。桌面应用固定跑 `desktop` profile,且该 profile 由应用独占管理,`dsh --profile desktop` 直接启动会被拒绝;安装或升级插件可用 `dsh plugin --profile desktop add <包>`,如果应用拒绝写入,再走「设置 → 插件 → 添加插件」,地址填 `npm:@chengzhao-dev/dsh-quarto-book@0.6.0`。想跟 GitHub 标签,也可以写 `github:chengzhao-dev/dsh-quarto-book#v0.6.0`。
32
+ `--profile` 换成你实际运行的那个 profile。桌面应用固定跑 `desktop` profile,且该 profile 由应用独占管理,`dsh --profile desktop` 直接启动会被拒绝;安装或升级插件可用 `dsh plugin --profile desktop add <包>`,如果应用拒绝写入,再走「设置 → 插件 → 添加插件」,地址填 `npm:@chengzhao-dev/dsh-quarto-book@0.6.1`。想跟 GitHub 标签,也可以写 `github:chengzhao-dev/dsh-quarto-book#v0.6.1`。
33
33
 
34
34
  从旧的无作用域包 `dsh-quarto-book` 升级时(该包已随 0.3.4 从 npm 删除,只影响之前装过它的 profile),先在同一个 profile 里 `dsh plugin --profile <profile> remove dsh-quarto-book` 再装新包。两个包同时留着会以相同的 rank 550 各注册一遍 `writing-quarto` 和 `designing-theme`。
35
35
 
@@ -50,10 +50,11 @@ dsh plugin --profile web add npm:@chengzhao-dev/dsh-quarto-book@0.6.0
50
50
  ```bash
51
51
  python scripts/verifying-content.py --root <book>
52
52
  python scripts/checking-links.py --root <book>
53
+ python scripts/checking-prose.py --root <book>
53
54
  python scripts/checking-names.py --root <book>
54
55
  ```
55
56
 
56
- `checking-names.py` 只在全仓重构时加跑;日常章节检查跑前两条即可。
57
+ `checking-prose.py` 只报中文混排里能直接判断的问题;`checking-names.py` 只在全仓重构时加跑。日常章节检查跑前三条即可。
57
58
 
58
59
  渲染后的 Callout、HTML 产物与 DOM 契约由 `quarto_render_check` 调包内脚本完成,不手跑 `checking-callouts.py`、`checking-output.py` 或 `checking-dom.py`;`designing-theme` 的 `scripts/checking-layout.py` 在渲染后的 HTML 上检查溢出、对比度和页面结构。`apply()` 不在加载时跑这些脚本,也不跑 `quarto render`。
59
60
 
package/locale/en.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "meta": {
3
- "title": "Quarto Book Writing",
4
- "description": "Write Quarto Book chapters, check their structure, and maintain the theme."
3
+ "title": "Quarto Book Writing & Theme",
4
+ "description": "Polish Chinese technical chapters, check book structure and mixed Chinese-English prose, and maintain a consistent, readable theme."
5
5
  }
6
6
  }
package/locale/zh.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "meta": {
3
- "title": "Quarto 图书写作",
4
- "description": "为 Quarto Book 写章节、检查结构并维护主题。"
3
+ "title": "Quarto 图书写作与主题",
4
+ "description": "润色中文技术章节,检查 Book 结构与中英混排,并维护一套统一、可读的阅读主题。"
5
5
  }
6
6
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@chengzhao-dev/dsh-quarto-book",
3
- "version": "0.6.0",
4
- "description": "为 Quarto Book 写章节、检查结构并维护主题。 · Write Quarto Book chapters, check their structure, and maintain the theme.",
3
+ "version": "0.6.2",
4
+ "description": "润色中文技术章节,检查 Book 结构与中英混排,并维护一套统一、可读的阅读主题。 · Polish Chinese technical chapters, check book structure and mixed Chinese-English prose, and maintain a consistent, readable theme.",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
7
7
  "exports": {
@@ -32,8 +32,8 @@
32
32
  "catalog": {
33
33
  "category": "skill",
34
34
  "summary": {
35
- "zh": "为 Quarto Book 写章节、检查结构并维护主题。",
36
- "en": "Write Quarto Book chapters, check their structure, and maintain the theme."
35
+ "zh": "润色中文技术章节,检查 Book 结构与中英混排,并维护一套统一、可读的阅读主题。",
36
+ "en": "Polish Chinese technical chapters, check book structure and mixed Chinese-English prose, and maintain a consistent, readable theme."
37
37
  },
38
38
  "capabilities": [
39
39
  "skills",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: designing-theme
3
- description: 维护 Quarto Book 主题资产(CSS、SCSS、字体、palette);调整页面样式、颜色或字体时使用,不用于改章节正文
3
+ description: 维护 Quarto Book 阅读主题:颜色、字体、间距、导航与 Callout 样式;不用于章节正文
4
4
  metadata:
5
5
  short-description: 维护 Quarto 图书主题
6
6
  ---
@@ -319,4 +319,4 @@ html[data-palette="hill-light"][data-mode="night"] body {
319
319
  --line-hover-bg: rgba(45, 106, 102, 0.06);
320
320
  --overlay-scrim: rgba(0, 0, 0, 0.42);
321
321
  }
322
- }
322
+ }
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: writing-quarto
3
- description: 编写和修改 Quarto Book 的 .qmd 章节,覆盖中文技术写作、Book 结构、代码与图表、渲染验证;不用于改 CSS、字体或主题颜色
3
+ description: 润色与重构 Quarto Book 章节:任务与结构、术语与中英混排、检查与渲染;不用于改主题样式
4
4
  metadata:
5
- short-description: 编写中文 Quarto 图书章节
5
+ short-description: 润色中文 Quarto 图书章节
6
6
  ---
7
7
 
8
8
  # Skill: writing-quarto
@@ -27,10 +27,10 @@ metadata:
27
27
  ## 九步
28
28
 
29
29
  1. 定任务:写出读者做完能完成的那件事,再列这一章要覆盖的任务。规则在 `steps/01-task.md`。
30
- 2. 定结构:排页面骨架、二级标题和篇幅预算,只定删留和标题。规则在 `steps/02-structure.md`。
30
+ 2. 定结构:排页面骨架、二级标题和篇幅预算,只定删留、标题与章题对齐。规则在 `steps/02-structure.md`。
31
31
  3. 排列内容块:导语、围栏、收束与 Callout 的次序,一个围栏只放正在讲的那一块。规则在 `steps/03-blocks.md`。
32
32
  4. 定段落:一段一个对象,段间衔接落在话题切换处。规则在 `steps/04-paragraphs.md`。
33
- 5. 定句子:对象先行,一句一动作。规则在 `steps/05-sentences.md`。
33
+ 5. 定句子:主语选执行者,对象先行,一句一动作。规则在 `steps/05-sentences.md`。
34
34
  6. 定用词:统一术语、主动语态和语气。规则在 `steps/06-wording.md`。
35
35
  7. 定版式:标点、中英混排、列表和加粗。规则在 `steps/07-layout.md`。
36
36
  8. 回读并退回:按现象判断属于哪一步,退回那一步改。规则在 `steps/08-reread.md`。
@@ -40,5 +40,7 @@ metadata:
40
40
 
41
41
  - [ ] 九步走完,每一步的产出都落在正文里。
42
42
  - [ ] 内容与链接核对通过:项目技能列了检查命令时按它执行,否则跑本技能 `scripts/verifying-content.py` 与 `checking-links.py`。
43
+ - [ ] 新人能按正文独立完成任务:前置、动作、结果与失败出口齐全,术语首用有解释;清单见 `quarto/readability.md`。
44
+ - [ ] 混排自查通过:`scripts/checking-prose.py --root <book>` 无高置信问题。
43
45
  - [ ] 调 `quarto_render_check`:`dry_run: true` 最多 2 次,确认 `safe_to_render`;`dry_run: false` 最多 1 次,确认 `html_report.usable` 为 true。不手跑 `quarto render`,也不截图 PNG。
44
46
  - [ ] 收尾调用一次 `quarto_pass_report`:`skill` 填 `writing-quarto`,`mode` 填 `polish` 或 `rewrite`,文件与步骤的改动次数、未改项、检查结论照实填。卡片就是回执,回复里不再复述;没改也传 0,当前文件不是章节时传 `executed: false`。
@@ -183,3 +183,31 @@
183
183
  ```
184
184
 
185
185
  判定信号:引言含「并列部分」「并列区域」等描述文本自身结构的元词,删去后读者仍知道看什么。拦截面见 `writing-quarto` 技能 `scripts/verifying-content.py` 的 FORBIDDEN_PATTERNS。
186
+
187
+ ## 标题口语化与章题脱节
188
+
189
+ 条文见 `steps/02-structure.md`「形式规则」「纵向承诺链」第 9 条。标题先看目录里要扫到的对象:人称、劝诱和时间从句都是导语的事;章题再看正文的主对象,title 承诺 A 而多数任务节在讲 B 时,改 title 或重排结构,不靠导语补丁。示例书首章改稿前后的对照:
190
+
191
+ ```text
192
+ 不佳(人称 + 劝诱;时间从句起头):
193
+ ## 自己走一局
194
+ ## 输入之后的回应
195
+
196
+ 改写(对象或「动词 + 具体宾语」;时间关系下放导语):
197
+ ## 对局演示
198
+ ## 落子规则
199
+ ```
200
+
201
+ ```text
202
+ 不佳(title 对局规则,正文主对象是程序回应):
203
+ 标题:对局规则
204
+ 引言:程序对每一次输入作出回应……
205
+ 任务节:输入之后的回应、自己走一局
206
+
207
+ 改写(title 的对象在引言、任务节和回顾三处兑现):
208
+ 标题:对局规则
209
+ 引言:井字棋是两名玩家轮流落子的对局……
210
+ 任务节:落子规则、胜负判定、对局演示
211
+ ```
212
+
213
+ 判定信号:标题带「自己」「跟我」「试试」或「…之后」「…之前」;把 title 与各 `##` 的中心名词并排,多数 `##` 的中心名词不是 title 的对象。
@@ -129,3 +129,17 @@
129
129
  ```
130
130
 
131
131
  判定信号:处所状语「在……头文件或源文件……里」后 12 字内出现「记住」「保存」或「选出」,判为状语配错谓语;同对象相邻两句被句号切开且异档混排同判。改写时位置句的谓语用「放进」,职责留前一句。
132
+
133
+ ## 主语不落到下游产物
134
+
135
+ 条文见 `steps/05-sentences.md`「主语选执行者或话题对象」。以章首旧稿为对照。
136
+
137
+ ```text
138
+ 不佳(「确定」的动作是程序在做,主语却是产物「回应」):
139
+ 终端画面里的回应确定一局井字棋要做什么。
140
+
141
+ 改写(程序作主语,回应退回宾语):
142
+ 程序每一轮都显示当前棋盘,并提示轮到谁落子。
143
+ ```
144
+
145
+ 判定信号:主语是「回应」「输入」「输出」「画面」「结果」「数据」,谓语却是「确定」「决定」「完成」这类程序的动作;改写时把执行者提到主语,产物退回宾语。
@@ -8,6 +8,20 @@
8
8
 
9
9
  一个概念全书只用一个词,统一术语与替换规则见 `steps/06-wording.md`。对照案例:示例章引言的「实现切片」改「阶段」后统称又停用,最终直接写具体目录路径。
10
10
 
11
+ ## 游戏与对局
12
+
13
+ 条文见 `steps/06-wording.md`「定用词:术语要稳」。「游戏」指作品或玩法整体,「对局」指具体一场;定义句用「游戏」,规则、落子与结果语境用「对局」,同一页不互换。
14
+
15
+ ```text
16
+ 不佳(定义句把作品整体说成一场对局):
17
+ 井字棋是两名玩家轮流落子的对局:先手用 X,后手用 O。
18
+
19
+ 改写(作品用「游戏」,具体一场才用「对局」):
20
+ 井字棋是两名玩家轮流落子的游戏:先手用 X,后手用 O。本章演示一局对局。
21
+ ```
22
+
23
+ 判定信号:定义「X 是…」时用「对局」;同一段里「游戏」与「对局」互换指同一对象。
24
+
11
25
  ## 术语标签对照句
12
26
 
13
27
  「ToFlatIndex 的声明」这种贴标签写法要改成什么:改成直接写位置事实。正文不给代码实体贴「是定义/是声明」标签,也不成对展开两者的区分——那是教 C++ 术语,不推进读者对状态面板工程的理解。位置与实现直接写具体事实(「换算写在 state.cpp」「常量在头文件里给出值」),「定义在 x.h」式位置动词不受限;声明引入名字、定义提供实体的辨析降为编辑与评审的内部口径,见 `steps/06-wording.md`「定用词:术语要稳」。以 示例章用数组存放九个数据项节改稿前为对照:
@@ -108,6 +108,20 @@ $$
108
108
 
109
109
  判定信号:`return` 行之外另排 `$$` 或行内数学模式;换算句没点名载体(「一个下标」「这个下标」「这次代入」互相指代,或导语用「这个下标」而全句未写出是 `cells_` 的数组下标);一句里同时出现跳过整行、列偏移、代入数字与得数;公式项列表;导语用反引号扛公式本体;代入段里列号与「第 N 列」混用(列号从 0 起算,「第 2 列」会让人从 1 数起);图已经标出的下标区间在图前再列一遍。「换算只发生在 `Board` 内部」这类局部实现事实本身合法(见 `language-scope.md` 的「流程交流不绑定媒介与行为刚性表述」的判定信号),多处复述或混进图导览段才违规。换算句式规则见 `steps/07-layout.md`「列表选择」换算条。
110
110
 
111
+ ## 标点分配:连续短句各用句号
112
+
113
+ 条文见 `quarto/prose.md`「标点分配」。五句各用句号,把共享主语的动作、并列成分和位置说明切成电报句;改成一句总说加冒号列举,声明与实现并成一句。
114
+
115
+ ```text
116
+ 不佳(五句各用句号,顿号串在分句之间):
117
+ `Board` 只保存九个棋盘格子。落子、满盘、成线与赢家都由它回答。写入前先检查坐标。声明在工程目录的 board.h。实现放在 board.cpp。
118
+
119
+ 改写(共享主语并成总说,四类问题用冒号列举,声明与实现并成一句):
120
+ `Board` 只保存九个棋盘格子。它回答四类问题:坐标是否越界、棋盘是否已满、是否成线以及赢家是谁。写入前先检查坐标。接口声明在工程目录的 board.h,实现放在 board.cpp。
121
+ ```
122
+
123
+ 判定信号:同一段连续 4 个以上句号,且多数句子短于 15 字;顿号用在分句之间;「声明在…的…」把位置拆成两句。
124
+
111
125
  ## 未定义名字的调用链
112
126
 
113
127
  以 示例章改稿前为对照。章引言用后文才定义的名字串起一条必须看懂的调用链,读者在第一个分句就遇到三个没讲过的名字(步骤:排列内容块;规则见 `steps/03-blocks.md`「内容块前后的闭环」):
@@ -1,6 +1,6 @@
1
1
  # 中文写作的章级定稿对照(附录)
2
2
 
3
- 本文件是低频章级对照附录,不进检索索引与评测;仅人审或用户点名时读。规则与通用对照见 `examples/06-wording-cases.md`。
3
+ 本文件是第 8 步的低频章级对照附录:信号表判不准,或用户点名时才读;不进检索索引与评测。规则与通用对照见 `examples/06-wording-cases.md`。
4
4
 
5
5
  ## 操作节删除离题辩白
6
6
 
@@ -117,7 +117,7 @@ title: "章节标题"
117
117
  - **标题层级**:`##` 表示阅读阶段、范围或一个可验收任务,`###` 表示该阶段中可独立完成或观察的子任务。正文最多到 `###`,禁止 `####` 及更深标题。
118
118
  - `###` 只用于源码阅读、验证、可选分支、排错症状等拥有独立动作或观察结果的内容。只有一两句话的解释并入相邻段落,不为目录对称强行拆分。
119
119
  - 根首页、part 落地页的卡片标题可以保留 `###`,那是导航组件的视觉层级,不改变正文最多三级的约束。
120
- - 归并或改名标题前先检索 `@sec-` 与 `#anchor`,避免破坏交叉引用。标题字数与形式的建议值见 `steps/02-structure.md`。
120
+ - 归并或改名标题前先检索 `@sec-` 与 `#anchor`,避免破坏交叉引用。标题字数与形式见 `steps/02-structure.md`「标题分层」与「形式规则」,章题与正文的对齐见同文件「纵向承诺链」第 9 条。
121
121
 
122
122
  ## 输出结构
123
123
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 速查:中文句用中文标点 · 英文句子内部保留英文标点 · 中西文之间一个半角空格 · 数字与单位之间一个空格 · 行内代码只标记要逐字识别的对象
4
4
 
5
- 本文件是标点、空格、引号、数字和英文术语写法的唯一出处;句子结构与用词仍归 `steps/05-sentences.md` 与 `steps/06-wording.md`,版式列表与加粗归 `steps/07-layout.md`。标点依据 GB/T 15834—2011 与 W3C CLREQ,空格与润色取舍是本仓约定。
5
+ 本文件是标点、空格、引号、数字和英文术语写法的唯一出处;句子结构与用词仍归 `steps/05-sentences.md` 与 `steps/06-wording.md`,版式列表与加粗归 `steps/07-layout.md`,新人可读性与任务完整性归 `quarto/readability.md`。标点依据 GB/T 15834—2011 与 W3C CLREQ,空格与润色取舍是本仓约定。
6
6
 
7
7
  ## 一条主轴
8
8
 
@@ -19,6 +19,29 @@
19
19
  - 书名号用 `《》`,内层用 `〈〉`。
20
20
  - 冒号只用于引出列表、代码、图或结论;词条式冒号按 `steps/04-paragraphs.md`「散文伪标签冒号禁令」改写。
21
21
 
22
+ ### 标点分配
23
+
24
+ 句号结束一个句子,不用来分隔并列成分。一个自然段通常 3 到 6 句;连续 4 句以上、每句都短于 15 字时,先按下面分工合并,再回读:
25
+
26
+ | 符号 | 用在 | 不用在 |
27
+ | --- | --- | --- |
28
+ | 顿号 `、` | 并列的词或短语(行、列与对角线) | 分句之间 |
29
+ | 逗号 `,` | 分句之间、状语与主句之间、两项并列 | 三项以上并列词语 |
30
+ | 分号 `;` | 并列分句,且分句内部已有逗号 | 单个短句末尾 |
31
+ | 冒号 `:` | 总说之后引出列举、说明、代码或结论 | 一句之内第二次;伪标签 |
32
+ | 句号 `。` | 句子结束 | 标题、图题、列表项内部 |
33
+
34
+ - 相邻两句共享主语或动作时并成一句,用逗号或分号连接;主语或动作换了才保留句号。
35
+ - 列举三项以上用顿号;列举项本身含逗号或分句时,各项之间用分号,末项用「和」或句号。
36
+ - 冒号前必须是总说或提示语;冒号后的内容离开前文能读成完整一句。
37
+ - 同一列表的末项标点统一:都用句号,或都不带句号。
38
+
39
+ 反例:`Board` 只保存九个棋盘格子。落子、满盘、成线与赢家都由它回答。写入前先检查坐标。声明在工程目录的 board.h。实现放在 board.cpp。
40
+
41
+ 改写:`Board` 只保存九个棋盘格子。它回答四类问题:坐标是否越界、棋盘是否已满、是否成线以及赢家是谁。写入前先检查坐标。接口声明在工程目录的 board.h,实现放在 board.cpp。
42
+
43
+ 判定信号:同一段连续 4 个以上句号且多数句子短于 15 字;顿号串在分句之间;声明与实现各占一句,只剩位置碎片。
44
+
22
45
  ## 空格
23
46
 
24
47
  - 中文与拉丁字母、数字之间加一个半角空格:`使用 Quarto 渲染`、`共 9 格`。
@@ -30,7 +53,8 @@
30
53
 
31
54
  ## 引号与术语
32
55
 
33
- - 术语首次出现写「中文译名(English original)」,后文统一用中文或原文,全书只选一种。
56
+ - 术语首次出现写「中文译名(English original)」,紧跟一句可观察行为或用途解释;后文统一用中文或原文,全书只选一种。
57
+ - 缩写首次出现写全称,再写括号缩写;读者不需要额外查表就能继续读。
34
58
  - API、类型、函数、参数、命令、路径和取值一律用行内代码,不翻译、不改大小写。
35
59
  - 英文句子独立成句时,句末用英文标点;中文句子里夹一个英文词时,句末仍用中文标点。
36
60
  - 不用全角拉丁字母或数字;不用中文引号包代码;不在术语中间插入空格。
@@ -42,6 +66,13 @@
42
66
  - 版本号、坐标、代码里的数字保持源码形式,不补空格、不换全角。
43
67
  - 单位符号与数字之间加空格;角度、百分比按行业习惯,同一本书内保持一致。
44
68
 
69
+ ## 条件与步骤
70
+
71
+ - 条件写在动作前:「若要删除整个文档,先点 Delete」,不写「点 Delete,若你想删除整个文档」。
72
+ - 这条只约束操作步骤;解释分支结果时仍先写可观察结果,再补条件。
73
+ - 一步一个动作,动作后紧跟结果或验收方式;不要把三个动作塞进一句。
74
+ - 步骤只写读者此刻要做的事,设计取舍另起一段放在动作完成后。
75
+
45
76
  ## 去套话
46
77
 
47
78
  - 删「进行/作出+名词」「值得注意的是」「不难发现」「综上所述」「首先、其次、最后」的堆叠。
@@ -75,7 +106,8 @@
75
106
  - 段首是否用「这一章」「接下来」「首先」代替了具体对象。
76
107
  - 是否出现「进行」「负责」「完成」加名词的空心动作,能换成具体动词吗。
77
108
  - 段内是否至少有一句 20 字以内的短句。
78
- - 首次出现的英文术语是否给了中文,后文是否统一。
109
+ - 首次出现的英文术语是否给了中文,是否紧跟一句可观察解释,后文是否统一。
110
+ - 条件是否写在动作前,一句是否只做一个动作。
79
111
 
80
112
  ## 依据
81
113
 
@@ -0,0 +1,55 @@
1
+ # 新人可读性与任务完整性
2
+
3
+ > 速查:读者能独立做完 = 前置 + 动作 + 结果 + 失败出口;术语首次出现给定义,条件写在动作前。
4
+
5
+ 本文件补句子规则之外的两件事:新人能不能独立完成任务,术语有没有解释。句子、段落与版式仍归 `steps/05-sentences.md`、`steps/04-paragraphs.md` 与 `steps/07-layout.md`;本文件不重复它们的规则。
6
+
7
+ ## 读者三问
8
+
9
+ 每个任务节要让读者回答三个问题:我在哪里、我要做什么、做完看到什么。
10
+
11
+ - **前置**:开始前需要哪一章的结果、哪个文件、哪个环境或哪条命令。缺一项就补一句。
12
+ - **动作**:一个步骤一个动作,动词开头,对象明确。
13
+ - **结果**:命令输出、文件变化、界面变化、返回值或测试结果,至少有一项可观察。
14
+ - **失败出口**:出错时看哪条信息、回到哪一节、下一步试什么。
15
+
16
+ 只写「运行脚本」而不写运行后看到什么,读者无法判断自己是否成功。
17
+
18
+ ## 术语
19
+
20
+ 术语的写法(首次「中文(English)」、一句可观察解释、缩写先全称、后文统一)以 `quarto/prose.md`「引号与术语」为准。本节只管解释放在哪里:
21
+
22
+ - 解释放在首次真正使用的位置,不放章首清单,也不留给下一章补课。
23
+ - 能换成日常词就不造术语;不为「显得专业」引入新词。
24
+ - 判定句:把术语换成「这个东西」,句子是否仍然成立?不成立说明解释不够。
25
+
26
+ ## 步骤完整
27
+
28
+ - 动作之后紧跟结果或验收方式,结果不要等到节末才出现。
29
+ - 失败路径写触发条件、可观察现象与恢复动作,不写空泛的「注意报错」。
30
+ - 操作节不插入与当前动作无关的设计辩论;取舍另起一段放在动作完成后。
31
+ - 条件与动作的先后见 `quarto/prose.md`「条件与步骤」。
32
+
33
+ ## 完整性清单
34
+
35
+ - [ ] 读者知道开始前需要什么:环境、文件、上一章产物。
36
+ - [ ] 每个任务有可观察的完成标志,不只写「运行一下」。
37
+ - [ ] 新术语首用有解释,缩写有全称。
38
+ - [ ] 条件在动作前,一句只做一个动作。
39
+ - [ ] 失败时知道看什么、回到哪一节。
40
+ - [ ] 没有「以后会讲」但当前就必须使用的名字。
41
+ - [ ] 删掉本段后读者仍能完成任务的内容已经删除。
42
+ - [ ] 读者不需要作者的补充说明,就能把这一节独立走完。
43
+
44
+ ## 混排自查
45
+
46
+ `python <skill>/scripts/checking-prose.py --root <book>` 检查中文与拉丁字母、数字、行内代码之间的空格,以及中文句里的半角标点。脚本只报高置信项,不替代人工回读。
47
+
48
+ ## 依据
49
+
50
+ - Google developer documentation style guide:https://developers.google.com/style/highlights
51
+ - Google Jargon:https://developers.google.com/style/jargon
52
+ - Google sentence structure(条件先于指令):https://developers.google.com/style/sentence-structure
53
+ - Diátaxis 质量模型:https://diataxis.fr/quality/
54
+ - Digital.gov Plain language:https://digital.gov/guides/plain-language/principles
55
+ - 中文技术文档写作风格指南:https://zh-style-guide.readthedocs.io/
@@ -35,6 +35,23 @@
35
35
 
36
36
  渲染后确认:目录深度与位置符合预期、明暗两套高亮都可读。渲染产物结构由 `checking-output.py` 与 `checking-callouts.py` 自动核对;页面宽度与视觉依据见 `steps/02-structure.md`。主题或目录看起来没生效时先硬刷新排除缓存,再核对选项嵌套层级。
37
37
 
38
+ ## 排查顺序
39
+
40
+ 先确定失败在哪一层,再查症状。一次只改一层,改完复跑同一层;不要用改 YAML 或重装依赖来碰运气。
41
+
42
+ 1. **内容层**:先跑 `verifying-content.py`(或项目技能的内容检查命令)。章节表、标题、句段长度、围栏与链接的失败都在这一层修。
43
+ 2. **混排层**:跑 `checking-prose.py`。缺空格、中文句里的半角标点在这一层修;不要顺手改句子结构。
44
+ 3. **渲染层**:调 `quarto_render_check` 的 `dry_run: false`。失败时先读 `render_result` 末尾的报错块,判断是 YAML、Lua 过滤器还是代码执行。
45
+ 4. **HTML 层**:读 `html_report.checks`。`index_html`、`utf8`、`charset` 失败回写作层;`dom` 或版式失败转 `designing-theme`。
46
+
47
+ 排查时守住三条:
48
+
49
+ - **首个错误优先**:只修报错里的第一处,修完复跑;不要一次改五处再猜是哪处生效。
50
+ - **缩小范围**:整书失败时先只渲染当前章节,确认单章能过,再看全书配置与跨章链接。
51
+ - **不做破坏性验证**:不删 `_book/`、不关检查、不改成 `warning` 绕过;失败原因写进回复或 `temp/` 台账。
52
+
53
+ 找不到失败层时,把完整命令、首个错误原文与最近一次改动列出来,再按上表从上往下复跑。
54
+
38
55
  ## 渲染与发布排查索引
39
56
 
40
57
  按症状查此表:每条只给可执行处置。行为成因与取舍的唯一出处是知识库,用
@@ -40,7 +40,10 @@ def collect_sources(root: Path, paths: list[str]) -> list[Path]:
40
40
  found = list(root.rglob("*.qmd")) + list(root.rglob("*.md"))
41
41
  return sorted(
42
42
  path for path in found
43
- if not any(part in {".git", "_book", "_site", "node_modules", ".quarto"} for part in path.parts)
43
+ if not any(
44
+ part in {".git", ".cache", ".quarto", "_book", "_freeze", "_site", "build", "node_modules", "temp"}
45
+ for part in path.parts
46
+ )
44
47
  )
45
48
 
46
49
 
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env python3
2
+ """检查 Quarto 章节里的高置信中英混排问题。
3
+
4
+ 只报能直接判断的错误:中文与拉丁字母、数字或行内代码之间缺空格,
5
+ 中文句里混入半角标点,中文标点前多了空格。句子长短、术语解释等
6
+ 仍需按 writing-quarto 的步骤人工回读。
7
+
8
+ 用法:
9
+ python checking-prose.py --root <book>
10
+ python checking-prose.py --root <book> --json
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import argparse
16
+ import json
17
+ import re
18
+ import sys
19
+ from dataclasses import dataclass
20
+ from pathlib import Path
21
+
22
+ CJK = r"\u4e00-\u9fff"
23
+ CJK_CHAR = re.compile(f"[{CJK}]")
24
+ CJK_LATIN = re.compile(f"([{CJK}])([A-Za-z0-9])")
25
+ LATIN_CJK = re.compile(f"([A-Za-z0-9])([{CJK}])")
26
+ HALF_PUNCT_IN_CJK = re.compile(f"([{CJK}])[,.;:?!]([{CJK}])")
27
+ SPACE_BEFORE_PUNCT = re.compile(f"([^\\s])[ \\t]+([,。;:!?、])")
28
+ DOUBLE_SPACE = re.compile(r"(?<=\S)[ \t]{2,}(?=\S)")
29
+ FENCE = re.compile(r"^\s*(```+|~~~+)")
30
+ INLINE_CODE = re.compile(r"`[^`\n]*`")
31
+ URL = re.compile(r"https?://[^\s)>\]]+")
32
+ LINK_TARGET = re.compile(r"\]\([^)]*\)")
33
+ HTML_TAG = re.compile(r"<[^>]+>")
34
+ SKIP_PREFIXES = (":::", "| ---", "|---")
35
+ SKIP_DIRS = {"_book", ".quarto", "node_modules", ".git", "temp", ".cache"}
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class Issue:
40
+ path: str
41
+ line: int
42
+ message: str
43
+
44
+
45
+ def strip_frontmatter(text: str) -> str:
46
+ lines = text.splitlines()
47
+ if lines and lines[0].strip() == "---":
48
+ for index in range(1, len(lines)):
49
+ if lines[index].strip() == "---":
50
+ return "\n".join(lines[index + 1 :])
51
+ return text
52
+
53
+
54
+ def mask_links_and_tags(line: str) -> str:
55
+ line = URL.sub("\x00", line)
56
+ line = LINK_TARGET.sub("\x00", line)
57
+ line = HTML_TAG.sub("\x00", line)
58
+ return line
59
+
60
+
61
+ def mask_inline_code(line: str) -> str:
62
+ return INLINE_CODE.sub("\x00", line)
63
+
64
+
65
+ def check_line(path: str, number: int, line: str) -> list[Issue]:
66
+ stripped = line.strip()
67
+ if not stripped or stripped.startswith(SKIP_PREFIXES) or stripped.startswith("<!--"):
68
+ return []
69
+ if stripped.startswith("#") and stripped.endswith("#"):
70
+ return []
71
+ if stripped.startswith("|") and set(stripped) <= set("|-: "):
72
+ return []
73
+
74
+ issues: list[Issue] = []
75
+ base = mask_links_and_tags(line)
76
+ visible = mask_inline_code(base)
77
+
78
+ for match in CJK_LATIN.finditer(visible):
79
+ issues.append(Issue(path, number, f"中文与拉丁字母/数字之间缺空格:{match.group(0)}"))
80
+ for match in LATIN_CJK.finditer(visible):
81
+ issues.append(Issue(path, number, f"拉丁字母/数字与中文之间缺空格:{match.group(0)}"))
82
+ for match in HALF_PUNCT_IN_CJK.finditer(visible):
83
+ issues.append(Issue(path, number, f"中文句里混入半角标点:{match.group(0)}"))
84
+ for match in SPACE_BEFORE_PUNCT.finditer(visible):
85
+ issues.append(Issue(path, number, f"中文标点前多了空格:{match.group(0)}"))
86
+ for match in DOUBLE_SPACE.finditer(visible):
87
+ issues.append(Issue(path, number, f"正文里有连续空格:{match.group(0)!r}"))
88
+
89
+ for match in INLINE_CODE.finditer(base):
90
+ before = base[match.start() - 1] if match.start() else ""
91
+ after = base[match.end()] if match.end() < len(base) else ""
92
+ if before and CJK_CHAR.match(before):
93
+ issues.append(Issue(path, number, "中文与行内代码之间缺空格"))
94
+ if after and CJK_CHAR.match(after):
95
+ issues.append(Issue(path, number, "行内代码与中文之间缺空格"))
96
+
97
+ return issues
98
+
99
+
100
+ def scan_file(root: Path, path: Path) -> list[Issue]:
101
+ text = path.read_text(encoding="utf-8", errors="replace")
102
+ text = strip_frontmatter(text)
103
+ issues: list[Issue] = []
104
+ in_fence = False
105
+ for number, line in enumerate(text.splitlines(), start=1):
106
+ if FENCE.match(line):
107
+ in_fence = not in_fence
108
+ continue
109
+ if in_fence:
110
+ continue
111
+ issues.extend(check_line(path.relative_to(root).as_posix(), number, line))
112
+ return issues
113
+
114
+
115
+ def collect_files(root: Path) -> list[Path]:
116
+ files: list[Path] = []
117
+ for path in sorted(root.rglob("*.qmd")):
118
+ if any(part in SKIP_DIRS for part in path.relative_to(root).parts):
119
+ continue
120
+ files.append(path)
121
+ return files
122
+
123
+
124
+ def main() -> int:
125
+ parser = argparse.ArgumentParser(description=__doc__)
126
+ parser.add_argument("--root", default=".", help="书仓库根目录")
127
+ parser.add_argument("--json", action="store_true", help="输出 JSON")
128
+ parser.add_argument("--quiet", action="store_true", help="没有问题时也保持安静")
129
+ args = parser.parse_args()
130
+
131
+ root = Path(args.root).resolve()
132
+ if not root.is_dir():
133
+ print(f"失败 根目录不存在:{root}", file=sys.stderr)
134
+ return 2
135
+
136
+ issues: list[Issue] = []
137
+ for path in collect_files(root):
138
+ issues.extend(scan_file(root, path))
139
+
140
+ if args.json:
141
+ print(
142
+ json.dumps(
143
+ {
144
+ "root": str(root),
145
+ "files": len(collect_files(root)),
146
+ "issues": [issue.__dict__ for issue in issues],
147
+ },
148
+ ensure_ascii=False,
149
+ indent=2,
150
+ )
151
+ )
152
+ elif issues:
153
+ print(f"发现 {len(issues)} 个混排问题:")
154
+ for issue in issues:
155
+ print(f"{issue.path}:{issue.line}: {issue.message}")
156
+ elif not args.quiet:
157
+ print(f"OK:{len(collect_files(root))} 个章节通过混排检查。")
158
+
159
+ return 1 if issues else 0
160
+
161
+
162
+ if __name__ == "__main__":
163
+ raise SystemExit(main())
@@ -97,6 +97,8 @@ def main() -> int:
97
97
  failures.append(f"{name}: 输出缺少「{needle}」\n{output[:600]}")
98
98
 
99
99
  expect("合法样书", VALID_CHAPTER, 0, "OK")
100
+ expect("标题口语化", VALID_CHAPTER.replace("## 目标", "## 自己走一局"), 1, "标题带人称")
101
+ expect("标题带行内代码", VALID_CHAPTER.replace("## 目标", "## 运行 `app`"), 1, "标题带行内代码")
100
102
  expect("正文 H1", VALID_CHAPTER.replace("## 目标", "# 目标"), 1, "正文出现 H1")
101
103
  expect("缺 title", VALID_CHAPTER.replace("title: 入门\n", ""), 1, "缺少 YAML frontmatter")
102
104
  expect(
@@ -141,7 +143,7 @@ def main() -> int:
141
143
  print(f" FAIL {line}")
142
144
  return 1
143
145
 
144
- print("PASS:9 个用例全部通过")
146
+ print("PASS:11 个用例全部通过")
145
147
  return 0
146
148
 
147
149