@chengzhao-dev/dsh-quarto-book 0.6.8 → 0.6.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -29,7 +29,7 @@ Both skills are discovered from the package's `skills/` directory and loaded on
29
29
  2. Open a session in the book repository. `_quarto.yml` needs a standalone `type: book` line, and the book needs at least one `.qmd` or `.md` chapter source; only then do both skill names appear.
30
30
  3. Call a skill: `writing-quarto` for prose, `designing-theme` for the theme.
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 with `dsh plugin --profile desktop add <package>`, and use the app's Settings → Plugins → Add plugin only if the app refuses that write. To pin a version, write `npm:@chengzhao-dev/dsh-quarto-book@0.6.8`; to follow a GitHub tag, write `github:chengzhao-dev/dsh-quarto-book#v0.6.8`.
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 with `dsh plugin --profile desktop add <package>`, and use the app's Settings → Plugins → Add plugin only if the app refuses that write. To pin a version, write `npm:@chengzhao-dev/dsh-quarto-book@0.6.11`; to follow a GitHub tag, write `github:chengzhao-dev/dsh-quarto-book#v0.6.11`.
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
 
package/README.md CHANGED
@@ -29,7 +29,7 @@
29
29
  2. 在书仓库里打开会话。`_quarto.yml` 要有独立一行的 `type: book`,书根至少有一个 `.qmd` 或 `.md` 章节源文件;两个技能名这时才会出现。
30
30
  3. 调用技能:写正文用 `writing-quarto`,改主题用 `designing-theme`。
31
31
 
32
- `--profile` 换成你实际运行的那个 profile。桌面应用固定跑 `desktop` profile,且该 profile 由应用独占管理,`dsh --profile desktop` 直接启动会被拒绝;安装或升级用 `dsh plugin --profile desktop add <包>`,应用拒绝写入时再走「设置 → 插件 → 添加插件」。需要固定版本时写 `npm:@chengzhao-dev/dsh-quarto-book@0.6.8`,跟 GitHub 标签时写 `github:chengzhao-dev/dsh-quarto-book#v0.6.8`。
32
+ `--profile` 换成你实际运行的那个 profile。桌面应用固定跑 `desktop` profile,且该 profile 由应用独占管理,`dsh --profile desktop` 直接启动会被拒绝;安装或升级用 `dsh plugin --profile desktop add <包>`,应用拒绝写入时再走「设置 → 插件 → 添加插件」。需要固定版本时写 `npm:@chengzhao-dev/dsh-quarto-book@0.6.11`,跟 GitHub 标签时写 `github:chengzhao-dev/dsh-quarto-book#v0.6.11`。
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chengzhao-dev/dsh-quarto-book",
3
- "version": "0.6.8",
3
+ "version": "0.6.11",
4
4
  "description": "把一份 Quarto Book 写成可读、可查、可发布的中文技术书:九步章节写作、结构/链接/图表检查、统一主题与渲染验收。 · Write, check, and publish a Chinese Quarto Book: nine-step chapter writing, structure/link/figure checks, a consistent theme, and render acceptance.",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -47,9 +47,10 @@
47
47
  "icon": "./icon.svg",
48
48
  "scripts": {
49
49
  "build": "tsc -p tsconfig.json",
50
- "test": "npm run build && node --test \"tests/**/*.test.mjs\"",
51
- "check": "tsc --noEmit -p tsconfig.json && node scripts/checking-skill-links.mjs && node scripts/checking-skill-budget.mjs && node scripts/checking-package.mjs",
50
+ "test": "npm run build && node scripts/checking-github-workflows.mjs && node --test \"tests/**/*.test.mjs\"",
51
+ "check": "tsc --noEmit -p tsconfig.json && node scripts/checking-skill-links.mjs && node scripts/checking-skill-budget.mjs && node scripts/checking-package.mjs && node scripts/checking-github-workflows.mjs",
52
52
  "check:lib": "node scripts/checking-lib.mjs",
53
+ "check:workflows": "node scripts/checking-github-workflows.mjs",
53
54
  "prepack": "npm run build",
54
55
  "prepublishOnly": "npm run check && npm run check:lib",
55
56
  "pack": "node -e \"require('fs').mkdirSync('temp',{recursive:true})\" && npm pack --pack-destination temp",
@@ -85,6 +86,7 @@
85
86
  "devDependencies": {
86
87
  "@deepseek-ai/dsh-skill": ">=0.2.0-rc.1 <0.3.0-0",
87
88
  "@types/node": "^22.20.5",
88
- "typescript": "^5.9.3"
89
+ "typescript": "^5.9.3",
90
+ "yaml": "^2.9.1"
89
91
  }
90
92
  }
@@ -56,7 +56,7 @@
56
56
  border-top: 1px solid var(--table-border);
57
57
  border-bottom: 1px solid var(--table-border);
58
58
  }
59
- .table > tbody > tr:last-child > * {
59
+ #quarto-document-content .table > tbody > tr:last-child > * {
60
60
  border-bottom: 1px solid var(--table-border);
61
61
  }
62
62
 
@@ -78,9 +78,15 @@
78
78
  padding: 0 0 0.5rem;
79
79
  }
80
80
 
81
- /* 斑马纹压平:书籍表格靠表头线与列距,不靠交替底色(打印时还要 print-color-adjust)。 */
81
+ /* 斑马纹默认关闭:三五行短表靠表头线与列距就够,不加交替底色。
82
+ 只有作者在长表上显式加 .table-striped 时才启用极浅一档,避免整本书变成条纹墙。 */
82
83
  #quarto-document-content .table-striped > tbody > tr:nth-of-type(odd) > * {
83
- background-color: transparent;
84
+ background-color: color-mix(in srgb, var(--table-border) 6%, transparent);
85
+ }
86
+
87
+ /* 悬停只提亮当前行,帮助横向扫读;不改变默认表体,也不替代表头层级。 */
88
+ #quarto-document-content .table > tbody > tr:hover > * {
89
+ background-color: color-mix(in srgb, var(--table-border) 10%, transparent);
84
90
  }
85
91
 
86
92
  /* 任务列表:checkbox 跟强调色,不加卡片。 */
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: writing-quarto
3
- description: 写作、布局、排版与润色 Quarto Book 章节:任务与结构、内容块与表格、段落与句子、中英混排与术语、检查与渲染;不用于改主题样式
3
+ description: 写作、布局、排版与润色 Quarto Book 章节:任务与结构、内容块与表格、段落与句子、中英混排与术语、改 qmd 代码围栏与块后解说、检查与渲染;不用于改主题样式与只改 .cpp。
4
4
  metadata:
5
5
  short-description: 润色中文 Quarto 图书章节
6
6
  ---
@@ -47,11 +47,11 @@ metadata:
47
47
  2. 定结构:排页面骨架、二级标题和篇幅预算,只定删留、标题与章题对齐。规则在 `steps/02-structure.md`。
48
48
  3. 排列内容块:导语、围栏、收束与 Callout 的次序,一个围栏只放正在讲的那一块。规则在 `steps/03-blocks.md`,表格写法见 `quarto/tables.md`。
49
49
  图注写在图下、图前只写引出关系;三类图的统一写法见 `quarto/figures.md`。
50
- 4. 定段落:一段一个对象,段间衔接落在话题切换处。规则在 `steps/04-paragraphs.md`。
50
+ 4. 定段落:一段一个关键,下一句接住上一句的新信息;段间衔接落在话题切换处。规则在 `steps/04-paragraphs.md`。
51
51
  5. 定句子:主语选执行者,对象先行,一句一动作。规则在 `steps/05-sentences.md`。
52
52
  6. 定用词:统一术语、主动语态和语气。规则在 `steps/06-wording.md`。
53
53
  7. 定版式:标点、中英混排、列表和加粗。规则在 `steps/07-layout.md`。
54
- 8. 回读并退回:按现象判断属于哪一步,退回那一步改。规则在 `steps/08-reread.md`。
54
+ 8. 回读并退回:按现象判断属于哪一步,先按 `references/priority.md` 判冲突,再退回那一步改。规则在 `steps/08-reread.md`。
55
55
  9. 检查与渲染:先核对内容与链接,再调 `quarto_render_check` 渲染并检查 HTML。规则在 `steps/09-check.md`。
56
56
 
57
57
  ## 完成判据
@@ -225,3 +225,26 @@
225
225
  ```
226
226
 
227
227
  判定信号:标题带「自己」「跟我」「试试」或「…之后」「…之前」;把 title 与各 `##` 的中心名词并排,多数 `##` 的中心名词不是 title 的对象。
228
+
229
+ ## 八条线拆成两个实体标题
230
+
231
+ 标题在数条目时,读者不知道这一节要认识哪个实体;正文也容易把坐标表、成线判断和归属句挤在同一节(规则见 `steps/02-structure.md`「形式规则」):
232
+
233
+ ```text
234
+ 不佳(标题数条目,一节混讲坐标表、算法和归谁用):
235
+ ### 八条线
236
+ 八条线的坐标写在 board.cpp 里,存成一张 constexpr 常量表。HasLine 与 IsLineComplete 共用它。
237
+ Game 和 Ai 调用查询,不读它,所以不放进 Board 类里:
238
+ (kLines 围栏)
239
+ (算法旁白与 HasLine 围栏)
240
+ ```
241
+
242
+ ```text
243
+ 改写(拆成两个实体标题,每节只讲一个实体):
244
+ ### 线坐标表
245
+ (只引出 kLines 围栏)
246
+ ### 成线判断
247
+ (只引出 HasLine 围栏;共用 kLines 留在三连判定节)
248
+ ```
249
+
250
+ 判定信号:三级标题是「八条线」「三种类型」「四个入口」这类集合数量;正文先数条目,再讲其中一个实现,最后补归属。处理:条目能各自成实体就拆标题;不能就把标题改成承载它们的对象。
@@ -149,3 +149,16 @@ $ORIGIN 表示可执行文件所在目录,查找位置随文件移动。
149
149
  ```
150
150
 
151
151
  判定信号:块后连续出现围栏内的常量名、字段名或数字,句子只是把 `X = 3` 读成「`X` 取 3」;删掉块后句,读者仍能从围栏和导语得到同一事实。
152
+
153
+ ## 块后逐行解说实现
154
+
155
+ 围栏已经写出调用和边界,块后仍逐行解释 `at()`、异常传播和返回值:
156
+
157
+ ```text
158
+ 不佳(围栏已写 IsInRange、at()、三种拒绝和返回值;块后逐行翻译):
159
+ 检查通过后直接调用 cells_.at()。越界已经在前一步拦住;PlaceCell 不标 noexcept,数组访问抛出的异常会照常传给调用方。PlaceCell 用返回值报告结果:三种拒绝都不改棋盘,成功时新棋子立刻能从 GetCellType 读回。调用方检查这个返回值就能决定下一步,不必比较落子前后的棋盘。
160
+
161
+ 改写(边界写进源码阶段注释,块后不再复述):
162
+ [围栏:PlaceCell 的 if 判断、at() 赋值与 return true]
163
+ ```
164
+ 判定信号:块后连续点名围栏里的调用(`at()`、`noexcept`、返回值),或把「三种拒绝不改棋盘」「成功后能读回」再讲一遍;删掉后读者仍能从围栏与源码注释得到同一事实。这是实现边界,不属于调用方必须知道的章节事实。
@@ -4,24 +4,17 @@
4
4
 
5
5
  ## 摘录链接密度
6
6
 
7
- 章引言的源文件链接一章挂几次:挂一次。正文写 state.h 这类源文件名,链接怎么挂:章引言挂一次,正文复提用裸文件名。以 示例章改稿前为对照:摘录块每节重新挂一次同文件链接,读者在正文里反复撞见同一串 URL,阅读断点全落在链接上。链接分三层:章引言是全章介绍位,首次点名源文件并挂链接(示例章引言);摘录围栏的短 filename 自明文件;正文复提用裸文件名。
7
+ 章引言的源文件链接是本章主源文件的链接位:章引言挂一次,正文再点名同一文件时复用同一链接,读者不用回翻章首。不属于本章主源的文件用行内代码;摘录导语仍先报块内标识符,不把链接当作导语内容。
8
8
 
9
9
  ```text
10
- 不佳(同节两个摘录导语各挂一条同文件链接):
11
- 先在 [state.h](…) 里声明状态面板的尺寸常量……
12
- 每个数据项保存 [state.h](…) 中 `CellState` 的三种取值之一。
10
+ 不佳(章引言不挂链,正文才第一次出现链接):
11
+ 规则在 state.h 和 state.cpp。……每个数据项保存 [state.h](…) 中 `CellState` 的三种取值之一。
13
12
 
14
- 改写(每章每条仓库路径只在章引言保留一条,供片段校验解析源路径;后续导语与正文用行内代码):
15
- 先在 [state.h](…) 里声明状态面板的尺寸常量……
16
- 每个数据项保存 `CellState` 的三种取值之一。
13
+ 改写(章引言挂本章主源文件,正文复提时复用):
14
+ 规则写在 [state.h](https://github.com/your-org/your-book/blob/main/src/include/state.h) 和 [state.cpp](https://github.com/your-org/your-book/blob/main/src/state.cpp) 里。……每个数据项保存 [state.h](https://github.com/your-org/your-book/blob/main/src/include/state.h) 中 `CellState` 的三种取值之一。
17
15
  ```
18
16
 
19
- ```text
20
- 不佳(章引言不挂链,正文又逐节重挂同文件链接):规则在 state.h 和 state.cpp。……每个数据项保存 [state.h](…) 中 `CellState` 的三种取值之一。
21
- 改写:规则写在 [state.h](https://github.com/your-org/your-book/blob/main/src/include/state.h) 和 [state.cpp](https://github.com/your-org/your-book/blob/main/src/state.cpp) 里。……每个数据项保存 `CellState` 的三种取值之一。
22
- ```
23
-
24
- 判定信号:同一章内同一 URL 的 blob 链接 ≥2(`verifying-content.py` 判失败「同章重复文件链接」);或摘录导语为「告诉读者文件在哪」挂链,而非预告块内内容;或正文复提源文件仍挂着链接。
17
+ 判定信号:章引言没有挂本章主源文件链接;正文链接的文件不在章引言的主源文件集合里(`verifying-content.py` 对非章引言主源文件的同章重复 URL 判失败);或摘录导语只报文件位置、不预告块内标识符。
25
18
 
26
19
  ## 摘录导语只报出处
27
20
 
@@ -99,16 +99,98 @@
99
99
  ```
100
100
 
101
101
  判定信号:章首或 `##` 首句的主语是下游对象(格子、对局、终端),而不是标题对象;把主语换成标题对象后句子仍成立,说明原句已经漂移。
102
- ## 重复交代可数事实
103
102
 
104
- 以棋盘章成员与接口节的一段为对照。引言已经写过「3 行 3 列,共九个棋盘格子」,类注释又写「3×3 个格子……格子数量不会改变」,块后散文再讲两遍「九个棋盘格子」「数量从开局到终局不变」「编译期就能确定」,读者读到的是一件事的三种说法:
103
+ ## 同一函数叠三件事
104
+
105
+ 以示例章的坐标换算节为对照。同一个 `ToFlatIndex` 上叠了三件互不依赖的事:调用前要先过范围检查、`Position` 不拥有合法区间、换算出下标后交给读写入口。三句都绕着函数转,现有「一段一个对象」看不出问题;盖住 `ToFlatIndex` 后是三件事(规则见 `steps/04-paragraphs.md`「两关:一个关键、一条次序」):
106
+
107
+ ```text
108
+ 不佳(调用条件、类型分工、后继入口叠在同一段):
109
+ `ToFlatIndex` 把行列坐标换算成数组下标。调用前要先经过 `IsInRange`;`Position` 只保存一对行列,不拥有合法区间。换算出下标后,`Read` 与 `Write` 都从 `cells_` 取元素。
110
+
111
+ 改写(段首只留换算这一件,其余各归各节):
112
+ `ToFlatIndex` 把一对行列坐标换算成 `cells_` 的数组下标。
113
+ ```
114
+
115
+ 判定信号:盖住函数名后,段内还能拆出「什么时候能调」「类型各管什么」「后面谁接」三个独立判断;三句主语在函数、条件、类型、入口之间轮换。处理:调用条件归 `IsInRange` 节,类型分工归 `Position` 节,后继入口归各自的读写节;本段只留函数做什么。
116
+
117
+ ## 空结果、数量与存放方式
118
+
119
+ 以示例章的构造面板块后段为对照。第一句的新信息是空面板,第二句却从数据项数量起头,再用冒号甩出存放类型和下标轴向;句首接不上上一句句末(规则见 `steps/04-paragraphs.md`「两关:一个关键、一条次序」):
120
+
121
+ ```text
122
+ 不佳(空结果、数量、存放方式夹在一段,句首对不上句末):
123
+ `Panel()` 构造出来就是空面板。九个数据项的数量在编译期确定,`cells_` 用二维 `std::array` 存放:外层是行,内层是列。
124
+
125
+ 改写(空面板与轴向写回围栏注释,块后只留编译期这一件):
126
+ 这九个数据项的数量在编译期确定。
127
+ ```
128
+
129
+ 判定信号:段首句讲构造结果,下一句改用数量开头;冒号后不是列表、围栏或图,而是一组并列的新概念;删掉数量句后存放方式仍由围栏注释交代。处理:先按第一关数出空结果、数量、存放方式三件,只留读者在块后还需要的一件;其余写回围栏注释或删除。
130
+
131
+ ## 混段:函数、终端与测试
132
+
133
+ 不佳(一段里同时讲读接口、终端和测试):
134
+
135
+ `GetCellType`、`IsCellEmpty`、`IsInRange` 负责读格子;终端用 `ReadHumanMove` 和 `PrintGame` 显示棋盘;测试用 `gtest_discover_tests` 注册六组用例。
136
+
137
+ 改写(先一句导语,再一项一件):
138
+
139
+ `PlaceCell` 尝试把棋子写进棋盘,写入前先检查坐标与目标格。
140
+
141
+ - `IsInRange`:检查坐标是否落在棋盘内。
142
+ - `IsCellEmpty`:检查目标格是否为空。
143
+ - `PlaceCell`:通过两项检查后写入棋子,失败时棋盘不变。
144
+
145
+ 终端读入、终端打印和测试注册各自另起一段或另起列表。
146
+
147
+ 判定信号:一段里出现三个以上函数名,且分句之间没有「因此」这类递进关系;读者无法从中挑出下一步要改的那一处。
148
+
149
+ ## 接口、调用场合与实现细节的次序
150
+
151
+ 以示例章的只读接口节为对照。表格已经列出 `Get`、`IsEmpty`、`IsFull` 的返回与边界,下一段却用「读格子、判空格、判满盘」代替函数名,紧接着又插 `Get` 的实现;读者还没看完三个接口的调用场合,就被带到单个函数的越界细节。
105
152
 
106
153
  ```text
107
- 不佳(同一事实讲三遍,第二句和第三句还互相重复):
108
- `Board()` 构造之后就是一张空棋盘。出界坐标不会写入棋盘;`Board` 保存九个棋盘格子的类型,数量从开局到终局不变。九个棋盘格子的数量编译期就能确定,所以 `cells_` 用二维 `std::array` 存放。
154
+ 不佳(函数名被中文动作替代,用法和实现夹在一起):
155
+ `Get`、`IsEmpty`、`IsFull` 都只读面板。读格子、判空格、判满盘是三件不同的事。
156
+ `Get` 的边界判断与 `std::array::at` 相同,越界时抛 `std::out_of_range`。异常消息先写来源,再写失败条件与实际值。
157
+ `Get` 和 `IsEmpty` 都先检查坐标。调用方按场合选择:
158
+ - 终端逐行调用 `Get`。
159
+ - 测试用 `Get` 核对空格、已有值与越界坐标。
160
+ ```
109
161
 
110
- 改写(只保留块后必须新给的实现事实:存放方式与下标含义):
111
- `Board()` 构造出来是空棋盘。格子数在编译期确定,`cells_` 用二维 `std::array` 存放:外层是行,内层是列,`cells_[row][col]` 对应坐标 `(row, col)`。
162
+ ```text
163
+ 改写(先三个接口与调用场合,再单列 Get 的越界处理):
164
+ `Get`、`IsEmpty`、`IsFull` 都只读面板,区别在返回什么、越界怎么处理:
165
+ | 接口 | 返回 | 边界 |
166
+ ...
167
+ 调用方按场合选择:
168
+ - 终端逐行调用 `Get`。
169
+ - 判断单个格子是否为空时调用 `IsEmpty`。
170
+ - `IsFull` 用来判断是否满盘。
171
+
172
+ ### Get 的越界处理
173
+
174
+ `Get` 越界时抛 `std::out_of_range`。相关异常如下:
175
+ ```
176
+
177
+ 判定信号:同一段先用函数名、再用「读格子/判空格」一套中文动作;表格后没有先把三个接口的用法讲完,就插进某一个函数的实现;实现句还带着「与 `std::array::at` 相同」这类比较。处理:函数名作名词,返回、边界、调用场合先成组讲完;单个函数的实现另起小节,只留读者需要知道的异常与必要代码,实现比较和通用异常组织法删掉。
178
+
179
+ ## GetWinner 的三段删前样本
180
+
181
+ 以棋盘章的 `GetWinner` 块后为对照。声明围栏已经给出返回类型,块后却用三段分别讲算法、`optional` 语义和后章归属;其中「平局与进行中留在 `GameResult`」「由 `Game` 结合棋盘查询」「不放进 `Board`」都是后章视角(规则见 `steps/04-paragraphs.md`「情形列表后停止」):
182
+
183
+ ```text
184
+ 不佳(三段:算法 → optional 归属 → 读取路径与 Game/Ai 归属):
185
+ GetWinner 按 kX、kO 的次序检查,哪一种已经三连就返回哪一种,都没有则返回 std::nullopt。行、列和两条对角线各算一条三连。
186
+ std::optional<CellType> 区分「有赢家」和「没有赢家」;平局与「进行中」是对局流程的结论,由 Game 结合棋盘的查询得出,不放进 Board。
187
+ GetCellType 读一个棋盘格子,GetWinner 检查各行、各列和两条对角线;这两个读取函数都经过 cells_,都不修改棋盘。
188
+ ```
189
+
190
+ ```text
191
+ 改写(互斥结果两项,列表后停止):
192
+ - **有三连**:返回形成三连的棋盘格子类型,kX 或 kO。
193
+ - **没有三连**:返回 std::nullopt。
112
194
  ```
113
195
 
114
- 自查:删掉重复句后,读者还缺哪一条事实?只补缺的那条;「数量不变」「出界不写入」若已在类注释或 `IsInRange` 的契约里写过,就不再复述。
196
+ 判定信号:声明围栏已经写清返回类型,块后又解释 `optional` 的语义;段落点名 `Game`、`GameResult`、`Ai` 并说明归属;同一段把 `GetWinner` 与另一个读取函数并排讲。处理:只列读者从声明里看不出的结果,互斥结果写成两项列表;后章类型与归属整句删除。
@@ -27,6 +27,18 @@
27
27
 
28
28
  判定信号:设计说明句里选择先于依据,读者要把因果倒过来才能读;「是规则的一部分」这类只贴类别、不改变下一步的分句;删掉关系词后读者仍要自己补因果;「正好装下」在同段复述同一核;后续小节专讲的对象在本段末尾写成「由 X 完成」。
29
29
 
30
+ ## 技能腔:解释作者的选择
31
+
32
+ 不佳(把「这里不定义自己的异常类型」写进正文,读者没有下一步动作):
33
+
34
+ `GetCellType` 越界时抛出 `std::out_of_range`,消息里带行号和列号。这里不定义自己的异常类型:调用方用 `std::out_of_range` 就能接住,测试也不必为项目再记一个类型名。
35
+
36
+ 改写(只留读者看得到的结果):
37
+
38
+ `GetCellType` 越界时抛出 `std::out_of_range`,消息里带行号和列号。
39
+
40
+ 判定信号:句子在解释作者为什么不另造类型、测试为什么少记一个名字、注释该怎么组织;删掉后读者执行的动作不变。
41
+
30
42
  ## 拉链句
31
43
 
32
44
  一句话把两组对象两两配对,删掉任一配对、剩下的仍能单独成立,原句就是拉链句:读者要按顺序把左组一一配到右组,且两个配对共用同一个判据(判定与三条改写见 `steps/05-sentences.md`「拉链句」;步骤链、同物两处与回顾不按拉链判)。以 示例章章首改稿前与 示例章改稿前为对照:
@@ -45,7 +45,7 @@ README 遵循 GitHub Markdown。根目录 `README.md` 是在线阅读链接的
45
45
  - YAML `title`、Markdown 各级标题和导航文本不使用反引号。标题中的技术名词直接写成普通文字。
46
46
  - 反引号只包裹需要精确识别的技术对象:命令、工具、参数、文件名、路径、配置键、API、标识符和包名。普通概念、平台和产品名称不自动加反引号。
47
47
  - 同一对象在同一章节内必须使用一致的标记方式。
48
- - 指向仓库文件时写成可跳转链接,不用反引号包住路径冒充链接;仓库内路径链接统一用 GitHub 绝对链接(目录 `tree/main/<路径>`、文件 `blob/main/<路径>`),行文中首次写全路径、其后用中文短描述指称。规则见 `quarto/render.md`。例外:自测问答内的文件、路径与标识符指称统一用行内代码,不挂链接(规则见 `steps/02-structure.md`「六段块序列」)。
48
+ - 本章主源文件写成可跳转的 GitHub 绝对链接(文件 `blob/main/<路径>`):章引言首次点名时挂链接,正文再点名同一文件时复用同一链接,不用反引号包住路径冒充链接。不属本章主源的其他仓库文件用行内代码,不挂 `[]()`;跨章指称用章节标题链接。行文中首次写全路径、其后用中文短描述指称。规则见 `quarto/render.md`。例外:自测问答内的文件、路径与标识符指称统一用行内代码,不挂链接(规则见 `steps/02-structure.md`「六段块序列」)。
49
49
  - 代码块来源链接嵌进同节导语句子里,不单独成行贴在围栏上方;整行仅为一个链接的「裸链接行」一律改写成含对象与动作的完整句子。代码块标题 `filename` 是短显示名字符串,不写 Markdown 链接。
50
50
  - 中英文之间保留空格。链接标签两侧的间距按标签内首尾字符决定,标签内不写反引号。
51
51
  - 正文只保留读者当前动作必需的外链。手册、完整参数列表和上游项目文档在首次需要该资料的任务中就地链接,不设章末集中区。
@@ -12,6 +12,12 @@
12
12
 
13
13
  设计页先固定读者可观察的输入、输出和规则;布局页只说明目录、文件职责和依赖方向;状态与规则页再展开实现和终局;终端或验收页最后讲配置、编译、测试和运行。标题按实际任务命名,数量是预算而不是承诺;不要在早期页面预告或执行后续验收动作。
14
14
 
15
+ ## 对象章的去冗顺序
16
+
17
+ 对象章先按章的视角删留:删掉后章类型名与个数标题,删掉块后归属段和「留在某类型」「由某类得出」「不放进本章」的整句;互斥结果改成「情形:结果」列表,列表后停止。标题点名本节实体,不数条目。删完再按九步润色;先删留,后改句。
18
+
19
+ 每一章只跑该章的增量验证;全部内容检查通过后才在仓库根渲染 Book。
20
+
15
21
  ## 收尾
16
22
 
17
23
  全仓重构后,核对导航与路由表、标题层级、章节链接、源码摘录、命令和输出。先运行增量验证定位问题,再对所有 QMD 执行全量 `verifying-content.py`,最后在仓库根目录渲染 Book。按章推进时,每一章的出口是 `verifying-content.py --root <书根> --paths <该章>`;全部章节通过之后才 `render`。涉及知识库时同步索引、健康检查和评测;涉及代码时用实际构建、CTest 或示例验证核对文档。
@@ -36,6 +36,12 @@
36
36
  - 不写固定宽度(`{width=...}`、`style="width:..."`),也不覆盖 `table-layout`;比例之外的几何由主题负责。
37
37
  - 长单元格先改文案:删重复修饰,把条件与结果按「条件 + 结果」排成一句。仍长时允许格内折行,`overflow-wrap: break-word` 已由主题处理。
38
38
 
39
+ ## 视觉层级
40
+
41
+ - 默认表格是 booktabs:表头上下一道细线、表尾一道细线、表头浅底,表体没有内部横线与竖线。行距和列距负责分区,短表不靠斑马纹。
42
+ - 三到五行、一屏能读完的短表不要加 `.table-striped`;长表需要横向追踪时再加,主题只给极浅的交替底色。悬停提亮由主题统一提供,不在正文里写。
43
+ - 不手写 `border`、`background` 或对齐样式;表尾线、表头层级和响应式滚动都由主题负责。
44
+
39
45
  ## 单元格
40
46
 
41
47
  - 一格一个事实,对象先行。不写「名词:一句」的伪列表,列已经分出属性名。
@@ -16,6 +16,15 @@
16
16
 
17
17
  骨架遍改次序,章节遍改正文,全书遍改一致性;三遍不混做。
18
18
 
19
+ ## 章的视角
20
+
21
+ 先定这一章站在哪里看,再定标题与段落:
22
+
23
+ - **总览章**写整体形状:交代对象在全局里的位置、彼此关系与阅读路线,可以点名后章对象,但不展开它的数据与函数。
24
+ - **对象章**只写本章对象:数据成员、构造后的状态、公开接口与实现选择。后章才展开的类型名、个数标题、块后归属段不写;「留在某类型」「由某类得出」「不放进本章」这类归属句整句删除。
25
+ - 对象章内的互斥结果写成无序列表,每项「情形:结果」,列表后停止;标题点名这一节的实体,不数条目(`八条线` 拆成 `线坐标表`、`成线判断`)。
26
+ - 总览章与对象章的分界见项目章节表;本书 `01-rules.qmd`、`02-mapping-layout.qmd`、`viewing-program.qmd` 与两份 index 是总览,`03` 起各章是对象章。
27
+
19
28
  ## 以井字棋书为例
20
29
 
21
30
  | 阶段 | 章节 | 交给下一章的结果 |
@@ -0,0 +1,28 @@
1
+ # 规则冲突时的优先级
2
+
3
+ 写作规则不是同一层的检查表。两条规则冲突时,先保住更高层,再谈低层的「更短」「更统一」「更漂亮」。
4
+
5
+ | 级别 | 保住什么 | 典型判断 | 冲突时让位给谁 |
6
+ | --- | --- | --- | --- |
7
+ | 1 事实与任务 | 读者能完成这一步;事实、条件、数字、边界不走样 | 这条命令能不能照做?这个结论有没有原文依据? | 不让位。为短、为顺、为统一删掉必要条件,都是失败 |
8
+ | 2 结构 | 一段一个关键;块与块的次序能读成一条线 | 这段到底在回答哪个问题?下一段该接什么? | 让位给事实;高于清晰、一致、风格 |
9
+ | 3 清晰 | 主语和动作明确;指代有着落;术语首用能懂 | 读者要不要回读上一句才知道「它」指谁? | 让位给事实与结构;高于一致、风格 |
10
+ | 4 一致 | 同类对象用同一种句式、术语、格式 | 两个表格的列名是否同一套叫法? | 让位给事实、结构、清晰;高于风格 |
11
+ | 5 风格 | 简短、节奏、强调、语气 | 这句能不能再短一点? | 前四级都通过后才动 |
12
+
13
+ ## 常见冲突
14
+
15
+ - **更短 vs 更完整**:删掉条件、范围、失败出口换来的短,不算润色;先补回事实,再压句子。
16
+ - **统一格式 vs 准确表达**:统一句式不能把不同的行为写成同一句话;先让每句准确,再统一能统一的部分。
17
+ - **强调语言 vs 讲清内容**:消息先说来源、条件、实际值和限度;用哪种自然语言由项目约定,不由润色规则决定。
18
+ - **视觉整齐 vs 可读**:表格先保证表头、行列关系和表尾闭合,再谈斑马纹、色块和装饰。
19
+
20
+ ## 执行顺序
21
+
22
+ 1. 先写清读者做完这一步要得到什么,列出不能丢的事实。
23
+ 2. 再查段与块的次序;结构没定,不改句子。
24
+ 3. 再查主语、指代、术语和句子骨架。
25
+ 4. 再统一术语、格式、并列结构。
26
+ 5. 最后才做简短、节奏和语气。低层改动如果碰到高层,停手退回。
27
+
28
+ 同级冲突时,选更靠近读者任务的那条;仍分不出,保留原文,不凭风格偏好重写。
@@ -171,7 +171,14 @@ def main() -> int:
171
171
  failures: list[str] = []
172
172
  passed_cases = 0
173
173
 
174
- def expect(name: str, chapter: str, code: int, needle: str, project: bool = False) -> None:
174
+ def expect(
175
+ name: str,
176
+ chapter: str,
177
+ code: int,
178
+ needle: str,
179
+ project: bool = False,
180
+ checks_json: str | None = None,
181
+ ) -> None:
175
182
  nonlocal passed_cases
176
183
  passed_cases += 1
177
184
  with tempfile.TemporaryDirectory() as raw:
@@ -185,7 +192,8 @@ def main() -> int:
185
192
  encoding="utf-8",
186
193
  )
187
194
  (skill_dir / "checks.json").write_text(
188
- '{"forbidden_terms": ["棋盘格子"]}', encoding="utf-8"
195
+ checks_json or '{"forbidden_terms": ["棋盘格子"]}',
196
+ encoding="utf-8",
189
197
  )
190
198
  result = run_case(root)
191
199
  output = result.stdout + result.stderr
@@ -235,8 +243,44 @@ def main() -> int:
235
243
  + f"\n[board.h]({duplicate_link})\n\n[board.h]({duplicate_link})\n",
236
244
  1, "同章重复文件链接",
237
245
  )
246
+ expect(
247
+ "章引言主源文件可重复链接",
248
+ "---\ntitle: 入门\n---\n\n"
249
+ f"本章的声明在 [board.h]({duplicate_link})。\n\n"
250
+ "## 目标\n\n"
251
+ "这一章说明一个可以重复验证的目标。读者读完之后能够判断结果是否正确。\n\n"
252
+ f"正文再提 [board.h]({duplicate_link})。\n",
253
+ 0, "OK",
254
+ )
255
+ expect(
256
+ "链接目标不计入句长",
257
+ VALID_CHAPTER + f"\n读 [board.h]({duplicate_link})。\n",
258
+ 0, "OK",
259
+ )
238
260
  expect("标题口语化", VALID_CHAPTER.replace("## 目标", "## 自己走一局"), 1, "标题带人称")
239
261
  expect("标题带行内代码", VALID_CHAPTER.replace("## 目标", "## 运行 `app`"), 1, "标题带行内代码")
262
+ expect(
263
+ "三级标题在数条目",
264
+ VALID_CHAPTER.replace("### 步骤", "### 八条线"),
265
+ 1,
266
+ "三级标题在数条目",
267
+ )
268
+ expect(
269
+ "对象章点到后章类型",
270
+ VALID_CHAPTER + "\n三类结果都交给 `LaterType` 处理。\n",
271
+ 1,
272
+ "对象章点到后章类型",
273
+ project=True,
274
+ checks_json='{"later_chapter_names": {"content/part/01-intro.qmd": ["LaterType"]}}',
275
+ )
276
+ expect(
277
+ "围栏内后章类型不报",
278
+ VALID_CHAPTER + "\n```cpp\nLaterType value;\n```\n",
279
+ 0,
280
+ "OK",
281
+ project=True,
282
+ checks_json='{"later_chapter_names": {"content/part/01-intro.qmd": ["LaterType"]}}',
283
+ )
240
284
  expect("正文 H1", VALID_CHAPTER.replace("## 目标", "# 目标"), 1, "正文出现 H1")
241
285
  expect("缺 title", VALID_CHAPTER.replace("title: 入门\n", ""), 1, "缺少 YAML frontmatter")
242
286
  expect(
@@ -14,7 +14,8 @@ Project-specific rules are optional. If the book has
14
14
  {
15
15
  "forbidden_terms": ["..."],
16
16
  "fragment_prefixes": ["games/", "shared/"],
17
- "chapter_titles": {"content/part/01-intro.qmd": "入门"}
17
+ "chapter_titles": {"content/part/01-intro.qmd": "入门"},
18
+ "later_chapter_names": {"content/part/02-board.qmd": ["LaterType"]}
18
19
  }
19
20
 
20
21
  Without a project skill, those checks are skipped and only the generic rules
@@ -54,6 +55,16 @@ COINED_RELATION_RE = re.compile(r"(?:组成|依赖|继承|调用)边")
54
55
  CARRIER_RE = re.compile("承载")
55
56
  NEXT_SECTION_RE = re.compile(r"(?:下一节|上一节)")
56
57
  LADDER_RE = re.compile(r"(?:分别交给|各说一层)")
58
+ SKILL_TONE_RE = re.compile(r"这里不定义|测试也不必|不必为[^。;\n]{0,20}再记|不写双层循环")
59
+ INLINE_NAME_RE = re.compile(r"`([A-Za-z_][A-Za-z0-9_]*)`")
60
+ FUNCTION_NAME_RE = re.compile(
61
+ r"^(?:Is|Get|Set|Has|Can|Completes|Place|Read|Write|Print|Run|Make|Collect|Judge|"
62
+ r"Score|Search|Choose|Add|Clear|Reset|Find|Build|Parse|Format|Render|Report|Show|"
63
+ r"Start|Stop|Create|Destroy|Update|Check|Validate|To)[A-Z0-9_]"
64
+ r"|^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$"
65
+ )
66
+ TERMINAL_RE = re.compile(r"终端|打印|显示|输入|读入|命令行")
67
+ TEST_RE = re.compile(r"测试|断言|用例|gtest|CTest")
57
68
 
58
69
  TREE_CATEGORY_COMMENTS = frozenset({
59
70
  "头文件", "源文件", "源码", "实现", "测试", "配置文件",
@@ -76,6 +87,7 @@ HEADING_RE = re.compile(r"^(#{1,6})\s+(.*?)\s*$")
76
87
  HEADING_PERSONAL_RE = re.compile(r"自己|咱们|我们|你|您|跟我|试试|来看")
77
88
  FENCE_RE = re.compile(r"^\s*(```+|~~~+)\s*(\{[^}]*\}|[^\s`]*)?")
78
89
  LINK_RE = re.compile(r"\[[^\]]*\]\(([^)\s]+)\)")
90
+ LINK_LABEL_RE = re.compile(r"\[([^\]]*)\]\([^)]*\)")
79
91
  CJK_RE = re.compile(r"[\u3400-\u9fff]")
80
92
  TITLE_RE = re.compile(r"^title:\s*(.+?)\s*$", re.MULTILINE)
81
93
 
@@ -194,22 +206,40 @@ def check_heading(path: Path, line: int, level: int, text: str, issues: list[Iss
194
206
  issues.append(Issue(path, line, "标题带人称或口语劝诱;改写成对象名或「动词 + 具体宾语」"))
195
207
  if "`" in text:
196
208
  issues.append(Issue(path, line, "标题带行内代码;文件名留给正文,标题写对象"))
209
+ if level == 3 and re.search(r"[0-9一二三四五六七八九十百]+条", text):
210
+ issues.append(Issue(path, line, "三级标题在数条目;拆成实体标题或改成承载对象"))
197
211
 
198
212
 
199
- def check_prose_block(path: Path, start_line: int, text: str, issues: list[Issue]) -> None:
213
+ def check_prose_block(
214
+ path: Path, start_line: int, text: str, issues: list[Issue], is_list: bool = False
215
+ ) -> None:
200
216
  compact = text.strip()
201
217
  if not compact:
202
218
  return
203
219
  if CHAPTER_NUMBER_RE.search(compact):
204
220
  issues.append(Issue(path, start_line, "第 N 章式编号指称(用章节标题或链接)"))
221
+ if SKILL_TONE_RE.search(compact):
222
+ issues.append(Issue(path, start_line, "技能腔:只约束作者的选择,整句删掉"))
223
+ if not is_list and CJK_RE.search(compact):
224
+ names = {
225
+ match.group(1)
226
+ for match in INLINE_NAME_RE.finditer(compact)
227
+ if FUNCTION_NAME_RE.search(match.group(1))
228
+ }
229
+ if len(names) >= 3 and TERMINAL_RE.search(compact) and TEST_RE.search(compact):
230
+ issues.append(Issue(
231
+ path, start_line,
232
+ f"一段里出现 {len(names)} 个函数名,还同时讲终端与测试;拆成列表,一项一件",
233
+ ))
205
234
  if CJK_RE.search(compact):
206
- length = len(re.sub(r"\s+", "", compact))
235
+ visible = LINK_LABEL_RE.sub(r"\1", compact)
236
+ length = len(re.sub(r"\s+", "", visible))
207
237
  if length > MAX_PARAGRAPH_CHARS:
208
238
  issues.append(Issue(
209
239
  path, start_line,
210
240
  f"段落 {length} 字符,超过 {MAX_PARAGRAPH_CHARS};拆成两段或删枝蔓",
211
241
  ))
212
- for sentence in re.split(r"(?<=[。!?])", compact):
242
+ for sentence in re.split(r"(?<=[。!?])", visible):
213
243
  sentence = sentence.strip()
214
244
  if len(sentence) > MAX_SENTENCE_CHARS:
215
245
  issues.append(Issue(
@@ -569,7 +599,9 @@ def check_file(path: Path, root: Path, project: dict, chapters: list[str]) -> li
569
599
 
570
600
  if raw.lstrip().startswith((">", "-", "*", "+", "1.")):
571
601
  flush_paragraph()
572
- check_prose_block(path, number, re.sub(r"^\s*(?:[>|]|[-*+]|\d+\.)\s*", "", raw), issues)
602
+ check_prose_block(
603
+ path, number, re.sub(r"^\s*(?:[>|]|[-*+]|\d+\.)\s*", "", raw), issues, is_list=True
604
+ )
573
605
  continue
574
606
 
575
607
  if not paragraph:
@@ -601,6 +633,18 @@ def check_file(path: Path, root: Path, project: dict, chapters: list[str]) -> li
601
633
  fenced_lines.add(number)
602
634
 
603
635
  blob_links: dict[str, list[int]] = {}
636
+ first_heading = next(
637
+ (n for n, raw in enumerate(lines, start=1) if HEADING_RE.match(raw)),
638
+ len(lines) + 1,
639
+ )
640
+ intro_blob_targets: set[str] = set()
641
+ for index, raw in enumerate(lines, start=1):
642
+ if index >= first_heading or index in fenced_lines:
643
+ continue
644
+ for match in LINK_RE.finditer(raw):
645
+ target = match.group(1).strip()
646
+ if target.startswith("https://github.com/") and "/blob/" in target:
647
+ intro_blob_targets.add(target)
604
648
 
605
649
  for index, raw in enumerate(lines, start=1):
606
650
  if index in fenced_lines:
@@ -618,19 +662,16 @@ def check_file(path: Path, root: Path, project: dict, chapters: list[str]) -> li
618
662
  issues.append(Issue(path, index, f"本地链接不可达:{target}"))
619
663
 
620
664
  for target, numbers in blob_links.items():
621
- if len(numbers) > 1:
665
+ if len(numbers) > 1 and target not in intro_blob_targets:
622
666
  issues.append(Issue(
623
667
  path, numbers[1],
624
- f"同章重复文件链接:{target}(首次在第 {numbers[0]} 行);每章每条仓库路径至多一条",
668
+ f"同章重复文件链接:{target}(首次在第 {numbers[0]} 行);"
669
+ "只有章引言挂过的本章主源文件可重复,其他仓库路径每章至多一条",
625
670
  ))
626
671
 
627
672
  # 章引言(第一个标题之前)只留承接上一章的一个章节链接;其余章节引用下沉到正文。
628
673
  # 着陆页 index.qmd 是目录页,不受这条预算约束。
629
674
  if path.name != "index.qmd":
630
- first_heading = next(
631
- (n for n, raw in enumerate(lines, start=1) if HEADING_RE.match(raw)),
632
- len(lines) + 1,
633
- )
634
675
  intro_links: list[tuple[int, str]] = []
635
676
  for index, raw in enumerate(lines, start=1):
636
677
  if index >= first_heading or index in fenced_lines:
@@ -658,6 +699,18 @@ def check_file(path: Path, root: Path, project: dict, chapters: list[str]) -> li
658
699
  issues.append(Issue(path, index, f"项目禁用词「{term}」"))
659
700
  break
660
701
 
702
+ later_names = project.get("later_chapter_names", {}).get(rel, [])
703
+ if later_names:
704
+ for index, raw in enumerate(lines, start=1):
705
+ if index in fenced_lines or raw.lstrip().startswith(("```", " ")):
706
+ continue
707
+ for name in later_names:
708
+ if name and re.search(rf"(?<![A-Za-z0-9_]){re.escape(name)}(?![A-Za-z0-9_])", raw):
709
+ issues.append(Issue(
710
+ path, index,
711
+ f"对象章点到后章类型「{name}」;删掉这句,留给后章",
712
+ ))
713
+
661
714
  for index, raw in enumerate(lines, start=1):
662
715
  if raw.lstrip().startswith(("```", " ")):
663
716
  continue
@@ -105,6 +105,8 @@
105
105
  - 英文文件名承担稳定路径职责,多词形态见本文件;中文 title 承担理解职责,两者不必逐字翻译。
106
106
  - 例外:part 落地页(`_quarto.yml` 中 part 引用的 index.qmd)title 即 part 名(如「示例项目」),不受 2-6 汉字硬限;副题信息只放落地页引言,章节与落地页 qmd 一律不写 `description`(front matter 出处见 basics.md),后续其他 part 同样处理。
107
107
 
108
+ - **实体标题而不是条目数**:标题若是集合的数量(`八条线`、`三种类型`、`四个入口`),说明这一节在数条目。先看条目能否各自成为实体;能,就拆成实体标题(`八条线` 拆成 `线坐标表`、`成线判断`);不能,就改成承载它们的对象。标题不替读者数个数。对照见 `examples/02-structure-shape.md`「八条线拆成两个实体标题」。
109
+
108
110
  ### 定语「的」的取舍
109
111
 
110
112
  标题(YAML `title` 与 `##`、`###` 同规)里的定语「的」按定语与中心语的关系取舍,不默认加、也不默认省:
@@ -56,7 +56,7 @@ int main() {
56
56
  - **filename 一律短显示名**:只写 `main.cpp`、`CMakeLists.txt`、`running-build.sh`、`example/` 这类短名;禁止把 `src/...` 或 `shared/...` 深层路径塞进标题栏。完整位置由章引言的 GitHub 链接和目录树提供;`verifying-content.py` 用本章内、代码块之前 basename 匹配的最近一条 blob 链接解析真实路径再做片段核对。
57
57
  - **filename 不写链接**:`filename="..."` 是纯文本短名,禁止塞 Markdown 链接;`resolve_fragment_source` 的解析只认正文句子里的链接,标题栏写链接既渲染不出可点样式,也会被 `verifying-content.py` 判失败。
58
58
  - **blob 链接嵌进导语句**:GitHub blob 链接写在同节导语的完整句子里(正例:「apps/CMakeLists.txt 用一行目标属性固定运行时库查找」,其中文件名是 GitHub 链接的锚文本),禁止整行仅为链接单独成行贴在代码围栏上方(可夹空行,`verifying-content.py` 判失败)。悬空的裸链接行既不像正文也不像说明,读者扫读时只剩一个锚点。
59
- - **摘录链接每章每条仓库路径至多一条**:章引言是全章链接位,首次点名源文件并挂链接(如「声明在头文件 `state.h`,实现在源文件 `state.cpp`」,这两个名字是该章仅有的源文件链接);后文和各节导语改成行内代码,不再包 `[]()`,导语讲块里要看的标识符;同一章摘录另一阶段目录的同名文件时,在切换处再挂一条不同 URL(对照见 `examples/03-excerpt-lead.md`「摘录链接密度」;`verifying-content.py` 对同章同 URL 重复判失败)。本条挂在写作步骤的定版式。
59
+ - **本章主源文件可重复挂链接,其他仓库路径每章至多一条**:章引言用可跳转链接(内联 `[]()` 或同页引用式 `[label][id]`)挂本章主源文件(如 `board.h`、`board.cpp`),正文再点名这些文件时复用同一 GitHub 链接,读者不用回翻章首。不在章引言挂链接的其他仓库文件,正文用行内代码,不包链接;跨章指称用章节标题链接。同一章摘录另一阶段目录的同名文件时,在切换处再挂一条不同 URL(对照见 `examples/03-excerpt-lead.md`「摘录链接密度」;`verifying-content.py` 只对非章引言主源文件的同章重复 URL 判失败)。本条挂在写作步骤的定版式。
60
60
  - 片段内注释硬限:每处实现注释(`//` 或 `#`)不超过 3 行(与源文件注释规范的 1–3 行同一口径)、单行不超过 100 列(与源文件 `ColumnLimit: 100` 同一口径;正文散文的 40 字限不套到代码注释),只写这段代码做什么;CMake/Shell 等命令型片段每条命令保留一行意图注释。Doxygen `///` 声明块(概述、空 `///` 与细节)是声明骨架的一部分,摘录随源保留,不受此限。命名惯例、官方建议、未来演化等规范说理一律不进代码块;术语卡和可跳过的操作提醒可以进 callout,注释怎么写、行尾先写什么、`///` 与 `@` 标签怎么分工只留在 `.agents`,面向作者的进知识文件。
61
61
  - 首讲某段源码的章节不省略源码既有的职责注释:注释是读者读到的第一层讲解;「注释与空行可以省略」只服务后续章节复现已见代码。源码缺注释时交给项目 C++ 流程补齐,本技能不替源码补注释。
62
62
  - 讲解段只解释片段中实际出现的代码;片段没有展示的内容要么补进片段、要么进 callout,不在正文列表里讲代码外面的东西。
@@ -12,12 +12,28 @@
12
12
  - 每段只有一个主题或中心句;中心句放在段首,后面的句子都服务于它。段内句子改完后,段首仍是中心句,一段仍只有一个对象;句子的六个成分见 `steps/05-sentences.md`「六个成分」。
13
13
  - 一段一对象:同一段不夹杂两个并列对象,对象切换必须新起段;配套列表只服务本段对象,不得跨对象拼装列表项(对照见 `examples/03-block-after.md`「单段混并列对象」)。
14
14
  - 段落不超过 7 行,最佳 4 行以内;超过即拆或改列表。
15
- - 同一可数事实全章只交代一次:格子数、尺寸、类型、成员数量这类事实第一次出现时写全(如引言里的「3 行 3 列,共九个棋盘格子」),后面用「这些格子」「其余格子」回指,不复述数字或同一个名词短语;代码块前后的散文也不重复块内注释已经写过的数量与类型(对照见 `examples/04-paragraph-cases.md`「重复交代可数事实」)。
15
+ - 同一可数事实全章只交代一次:格子数、尺寸、类型、成员数量这类事实第一次出现时写全(如引言里的「3 行 3 列,共九个棋盘格子」),后面用「这些格子」「其余格子」回指,不复述数字或同一个名词短语;代码块前后的散文也不重复块内注释已经写过的数量与类型(对照见 `examples/04-paragraph-cases.md`「空结果、数量与存放方式」)。
16
16
  - 多个独立概念禁止塞进同一段:承接、目标、范围边界、阅读顺序、术语解释各归各段。
17
17
  - 段落之间用一个空行分隔,段首不缩进。
18
18
  - 代码块后的逐条讲解用无序列表:每项格式为「行内代码标识符 + 全角冒号 + 一句职责」(如 `` `enable_testing()`:开启 CTest。``),描述从简,全页标记方式一致;列表前保留一句引言交代工程语境,列表后需要时补一句收束或尾置 Callout。有先后顺序的排查步骤仍用有序列表;块前导语直接点名对象做什么,不用「先…再…最后…」,元计数禁令见 `steps/03-blocks.md`。
19
19
  - 删段还是拆段,先按 `steps/05-sentences.md`「一次对话」数这段有几件内容再定删或拆;判定全文见该文件「一次对话」。
20
20
 
21
+ ### 情形列表后停止
22
+
23
+ 互斥结果写成无序列表,每项「情形:结果」,列表后停止。列表已经逐项给出每种情形和结果,后面不再补总述、归属、下一步预告;要补的只能是列表没有的信息。两项互斥也照此,不并成一句(版式例外见 `steps/07-layout.md`「列表选择」)。对照见 `examples/04-paragraph-cases.md`「GetWinner 的三段删前样本」。
24
+
25
+ ### 两关:一个关键、一条次序
26
+
27
+ 每个自然段按次序过两关:先数关键,再排次序,决定删留。第 5 步只改留下的句子;润色不负责从一串概念里挑关键。
28
+
29
+ **第一关:一个关键。** 盖住段中标识符后,这段还能不能只复述一件事?同一函数上的调用条件、类型分工、后继入口算三件;空棋盘、格子数、存放方式也算三件。只留一件,放在段首。代码块后只留围栏注释没写的那一件;围栏已经写过的,从块后删掉。多核并列的拆法见本文件「拆核四步(多核混段的拆段方法)」,不在这里重列一遍。
30
+
31
+ **第二关:一条次序。** 后一句必须接住前一句句末的新信息:复用那个词,或按已有的零形回指省掉主语。另起一个前文没有的概念就拆段,不用「另外」「同时」接上。新信息不必再原词写成下一句的语法主语,否则会和已有的零形回指冲突。一段只用一种次序:时间先后,或从外到内。冒号后面不得再甩出一组并列新概念。
32
+
33
+ 「过简断裂」只抓「新主语且没有承接词」,管不住同一主语从数量跳到存放方式、再用冒号甩出两层下标。第二关补的是整段的次序,不是再加一个承接词。两关的改写对照见 `examples/04-paragraph-cases.md`「同一函数叠三件事」「空结果、数量与存放方式」。
34
+
35
+ 依据:Google Technical Writing「Paragraphs」要求把一件事的依赖拆开,排成读者能跟着走的一条流;West Virginia University SpeakWrite 的 Known-New 与 Chicago Harris 的 Old-to-New 要求句首放读者刚读过的信息、句末放新信息,Gopen 与 Swan 的 topic position / stress position 是这两篇的出处。段首关键与三到七行的长度限制另见 Google developer documentation style guide「Paragraph structure」、Microsoft Style Guide「Scannable content」。
36
+
21
37
  ### 默认总分与并列槽位
22
38
 
23
39
  读者正文默认先总后分;段首先给这一段的整体,后面的句子或列表再展开部分。列表前的那一句就是“总”,列表项是“分”。分总只留一种情况:紧挨在前的代码块或图已经把部分摆在读者眼前,下一句只给这些部分一个总名。
@@ -34,6 +50,7 @@
34
50
  | --- | --- |
35
51
  | 段内出现第二个互不依赖的对象或主题 | 新起段;新段首句接住上段对象(衔接见本文件「段间:过渡句只在话题切换处」) |
36
52
  | 段内出现「话题标签:解释」词条式冒号,或两个以上互不依赖的中心并列推进 | 按「拆核四步(多核混段的拆段方法)」判关系:递进同段衔接推进,并列拆段;词条式冒号改完整主谓句(见「散文伪标签冒号禁令」) |
53
+ | 一段同时讲多个函数、终端打印和测试核对,句子互不从属 | 改成列表,一项一个函数或一种调用场合;正反例见 `examples/04-paragraph-cases.md`「混段:函数、终端与测试」 |
37
54
  | 一段同时承担「承接上一章」+「本章交付物清单」+「源文件位置」+「摘录声明」 | 章引言不拆段,形态硬限与摘录声明删除见 `steps/02-structure.md`;其他段落按职责拆段,交付物多项且需扫读 → 列表(目录式堆砌对照见 `examples/02-structure-shape.md`) |
38
55
  | 超过约 4 行舒适带、或逼近 7 行 / 篇幅 token 上限 | 先找逻辑边界拆,禁止为凑行数在句中腰斩 |
39
56
  | 连续 ≥3 段纯散文且无列表/代码/图 | 改写见本文件「句序与段落形态」 |
@@ -35,12 +35,13 @@
35
35
  - 每个分句只担任一种角色:可观察事实、不变约束、因此作出的选择。次序固定为**事实 → 约束 → 因此选择**;选择句不得出现在依据之前——先写「长度取常量」再补「九个格子」,读者要把因果倒过来读。
36
36
  - 光有回指不够。后一分句若不是同一谓语的延续,必须写出「因此 / 所以」;删掉关系词后读者仍要自己补因果,判为不合格。可观察事实(「状态面板一共九个格子」)可以在前,读者据此才知道选择成立。
37
37
  - 「是示例项目规则的一部分」这类只贴类别、不改变下一步的分句删除:它不回答「所以代码怎么办」。
38
+ - 「因此选择」必须是读者下一步要执行的动作。只约束作者的选择(不另造类型、测试少记一个名字、注释该怎么组织)整句删掉,不改写成另一句解释;正文不展开与本章对象无关的标准库机制,也不写「不写甲,而是乙」。
38
39
  - 一句里用逗号甩出的独立判断,最多留「事实,约束」或「约束,所以选择」一对;第三个判断另起句或删除。单句仍守本文件「句子规则」的 40 字与一动作上限。
39
40
  - 递进同一核不拆段复述:长度和装下它的数组是同一个核,同段用「因此」接住,不另起一段再说一遍「正好装下」。
40
41
  - 后续小节专讲的对象,本段不写「由 X 完成」:换算如果由下一小节专讲,本段不提前交接(「换算由下一小节完成」);块内注释或下节首句已经说出的职责,整句删除。
41
42
  - 与「对象先行」的对齐方式见 `steps/04-paragraphs.md`「句序与段落形态」。
42
43
 
43
- 判定信号:设计说明句里选择先于依据;逗号甩出第三个独立判断;分句只贴类别不改变下一步;删掉关系词后因果要读者自补;段末提前交接后续小节的对象。改写对照见 `examples/05-sentence-frame.md`「句内骨架:先依据后选择」。类型接线句与被下一句否定的全称按本文件「一次对话」判删留,不按本节改写句内次序。
44
+ 判定信号:设计说明句里选择先于依据;逗号甩出第三个独立判断;分句只贴类别不改变下一步;删掉关系词后因果要读者自补;段末提前交接后续小节的对象;「因此选择」只约束作者、读者没有下一步动作。改写对照见 `examples/05-sentence-frame.md`「句内骨架:先依据后选择」与「技能腔:解释作者的选择」。类型接线句与被下一句否定的全称按本文件「一次对话」判删留,不按本节改写句内次序。
44
45
 
45
46
  ### 拉链句
46
47
 
@@ -128,6 +129,8 @@
128
129
  - 读者正文(`content/**/*.qmd`、根 `README.md`、`index.qmd` 的围栏外正文)不用「先…再…」「先…然后…」「先…最后…」串动作或阅读顺序。每一步写成独立陈述,主语是对象或读者要做的事。代码块导语直接点名对象做什么。`.agents` 的操作步骤、引号和围栏里的反例不套这条。
129
130
  - 读者正文点名类时直接写反引号里的类名,例如 `Game`、`Board`、`Ai`。类名前面不加「类」。
130
131
 
132
+ - 点名后章类型再说明「这件事不在这里」也删除:类型名已经越界,否定句不豁免;不要改写成另一句否定句。只保留本章对象与本章要读者做的判断。
133
+
131
134
  ### 简洁边界
132
135
 
133
136
  简洁的上界是不丢失理解所需的信息。为省字而砍掉动作、对象或顺序的电报式省略句一律禁止:
@@ -40,6 +40,8 @@
40
40
  - **不用列表**:后句用前句得数的乘加不是并列要点。列表仍只用于没有先后的并列,或配置、编译这种各自能做完的步骤。禁止「公式项:一句职责」。
41
41
  - **块位**:定义就是围栏里与源码相同的 `return` 行。`###` 导语仍是一句,点名函数和 `cells_` 的数组下标,并点明实现文件。原理段不重复 `static_cast`。图前一句点名得数是哪一个数组下标,不把图里已标的区间再念一遍。正文不另排 `$$` 或行内数学模式。
42
42
 
43
+ - **两项互斥也用列表**:两项是互斥结果(有三连/没有三连、接受/拒绝)而不是同一对象的两个属性时,不并成一句,改无序列表「情形:结果」,列表后停止。这是「只有两项且能写进一句」的窄例外。
44
+
43
45
  ### 加粗使用
44
46
 
45
47
  - 加粗只用于列表项或段首的引导词(中心语前置),标记结构,不强调情绪。
@@ -31,7 +31,7 @@
31
31
  | 定句子 | 节首句以「这些/这个/这一步」作话题且同句列不出对象名(所指要等后面的 `###` 或代码围栏) | 节首同句列出对象名;已逐个写出名字后的回指保留 | `steps/05-sentences.md`「代词不能代替未写出的对象」、`examples/05-sentence-reference.md`「定结构与定句子:代词不能代替未写出的对象」 |
32
32
  | 定句子 | 类型接线句(两个逗号分句各自是「成员叫法 + 使用 + 某一章的类型」,且两项都已在紧邻导语与围栏出现) | 整段删除,不走拉链的推迟、拆句、一项一行 | `steps/05-sentences.md`「一次对话」、`examples/04-paragraph-cases.md`「定段落:类型接线段整段删除」 |
33
33
  | 定句子 | 一句里的全称(全部、所有、唯一)被下一句否定 | 改范围或删除;全称读完下一句仍成立才保留 | `steps/05-sentences.md`「一次对话」 |
34
- | 定句子 | 先决定后补依据 / 逗号甩判断(一句用逗号并列三个以上独立判断)/ 「由 X 完成」式被动交接 | 按「句内骨架」改为事实 → 约束 → 因此选择;「由 X 完成」在定用词改主动句 | `steps/05-sentences.md`「句内骨架」 |
34
+ | 定句子 | 先决定后补依据 / 逗号甩判断(一句用逗号并列三个以上独立判断)/ 「由 X 完成」式被动交接 | 按「句内骨架」改为事实 → 约束 → 读者下一步要执行的动作;只约束作者的选择整句删除;「由 X 完成」在定用词改主动句 | `steps/05-sentences.md`「句内骨架」 |
35
35
  | 定句子 | 元叙述开场(「是规则,代码把它写成」「因此记作/写成 `class`」) | 对象先行:改为「对象 → 名称 → 约束 → 出处」 | `steps/06-wording.md`、`examples/01-language-scope.md` |
36
36
  | 定句子 | 拉链句(两组对象配对,删任一仍成立;「分别交给/各说一层/分别是」) | 按推迟 → 拆句 → 一项一行改写 | `steps/05-sentences.md`「拉链句」、`examples/05-sentence-frame.md`「拉链句」 |
37
37
  | 定句子 | 假对比「这不仅是 X,更是 Y」两半并不都提供信息;起跑式铺垫(「在开始之前」「首先我们来看」);句尾把一次操作拔高成意义 | 删宣布与拔高;两半都有信息时保留对比 | `steps/05-sentences.md`「情况:假对比、起跑式铺垫与意义拔高」 |
@@ -44,6 +44,10 @@
44
44
  | 定句子 | 行尾把格子状态、先手或后手、操作动作写进同一句 | 换成术语表「数据项类型」里的三句,与头文件逐字一致 | `steps/05-sentences.md`、`steps/06-wording.md` |
45
45
  | 定句子 | 一句里的量被「确定、体现、界定、表达」盖住(时间义「已经确定」合格) | 改写成相等、乘除加减、上下界或成员记法;「由 A 得到 B」「将 A 转换为 B」的换算注释不在此改 | `steps/05-sentences.md`「关系写成能核对的运算」 |
46
46
  | 定段落 | 散文伪标签冒号(话题 + `:` 词条式冒号);多核混段 | 词条式冒号改完整主谓句或真列表项;多核混段按拆核四步改写 | `steps/04-paragraphs.md`「散文伪标签冒号禁令」「拆核四步(多核混段的拆段方法)」 |
47
+ | 定结构 | 三级标题在数条目(`八条线`、`三种类型`)或标题对象是集合 | 拆成实体标题(`线坐标表`、`成线判断`)或改成承载对象;标题不数个数 | `steps/02-structure.md`「形式规则」、`examples/02-structure-shape.md`「八条线拆成两个实体标题」 |
48
+ | 定段落 | 块后归属段/复述注释(`留在某类型`、`由某类得出`、`不放进本章`、`后文只调用`) | 整段删除;互斥结果的列表后不再补总述 | `steps/04-paragraphs.md`「情形列表后停止」、`examples/04-paragraph-cases.md`「GetWinner 的三段删前样本」 |
49
+ | 定段落 | 一段有多个可独立复述的关键(调用条件、类型分工、后继入口;或空结果、数量、存放方式),或块后复述围栏注释 | 只留一件放段首;其余下沉到各自小节或删除,块后只留注释没写的那件 | `steps/04-paragraphs.md`「两关:一个关键、一条次序」、`examples/04-paragraph-cases.md`「同一函数叠三件事」「空结果、数量与存放方式」 |
50
+ | 定段落 | 后一句不接前一句句末的新信息、另起前文没有的概念;或冒号后甩出一组并列新概念 | 复用句末的词或按零形回指省主语;另起概念就拆段;冒号后的并列改完整句或列表 | `steps/04-paragraphs.md`「两关:一个关键、一条次序」 |
47
51
  | 定版式 | 成员函数声明间无空行、qmd 围栏坍缩 | 补空 1 行;围栏保留源码空白 | `steps/03-blocks.md`「类与多函数摘录的垂直空白」 |
48
52
  | 定版式 | 行尾超 100 列 | 超宽整条回退上一行 | `steps/03-blocks.md` |
49
53
  | 定版式 | 列表项字数失衡(冒号后最长 ≥ 最短 ×2,或引导词相差 ≥4) | 删长项多余中心 → 拆多中心 → 过短项按简洁边界补全 | `steps/07-layout.md`「列表选择」、`examples/07-layout-list.md`「列表字数带失衡」 |
@@ -61,6 +65,8 @@
61
65
 
62
66
  润色任务沿 `SKILL.md` 的路由执行:增量任务只走与改动对应的步骤;新章、新阶段或用户明确要求全章重构才从定任务走到通过检查。判定标准见 `steps/06-wording.md`。书根 `README.md` 与 `README.en.md` 是仓库说明页,按 `SKILL.md` 的例外只走第 5–8 步,这个例外优先于本步的跳步判定。两种走法共用 1–9:**增量润色**保持最小改动,只改判为不合格的句子,不做风格重塑;**全章重构**按 `SKILL.md`「两种走法」走——章级(1–2)先定删留不动句子,节级(3–4)只拆块和段,句级(5–7)逐句拆开再写回去,该层全部重组,不走最小改动。执行时遵守定段落节的既有判定:分句骨架在定句子判为不合格,不在定段落里顺手改句子。
63
67
 
68
+ 同一处同时命中多条规则时,先按 `references/priority.md` 排级:事实与读者任务 > 结构 > 清晰 > 一致 > 风格。高层没有解决,不先做低层润色;低层改动碰到高层事实,停手退回。
69
+
64
70
  **先切模块,再套规则。** 任何一步动笔前先把文本切成单位,围栏、YAML 与链接 URL 跳过(脚本按同一口径统计,计数见 `verifying-content.py` 输出):
65
71
 
66
72
  - **章**:标题到下一个 `##` 之前。只做定任务、定结构。