@chengzhao-dev/dsh-quarto-book 0.6.17 → 0.6.18
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 +1 -1
- package/README.md +1 -1
- package/lib/tools/pass-report.js +1 -1
- package/lib/tools/render-check.js +1 -1
- package/package.json +2 -2
- package/skills/designing-theme/SKILL.md +1 -1
- package/skills/writing-quarto/SKILL.md +7 -7
- package/skills/writing-quarto/examples/03-excerpt-lead.md +19 -0
- package/skills/writing-quarto/examples/04-paragraph-cases.md +17 -13
- package/skills/writing-quarto/quarto/comments.md +1 -0
- package/skills/writing-quarto/quarto/prose.md +1 -0
- package/skills/writing-quarto/quarto/terminal.md +2 -0
- package/skills/writing-quarto/references/fence-semantics.md +6 -6
- package/skills/writing-quarto/references/personas.md +15 -0
- package/skills/writing-quarto/scripts/testing-content.py +83 -2
- package/skills/writing-quarto/scripts/verifying-content.py +166 -1
- package/skills/writing-quarto/steps/02-structure.md +4 -3
- package/skills/writing-quarto/steps/03-blocks.md +11 -5
- package/skills/writing-quarto/steps/06-wording.md +11 -9
- package/skills/writing-quarto/steps/07-layout.md +1 -0
- package/skills/writing-quarto/steps/08-reread.md +4 -4
- package/skills/writing-quarto/steps/09-check.md +1 -0
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.
|
|
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.18`; to follow a GitHub tag, write `github:chengzhao-dev/dsh-quarto-book#v0.6.18`.
|
|
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.
|
|
32
|
+
`--profile` 换成你实际运行的那个 profile。桌面应用固定跑 `desktop` profile,且该 profile 由应用独占管理,`dsh --profile desktop` 直接启动会被拒绝;安装或升级用 `dsh plugin --profile desktop add <包>`,应用拒绝写入时再走「设置 → 插件 → 添加插件」。需要固定版本时写 `npm:@chengzhao-dev/dsh-quarto-book@0.6.18`,跟 GitHub 标签时写 `github:chengzhao-dev/dsh-quarto-book#v0.6.18`。
|
|
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/lib/tools/pass-report.js
CHANGED
|
@@ -247,7 +247,7 @@ const RESULT_SCHEMA = {
|
|
|
247
247
|
export function registerPassReportTool(tools) {
|
|
248
248
|
tools.register({
|
|
249
249
|
name: 'quarto_pass_report',
|
|
250
|
-
description: '
|
|
250
|
+
description: '把一次写作或主题润色的改动压成一张回执卡,卡片即回执,不写文件。',
|
|
251
251
|
parameters: {
|
|
252
252
|
type: 'object',
|
|
253
253
|
additionalProperties: false,
|
|
@@ -744,7 +744,7 @@ function refusedResult(warning) {
|
|
|
744
744
|
export function registerRenderCheckTool(tools) {
|
|
745
745
|
tools.register({
|
|
746
746
|
name: 'quarto_render_check',
|
|
747
|
-
description: '
|
|
747
|
+
description: '检查 Quarto Book 的渲染输出目录、最多运行一次 quarto render 并返回 HTML 可用性报告,非书项目直接拒绝。',
|
|
748
748
|
parameters: {
|
|
749
749
|
type: 'object',
|
|
750
750
|
additionalProperties: false,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chengzhao-dev/dsh-quarto-book",
|
|
3
|
-
"version": "0.6.
|
|
4
|
-
"description": "把一份 Quarto Book 写成可读、可查、可发布的中文技术书:九步章节写作、结构/链接/图表检查、统一主题与渲染验收。
|
|
3
|
+
"version": "0.6.18",
|
|
4
|
+
"description": "把一份 Quarto Book 写成可读、可查、可发布的中文技术书:九步章节写作、结构/链接/图表检查、统一主题与渲染验收。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: writing-quarto
|
|
3
|
-
description:
|
|
3
|
+
description: 写、改、检查与润色 Quarto Book 章节正文时使用;不用于改主题样式与只改 .cpp。
|
|
4
4
|
metadata:
|
|
5
5
|
short-description: 润色中文 Quarto 图书章节
|
|
6
6
|
---
|
|
@@ -13,21 +13,21 @@ metadata:
|
|
|
13
13
|
|
|
14
14
|
- 新建或修改 `content/**/*.qmd` 章节、`index.qmd` 或 `_quarto.yml`;内容与链接核对走本技能 `scripts/` 下的脚本,HTML 是否可用走 `quarto_render_check` 工具。
|
|
15
15
|
- **不适用**:改主题样式、颜色或字体(转 `designing-theme`);讲解书里项目的业务领域知识(那是项目正文的职责,不是本技能)。
|
|
16
|
-
- 项目带有 `.agents/skills/quarto-book-project/SKILL.md` 时,读完本技能再读它,并只按它指向的 `writing/`
|
|
17
|
-
- 书仓库根目录的 `README.md` 只走第 5–8 步:它是仓库说明页,不是章节;不套第 1–4 步的任务与结构骨架,不写「常见错误」「自测问题」,不调 `quarto_render_check`。第 8 步发现问题只退回第 5、6、7 步。`README.en.md` 同样处理;`CHANGELOG.md`、`LICENSE`、`AGENTS.md`
|
|
16
|
+
- 项目带有 `.agents/skills/quarto-book-project/SKILL.md` 时,读完本技能再读它,并只按它指向的 `writing/` 文件读章节对照;没有就跳过,不要猜书名、章节名和术语。书内 `context/` 旧副本不再打开,写法以本技能为准。
|
|
17
|
+
- 书仓库根目录的 `README.md` 只走第 5–8 步:它是仓库说明页,不是章节;不套第 1–4 步的任务与结构骨架,不写「常见错误」「自测问题」,不调 `quarto_render_check`。第 8 步发现问题只退回第 5、6、7 步。`README.en.md` 同样处理;`CHANGELOG.md`、`LICENSE`、`AGENTS.md` 不在写作范围。这条只在书仓库里适用。
|
|
18
18
|
|
|
19
19
|
## 加载与结束
|
|
20
20
|
|
|
21
|
-
- 一次激活只做一类文件:章节或主题。写章节时只打开项目技能或 `_quarto.yml`
|
|
21
|
+
- 一次激活只做一类文件:章节或主题。写章节时只打开项目技能或 `_quarto.yml` 点名的章节;围栏注释与源文件不一致时回源改围栏,不读无关实现。句子与条文、项目技能或既有正文冲突时,先列冲突位置、原句与出处,由用户选一条;不自行取舍,也不默认采用计划。
|
|
22
22
|
- 项目提供作用域命令就先跑它,只读它列出的文件;渲染产物、构建目录与 Quarto 缓存不读。
|
|
23
|
-
-
|
|
23
|
+
- 增量任务只开本文件、当前一步与这一步点名的一个文件;第 8 步可点名 `references/personas.md` 与 `examples/08-appendix.md` 两个,其余步骤仍只一个;同轮不把同号案例、其余步骤和条文一起打开。
|
|
24
24
|
- 同一文件最多退回上一步一次;第二次仍失败就停在检查输出上,不重走整段。
|
|
25
|
-
- 项目技能列出检查命令时只跑那一条;没有就跑本技能 `scripts/` 下的内容与链接检查。退出码为 0 就在同一轮结束 goal
|
|
25
|
+
- 项目技能列出检查命令时只跑那一条;没有就跑本技能 `scripts/` 下的内容与链接检查。退出码为 0 就在同一轮结束 goal。
|
|
26
26
|
- 不探测同 profile 的其他插件,也不把它们写成依赖;子会话按自己的 cwd 重新判定门禁,不把技能正文塞进子代理提示。
|
|
27
27
|
|
|
28
28
|
## 整书走法
|
|
29
29
|
|
|
30
|
-
改多章或排目录时,先读 `references/book-journey.md
|
|
30
|
+
改多章或排目录时,先读 `references/book-journey.md`:先定全书主线与章节次序,再逐章走九步,最后做全书一致性回读。
|
|
31
31
|
|
|
32
32
|
单章与整书共用同一套九步,不另造一套章节规则;项目有章节表时以项目表为准。技能文件体量受 `references/token-budget.md` 约束,新增整书规则只写进 `book-journey.md`。
|
|
33
33
|
|
|
@@ -174,3 +174,22 @@ constexpr bool IsInRange(Position position) {
|
|
|
174
174
|
```
|
|
175
175
|
|
|
176
176
|
判定信号:章引言(第一个 `##` 之前)出现两个及以上 `.qmd` 链接;除承接上一章的那一个之外,其余都在正文有对应的「首次真正用到」的句子可以迁入。`verifying-content.py` 对引言多余章节链接直接报错。
|
|
177
|
+
|
|
178
|
+
## 声明注释摘一半
|
|
179
|
+
|
|
180
|
+
围栏只留概述行,把 `@param`、`@return` 与空 `///` 行删掉。
|
|
181
|
+
|
|
182
|
+
```text
|
|
183
|
+
不佳(围栏只剩第一行):
|
|
184
|
+
/// 检查两个坐标是否相同。
|
|
185
|
+
[[nodiscard]] constexpr bool operator==(CellPosition lhs, CellPosition rhs) noexcept {
|
|
186
|
+
|
|
187
|
+
改写(整段照抄):
|
|
188
|
+
/// 检查两个坐标是否相同。
|
|
189
|
+
///
|
|
190
|
+
/// @param lhs 左操作数。
|
|
191
|
+
/// @return 相同返回 true。
|
|
192
|
+
[[nodiscard]] constexpr bool operator==(CellPosition lhs, CellPosition rhs) noexcept {
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
围栏注释是源文件的前缀。条文见 `steps/03-blocks.md`「摘录与源文件注释」。
|
|
@@ -148,19 +148,10 @@
|
|
|
148
148
|
|
|
149
149
|
## 接口、调用场合与实现细节的次序
|
|
150
150
|
|
|
151
|
-
|
|
151
|
+
以示例章的只读接口节为对照。声明围栏里给了 `Get`、`IsEmpty`、`IsFull` 三个查询,接着却把 `Get` 的实现拆到下一个 `###`;读者刚看完三个接口的返回与边界,又被带回越界细节。
|
|
152
152
|
|
|
153
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 的越界处理):
|
|
154
|
+
不佳(声明与实现被小节拆开,标题盖住同类查询):
|
|
164
155
|
`Get`、`IsEmpty`、`IsFull` 都只读面板,区别在返回什么、越界怎么处理:
|
|
165
156
|
| 接口 | 返回 | 边界 |
|
|
166
157
|
...
|
|
@@ -170,11 +161,24 @@
|
|
|
170
161
|
- `IsFull` 用来判断是否满盘。
|
|
171
162
|
|
|
172
163
|
### Get 的越界处理
|
|
173
|
-
|
|
174
164
|
`Get` 越界时抛 `std::out_of_range`。相关异常如下:
|
|
175
165
|
```
|
|
176
166
|
|
|
177
|
-
|
|
167
|
+
```text
|
|
168
|
+
改写(Get 的声明与实现同节,另外两个查询单独成节):
|
|
169
|
+
`IsEmpty` 与 `IsFull` 都只读面板,区别在返回什么、边界怎么处理:
|
|
170
|
+
| 接口 | 返回 | 边界 |
|
|
171
|
+
| `IsEmpty` | 空面板格子为 `true` | 越界或已有棋子时返回 `false` |
|
|
172
|
+
| `IsFull` | 九个格子都有棋子时为 `true` | 不判断谁获胜 |
|
|
173
|
+
调用方按场合选择:
|
|
174
|
+
- 判断单个格子是否为空时调用 `IsEmpty`。
|
|
175
|
+
- `IsFull` 用来判断是否满盘。
|
|
176
|
+
|
|
177
|
+
### Get 的读取与越界
|
|
178
|
+
`Get` 合法坐标返回格子类型,越界时抛 `std::out_of_range`。声明和实现都放在这一节。
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
判定信号:同一段先用函数名、再用「读格子/判空格」一套中文动作;声明围栏在一个 `###`、实现却被拆到另一个 `###`;标题用「只读接口」盖住同章其他只读函数。处理:函数名作名词,返回、边界、调用场合按查询分组;一个函数的声明和实现留在同一个 `###`;`###` 点名这一节实际讲解的操作或对象,不用总称;通用异常组织法删掉。
|
|
178
182
|
|
|
179
183
|
## GetWinner 的三段删前样本
|
|
180
184
|
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
- 相邻两句共享主语或动作时并成一句,用逗号或分号连接;主语或动作换了才保留句号。
|
|
35
35
|
- 列举三项以上用顿号;列举项本身含逗号或分句时,各项之间用分号,末项用「和」或句号。
|
|
36
36
|
- 冒号前必须是总说或提示语;冒号后的内容离开前文能读成完整一句。
|
|
37
|
+
- 前半是一个动作、后半说明这个动作失败时怎样,用全角冒号引出说明,不用分号把说明当成并列分句。
|
|
37
38
|
- 同一列表的末项标点统一:都用句号,或都不带句号。
|
|
38
39
|
|
|
39
40
|
反例:`Board` 只保存九个棋盘格子。落子、满盘、成线与赢家都从这些格子得出。写入前先检查坐标。声明在工程目录的 board.h。实现放在 board.cpp。
|
|
@@ -54,6 +54,8 @@
|
|
|
54
54
|
|
|
55
55
|
终端画面、构建日志和机器协议分开展示,不把它们混成一个成功判据。终局示例先交代触发场景,再展示真实输出;代码块后的正文只解释读者需要观察的稳定部分。机器协议示例要说明消费对象、稳定字段及其与人类文本的关系,不能用“输出类似如下”代替实际验收依据。
|
|
56
56
|
|
|
57
|
+
章节里的可交互演示只保留渲染后页面上能操作的 HTML,不嵌终端转录,也不解释当前构建还不会打印;终端的实测输出只写在专门讲终端的那一章。
|
|
58
|
+
|
|
57
59
|
## 成功判据三件套
|
|
58
60
|
|
|
59
61
|
输出不稳定或篇幅过长的命令,不展示完整输出,改给可核对的判据。三条按可得性写,不必凑齐:
|
|
@@ -7,16 +7,16 @@
|
|
|
7
7
|
| 素材 | 围栏里是什么 | 正文可以讲什么 |
|
|
8
8
|
| --- | --- | --- |
|
|
9
9
|
| 声明围栏 | `.h` 里的签名、成员、类定义 | 契约:参数、返回值、异常、边界、副作用、与其它接口的分工 |
|
|
10
|
-
| 定义围栏 | `.cpp`(或头文件里的函数体)里的实现 |
|
|
10
|
+
| 定义围栏 | `.cpp`(或头文件里的函数体)里的实现 | 机制:用了哪个函数、什么表达式、是否复制、按什么次序 |
|
|
11
11
|
|
|
12
|
-
判据只有一句:**删掉定义文件,这句话还成立吗?**
|
|
12
|
+
判据只有一句:**删掉定义文件,这句话还成立吗?** 成立是契约,不成立是实现。正文不要求比较没采用的写法。
|
|
13
13
|
|
|
14
14
|
## 三种合法配对
|
|
15
15
|
|
|
16
16
|
| 配对 | 围栏 | 正文 | 用在 |
|
|
17
17
|
| --- | --- | --- | --- |
|
|
18
18
|
| 契约型 | 声明 | 契约文字 | 定义平凡:单行 `return`、一跳转发、两行赋值 |
|
|
19
|
-
| 机制型 | 声明 + 定义 | 实现文字 |
|
|
19
|
+
| 机制型 | 声明 + 定义 | 实现文字 | 定义非平凡,需要讲分支、算法或步骤 |
|
|
20
20
|
| 纯定义型 | 定义 | 实现文字 | 定义就在头文件(`constexpr`、模板),或声明已在本章前文出现过 |
|
|
21
21
|
|
|
22
22
|
补充一条**组合转发例外**:同一小节里几个一跳转发的定义可以合并成一个短围栏,正文讲它们如何组合;合并后仍遵守「正文只讲围栏里看得到的代码」。
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
## 平凡定义的判定
|
|
33
33
|
|
|
34
34
|
- 平凡:单次比较、单个 `return`、两行赋值、纯委托(`return Other();`)、只做一次边界检查后返回。
|
|
35
|
-
-
|
|
35
|
+
- 不平凡:含循环或分支组合、更新状态、与别的类型协作、需要讲清做了什么、按什么次序。
|
|
36
36
|
|
|
37
|
-
平凡判例:`IsInRange`
|
|
37
|
+
平凡判例:`IsInRange` 的边界比较、`IsFull` 的 `none_of` 一行。
|
|
38
38
|
|
|
39
39
|
## 正反例(棋盘章的真实片段)
|
|
40
40
|
|
|
@@ -71,5 +71,5 @@ GameResult Game::JudgeAfterPlace(const Board& board, Player player) {
|
|
|
71
71
|
## 与其它规则的关系
|
|
72
72
|
|
|
73
73
|
- **代码块门槛**(`steps/03-blocks.md`):平凡实现不出围栏;本文件给的是「已经决定要讲」时该怎么配围栏。
|
|
74
|
-
- **正文与注释分工**:契约与边界写进
|
|
74
|
+
- **正文与注释分工**:契约与边界写进 `///`,正文不转述;实现文字只补围栏里没有的上下文(怎么组合、和谁配合)。
|
|
75
75
|
- **检查器**:`scripts/verifying-content.py` 按 `###` 小节扫描,只有 `.h` 围栏而正文出现实现词时报 `decl-without-def`;「常见错误」「自测问题」「本章回顾」与表格行豁免。
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# 回读人格
|
|
2
|
+
|
|
3
|
+
第 8 步回读按章型选一副眼光;规则仍由各步骤与条文负责,这里只指向。
|
|
4
|
+
|
|
5
|
+
## C++ 文档
|
|
6
|
+
|
|
7
|
+
本书主专业。围栏与源码一致,不把章节写成语言课,函数声明和实现留在同一个 `###`。块后写表达式做了什么,不写作者动机。细则见 `steps/02-structure.md`、`steps/06-wording.md`、`references/fence-semantics.md`。
|
|
8
|
+
|
|
9
|
+
## 中文技术写作
|
|
10
|
+
|
|
11
|
+
一段说完一件事。禁止「意图很直接」「这样写是为了」「不用猜」这类向读者解释作者为什么没选另一种写法的句子。已有禁令以 `steps/06-wording.md` 为准。
|
|
12
|
+
|
|
13
|
+
## 路由
|
|
14
|
+
|
|
15
|
+
`03-board`、`04-game`、`05-ai` 用「C++ 文档」,其余章用「中文技术写作」。
|
|
@@ -122,7 +122,7 @@ title: 入门
|
|
|
122
122
|
下面是一张只画类型和边的类图,读者可以对照箭头理解类型之间的依赖。
|
|
123
123
|
|
|
124
124
|
::: {.class-diagram}
|
|
125
|
-
```{mermaid}
|
|
125
|
+
```{mermaid role="img" aria-label="类型 A 依赖类型 B"}
|
|
126
126
|
%%| fig-cap: "类型 A 依赖类型 B。实线箭头从使用者指向被使用者。"
|
|
127
127
|
%%{init: {"fontFamily": "LXGW Bright Medium, system-ui, sans-serif", "class": {"hideEmptyMembersBox": true}}}%%
|
|
128
128
|
classDiagram
|
|
@@ -178,12 +178,17 @@ def main() -> int:
|
|
|
178
178
|
needle: str,
|
|
179
179
|
project: bool = False,
|
|
180
180
|
checks_json: str | None = None,
|
|
181
|
+
files: dict[str, str] | None = None,
|
|
181
182
|
) -> None:
|
|
182
183
|
nonlocal passed_cases
|
|
183
184
|
passed_cases += 1
|
|
184
185
|
with tempfile.TemporaryDirectory() as raw:
|
|
185
186
|
root = Path(raw)
|
|
186
187
|
make_book(root, chapter)
|
|
188
|
+
for relative, content in (files or {}).items():
|
|
189
|
+
target = root / relative
|
|
190
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
191
|
+
target.write_text(content, encoding="utf-8")
|
|
187
192
|
if project:
|
|
188
193
|
skill_dir = root / ".agents" / "skills" / "quarto-book-project"
|
|
189
194
|
skill_dir.mkdir(parents=True)
|
|
@@ -293,6 +298,18 @@ def main() -> int:
|
|
|
293
298
|
"---\ntitle: 入门\n---\n\n## 目标\n\n```mermaid\ngraph LR\n```\n",
|
|
294
299
|
1, "plain mermaid",
|
|
295
300
|
)
|
|
301
|
+
expect(
|
|
302
|
+
"mermaid 围栏带前导点",
|
|
303
|
+
"---\ntitle: 入门\n---\n\n## 目标\n\n"
|
|
304
|
+
'```{.mermaid role="img" aria-label="流程"}\ngraph LR\n```\n',
|
|
305
|
+
1, "不会载入图表脚本",
|
|
306
|
+
)
|
|
307
|
+
expect(
|
|
308
|
+
"mermaid 围栏正确写法",
|
|
309
|
+
"---\ntitle: 入门\n---\n\n## 目标\n\n"
|
|
310
|
+
'```{mermaid role="img" aria-label="流程"}\ngraph LR\n```\n',
|
|
311
|
+
0, "OK",
|
|
312
|
+
)
|
|
296
313
|
expect(
|
|
297
314
|
"未声明围栏",
|
|
298
315
|
"---\ntitle: 入门\n---\n\n## 目标\n\n```\nprint(1)\n```\n",
|
|
@@ -316,6 +333,67 @@ def main() -> int:
|
|
|
316
333
|
0, "OK",
|
|
317
334
|
)
|
|
318
335
|
|
|
336
|
+
excerpt_url = "https://github.com/your-org/your-book/blob/main/games/board.h"
|
|
337
|
+
excerpt_source = (
|
|
338
|
+
"/// 判断两个坐标是否相同。\n"
|
|
339
|
+
"///\n"
|
|
340
|
+
"/// @param lhs 左操作数。\n"
|
|
341
|
+
"/// @return 相同返回 true。\n"
|
|
342
|
+
"bool Same(int lhs, int rhs);\n"
|
|
343
|
+
)
|
|
344
|
+
excerpt_chapter = VALID_CHAPTER + (
|
|
345
|
+
f"\n声明见 [board.h]({excerpt_url})。\n\n"
|
|
346
|
+
)
|
|
347
|
+
expect(
|
|
348
|
+
"正文讲解文件头",
|
|
349
|
+
VALID_CHAPTER + "\n每个源文件开头都有一段 Doxygen 文件头。\n",
|
|
350
|
+
1, "文件级",
|
|
351
|
+
)
|
|
352
|
+
expect(
|
|
353
|
+
"围栏摘录 @file",
|
|
354
|
+
excerpt_chapter
|
|
355
|
+
+ "```{.cpp filename=\"board.h\"}\n/// @file board.h\n/// @brief 棋盘。\n```\n",
|
|
356
|
+
1, "文件级",
|
|
357
|
+
)
|
|
358
|
+
expect(
|
|
359
|
+
"围栏注释截断",
|
|
360
|
+
excerpt_chapter
|
|
361
|
+
+ "```{.cpp filename=\"board.h\"}\n"
|
|
362
|
+
"/// 判断两个坐标是否相同。\n"
|
|
363
|
+
"bool Same(int lhs, int rhs);\n"
|
|
364
|
+
"```\n",
|
|
365
|
+
1, "只摘了",
|
|
366
|
+
files={"games/board.h": excerpt_source},
|
|
367
|
+
)
|
|
368
|
+
expect(
|
|
369
|
+
"围栏注释整段一致",
|
|
370
|
+
excerpt_chapter
|
|
371
|
+
+ "```{.cpp filename=\"board.h\"}\n"
|
|
372
|
+
+ excerpt_source
|
|
373
|
+
+ "```\n",
|
|
374
|
+
0, "OK",
|
|
375
|
+
files={"games/board.h": excerpt_source},
|
|
376
|
+
)
|
|
377
|
+
expect(
|
|
378
|
+
"围栏整段不摘注释",
|
|
379
|
+
excerpt_chapter
|
|
380
|
+
+ "```{.cpp filename=\"board.h\"}\n"
|
|
381
|
+
"bool Same(int lhs, int rhs);\n"
|
|
382
|
+
"```\n",
|
|
383
|
+
0, "OK",
|
|
384
|
+
files={"games/board.h": excerpt_source},
|
|
385
|
+
)
|
|
386
|
+
expect(
|
|
387
|
+
"输出围栏不比对注释",
|
|
388
|
+
VALID_CHAPTER
|
|
389
|
+
+ "\n```text filename=\"运行效果\"\n"
|
|
390
|
+
"/// 判断两个坐标是否相同。\n"
|
|
391
|
+
"bool Same(int lhs, int rhs);\n"
|
|
392
|
+
"```\n",
|
|
393
|
+
0, "OK",
|
|
394
|
+
files={"games/board.h": excerpt_source},
|
|
395
|
+
)
|
|
396
|
+
|
|
319
397
|
expect("目录树合格", TREE_CHAPTER, 0, "OK")
|
|
320
398
|
expect("原生 SVG 缺图注", HTML_FIGURE_BAD, 1, "figcaption")
|
|
321
399
|
expect("原生 SVG 图注合格", HTML_FIGURE_GOOD, 0, "OK")
|
|
@@ -379,7 +457,10 @@ def main() -> int:
|
|
|
379
457
|
expect("类图图注写在 init 之后", wrong_order, 1, "init 之前")
|
|
380
458
|
expect(
|
|
381
459
|
"类图缺少页面容器",
|
|
382
|
-
MERMAID_CHAPTER.replace(
|
|
460
|
+
MERMAID_CHAPTER.replace(
|
|
461
|
+
'::: {.class-diagram}\n```{mermaid role="img" aria-label="类型 A 依赖类型 B"}',
|
|
462
|
+
'```{mermaid role="img" aria-label="类型 A 依赖类型 B"}',
|
|
463
|
+
).replace(
|
|
383
464
|
"```\n:::", "```"
|
|
384
465
|
),
|
|
385
466
|
1, ".class-diagram",
|
|
@@ -78,6 +78,12 @@ TEST_RE = re.compile(r"测试|断言|用例|gtest|CTest")
|
|
|
78
78
|
# 围栏与正文的配对:声明围栏只配契约文字。正文出现实现词而小节里没有定义围栏时
|
|
79
79
|
# 报 decl-without-def,规则与正反例见 references/fence-semantics.md。
|
|
80
80
|
FENCE_FILENAME_RE = re.compile(r'filename="([^"]+)"')
|
|
81
|
+
# 读者正文不摘文件级 Doxygen 头;成员注释的 @param、@return 不受这条限制。
|
|
82
|
+
EXCERPT_HEADER_RE = re.compile(r"@file\b|@brief\b|Doxygen\s*文件头")
|
|
83
|
+
REF_DEFINITION_RE = re.compile(r"^\s*\[([^\]]+)\]:\s*(\S+)\s*$")
|
|
84
|
+
REF_USE_RE = re.compile(r"\]\[([^\]]+)\]")
|
|
85
|
+
BLOB_URL_RE = re.compile(r"^https://github\.com/[^/]+/[^/]+/blob/[^/]+/(.+)$")
|
|
86
|
+
DOC_COMMENT_RE = re.compile(r"^\s*///")
|
|
81
87
|
IMPL_MARKER_RE = re.compile(r"复用|委托给|扫描|遍历|对每个|短路|表达式|状态位|缓存|不再单独|递归")
|
|
82
88
|
FENCE_SKIP_SECTION_RE = re.compile(r"常见错误|自测|回顾|练习|小结")
|
|
83
89
|
DECL_SUFFIXES = (".h", ".hpp", ".hh", ".hxx")
|
|
@@ -653,6 +659,159 @@ def check_fence_semantics(path: Path, lines: list[str]) -> list[Issue]:
|
|
|
653
659
|
return issues
|
|
654
660
|
|
|
655
661
|
|
|
662
|
+
def normalize_code(text: str) -> str:
|
|
663
|
+
return re.sub(r"\s+", " ", text).strip()
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
def collect_excerpt_links(lines: list[str]) -> list[tuple[int, str]]:
|
|
667
|
+
"""Return GitHub blob links in reading order, including reference-style uses."""
|
|
668
|
+
definitions: dict[str, str] = {}
|
|
669
|
+
for raw in lines:
|
|
670
|
+
definition = REF_DEFINITION_RE.match(raw)
|
|
671
|
+
if definition:
|
|
672
|
+
definitions.setdefault(definition.group(1), definition.group(2))
|
|
673
|
+
links: list[tuple[int, str]] = []
|
|
674
|
+
for number, raw in enumerate(lines, start=1):
|
|
675
|
+
for match in LINK_RE.finditer(raw):
|
|
676
|
+
links.append((number, match.group(1).strip()))
|
|
677
|
+
for match in REF_USE_RE.finditer(raw):
|
|
678
|
+
target = definitions.get(match.group(1))
|
|
679
|
+
if target:
|
|
680
|
+
links.append((number, target))
|
|
681
|
+
links.sort()
|
|
682
|
+
return links
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
def excerpt_fence_blocks(lines: list[str]) -> list[tuple[int, str, list[tuple[int, str]]]]:
|
|
686
|
+
"""Return (opening line, filename, body) for every fence with a filename."""
|
|
687
|
+
blocks: list[tuple[int, str, list[tuple[int, str]]]] = []
|
|
688
|
+
in_fence = False
|
|
689
|
+
start = 0
|
|
690
|
+
filename = ""
|
|
691
|
+
body: list[tuple[int, str]] = []
|
|
692
|
+
for number, raw in enumerate(lines, start=1):
|
|
693
|
+
fence = FENCE_RE.match(raw)
|
|
694
|
+
if fence:
|
|
695
|
+
if not in_fence:
|
|
696
|
+
in_fence = True
|
|
697
|
+
start = number
|
|
698
|
+
opening = FENCE_FILENAME_RE.search(raw)
|
|
699
|
+
filename = opening.group(1) if opening else ""
|
|
700
|
+
body = []
|
|
701
|
+
else:
|
|
702
|
+
in_fence = False
|
|
703
|
+
if filename:
|
|
704
|
+
blocks.append((start, filename, body))
|
|
705
|
+
filename = ""
|
|
706
|
+
body = []
|
|
707
|
+
continue
|
|
708
|
+
if in_fence:
|
|
709
|
+
body.append((number, raw))
|
|
710
|
+
return blocks
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
def source_doc_above(source: list[str], index: int) -> list[str]:
|
|
714
|
+
"""Collect the contiguous `///` block that sits directly above index."""
|
|
715
|
+
block: list[str] = []
|
|
716
|
+
cursor = index - 1
|
|
717
|
+
while cursor >= 0:
|
|
718
|
+
raw = source[cursor]
|
|
719
|
+
if not DOC_COMMENT_RE.match(raw) or raw.lstrip().startswith("///<"):
|
|
720
|
+
break
|
|
721
|
+
block.append(raw)
|
|
722
|
+
cursor -= 1
|
|
723
|
+
block.reverse()
|
|
724
|
+
return block
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
def check_excerpts(path: Path, root: Path, lines: list[str]) -> list[Issue]:
|
|
728
|
+
"""Reader text carries no file headers; kept doc comments match the source."""
|
|
729
|
+
issues: list[Issue] = []
|
|
730
|
+
for number, raw in enumerate(lines, start=1):
|
|
731
|
+
if EXCERPT_HEADER_RE.search(raw):
|
|
732
|
+
issues.append(Issue(
|
|
733
|
+
path, number,
|
|
734
|
+
"读者正文不摘文件级 Doxygen 头(@file、@brief、文件范围段);"
|
|
735
|
+
"删掉这段,围栏从它之后开始",
|
|
736
|
+
))
|
|
737
|
+
|
|
738
|
+
links = collect_excerpt_links(lines)
|
|
739
|
+
for start, filename, body in excerpt_fence_blocks(lines):
|
|
740
|
+
candidates = [
|
|
741
|
+
(number, target)
|
|
742
|
+
for number, target in links
|
|
743
|
+
if number < start and target.rsplit("/", 1)[-1] == filename
|
|
744
|
+
]
|
|
745
|
+
if not candidates:
|
|
746
|
+
continue
|
|
747
|
+
target = candidates[-1][1]
|
|
748
|
+
blob = BLOB_URL_RE.match(target)
|
|
749
|
+
if not blob:
|
|
750
|
+
continue
|
|
751
|
+
relative = blob.group(1)
|
|
752
|
+
source_path = (root / relative).resolve()
|
|
753
|
+
if root not in source_path.parents or not source_path.is_file():
|
|
754
|
+
continue
|
|
755
|
+
source = read_text(source_path).splitlines()
|
|
756
|
+
|
|
757
|
+
pending: list[tuple[int, str]] = []
|
|
758
|
+
pending_start = start
|
|
759
|
+
for number, raw in body:
|
|
760
|
+
stripped = raw.strip()
|
|
761
|
+
if DOC_COMMENT_RE.match(raw) and not stripped.startswith("///<"):
|
|
762
|
+
if not pending:
|
|
763
|
+
pending_start = number
|
|
764
|
+
pending.append((number, raw))
|
|
765
|
+
continue
|
|
766
|
+
if not pending:
|
|
767
|
+
continue
|
|
768
|
+
if not stripped:
|
|
769
|
+
pending = []
|
|
770
|
+
continue
|
|
771
|
+
declaration = normalize_code(raw)
|
|
772
|
+
fence_block = [item.strip() for _, item in pending]
|
|
773
|
+
pending = []
|
|
774
|
+
if not declaration or declaration.startswith(("#", "}", "{", "...")):
|
|
775
|
+
continue
|
|
776
|
+
matches = [
|
|
777
|
+
index for index, item in enumerate(source)
|
|
778
|
+
if normalize_code(item) == declaration
|
|
779
|
+
]
|
|
780
|
+
if not matches:
|
|
781
|
+
continue
|
|
782
|
+
# 同一行声明可能在多个类里重复(如各实现类的 override);任一处注释
|
|
783
|
+
# 与围栏整段一致即通过,避免把第一个同名声明当成唯一出处。
|
|
784
|
+
source_blocks = [
|
|
785
|
+
[item.strip() for item in source_doc_above(source, index)]
|
|
786
|
+
for index in matches
|
|
787
|
+
]
|
|
788
|
+
if any(block == fence_block for block in source_blocks):
|
|
789
|
+
continue
|
|
790
|
+
if not fence_block:
|
|
791
|
+
continue
|
|
792
|
+
prefixes = [
|
|
793
|
+
block for block in source_blocks
|
|
794
|
+
if len(block) > len(fence_block) and block[: len(fence_block)] == fence_block
|
|
795
|
+
]
|
|
796
|
+
if prefixes:
|
|
797
|
+
issues.append(Issue(
|
|
798
|
+
path, pending_start,
|
|
799
|
+
f"围栏只摘了 {filename} 注释的前 {len(fence_block)} 行;"
|
|
800
|
+
"整段补上 @param、@return 与空 /// 行,或整段不摘",
|
|
801
|
+
))
|
|
802
|
+
elif all(not block for block in source_blocks):
|
|
803
|
+
issues.append(Issue(
|
|
804
|
+
path, pending_start,
|
|
805
|
+
f"围栏带有注释,但 {filename} 对应声明上方没有 /// 注释;整段删掉",
|
|
806
|
+
))
|
|
807
|
+
else:
|
|
808
|
+
issues.append(Issue(
|
|
809
|
+
path, pending_start,
|
|
810
|
+
f"围栏注释与 {filename} 对应声明上方的不一致;整段照抄或整段不摘",
|
|
811
|
+
))
|
|
812
|
+
return issues
|
|
813
|
+
|
|
814
|
+
|
|
656
815
|
def check_file(path: Path, root: Path, project: dict, chapters: list[str]) -> list[Issue]:
|
|
657
816
|
issues: list[Issue] = []
|
|
658
817
|
text = read_text(path)
|
|
@@ -712,7 +871,12 @@ def check_file(path: Path, root: Path, project: dict, chapters: list[str]) -> li
|
|
|
712
871
|
elif fence_lang.startswith("{"):
|
|
713
872
|
inner = fence_lang.strip("{}").strip()
|
|
714
873
|
fence_is_html = inner == "=html"
|
|
715
|
-
if inner
|
|
874
|
+
if inner.startswith(".") and "mermaid" in inner:
|
|
875
|
+
issues.append(Issue(
|
|
876
|
+
path, number,
|
|
877
|
+
"Mermaid 围栏写成 `{.mermaid}`,Quarto 不会载入图表脚本,图会退化成代码块;改成 `{mermaid ...}`",
|
|
878
|
+
))
|
|
879
|
+
elif inner and not inner.startswith((".", "=")) and "mermaid" not in inner:
|
|
716
880
|
# ```{python} style attribute fences are valid.
|
|
717
881
|
pass
|
|
718
882
|
else:
|
|
@@ -780,6 +944,7 @@ def check_file(path: Path, root: Path, project: dict, chapters: list[str]) -> li
|
|
|
780
944
|
issues.append(Issue(path, fence_start, "代码围栏未闭合"))
|
|
781
945
|
|
|
782
946
|
issues.extend(check_fence_semantics(path, lines))
|
|
947
|
+
issues.extend(check_excerpts(path, root, lines))
|
|
783
948
|
|
|
784
949
|
for block in fence_blocks:
|
|
785
950
|
issues.extend(check_tree_block(path, block))
|
|
@@ -37,14 +37,14 @@
|
|
|
37
37
|
|
|
38
38
|
链接预算:引言只挂承接上一章的那 1 个章节链接和本章主源文件的 blob 链接;其余章节链接下沉到正文真正用到它的句子。同一段不堆多个章节链接——每个链接都是读者离开当前页的一次机会。
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
摘录免责句不写进读者正文,同源保证交给 `verifying-content.py`。
|
|
41
41
|
|
|
42
42
|
### 任务序列
|
|
43
43
|
|
|
44
44
|
- 每节一个可验证结果:任务结束时存在读者能自己复述的检查动作;只有「读完了解释」的内容并入相邻任务。
|
|
45
45
|
- 节内顺序:概念 → 最小示例 → 操作 → 验证 → 边界;先让读者看到行为,再解释规则。单次操作按「做什么 → 执行什么 → 看到什么 → 简短说明」推进。
|
|
46
46
|
- 任务节以该章标题里的对象为准,不把每章都写成「目录、设计、构建、测试」。
|
|
47
|
-
-
|
|
47
|
+
- 交付清单或边界说明不单独设 `##`,并入引言或相邻总览;连续值类型并成一个结果。
|
|
48
48
|
- 任务节内按「类型 → 简单函数 → 更难的函数」排列,最难的组装放在最后,满足先定义后调用:读者要读懂的行内标识符,其行为必须已在上文或紧邻声明中出现;未定义名字的调用链、节首概览与编号步骤整段删除。
|
|
49
49
|
- `##` 导语只用白话,不点后文才定义的函数名;有 `###` 时导语承接上文。
|
|
50
50
|
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
有 `class` 定义的章,任务级 `##` 按下面次序排;没有下一种内容就停,不为凑数加节:
|
|
54
54
|
|
|
55
55
|
1. 类型:本章用到的值类型(枚举与结构体),一节一个可验证结果,可下分三级标题。
|
|
56
|
-
2.
|
|
56
|
+
2. 成员变量:单独一节;读取成员的函数用三级标题。只判断下标区间的检查也用三级标题放在这一节,不单独占二级标题。类说明、构造函数与数据成员拼进同一个围栏。每个函数的声明和实现留在同一个 `###`;标题点名本节对象,不用「只读接口」这类总称。
|
|
57
57
|
3. 最重要的函数:这个类最重要的一个函数及配套函数。
|
|
58
58
|
4. 其余重要函数:还有一组就再加一节,没有就停。
|
|
59
59
|
|
|
@@ -217,6 +217,7 @@ YAML `title` 2–6 个汉字;`##` 与 `###` 2–10 个汉字,「常见错误
|
|
|
217
217
|
- 类章按「类型 → 成员变量 → 最重要函数 → 其余函数」排任务节;没有 `class` 的章按「类型 → 使用这些类型的函数」排。
|
|
218
218
|
- 只有一项交付、没有可独立复现失败症状的工程章,可省「常见错误」,其余固定收尾保留。
|
|
219
219
|
- 着陆页与根首页不套六段骨架,只核对整章预算、标题层级与卡片承诺。
|
|
220
|
+
- 已有的 `.feature-grid` 内 `.landing-card` 不得删除,也不得改成普通列表;卡片数量和链接与项目章节表一致,缺了按章节表补回。
|
|
220
221
|
- 全章重构或跨章搬迁时,先按「章节骨架」定删留,再进入标题与句子步骤。
|
|
221
222
|
|
|
222
223
|
## 正反例
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
同一 `##`/`###` 内 ≥2 个代码围栏时,先看是否是紧邻常量与其直接使用行:常量紧接着被下一行使用时,可放在同一个围栏。类型定义和后一步使用它的函数是两步,即使函数用到该类型也分成两块——一个围栏只放正在讲的那一个声明或函数,不把整份头文件、整份类或整条调用链贴进同一个围栏;后一个函数的函数体里可以调用已经讲过的名字,但不把那些函数的实现再贴一遍。每块前一句导语;`{python}` / `{mermaid}` 图或独立验证盒子打断处保持拆块。旧口径「同一小节递进片段必须合并为一个围栏」收窄到此:只有常量紧接着被下一行使用时才放在同一围栏。改写对照见 `examples/03-excerpt-lead.md`「递进片段合并」。
|
|
17
17
|
|
|
18
|
-
每个围栏在最小模块里的位置见「三级标题最小模块」。仍然禁止块前半句,禁止收束段把列表再讲一遍。可合并的块前与块后并成块前一句;简洁边界仍适用(`steps/05-sentences.md
|
|
18
|
+
每个围栏在最小模块里的位置见「三级标题最小模块」。仍然禁止块前半句,禁止收束段把列表再讲一遍。可合并的块前与块后并成块前一句;简洁边界仍适用(`steps/05-sentences.md`)。对照见 `examples/03-excerpt-lead.md`「多代码块一句导语」。
|
|
19
19
|
|
|
20
20
|
以代码块为中段,导语讲工程语境(这段代码为什么存在、为什么此刻看它);若有块后句,只讲注释没覆盖的新工程关系。语法细节不逐行展开。导语与块后都禁止关键字释义当开场:不写「X 是 `inline constexpr` 常量」「用 `enum class` 表达」「用有符号整型承载」「坐标字段为什么选定宽有符号」式语言课或选型旁白(除 01 设计章外工程 qmd 禁选型外链),改写对照见 `examples/01-language-scope.md`「语言课旁白与工程旁白」。
|
|
21
21
|
|
|
@@ -52,18 +52,24 @@ int main() {
|
|
|
52
52
|
```
|
|
53
53
|
````
|
|
54
54
|
|
|
55
|
-
-
|
|
55
|
+
- 片段逐字摘自源文件,不改写、合并或重排源代码行;注释与空行可整段省略,省略处不写标记行,口径见下节。
|
|
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
|
-
- **本章主源文件可重复挂链接,其他仓库路径每章至多一条**:章引言用可跳转链接(内联 `[]()` 或同页引用式 `[label][id]`)挂本章主源文件(如 `board.h`、`board.cpp`),正文再点名这些文件时复用同一 GitHub 链接,读者不用回翻章首。不在章引言挂链接的其他仓库文件,正文用行内代码,不包链接;跨章指称用章节标题链接。同一章摘录另一阶段目录的同名文件时,在切换处再挂一条不同 URL
|
|
60
|
-
- 片段内注释硬限:每处实现注释(`//` 或 `#`)不超过 3 行(与源文件注释规范的 1–3 行同一口径)、单行不超过 100 列(与源文件 `ColumnLimit: 100` 同一口径;正文散文的 40 字限不套到代码注释),只写这段代码做什么;CMake/Shell 等命令型片段每条命令保留一行意图注释。Doxygen `///`
|
|
61
|
-
-
|
|
59
|
+
- **本章主源文件可重复挂链接,其他仓库路径每章至多一条**:章引言用可跳转链接(内联 `[]()` 或同页引用式 `[label][id]`)挂本章主源文件(如 `board.h`、`board.cpp`),正文再点名这些文件时复用同一 GitHub 链接,读者不用回翻章首。不在章引言挂链接的其他仓库文件,正文用行内代码,不包链接;跨章指称用章节标题链接。同一章摘录另一阶段目录的同名文件时,在切换处再挂一条不同 URL(`verifying-content.py` 只对非章引言主源文件的同章重复 URL 判失败)。
|
|
60
|
+
- 片段内注释硬限:每处实现注释(`//` 或 `#`)不超过 3 行(与源文件注释规范的 1–3 行同一口径)、单行不超过 100 列(与源文件 `ColumnLimit: 100` 同一口径;正文散文的 40 字限不套到代码注释),只写这段代码做什么;CMake/Shell 等命令型片段每条命令保留一行意图注释。Doxygen `///` 声明块随源保留,不受此限。命名惯例、官方建议等说理不进代码块;术语卡与操作提醒可以进 callout。注释写法与 `///`、`@` 标签分工只留在 `.agents`,面向作者的进知识文件。
|
|
61
|
+
- 首讲某段源码的章节不省略源码既有的职责注释:注释是读者读到的第一层讲解;「整段省略」只服务后续章节复现已见代码。源码缺注释交给项目 C++ 流程。
|
|
62
62
|
- 讲解段只解释片段中实际出现的代码;片段没有展示的内容要么补进片段、要么进 callout,不在正文列表里讲代码外面的东西。
|
|
63
63
|
- include 短代码降级为可选手段:仅用于确实需要完整展示且很短的文件,并仍须包在带语言标注的围栏代码块内(包裹规则见 `quarto/render.md`)。
|
|
64
64
|
- 手动命令块标注执行位置,如 `filename="WSL Ubuntu(仓库根目录)"`。
|
|
65
65
|
- 输出效果类代码块(运行效果、预期输出)filename 只写场景名(如「运行效果」);终端样式、颜色等展示细节写进正文,不进 filename。输出摘录用单独一行 `...` 表示省略的工具输出,与源码片段的省略是两回事。
|
|
66
66
|
|
|
67
|
+
### 摘录与源文件注释
|
|
68
|
+
|
|
69
|
+
- 读者正文不摘文件级 Doxygen 头:`/// @file`、`/// @brief` 与文件范围段都不进正文,围栏从其后开始。
|
|
70
|
+
- 围栏保留的声明带 `///` 注释时,整段必须与源文件该声明上方一致,`@param`、`@return` 与空 `///` 行一并保留;整段不摘可以,只摘概述行不行。
|
|
71
|
+
- 润色或回读发现围栏与源文件不一致,打开该源文件把围栏改一致;只读对应声明。
|
|
72
|
+
|
|
67
73
|
### 代码块门槛
|
|
68
74
|
|
|
69
75
|
不是每段代码都值得出围栏。**值得出围栏**:类型布局(常量、枚举、结构体、类的声明)、多步或非显然的控制流、需要与图核对的公式实现,且块前有工程动机导语。
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
|
|
21
21
|
自测固定导语“试着自己把回答完整说出来”中的“回答”指读者自己的答复,允许保留;技能步骤里的“读者要能说出什么”是自检说明,不要求改写成读者句。其余示例必须给读者能直接读到的句子,不写“读者要能说出……”。
|
|
22
22
|
|
|
23
|
+
### 读者正文不解释作者的选择
|
|
24
|
+
|
|
25
|
+
读者正文不写作者为什么没选另一种写法。「意图很直接」「这样写是为了」「不用猜」都属这类句子。块后只写表达式做了什么、按什么次序。
|
|
26
|
+
|
|
23
27
|
### 绝对范围词要少用
|
|
24
28
|
|
|
25
29
|
读者正文默认不写「只 / 仅 / 唯一 / 一律 / 永远 / 一定 / 必然 / 全部 / 完全」这类把范围一次说死的词。删掉后句义不变就删;需要限定范围时,写出范围本身:尺寸、条件、文件名或入口。
|
|
@@ -112,26 +116,24 @@
|
|
|
112
116
|
|
|
113
117
|
每个维度给可操作判定信号,命中即改写:
|
|
114
118
|
|
|
115
|
-
1. **中心明确**:判定见 `steps/04-paragraphs.md
|
|
119
|
+
1. **中心明确**:判定见 `steps/04-paragraphs.md`「段落规则」。
|
|
116
120
|
2. **简洁**:把候选词句删掉,信息不变即删——无信息修饰(「非常」「其实」「完全」「简单来说」)、同义反复、重复交代的同一事实。
|
|
117
121
|
3. **连贯**:段间衔接的判法见 `steps/04-paragraphs.md`「段间:过渡句只在话题切换处」;句序违背时序或因果即调整;连接词表达的关系与上下文不符即改。句间衔接遵守 given-new 与设计句骨架,判定见 `steps/05-sentences.md`。
|
|
118
122
|
4. **可读性**:单句 20/40 字与拆句见 `steps/05-sentences.md`「句子规则」;复合句拆成简单句或并列句;三项以上并列写成列表,不写伪连贯长句;换算豁免见 `steps/07-layout.md`「列表选择」。同档并句的长度见 `steps/05-sentences.md`「同档并句」。
|
|
119
123
|
5. **不拗口**:命中即重写——三重定语链;一句里连续三个「的」;名词化堆叠(「进行 X 的实现」「对 X 作出更新」);双介词嵌套(「在…中通过…对…」)。
|
|
120
124
|
6. **指代明确**:「这 N 个」「它们」「其」的先行词必须唯一且紧邻;承前省略的判法见 `steps/05-sentences.md`「六个成分」。
|
|
121
|
-
7. **不冗余**:同一事实只讲一次与固定表述豁免的判定见 `steps/04-paragraphs.md
|
|
125
|
+
7. **不冗余**:同一事实只讲一次与固定表述豁免的判定见 `steps/04-paragraphs.md`「段内同义堆叠禁令」;同一对象同页统一称呼。
|
|
122
126
|
|
|
123
127
|
### 定用词:术语要稳
|
|
124
128
|
|
|
125
129
|
- 动词选用与主动语态见「目标语气」;「由 X 完成」在这一步改成主动句,改完露出第二个核就退回定段落。
|
|
126
|
-
-
|
|
130
|
+
- 英文标识保持大小写。
|
|
127
131
|
- 「游戏」指作品或玩法整体,「对局」指具体一场:定义「井字棋是…游戏」用「游戏」,规则、落子与结果语境用「对局」;同一页不把两者当同义词换用(对照见 `examples/06-wording-cases.md`「游戏与对局」)。
|
|
128
|
-
-
|
|
129
|
-
-
|
|
130
|
-
- 压缩动词写全:`重建`、`重编`、`重跑` 这类两字词,读者可能读成「重新创建」;写「重新生成」「重新编译」「重新运行」,同一页后续也不回退到压缩词。
|
|
131
|
-
- 连续出现三个「的」时的改写判定见「七个质量维度」第 5 条。
|
|
132
|
+
- 项目名在同一页首次写全(如「井字棋棋盘」),后文改用对象名(「棋盘」「动态库」「对局」);不逐段重复项目名,也不反复解释同一件事。
|
|
133
|
+
- 压缩动词写全:`重建`、`重编`、`重跑` 这类两字词,读者可能读成「重新创建」;写「重新生成」「重新编译」「重新运行」,同页不回退到压缩词。
|
|
132
134
|
- 正文提及枚举类型与成员函数时写源码完整标识符(`Result`、`Game::ApplyMove`),不设「概念简称」层;架构层名首次出现用一句可观察行为定义;讲读写路径第一句写屏幕上或调用处发生什么。下一句点名类型与函数。再下一句写边界(对照见 `examples/01-language-scope.md`「零基础可懂的读写路径与层名定义」)。
|
|
133
135
|
- 枚举成员的含义写在源码行尾注释里,正文不复述成释义列表或释义段;正文只写谁调用、如何验收、与邻章关系(叙事范围见 `steps/01-task.md`「叙事范围(硬边界)」)。
|
|
134
|
-
- C++
|
|
136
|
+
- C++ 概念点名按本步词表;读者侧禁用行见 `scripts/verifying-content.py`;口语腔、公文腔标准见「两轴禁例与允许写法」。
|
|
135
137
|
|
|
136
138
|
## 情况
|
|
137
139
|
|
|
@@ -145,7 +147,7 @@
|
|
|
145
147
|
|
|
146
148
|
### 情况:空泛套话与编辑残留
|
|
147
149
|
|
|
148
|
-
-
|
|
150
|
+
- **空泛套话**只在不指向具体对象时才算病:「深入探讨」「至关重要」「无缝」「赋能」「全方位」删掉后信息不变就删;确实点名对象时保留并写出那个对象(「无缝接入现有构建」写「不新增构建命令」)。
|
|
149
151
|
- **同义循环换词**:同一段里「实现」「落地」「完成」轮换指同一动作,读者会以为换了东西;统一成一个词。
|
|
150
152
|
- **客服腔**:「如果您有任何疑问,欢迎随时联系」只留操作与结果。
|
|
151
153
|
- **编辑过程残留**:「下面我们来看」「综上所述」「本节将」把写作动作写进正文,删;「本节」只在确需导航时留一次。
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
- **无序列表**:并列概念、范围边界、要点枚举——各项之间没有先后或因果;代码块后的逐条讲解格式见 `steps/04-paragraphs.md`「段落规则」。
|
|
22
22
|
- **有序列表**:有先后顺序的步骤或链路——执行顺序本身就是信息(如配置 → 编译 → 测试 → 运行)。
|
|
23
23
|
- **规则和条件不编号**:并列的许可、禁止、判定条件写自然段或分句;只有读者需要按顺序执行动作时才用编号。把「越界 / 占用 / 格式错误」写成 1、2、3,会让读者以为要按顺序检查。一段同时有规则和流程时,规则先成段,流程另起有序列表。
|
|
24
|
+
- 例外:终局判定的「获胜」「平局」这类并列结果,每条都有名称和可独立核对的条件时,可以用编号列表,让读者逐条对照。
|
|
24
25
|
- 列表项结构平行:起首用同类的词(都是名词短语,或都是动词短语),长度相近。
|
|
25
26
|
- **列表字数带**:同一列表各项保持视觉齐长,避免扫读节奏断裂。计量:引导词汉字数、冒号后汉字数(行内代码按标识符计)。失衡判定:冒号后最长 ≥ 最短 × 2,或引导词相差 ≥ 4。改写顺序:删长项多余中心 → 拆多中心 → 过短项按简洁边界补全 → 停止。豁免:出错原因/正确做法;有序步骤;着陆页卡片。列表齐长服从句段主规则,假拆禁令见 `steps/04-paragraphs.md`。
|
|
26
27
|
- 列表项起首用加粗引导词标记中心语,读者扫读时先看到分类再决定是否细读;关键词取同类词(如「显示状态面板」「读入坐标」),并按对象的发生顺序排列,读者扫读即得顺序与内容。
|
|
@@ -60,11 +60,11 @@
|
|
|
60
60
|
| 定用词 | 空泛套话(「深入探讨」「至关重要」「无缝」「赋能」)不指具体对象;同义循环换词;客服腔;编辑过程残留 | 改写成可核对的对象或删除;工程术语(反馈里的「闭环」)保留 | `steps/06-wording.md`「情况:空泛套话与编辑残留」 |
|
|
61
61
|
| 回读并退回 | 与改前文本对照:新增原文没有的事实、遗漏读者需要的信息、把限定说成确定 | 恢复原文或按现象退回对应步 |本文件「情况:对照改前文本」 |
|
|
62
62
|
|
|
63
|
-
机械项:`verifying-content.py` 扫读者 qmd 与 agents 的
|
|
63
|
+
机械项:`verifying-content.py` 扫读者 qmd 与 agents 的 `下一节`/`上一节`、`代码把它写成`、围栏外正文的 `由…完成` 与 `由…确定/体现/界定/表达`;agent 免责腔、章节页引言两个散文段与段落下限硬拦;过简断裂记 WARN,不追清零。句子切分全量执行,只印计数与失败行,失败收类型接线句、「接着看/第 N 个枚举」改口、全称被下一句否定三类;段中心与对话件数留给步骤与案例。
|
|
64
64
|
|
|
65
65
|
### 润色流程:沿着步骤走
|
|
66
66
|
|
|
67
|
-
润色任务沿 `SKILL.md` 的路由执行:增量任务只走与改动对应的步骤;新章、新阶段或用户明确要求全章重构才从定任务走到通过检查。判定标准见 `steps/06-wording.md`。书根 `README.md` 与 `README.en.md` 是仓库说明页,按 `SKILL.md` 的例外只走第 5–8
|
|
67
|
+
润色任务沿 `SKILL.md` 的路由执行:增量任务只走与改动对应的步骤;新章、新阶段或用户明确要求全章重构才从定任务走到通过检查。判定标准见 `steps/06-wording.md`。书根 `README.md` 与 `README.en.md` 是仓库说明页,按 `SKILL.md` 的例外只走第 5–8 步,这个例外优先于本步的跳步判定。三种走法见 `SKILL.md`「三种走法」,差别按那里执行。执行时遵守定段落节的既有判定:分句骨架在定句子判为不合格,不在定段落里顺手改句子。
|
|
68
68
|
|
|
69
69
|
同一处同时命中多条规则时,先按 `references/priority.md` 排级:事实与读者任务 > 结构 > 清晰 > 一致 > 风格。高层没有解决,不先做低层润色;低层改动碰到高层事实,停手退回。
|
|
70
70
|
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
|
|
83
83
|
1. **确定范围**:用户点名的文件,或本轮已改动的文件;项目提供作用域命令时先跑它,只读它列出的文件。全库中文普查只在用户明确要求审完全部中文文件时做,它自己就是那个 goal 的结束条件,不嵌进单章润色。
|
|
84
84
|
2. **沿步骤走本次文件**:每个文件只走它对应的步骤,每步只改该步判为不合格的句子;后一步发现前一步没做完,退回那一步一次,不在当前步补结构。某句只在定用词失败,就不重写它的结构;某段在定句子失败,先改骨架,再让定用词看一遍。已合格的句子不重润。
|
|
85
|
-
3. **改写**:最小改动,只改判为不合格的句子,不做风格重塑。改写不破检查:qmd 导语 token 与句数、段落篇幅、禁用词由 `verifying-content.py` 把关;中文文件保持 UTF-8 无 BOM、LF
|
|
85
|
+
3. **改写**:最小改动,只改判为不合格的句子,不做风格重塑。改写不破检查:qmd 导语 token 与句数、段落篇幅、禁用词由 `verifying-content.py` 把关;中文文件保持 UTF-8 无 BOM、LF。代码围栏逐字摘自源文件;注释与源文件不一致时回源改围栏(`steps/03-blocks.md`「摘录与源文件注释」);同源复制文件改一处同步其余并 diff 比对。改动触及知识库案例引用句时先 grep 案例文件再同步。
|
|
86
86
|
4. **验收同步**:按改动域跑校验(qmd → `verifying-content.py` + `checking-links.py`;有项目技能时只跑它列出的那一条命令,不跑全量 profile)。检查次数与停手见 `steps/09-check.md`「完成判据」。
|
|
87
87
|
|
|
88
88
|
### 完成前核对
|
|
@@ -116,6 +116,6 @@
|
|
|
116
116
|
|
|
117
117
|
## 正反例
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
回读先开 `references/personas.md`,按章型过一份人格;通用情况判得准就不打开 `examples/08-appendix.md`,判不准时再打开。第 8 步点名这两个文件,合计最多两个。
|
|
120
120
|
|
|
121
121
|
定稿前对照 `references/rules-cards.md` 的 C05、C06、C25、C26:删复述句与元叙述,补三句回顾,自测题只用本章内容。
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
- `dry_run: true` 最多 2 次。第 1 次返回 `safe_to_render: false` 时,只改 `output-dir` 一次。第 2 次仍不安全就停住,把 `warnings` 列给用户。
|
|
25
25
|
- `dry_run: false` 最多 1 次。渲染失败就停住,把 `errors` 列给用户,不自动重新运行。
|
|
26
26
|
- 成功时同一次返回里带 `html_report`,不再为同一份文件重新运行。
|
|
27
|
+
- 同一轮改多章时,每章只跑项目的章节检查;全部章改完后 `quarto_render_check` 只跑一轮,`dry_run: true` 最多 2 次,`dry_run: false` 1 次,`quarto_pass_report` 只收尾一次。
|
|
27
28
|
|
|
28
29
|
报告字段:
|
|
29
30
|
|