@chengzhao-dev/dsh-quarto-book 0.6.9 → 0.6.12

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.9`; to follow a GitHub tag, write `github:chengzhao-dev/dsh-quarto-book#v0.6.9`.
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.12`; to follow a GitHub tag, write `github:chengzhao-dev/dsh-quarto-book#v0.6.12`.
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.9`,跟 GitHub 标签时写 `github:chengzhao-dev/dsh-quarto-book#v0.6.9`。
32
+ `--profile` 换成你实际运行的那个 profile。桌面应用固定跑 `desktop` profile,且该 profile 由应用独占管理,`dsh --profile desktop` 直接启动会被拒绝;安装或升级用 `dsh plugin --profile desktop add <包>`,应用拒绝写入时再走「设置 → 插件 → 添加插件」。需要固定版本时写 `npm:@chengzhao-dev/dsh-quarto-book@0.6.12`,跟 GitHub 标签时写 `github:chengzhao-dev/dsh-quarto-book#v0.6.12`。
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.9",
3
+ "version": "0.6.12",
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,19 +99,34 @@
99
99
  ```
100
100
 
101
101
  判定信号:章首或 `##` 首句的主语是下游对象(格子、对局、终端),而不是标题对象;把主语换成标题对象后句子仍成立,说明原句已经漂移。
102
- ## 重复交代可数事实
103
102
 
104
- 以棋盘章成员与接口节的一段为对照。引言已经写过「3 行 3 列,共九个棋盘格子」,类注释又写「3×3 个格子……格子数量不会改变」,块后散文再讲两遍「九个棋盘格子」「数量从开局到终局不变」「编译期就能确定」,读者读到的是一件事的三种说法:
103
+ ## 同一函数叠三件事
104
+
105
+ 以示例章的坐标换算节为对照。同一个 `ToFlatIndex` 上叠了三件互不依赖的事:调用前要先过范围检查、`Position` 不拥有合法区间、换算出下标后交给读写入口。三句都绕着函数转,现有「一段一个对象」看不出问题;盖住 `ToFlatIndex` 后是三件事(规则见 `steps/04-paragraphs.md`「两关:一个关键、一条次序」):
105
106
 
106
107
  ```text
107
- 不佳(同一事实讲三遍,第二句和第三句还互相重复):
108
- `Board()` 构造之后就是一张空棋盘。出界坐标不会写入棋盘;`Board` 保存九个棋盘格子的类型,数量从开局到终局不变。九个棋盘格子的数量编译期就能确定,所以 `cells_` 用二维 `std::array` 存放。
108
+ 不佳(调用条件、类型分工、后继入口叠在同一段):
109
+ `ToFlatIndex` 把行列坐标换算成数组下标。调用前要先经过 `IsInRange`;`Position` 只保存一对行列,不拥有合法区间。换算出下标后,`Read` 与 `Write` 都从 `cells_` 取元素。
109
110
 
110
- 改写(只保留块后必须新给的实现事实:存放方式与下标含义):
111
- `Board()` 构造出来是空棋盘。格子数在编译期确定,`cells_` 用二维 `std::array` 存放:外层是行,内层是列,`cells_[row][col]` 对应坐标 `(row, col)`。
111
+ 改写(段首只留换算这一件,其余各归各节):
112
+ `ToFlatIndex` 把一对行列坐标换算成 `cells_` 的数组下标。
112
113
  ```
113
114
 
114
- 自查:删掉重复句后,读者还缺哪一条事实?只补缺的那条;「数量不变」「出界不写入」若已在类注释或 `IsInRange` 的契约里写过,就不再复述。
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
+ 判定信号:段首句讲构造结果,下一句改用数量开头;冒号后不是列表、围栏或图,而是一组并列的新概念;删掉数量句后存放方式仍由围栏注释交代。处理:先按第一关数出空结果、数量、存放方式三件,只留读者在块后还需要的一件;其余写回围栏注释或删除。
115
130
 
116
131
  ## 混段:函数、终端与测试
117
132
 
@@ -130,3 +145,52 @@
130
145
  终端读入、终端打印和测试注册各自另起一段或另起列表。
131
146
 
132
147
  判定信号:一段里出现三个以上函数名,且分句之间没有「因此」这类递进关系;读者无法从中挑出下一步要改的那一处。
148
+
149
+ ## 接口、调用场合与实现细节的次序
150
+
151
+ 以示例章的只读接口节为对照。表格已经列出 `Get`、`IsEmpty`、`IsFull` 的返回与边界,下一段却用「读格子、判空格、判满盘」代替函数名,紧接着又插 `Get` 的实现;读者还没看完三个接口的调用场合,就被带到单个函数的越界细节。
152
+
153
+ ```text
154
+ 不佳(函数名被中文动作替代,用法和实现夹在一起):
155
+ `Get`、`IsEmpty`、`IsFull` 都只读面板。读格子、判空格、判满盘是三件不同的事。
156
+ `Get` 的边界判断与 `std::array::at` 相同,越界时抛 `std::out_of_range`。异常消息先写来源,再写失败条件与实际值。
157
+ `Get` 和 `IsEmpty` 都先检查坐标。调用方按场合选择:
158
+ - 终端逐行调用 `Get`。
159
+ - 测试用 `Get` 核对空格、已有值与越界坐标。
160
+ ```
161
+
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。
194
+ ```
195
+
196
+ 判定信号:声明围栏已经写清返回类型,块后又解释 `optional` 的语义;段落点名 `Game`、`GameResult`、`Ai` 并说明归属;同一段把 `GetWinner` 与另一个读取函数并排讲。处理:只列读者从声明里看不出的结果,互斥结果写成两项列表;后章类型与归属整句删除。
@@ -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
  - 正文只保留读者当前动作必需的外链。手册、完整参数列表和上游项目文档在首次需要该资料的任务中就地链接,不设章末集中区。
@@ -10,6 +10,12 @@ Quarto 只执行 ` ```{mermaid} ` 属性围栏;plain 的 ` ```mermaid ` 围栏
10
10
 
11
11
  渲染核对方法:调用 `quarto_render_check`,在返回的 `html_report` 对应 HTML 中应出现 mermaid 图表容器(`mermaid-js` 或 `quarto-diagram` 类名),不应出现 `sourceCode mermaid` 高亮代码块。
12
12
 
13
+ ## 无障碍名称
14
+
15
+ 每个 `{mermaid}` 围栏都写角色与名称:`{.mermaid role="img" aria-label="调用链:Server 接收请求,交给 Controller 处理。"}`;`fig-cap` 给视觉读者,`aria-label` 给读屏软件,两处各写一句短名称。
16
+
17
+ 名称说清谁和谁、按什么顺序、结果落到哪,不写“流程图”这类对象名。缺名称时 axe 报 `svg-img-alt`;渲染核对在 HTML 里搜 `<pre class="mermaid" role="img"`。
18
+
13
19
  ## 块首固定 init 指令
14
20
 
15
21
  每个 `{mermaid}` 块的第一行写:
@@ -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,16 @@
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
+ - 整书遍同时统一陈述范围:部分页使用项目名与「这一部分」,不把单个样例抬成全书;只有书首页概括全书。页首导语不复述目录与章节卡片,卡片入口只写章节标题与一句交付结果。
28
+
19
29
  ## 以井字棋书为例
20
30
 
21
31
  | 阶段 | 章节 | 交给下一章的结果 |
@@ -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
@@ -53,6 +54,8 @@ CODE_META_RE = re.compile(r"对应的代码")
53
54
  COINED_RELATION_RE = re.compile(r"(?:组成|依赖|继承|调用)边")
54
55
  CARRIER_RE = re.compile("承载")
55
56
  NEXT_SECTION_RE = re.compile(r"(?:下一节|上一节)")
57
+ CALLOUT_OPEN_RE = re.compile(r"^\s*:::\s*\{?\s*\.callout-")
58
+ OPENING_CLICHE_RE = re.compile(r"^(?:本章|本节|这一章|这一节)(?:将|会|要)|^下面(?:介绍|讲)")
56
59
  LADDER_RE = re.compile(r"(?:分别交给|各说一层)")
57
60
  SKILL_TONE_RE = re.compile(r"这里不定义|测试也不必|不必为[^。;\n]{0,20}再记|不写双层循环")
58
61
  INLINE_NAME_RE = re.compile(r"`([A-Za-z_][A-Za-z0-9_]*)`")
@@ -86,6 +89,7 @@ HEADING_RE = re.compile(r"^(#{1,6})\s+(.*?)\s*$")
86
89
  HEADING_PERSONAL_RE = re.compile(r"自己|咱们|我们|你|您|跟我|试试|来看")
87
90
  FENCE_RE = re.compile(r"^\s*(```+|~~~+)\s*(\{[^}]*\}|[^\s`]*)?")
88
91
  LINK_RE = re.compile(r"\[[^\]]*\]\(([^)\s]+)\)")
92
+ LINK_LABEL_RE = re.compile(r"\[([^\]]*)\]\([^)]*\)")
89
93
  CJK_RE = re.compile(r"[\u3400-\u9fff]")
90
94
  TITLE_RE = re.compile(r"^title:\s*(.+?)\s*$", re.MULTILINE)
91
95
 
@@ -93,6 +97,10 @@ MAX_HEADING_LEVEL = 3
93
97
  MAX_PARAGRAPH_CHARS = 320
94
98
  MAX_SENTENCE_CHARS = 95
95
99
 
100
+ # 导语长度由项目在 checks.json 的 lead_chars 里声明,例如 [60, 140];没有声明
101
+ # 就不检查,避免把一个项目的版面预算带进另一本书。
102
+ LEAD_SKIP_RE = re.compile(r"^\s*(?:::|\||```|~~~|[-*+]\s|\d+\.\s|>|#)")
103
+
96
104
 
97
105
  @dataclass
98
106
  class Issue:
@@ -204,6 +212,8 @@ def check_heading(path: Path, line: int, level: int, text: str, issues: list[Iss
204
212
  issues.append(Issue(path, line, "标题带人称或口语劝诱;改写成对象名或「动词 + 具体宾语」"))
205
213
  if "`" in text:
206
214
  issues.append(Issue(path, line, "标题带行内代码;文件名留给正文,标题写对象"))
215
+ if level == 3 and re.search(r"[0-9一二三四五六七八九十百]+条", text):
216
+ issues.append(Issue(path, line, "三级标题在数条目;拆成实体标题或改成承载对象"))
207
217
 
208
218
 
209
219
  def check_prose_block(
@@ -228,13 +238,14 @@ def check_prose_block(
228
238
  f"一段里出现 {len(names)} 个函数名,还同时讲终端与测试;拆成列表,一项一件",
229
239
  ))
230
240
  if CJK_RE.search(compact):
231
- length = len(re.sub(r"\s+", "", compact))
241
+ visible = LINK_LABEL_RE.sub(r"\1", compact)
242
+ length = len(re.sub(r"\s+", "", visible))
232
243
  if length > MAX_PARAGRAPH_CHARS:
233
244
  issues.append(Issue(
234
245
  path, start_line,
235
246
  f"段落 {length} 字符,超过 {MAX_PARAGRAPH_CHARS};拆成两段或删枝蔓",
236
247
  ))
237
- for sentence in re.split(r"(?<=[。!?])", compact):
248
+ for sentence in re.split(r"(?<=[。!?])", visible):
238
249
  sentence = sentence.strip()
239
250
  if len(sentence) > MAX_SENTENCE_CHARS:
240
251
  issues.append(Issue(
@@ -490,6 +501,51 @@ def check_html_figure_block(path: Path, block: list[tuple[int, str]]) -> list[Is
490
501
  return issues
491
502
 
492
503
 
504
+ def visible_chars(text: str) -> int:
505
+ label = LINK_LABEL_RE.sub(r"\1", text)
506
+ return len(re.sub(r"\s+", "", label))
507
+
508
+
509
+ def check_lead(
510
+ path: Path, front: str, lines: list[str], issues: list[Issue], limits: list[int]
511
+ ) -> None:
512
+ """检查 title 与第一个标题之间的首段导语长度。
513
+
514
+ 着陆页(body-classes 里有 index-page 或 homepage)按卡片组织,不受限。
515
+ """
516
+ if "body-classes:" in front and ("index-page" in front or "homepage" in front):
517
+ return
518
+ first = len(lines)
519
+ for index, raw in enumerate(lines):
520
+ if HEADING_RE.match(raw):
521
+ first = index
522
+ break
523
+ paragraph: list[str] = []
524
+ start = 1
525
+ for index, raw in enumerate(lines[:first], start=1):
526
+ if not raw.strip() or LEAD_SKIP_RE.match(raw):
527
+ if paragraph:
528
+ break
529
+ continue
530
+ if not paragraph:
531
+ start = index
532
+ paragraph.append(raw.strip())
533
+ if not paragraph:
534
+ return
535
+ length = visible_chars(" ".join(paragraph))
536
+ low, high = limits[0], limits[1]
537
+ if length < low:
538
+ issues.append(Issue(
539
+ path, start,
540
+ f"导语 {length} 字符,短于 {low};补一句本章要解决什么",
541
+ ))
542
+ elif length > high:
543
+ issues.append(Issue(
544
+ path, start,
545
+ f"导语 {length} 字符,超过 {high};先删下一块已经摆出来的内容",
546
+ ))
547
+
548
+
493
549
  def check_file(path: Path, root: Path, project: dict, chapters: list[str]) -> list[Issue]:
494
550
  issues: list[Issue] = []
495
551
  text = read_text(path)
@@ -506,6 +562,9 @@ def check_file(path: Path, root: Path, project: dict, chapters: list[str]) -> li
506
562
  if leading:
507
563
  body = "\n" * leading.count("\n") + body
508
564
  lines = body.splitlines()
565
+ limits = project.get("lead_chars")
566
+ if isinstance(limits, list) and len(limits) == 2 and all(isinstance(v, int) for v in limits):
567
+ check_lead(path, front, lines, issues, limits)
509
568
  in_fence = False
510
569
  fence_lang = ""
511
570
  fence_start = 0
@@ -556,6 +615,11 @@ def check_file(path: Path, root: Path, project: dict, chapters: list[str]) -> li
556
615
  fence_blocks.append(fence_lines)
557
616
  if "mermaid" in fence_lang:
558
617
  mermaid_blocks.append(fence_lines)
618
+ if "aria-label=" not in fence_lang or "role=" not in fence_lang:
619
+ issues.append(Issue(
620
+ path, number,
621
+ 'mermaid 图缺少无障碍名称:围栏写 role="img" 与 aria-label="…"',
622
+ ))
559
623
  if fence_is_html:
560
624
  html_blocks.append(fence_lines)
561
625
  fence_lines = []
@@ -628,6 +692,42 @@ def check_file(path: Path, root: Path, project: dict, chapters: list[str]) -> li
628
692
  fenced_lines.add(number)
629
693
 
630
694
  blob_links: dict[str, list[int]] = {}
695
+ first_heading = next(
696
+ (n for n, raw in enumerate(lines, start=1) if HEADING_RE.match(raw)),
697
+ len(lines) + 1,
698
+ )
699
+ # 章首(第一个标题之前)只放自然段与列表:Callout 会把导语压成旁注,开篇套话则先讲
700
+ # 章节安排、不讲读者要做的事。着陆页是目录页,不受这条限制。
701
+ if path.name != "index.qmd":
702
+ opening_line = 0
703
+ opening_text = ""
704
+ for index, raw in enumerate(lines, start=1):
705
+ if index >= first_heading:
706
+ break
707
+ if index in fenced_lines:
708
+ continue
709
+ if CALLOUT_OPEN_RE.match(raw):
710
+ issues.append(Issue(
711
+ path, index,
712
+ "章首出现 Callout;第一个标题之前的导语只用自然段与列表(steps/02-structure.md)",
713
+ ))
714
+ continue
715
+ if not opening_text and raw.strip() and not raw.lstrip().startswith(("|", ">", "-", "*", "+")):
716
+ opening_line = index
717
+ opening_text = raw.strip()
718
+ if opening_text and OPENING_CLICHE_RE.match(opening_text):
719
+ issues.append(Issue(
720
+ path, opening_line,
721
+ "引言以「本章将…/下面介绍…」开场;第一句写读者要做成的事(steps/02-structure.md)",
722
+ ))
723
+ intro_blob_targets: set[str] = set()
724
+ for index, raw in enumerate(lines, start=1):
725
+ if index >= first_heading or index in fenced_lines:
726
+ continue
727
+ for match in LINK_RE.finditer(raw):
728
+ target = match.group(1).strip()
729
+ if target.startswith("https://github.com/") and "/blob/" in target:
730
+ intro_blob_targets.add(target)
631
731
 
632
732
  for index, raw in enumerate(lines, start=1):
633
733
  if index in fenced_lines:
@@ -645,19 +745,16 @@ def check_file(path: Path, root: Path, project: dict, chapters: list[str]) -> li
645
745
  issues.append(Issue(path, index, f"本地链接不可达:{target}"))
646
746
 
647
747
  for target, numbers in blob_links.items():
648
- if len(numbers) > 1:
748
+ if len(numbers) > 1 and target not in intro_blob_targets:
649
749
  issues.append(Issue(
650
750
  path, numbers[1],
651
- f"同章重复文件链接:{target}(首次在第 {numbers[0]} 行);每章每条仓库路径至多一条",
751
+ f"同章重复文件链接:{target}(首次在第 {numbers[0]} 行);"
752
+ "只有章引言挂过的本章主源文件可重复,其他仓库路径每章至多一条",
652
753
  ))
653
754
 
654
755
  # 章引言(第一个标题之前)只留承接上一章的一个章节链接;其余章节引用下沉到正文。
655
756
  # 着陆页 index.qmd 是目录页,不受这条预算约束。
656
757
  if path.name != "index.qmd":
657
- first_heading = next(
658
- (n for n, raw in enumerate(lines, start=1) if HEADING_RE.match(raw)),
659
- len(lines) + 1,
660
- )
661
758
  intro_links: list[tuple[int, str]] = []
662
759
  for index, raw in enumerate(lines, start=1):
663
760
  if index >= first_heading or index in fenced_lines:
@@ -685,6 +782,18 @@ def check_file(path: Path, root: Path, project: dict, chapters: list[str]) -> li
685
782
  issues.append(Issue(path, index, f"项目禁用词「{term}」"))
686
783
  break
687
784
 
785
+ later_names = project.get("later_chapter_names", {}).get(rel, [])
786
+ if later_names:
787
+ for index, raw in enumerate(lines, start=1):
788
+ if index in fenced_lines or raw.lstrip().startswith(("```", " ")):
789
+ continue
790
+ for name in later_names:
791
+ if name and re.search(rf"(?<![A-Za-z0-9_]){re.escape(name)}(?![A-Za-z0-9_])", raw):
792
+ issues.append(Issue(
793
+ path, index,
794
+ f"对象章点到后章类型「{name}」;删掉这句,留给后章",
795
+ ))
796
+
688
797
  for index, raw in enumerate(lines, start=1):
689
798
  if raw.lstrip().startswith(("```", " ")):
690
799
  continue
@@ -17,7 +17,7 @@
17
17
 
18
18
  **章际衔接(定结构检查项)**:第 N 章的引言、任务节与自测题只用第 1..N-1 章已建立的名字与概念;本章新术语在首次真正使用处就近落术语卡或行内括号;术语卡的升级判据见 `writing-quarto` 技能 `quarto/authoring.md`「Callout 升级判据」,写法见 `steps/03-blocks.md`。某概念接不上前章已建立的名字时,把它下沉回首次使用它的章建立,不在本章临时补课。
19
19
 
20
- - **无标题引言**:第一个 `##` 之前,写成**连贯读者文**回答读者三问——从哪来(承接上一章已成立的结果)、这一章做什么(本章要解决的问题与读者将获得的能力,正面陈述;范围能并则并,不写否定式后章预告)、代码在哪(可选:若本章有主源文件,用**同段末句**自然语言点名并挂全章 blob 链接,不另起「代码写在…里」独立段)。第一句写出谁在什么情况下做本章这件事,类章同时写出类名;类型怎么实现放到后面的句子。不另开背景节。**形态硬限(用户决定)**:首选**恰好 1 个自然段**(通常 2–4 句);必要时允许 **1 个自然段 + 1 个列表**(列表前引导句写在同一段末,或段落后紧跟列表;列表不算第二散文段);**禁止**出现两个散文自然段并立,更禁止 ≥3 段、单句孤儿段、结尾导航段。列表仅用于并列范围/对象,任务细节下沉正文 `##`,不用顿号串交付物冒充散文。**摘录免责句永不写进读者正文**:「本章摘录源码服务于讲解」「片段不保证与源文件逐字一致」「不等于文件内顺序」类作者旁白一律删除(旧「示例位置句开口子」已废止),同源保证交给 `verifying-content.py`;摘录免责只保留在 技能的权威文件与检查脚本里。不放 Callout,不写后续章节内容,不用「后面的 X」式前向指称引入后续章节才建立的结构件(如「实现放在后面的编号目录里」)。引言容纳多个独立概念时**并段或改列表**,禁止拆成两个散文段;拆分判据见 `steps/04-paragraphs.md`「拆分判据」。内容量硬限:token 预算见本文件「分层上限」;范围边界能并入承接段时不成列表。承接中引用其他章节一律用章节标题链接,不用「第 N 章」;**链接预算**:引言只挂承接上一章的那 1 个章节链接和本章主源文件的 blob 链接,其余章节链接下沉到正文真正用到它的句子,每处只挂当下需要的那个目标,同一段不堆多个章节链接(Google 技术写作指南把每个链接视为一次读者可能离开当前页的机会,链接越多,认知负担越重);本章回顾的下一章预告不写链接,直述预告一句(页面导航已有前进按钮),两者均见 `steps/06-wording.md`。任务概览默认并入承接句,正文小节就是任务的展开;预览列表仅当任务没有对应正文小节时使用(示例章四项工作列表已按此删除)。原 front matter `description` 的定位信息并入引言承接句,信息只保留引言一处、不同义两存;章节 qmd 不写 `description`(字段出处见 basics.md)。着陆页 `index` 按 landing 规则组织,不硬套本契约。
20
+ - **无标题引言**:第一个 `##` 之前,写成**连贯读者文**回答读者三问——从哪来(承接上一章已成立的结果)、这一章做什么(本章要解决的问题与读者将获得的能力,正面陈述;范围能并则并,不写否定式后章预告)、代码在哪(可选:若本章有主源文件,用**同段末句**自然语言点名并挂全章 blob 链接,不另起「代码写在…里」独立段)。第一句写出谁在什么情况下做本章这件事,类章同时写出类名;类型怎么实现放到后面的句子。不另开背景节。**形态硬限(用户决定)**:首选**恰好 1 个自然段**(通常 2–4 句);必要时允许 **1 个自然段 + 1 个列表**(列表前引导句写在同一段末,或段落后紧跟列表;列表不算第二散文段);**禁止**出现两个散文自然段并立,更禁止 ≥3 段、单句孤儿段、结尾导航段。列表仅用于并列范围/对象,任务细节下沉正文 `##`,不用顿号串交付物冒充散文。**摘录免责句永不写进读者正文**:「本章摘录源码服务于讲解」「片段不保证与源文件逐字一致」「不等于文件内顺序」类作者旁白一律删除(旧「示例位置句开口子」已废止),同源保证交给 `verifying-content.py`;摘录免责只保留在 技能的权威文件与检查脚本里。不放 Callout,不写后续章节内容,不用「后面的 X」式前向指称引入后续章节才建立的结构件(如「实现放在后面的编号目录里」)。引言容纳多个独立概念时**并段或改列表**,禁止拆成两个散文段;拆分判据见 `steps/04-paragraphs.md`「拆分判据」。陈述范围跟页面一样大:部分页只说这一部分的对局与代码,只有书首页可以写「这本书」;第一句写读者要做成的事,不用「本章将…」「下面介绍…」开场。内容量硬限:导语 60–140 个可见字符(链接只算标签文字,空白不计;超过先删下一块已经摆出来的内容,不足补本章要解决什么);项目可在 `.agents/skills/quarto-book-project/checks.json` 用 `"lead_chars": [60, 140]` 声明,`verifying-content.py` 按此检查,着陆页不受限;token 预算见本文件「分层上限」;范围边界能并入承接段时不成列表。承接中引用其他章节一律用章节标题链接,不用「第 N 章」;**链接预算**:引言只挂承接上一章的那 1 个章节链接和本章主源文件的 blob 链接,其余章节链接下沉到正文真正用到它的句子,每处只挂当下需要的那个目标,同一段不堆多个章节链接(Google 技术写作指南把每个链接视为一次读者可能离开当前页的机会,链接越多,认知负担越重);本章回顾的下一章预告不写链接,直述预告一句(页面导航已有前进按钮),两者均见 `steps/06-wording.md`。任务概览默认并入承接句,正文小节就是任务的展开;预览列表仅当任务没有对应正文小节时使用(示例章四项工作列表已按此删除)。原 front matter `description` 的定位信息并入引言承接句,信息只保留引言一处、不同义两存;章节 qmd 不写 `description`(字段出处见 basics.md)。着陆页 `index` 按 landing 规则组织,不硬套本契约。
21
21
  - **任务序列**:每节一个可验证结果。判定标准是任务结束时存在一个读者能自己复述的检查动作;只有「读完了解释」的内容并入相邻任务。概念 → 最小示例 → 操作 → 验证 → 边界,先让读者看到行为再解释规则;单次操作按「做什么 → 执行什么 → 看到什么 → 简短说明」推进。各章的任务节以该章标题里的对象为准,不把每一章都写成「目录、设计、构建、测试」四节。设计范围以这一章能在 `src/` 里核对的源文件收束。内容量硬限:任务级 `##` 通常不超过 4 个;`##` 与 `###` 开头的承接句数见本文件「分层上限」,写完直接进入代码、命令或列表;超出即为凑字数信号,禁令见 `steps/04-paragraphs.md`「凑字数禁令」。有 `###` 子节时,二级标题(`##`)的引言须承接上文;恰好 2 个子节时一句点一个,3 个或 4 个时不再逐个点名,点名交给三级标题的句式见本文件「标题分层」,点名用读者已认识的词,不点后文才定义的类型名;一句之内禁止把两个子节对象配成「由 X 定位,并保存 Y」「分别交给」式的两组对应(对照见 `examples/05-sentence-frame.md`「拉链句」)。交付清单或纯边界说明不单独设 `##`,并入引言或相邻工程总览节;同一头文件中连续的值类型优先按一个可验证结果归并到同一任务节。任务节内的讲述顺序按「类型 → 简单函数 → 更难的函数」排列,最难的组装放在最后,并满足先定义后调用:读者要跟着读懂的行内代码标识符,其行为必须已经出现过——上一章、上文,或同一小节里紧挨着的声明和一句行为——上一章已建立的名字视为已知;未定义名字的调用链、节首概览与编号步骤整段删除。`##` 导语只用白话,不点后文才定义的函数名。章内顺序的依据见 `SKILL.md`「九步」的定结构一条,本文件不复述。
22
22
  - **常见错误**:服务失败后回来的读者。每个症状一个 `###`,标题后第一句写触发条件和可观察现象。误用型条目一段成文写原因与正确做法;原因与做法各需一句完整交代时(如原因牵涉私有段的设计动机),用症状句导语加「出错的原因/正确的做法」引导词列表,列表可作为节导语的合法收尾,导语预算只约束症状句;排查型故障展开定位 → 修复 → 验证三步。形态分野与细则是 `writing-quarto` 技能 `quarto/terminal.md`「常见错误条目形态与三步诊断流」。入门章节只保留最可能遇到的 2-3 项,不是完整 FAQ。
23
23
  - **自测问题**:服务刚跑通主线、需要确认理解的读者。每章 0-7 道题,题目问本章已经写明的一件可观察结果(会怎样、是哪一个、从几开始),没有这种结果就不设题;只有本章写了原因、而且情景已经说全时,才用「为什么」。题面用本章已经用过的词说全,不用本章未定义的概念,不问「为什么不做成另一种」;题干是合上书的一问,只看本题题干、不看答案与上一题;题干代词的判法见 `steps/05-sentences.md`「代词不能代替未写出的对象」,允许「什么」——它在要一个名字或一句文字,不是指上文没出现的东西;题面里每个细节都要改变答案,答案不受细节影响时不单点某一种情形。题面只描述一个自洽局面,每个事实都要能同时成立,禁止把互相矛盾的局面写成题干(同一句里先说轮到甲方、又说这一步的用户是 O,读者听到的是程序自相矛盾,不是一次非法调用)——「轮到谁」和「这一步的用户」若是同一个对象,只写一次。要考拒绝,用正文已经用过的那条规则问可观察结果(正例:「空状态面板开局,轮到甲方。没轮到的一方来操作时,状态面板会改变吗?」,答案首句「不会。操作的用户不是当前用户,`Place` 返回 `kRejected`,状态面板和轮次都保持原样。」);不为了出题制造正文没走过的矛盾局面。一道题只问一件可观察结果,不把两个观察捆进同一题(如「状态面板是空的吗」与「轮到谁」并列),拆开或删去一个。固定导语「不要展开答案,试着自己把回答完整说出来,写完之后展开对照;说不出来的部分,回到对应小节对照正文。」;答案写在 `::: {.answer}` 围栏 div 内,由书内 `filters/answering-disclosure.lua` 渲染为默认收起的原生 `<details>`;答案只使用本章已讲过的概念。题目同样必须能由本章正文回答:答案依据须是正文已写明的内容,正文没讲的概念不设题;若问题值得问,先在正文补上依据再设题。答案先答所问:首句直接回答题面,一到两句,不补题面之外的对比、延伸或重复题面(对照与归属由正文承载)。问答中的文件、路径与标识符指称统一用行内代码,不挂链接(`verifying-content.py` 对自测节链接记 WARN);所属目录在答案前文已确立时,路径可省去重复的顶层前缀(如先写 `example/`,后文写 `include/state.h`)。
@@ -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
  读者正文默认先总后分;段首先给这一段的整体,后面的句子或列表再展开部分。列表前的那一句就是“总”,列表项是“分”。分总只留一种情况:紧挨在前的代码块或图已经把部分摆在读者眼前,下一句只给这些部分一个总名。
@@ -129,6 +129,8 @@
129
129
  - 读者正文(`content/**/*.qmd`、根 `README.md`、`index.qmd` 的围栏外正文)不用「先…再…」「先…然后…」「先…最后…」串动作或阅读顺序。每一步写成独立陈述,主语是对象或读者要做的事。代码块导语直接点名对象做什么。`.agents` 的操作步骤、引号和围栏里的反例不套这条。
130
130
  - 读者正文点名类时直接写反引号里的类名,例如 `Game`、`Board`、`Ai`。类名前面不加「类」。
131
131
 
132
+ - 点名后章类型再说明「这件事不在这里」也删除:类型名已经越界,否定句不豁免;不要改写成另一句否定句。只保留本章对象与本章要读者做的判断。
133
+
132
134
  ### 简洁边界
133
135
 
134
136
  简洁的上界是不丢失理解所需的信息。为省字而砍掉动作、对象或顺序的电报式省略句一律禁止:
@@ -40,6 +40,8 @@
40
40
  - **不用列表**:后句用前句得数的乘加不是并列要点。列表仍只用于没有先后的并列,或配置、编译这种各自能做完的步骤。禁止「公式项:一句职责」。
41
41
  - **块位**:定义就是围栏里与源码相同的 `return` 行。`###` 导语仍是一句,点名函数和 `cells_` 的数组下标,并点明实现文件。原理段不重复 `static_cast`。图前一句点名得数是哪一个数组下标,不把图里已标的区间再念一遍。正文不另排 `$$` 或行内数学模式。
42
42
 
43
+ - **两项互斥也用列表**:两项是互斥结果(有三连/没有三连、接受/拒绝)而不是同一对象的两个属性时,不并成一句,改无序列表「情形:结果」,列表后停止。这是「只有两项且能写进一句」的窄例外。
44
+
43
45
  ### 加粗使用
44
46
 
45
47
  - 加粗只用于列表项或段首的引导词(中心语前置),标记结构,不强调情绪。
@@ -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
  - **章**:标题到下一个 `##` 之前。只做定任务、定结构。
@@ -45,6 +45,8 @@
45
45
 
46
46
  不手跑 `quarto render`,也不手跑 `checking-output.py`、`checking-callouts.py`、`checking-dom.py`。工具已经按当前书配置调用它们。
47
47
 
48
+ 渲染之外还有一层可访问性核对:书配置的 debug 项里可打开 `axe: true`,Quarto 会在浏览器里加载 axe-core 报告 `heading-order`、`svg-img-alt` 这类问题。报告只作提示,改法是给图补 `aria-label`、补齐跳过的标题层级,不把行为项当作新步骤。
49
+
48
50
  ### 执行回执
49
51
 
50
52
  检查结论确定后,调用一次 `quarto_pass_report`,把这次执行的结论压成一张卡片: