@chengzhao-dev/dsh-quarto-book 0.6.18 → 0.6.19

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.
@@ -111,6 +111,8 @@ HEADING_PERSONAL_RE = re.compile(r"自己|咱们|我们|你|您|跟我|试试|
111
111
  FENCE_RE = re.compile(r"^\s*(```+|~~~+)\s*(\{[^}]*\}|[^\s`]*)?")
112
112
  LINK_RE = re.compile(r"\[[^\]]*\]\(([^)\s]+)\)")
113
113
  LINK_LABEL_RE = re.compile(r"\[([^\]]*)\]\([^)]*\)")
114
+ LIST_LINE_RE = re.compile(r"^\s*(?:[-*+]|\d+\.)\s+")
115
+ GUIDED_ITEM_RE = re.compile(r"^\*\*[^*\n]{1,30}\*\*:")
114
116
  CJK_RE = re.compile(r"[\u3400-\u9fff]")
115
117
  TITLE_RE = re.compile(r"^title:\s*(.+?)\s*$", re.MULTILINE)
116
118
 
@@ -301,6 +303,70 @@ def check_prose_block(
301
303
  ))
302
304
  break
303
305
 
306
+ if not is_list and CJK_RE.search(compact):
307
+ sentences = [
308
+ sentence.strip()
309
+ for sentence in re.split(r"(?<=[。!?])", compact)
310
+ if sentence.strip()
311
+ ]
312
+ seen_names: set[str] = set()
313
+ for index, sentence in enumerate(sentences):
314
+ if index > 0:
315
+ if sentence.startswith("它") and len(sentence) > 1 and sentence[1] != "们":
316
+ issues.append(Issue(
317
+ path, start_line,
318
+ "过简断裂:「它」开头的句子没有接住前句句末的对象;写回原对象名",
319
+ ))
320
+ break
321
+ lead = INLINE_NAME_RE.match(sentence)
322
+ if lead and lead.group(1) not in seen_names:
323
+ issues.append(Issue(
324
+ path, start_line,
325
+ f"过简断裂:后句以新标识符「{lead.group(1)}」开头,前句没有承接;先用回指词接住",
326
+ ))
327
+ break
328
+ for match in INLINE_NAME_RE.finditer(sentence):
329
+ seen_names.add(match.group(1))
330
+
331
+
332
+ def visible_length(text: str) -> int:
333
+ visible = LINK_LABEL_RE.sub(r"\1", text)
334
+ visible = re.sub(r"[*`]", "", visible)
335
+ return len(re.sub(r"\s+", "", visible))
336
+
337
+
338
+ def check_list_block(
339
+ path: Path, items: list[tuple[int, str]], issues: list[Issue], landing: bool = False
340
+ ) -> None:
341
+ if len(items) < 2:
342
+ return
343
+ guided = [bool(GUIDED_ITEM_RE.match(text)) for _, text in items]
344
+ if any(guided) and not all(guided):
345
+ issues.append(Issue(
346
+ path, items[0][0],
347
+ "同一列表混用普通句子和「**引导词**:」句式;统一成多数项使用的那一种",
348
+ ))
349
+ return
350
+ lengths = [
351
+ (line, visible_length(text))
352
+ for line, text in items
353
+ if text.strip()
354
+ ]
355
+ lengths = [(line, length) for line, length in lengths if length > 0]
356
+ if len(lengths) < 2 or landing:
357
+ return
358
+ joined = "".join(text for _, text in items)
359
+ if "出错的原因" in joined and "正确的做法" in joined:
360
+ return
361
+ longest = max(lengths, key=lambda pair: pair[1])
362
+ shortest = min(lengths, key=lambda pair: pair[1])
363
+ if longest[1] < 2 * shortest[1]:
364
+ return
365
+ issues.append(Issue(
366
+ path, longest[0],
367
+ f"列表项字数失衡:最长 {longest[1]} 字,最短 {shortest[1]} 字;删长项多余中心或把长项收成一句",
368
+ ))
369
+
304
370
 
305
371
  def display_width(text: str) -> int:
306
372
  return sum(2 if unicodedata.east_asian_width(char) in {"W", "F"} else 1 for char in text)
@@ -841,7 +907,9 @@ def check_file(path: Path, root: Path, project: dict, chapters: list[str]) -> li
841
907
  fence_is_html = False
842
908
  paragraph: list[str] = []
843
909
  paragraph_start = 0
910
+ list_items: list[tuple[int, str]] = []
844
911
  last_fence_end: int | None = None
912
+ landing = path.resolve() == (root / "index.qmd").resolve()
845
913
 
846
914
  def flush_paragraph() -> None:
847
915
  nonlocal paragraph
@@ -849,10 +917,17 @@ def check_file(path: Path, root: Path, project: dict, chapters: list[str]) -> li
849
917
  check_prose_block(path, paragraph_start, " ".join(paragraph), issues)
850
918
  paragraph = []
851
919
 
920
+ def flush_list() -> None:
921
+ nonlocal list_items
922
+ if len(list_items) >= 2:
923
+ check_list_block(path, list_items, issues, landing=landing)
924
+ list_items = []
925
+
852
926
  for number, raw in enumerate(lines, start=1):
853
927
  fence = FENCE_RE.match(raw)
854
928
  if fence:
855
929
  flush_paragraph()
930
+ flush_list()
856
931
  if not in_fence:
857
932
  in_fence = True
858
933
  fence_start = number
@@ -906,6 +981,7 @@ def check_file(path: Path, root: Path, project: dict, chapters: list[str]) -> li
906
981
  heading = HEADING_RE.match(raw)
907
982
  if heading:
908
983
  flush_paragraph()
984
+ flush_list()
909
985
  level = len(heading.group(1))
910
986
  heading_text = heading.group(2)
911
987
  if level == 1:
@@ -924,21 +1000,27 @@ def check_file(path: Path, root: Path, project: dict, chapters: list[str]) -> li
924
1000
 
925
1001
  if raw.lstrip().startswith("|"):
926
1002
  flush_paragraph()
1003
+ flush_list()
927
1004
  # 表格行按列组织,不套散文句长阈值;列数与表头一致性由表格检查负责。
928
1005
  continue
929
1006
 
930
1007
  if raw.lstrip().startswith((">", "-", "*", "+", "1.")):
931
1008
  flush_paragraph()
932
- check_prose_block(
933
- path, number, re.sub(r"^\s*(?:[>|]|[-*+]|\d+\.)\s*", "", raw), issues, is_list=True
934
- )
1009
+ item_text = re.sub(r"^\s*(?:[>|]|[-*+]|\d+\.)\s*", "", raw)
1010
+ if LIST_LINE_RE.match(raw):
1011
+ list_items.append((number, item_text))
1012
+ else:
1013
+ flush_list()
1014
+ check_prose_block(path, number, item_text, issues, is_list=True)
935
1015
  continue
936
1016
 
1017
+ flush_list()
937
1018
  if not paragraph:
938
1019
  paragraph_start = number
939
1020
  paragraph.append(raw.strip())
940
1021
 
941
1022
  flush_paragraph()
1023
+ flush_list()
942
1024
 
943
1025
  if in_fence:
944
1026
  issues.append(Issue(path, fence_start, "代码围栏未闭合"))
@@ -1,65 +1,48 @@
1
1
  # 第 1 步:定任务
2
2
 
3
- 产出:一句读者做完能完成的事,加上这一章要覆盖的任务清单。
4
- 这一步不动标题层级,也不改句子。
3
+ 产出:一句读者做完能完成的事,加上本章要覆盖的任务清单。
4
+ 这一步不动标题层级,也不改句子;两条规则冲突时按 `references/priority.md` 排级,本步先让位给读者任务与事实。
5
5
 
6
6
  ## 通用情况
7
7
 
8
- ### 读者
8
+ ### 读者与术语门槛
9
9
 
10
- 读者有编程基础,可能第一次接触本书使用的语言、构建工具或自动化测试。每页只解决一个任务。正文按书的实际技术栈写,不预设某一种语言或操作系统;需要环境差异时在页面里就地交代。
10
+ 读者有编程基础,可能第一次接触本书使用的语言、构建工具或自动化测试。每页只解决一个任务;正文按书的实际技术栈写,不预设某一种语言或操作系统,需要环境差异时在页面里就地交代。
11
+
12
+ - 不假设读者知道工具名、命令参数、目录布局或错误输出代表的含义。
13
+ - 标准术语首次出现时用一句话解释或就近术语卡,再进入命令;解释写成可观察行为,缩写先写全称(写法见 `quarto/readability.md`)。
14
+ - 只保留判断成功所需的输出,不解释对当前操作没有影响的内部格式。
15
+ - 面向新人的正文不堆砌入门前置名词;标题只用读者此刻已认识的词。
11
16
 
12
17
  ### 叙事范围(硬边界)
13
18
 
14
- 本书定位是可用工程的用法与验收;编程语言机制的系统讲解不属于正文。`content/**` 正文因此只写五类内容:领域语义、分层与依赖、接口契约、构建与测试验收、可验证动作。
19
+ 本书定位是可用工程的用法与验收;编程语言机制的系统讲解不属于正文。`content/**` 正文只写五类内容:领域语义、分层与依赖、接口契约、构建与测试验收、可验证动作。
15
20
 
16
- - **禁止**用正文讲解编程语言机制(枚举、结构体、命名空间、`inline`、`constexpr`、翻译单元、ODR 等),也禁止风格指南逐条教学(命名大小写、前缀、include guard 对比等)。
21
+ - **语言机制与风格指南的取舍先看项目技能**:项目技能写明禁止时,正文不展开枚举、结构体、命名空间、`inline`、`constexpr`、翻译单元、ODR,也不逐条教命名大小写、前缀或 include guard。项目没写时按读者门槛判断,不要默认开语言课。
17
22
  - 枚举取值列表的位置见 `steps/03-blocks.md`「三级标题最小模块」。
18
23
  - **禁止**在工程章(非设计章)展开数值选型旁白:不写「用有符号整型承载…」「某字段为什么选定宽有符号整型…」,也不为选型链到外部语言笔记。`int` 可出现在代码摘录中,旁白不解释「为什么有符号」。
19
- - **禁止课堂类比**:工程坐标系、API 与数据结构只用程序事实与可观察行为表述,不写「数学课」「平面直角坐标系」等跨学科课堂对照(如「与数学课的直角坐标系正好相反」)。作者构思时可借类比想问题,落盘到 qmd 前必须换成工程对象与可观察行为;图注已交代原点与轴向的,正文不复述对照。
20
- - **禁止突兀跨仓依据句**:不写「写法依据见 / 依据见 / 机制见 / 选型见 / 承诺见 / 用法见 / 陷阱见」+ 外部语言笔记。工程约定由本书拥有;外部教材不是工程写法的依据机关。
21
- - **跨仓外链默认零条**。工程叙事须自洽。仅当本小节工程点已讲完、且确有系统学语法需求时,可在小节末**单独成句**尾置可选深潜,口吻为「若要系统阅读…,见对应章节(GitHub 绝对 URL)。」同一章此类深潜宜 ≤2,能删则删。仅设计章可保留一次分册级链接。本书固定采用外部章节的构建约定时,用「本书固定采用…」陈述,仍放工程点之后,禁用「写法依据见」。
24
+ - **禁止课堂类比**:工程坐标系、API 与数据结构只用程序事实与可观察行为表述,不写「与数学课的直角坐标系正好相反」这类跨学科课堂对照;图注已交代原点与轴向的,正文不复述对照。
25
+ - **禁止突兀跨仓依据句**:不写「写法依据见 / 依据见 / 机制见 / 选型见 / 承诺见 / 用法见 / 陷阱见」+ 外部语言笔记。工程约定由本书拥有,外部教材不是工程写法的依据机关。
26
+ - **跨仓外链默认零条**:工程叙事须自洽。仅当本小节工程点已讲完、且确有系统学语法需求时,可在小节末单独成句尾置可选深潜,口吻为「若要系统阅读…,见对应章节(GitHub 绝对 URL)」,同一章宜 ≤2,能删则删;仅设计章可保留一次分册级链接。本书固定采用外部章节的构建约定时,用「本书固定采用…」陈述,仍放工程点之后。
22
27
  - **允许**代码块逐字展示源码(关键字照抄真实代码);旁白回答工程问题:这段公共定义解决什么问题、谁读取或调用它、如何验证。
23
- - **禁止接口清单压缩段**:不得把私有成员、公开成员名单、`const`、`[[nodiscard]]` 等接口属性压进同一段旁白;定稿形态为场景(动作 → 点名 → 边界)→ 代码 → 块后只补注释未覆盖的工程信息;语言属性不进旁白讲义(对照见 `examples/04-paragraph-opening.md`「接口清单压缩段」)。
28
+ - **禁止接口清单压缩段**:不把私有成员、公开成员名单、`const`、`[[nodiscard]]` 等接口属性压进同一段旁白;定稿形态为场景(动作 → 点名 → 边界)→ 代码 → 块后只补注释未覆盖的工程信息(对照见 `examples/04-paragraph-opening.md`「接口清单压缩段」)。
24
29
  - 判定口诀:句子的主语应是工程对象(模块、接口、数据、文件),不是语言构造(`enum class`、翻译单元)。反例与改写对照见 `examples/01-language-scope.md` 与 `examples/04-paragraph-cases.md`。
25
30
 
26
- ### 内容优先级
27
-
28
- 章节出现取舍时,按下面的顺序判断,不用较低层级的格式或样式要求覆盖较高层级的教学目标:
29
-
30
- 1. 读者任务与章节边界
31
- 2. 信息顺序和渐进披露
32
- 3. 页面块与段落布局
33
- 4. 技术正确性和可验证性
34
- 5. 标题、列表、Callout 和代码块等表达形式
35
- 6. 句子精简、术语和标点
36
- 7. 视觉样式与装饰
37
-
38
- 新增内容先确定它在既有页面块序列中的位置,再写入相邻小节。修改后回读完整小节及前后切口,确认动作、结果和下一步仍然相邻。只有步骤存在先后依赖时使用有序列表;同级选项使用无序列表;连续因果解释保留为短段落。
31
+ ### 定任务的做法
39
32
 
40
- 目录树先核对磁盘,再只保留当前任务需要的文件;注释、排列顺序与树后读法句统一见 `steps/03-blocks.md`「情况:目录树」。GitHub blob 链接嵌进同节导语句子里,不单独成行贴在代码围栏上方。线性构建流程用标题和文字说明,不用 Mermaid 代替。
41
-
42
- ### 术语门槛
43
-
44
- - 保留编译、链接、目标、状态、回合等标准术语;首次出现时用一句话解释或就近术语卡,再进入命令。解释写成可观察行为,缩写先写全称;细则见 `quarto/readability.md`。
45
- - 不解释对当前操作没有影响的内部格式。只保留判断成功所需的输出。
46
- - 面向新人的正文不堆砌入门前置名词;标题只用读者此刻已认识的词。
47
-
48
- ### 定任务:先让新人完成任务
49
-
50
- - 开篇场景的写法见 `steps/02-structure.md`「六段块序列」。
51
- - 每页一条成功路径与后置内容的判定见 `steps/06-wording.md`;主线闭合见 `steps/02-structure.md`。
52
- - 标准术语的首次解释与术语卡落点见 `steps/02-structure.md`(写法见 `quarto/authoring.md`);缩写先写全称。
53
- - 不假设读者知道工具名、命令参数、目录布局或错误输出代表的含义。
54
- - 目录式堆砌禁令与清单下沉判据见 `steps/02-structure.md`。
55
- - 标题与引言的单事承诺、承诺用词与 title 变更同步见 `steps/02-structure.md`。
56
- - 语言课禁令、否定式预告、不写「下一节」都在这一步判:它们决定本章能不能出现某句,不是措辞问题(细则见 `steps/05-sentences.md`「句子规则」、本文件「叙事范围」)。
33
+ - 任务写成读者做完能完成的一件事,不写成语法清单或功能列表;每页一条成功路径。
34
+ - 开篇场景的写法见 `steps/02-structure.md`「六段块序列」;标准术语的首次解释与术语卡落点同见该节(写法见 `quarto/authoring.md`)。
35
+ - 目录式堆砌禁令与清单下沉判据见 `steps/02-structure.md`;标题与引言的单事承诺、承诺用词与 title 变更同步同见该文件。
36
+ - 语言课禁令、否定式预告、不写「下一节」在这一步判:它们决定本章能不能出现某句,不是措辞问题(细则见 `steps/05-sentences.md`「句子规则」与本文件「叙事范围(硬边界)」)。
37
+ - 新增内容先确定它在既有页面块序列中的位置,再写入相邻小节;修改后回读完整小节及前后切口,确认动作、结果和下一步仍然相邻。
38
+ - 只有步骤存在先后依赖时用有序列表;同级选项用无序列表;连续因果解释保留为短段落。
39
+ - 目录树先核对磁盘,再只保留当前任务需要的文件,注释与读法句见 `steps/03-blocks.md`「情况:目录树」。GitHub blob 链接嵌进同节导语句子,不单独成行贴在围栏上方。线性构建流程用标题和文字说明,不用 Mermaid 代替。
57
40
 
58
41
  ## 情况
59
42
 
60
43
  ### 情况:语言课
61
44
 
62
- 讲语言特性时,任务写成「读者能读懂一段真实代码」,不写成语法清单。叙事范围跟着读者的语言门槛收窄。对照 `examples/01-language-scope.md`。
45
+ 讲语言特性时,任务写成「读者能读懂一段真实代码」,不写成语法清单;叙事范围跟着读者的语言门槛收窄。对照 `examples/01-language-scope.md`。
63
46
 
64
47
  ### 情况:否定式预告
65
48
 
@@ -71,7 +54,7 @@
71
54
 
72
55
  ### 情况:没有项目技能
73
56
 
74
- 书里没有 `.agents/skills/quarto-book-project/` 时跳过书名、读者和章节表,不猜也不补。任务只从当前文件和 `_quarto.yml` 读到的内容定。
57
+ 书里没有 `.agents/skills/quarto-book-project/` 时跳过书名、读者和章节表,不猜也不补;任务只从当前文件与 `_quarto.yml` 读到的内容定。
75
58
 
76
59
  ## 正反例
77
60
 
@@ -2,16 +2,12 @@
2
2
 
3
3
  产出:章内页面骨架和二级标题清单。
4
4
 
5
- 边界:这一步只定删留和标题,不改句子;全章重构也先停在这里。页面宽度归 `designing-theme`,图与代码的桥接归第 3 步,段落节奏归第 4 步。
5
+ 边界:这一步只定删留和标题,不改句子;整章改写也先停在这里。页面宽度归 `designing-theme`,图与代码的桥接归第 3 步,段落节奏归第 4 步。
6
6
 
7
7
  ## 通用情况
8
8
 
9
9
  绝大多数章按下面的六段块序列展开;类章、能力章与着陆页的差异见「情况」。本步的规则默认按这一通用形状判断。
10
10
 
11
- ### 章节骨架
12
-
13
- 六段块序列是骨架的默认顺序。
14
-
15
11
  ### 六段块序列
16
12
 
17
13
  ```text
@@ -89,7 +85,7 @@
89
85
  - 「下一章 <预告>」不写链接,直述一句;预告不引入下一章才建立的术语。
90
86
  - 每个说法都要能在正文找到出处,禁止空泛能力句或只写预告;任务节变化时同步改写。
91
87
 
92
- ## 一章一个关键
88
+ ### 一章一个关键
93
89
 
94
90
  每一章只有一个关键,标题、各级标题、导语和自然段都是它的部分或核对。关键分两类:
95
91
 
@@ -98,17 +94,6 @@
98
94
 
99
95
  固定收尾「常见错误」「自测问题」「本章回顾」不改名,不升成关键。
100
96
 
101
- ### 术语与验证策略
102
-
103
- - 标题、导语与任务节只用读者在前文已认识的名字;本章新术语在首次真正使用处就近定义,不在标题里抢先出现。
104
- - 每个任务节交付一个可观察结果;章末「本章回顾」只复述这些结果,不引入新承诺。项目有章节表时,关键、`title` 与二级标题同轮同步。
105
-
106
- ### 源文件与编译顺序
107
-
108
- 章的关键与头文件里声明的物理顺序冲突时,以编译要求为准:被用到的类型先声明,被依赖的类型留在使用它的类前面。叙述主次由章首与横幅承担,不由声明顺序承担。
109
-
110
- ## 标题
111
-
112
97
  ### 标题分层
113
98
 
114
99
  | 层级 | 长度 | 形式 |
@@ -117,6 +102,7 @@
117
102
  | `##` | 2–10 个汉字 | 这一阶段交出的对象,或读者必须动手的短任务:「限定语 + 对象」或「动词 + 具体宾语」 |
118
103
  | `###` | 2–10 个汉字 | 该阶段里的一个实体或一步具体动作;「常见错误」下的症状标题可到 12 个汉字 |
119
104
 
105
+ - 字数按可见汉字计,英文、数字和标点不计入;part 落地页的 title 例外,不受 2–6 汉字硬限。
120
106
  - `##` 承担大任务,要让读者看出它承担本章 title 的哪部分;`###` 承担可独立执行、观察或验证的子任务;正文最多到 H3,不写 `####`。
121
107
  - 只有解释或一两句结论的内容不升级为标题;`##` 含多个连续子流程或篇幅触达预算时,拆 `###`,不整块下压。
122
108
  - 一个 `##` 下设 `###` 时至少两个:同级只有一个时,删去标题归回正文,或补出真正独立的同级小节。
@@ -125,30 +111,22 @@
125
111
  - 标题与内容匹配:标题动词承诺的动作必须是正文实际让读者完成的动作;只要求观察的用「观察、查看、核对」,不用「走完、完成」。任务级标题不与页面 title 共用核心动词;`##` 的宾语要涵盖其下 `###` 的宾语。
126
112
  - 层级不跳级;下级标题不重复上级标题的名字;标题与导语不写拗口动宾,不用实现细节当主叙事。
127
113
 
128
- ### 标题字数
129
-
130
- YAML `title` 2–6 个汉字;`##` 与 `###` 2–10 个汉字,「常见错误」下的症状标题可到 12 个。按可见汉字计,英文、数字和标点不计入;part 落地页的 title 例外。
131
-
132
- ### 句式
114
+ ### 形式规则
133
115
 
134
116
  - 二级、三级标题只用两组词:要么「限定语 + 对象」(数据项坐标、操作后的结果),要么「动词 + 具体宾语」(检查是否三连、放入一枚元素)。同一个 `##` 下的 `###` 只用其中一种。
135
117
  - 任务级 `##` 在同一章内用同一种句式;类章与流程章统一收成名词短语,动作交给正文。
136
118
  - 标题若是集合的数量(`八条线`、`三种类型`),先看条目能否各自成为实体;能就拆成实体标题,不能就改成承载它们的对象。标题不替读者数个数。
137
- - 标题不用人称、劝诱和口语动词(「自己走一局」「跟我做」),不把读者写进标题。
138
- - 标题不以时间或过程从句起头(「输入之后的回应」还原成「程序回应」),时间关系留给导语。
119
+ - 标题不用人称、劝诱和口语动词(「自己走一局」「跟我做」),不把读者写进标题;不以时间或过程从句起头(「输入之后的回应」还原成「程序回应」),时间关系留给导语。
139
120
  - 标题不用视图隐喻和英文类名:`程序全图` 改 `程序结构`,`Ai 落子` 改 `电脑落子`。
140
121
  - 标题禁元叙述与空心题:不写「本节说明…」「本章规划…」,不写读不出动作对象的短语。
141
122
  - 标题与导航不使用行内代码;文件名承担路径职责,中文 title 承担理解职责,两者不必逐字翻译。
142
123
  - 过程节不承载回顾职责:`### 本章实现边界` 一类标题不设,边界话并入 `## 本章回顾`。
143
- - 例外:part 落地页的 title 即 part 名,不受 2–6 汉字硬限;章节与落地页一律不写 `description`。
144
-
145
- ### 形式规则
146
-
147
124
  - 不加冒号、破折号、括号补充说明;并列结构可用顿号或逗号。
148
125
  - 不堆「教程」「详解」「学习」「验证:」等泛化或解释性后缀。
149
126
  - 不用「看懂、读懂、弄清」类口语领会词,不预设读者的领悟结果。
150
127
  - 不用「并」串联两个动作,不用把字句;两个动作各有任务节承载时择主撰写对象。
151
128
  - 指示(这一步、这次)、过程动词(记下、收进、用到)、方式(用数组、先…再…)与数量词(三种、四个)不进标题,下放导语或正文。
129
+ - 章节与落地页一律不写 `description`;part 落地页的 title 即 part 名。
152
130
 
153
131
  ### 定语「的」
154
132
 
@@ -162,7 +140,7 @@ YAML `title` 2–6 个汉字;`##` 与 `###` 2–10 个汉字,「常见错误
162
140
 
163
141
  改 title 不改文件名,除非主题边界或页面职责变化;改后跑 `quarto_render_check` 看右侧目录与导航。页面 title 变化时,part 落地页的卡片标题与一句话描述同步改为新承诺。项目技能有章节表时,同轮同步表的 `title` 与 `##` 顺序;表与正文不一致按章题脱节处理。
164
142
 
165
- ## 纵向承诺链
143
+ ### 纵向承诺链
166
144
 
167
145
  定结构时逐条对照,不新开第 10 步。每条的要求都是「下一层必须兑现上一层对象」:
168
146
 
@@ -176,14 +154,10 @@ YAML `title` 2–6 个汉字;`##` 与 `###` 2–10 个汉字,「常见错误
176
154
  8. 类章任务节顺序按上文;任务章的 `###` 顺序与构建脚本步骤顺序一致;目录章的「有几个目录」与目录树项数一致。
177
155
  9. 章题一致:引言第一句、各 `##` 中心名词、回顾能力句并排看,三处对象必须与 title 相同;不同说明正文写成了另一章,先改 title 或重排任务节。
178
156
 
179
- ## 分层上限
180
-
181
- ### 计数口径
157
+ ### 分层上限
182
158
 
183
159
  估算 token = 中日韩字符数 × 1.5 + 其他字符数 ÷ 4;只用于预算比较,不声称等于真实 token 数。计入正文、标题、列表、表格、Callout 与链接锚文本;不计 YAML、链接目标、代码围栏、mermaid 与可执行单元全文。任何链接只计锚文本。
184
160
 
185
- ### 上限表
186
-
187
161
  | 层级 | 上限 |
188
162
  | --- | --- |
189
163
  | 整章(frontmatter 外,只含计入部分) | 5000 token |
@@ -198,30 +172,30 @@ YAML `title` 2–6 个汉字;`##` 与 `###` 2–10 个汉字,「常见错误
198
172
  | 单个正文段落 | 70–220 token;写作舒适带 90–180。豁免下限:章引言、节导语、紧邻块的单句前导、回顾、自测固定导语、Callout 内段落 |
199
173
  | `##` 节导语 | 120 token 且 ≤2 句 |
200
174
  | `###` 节导语 | 80 token 且 ≤1 句 |
201
- | 单个 `>` 引用块 | 80 token |
202
- | 一章 Callout | 超过 8 个失败;常用 1–3 个 |
203
175
 
204
- 数字依据:技术写作社区实践是单节 3–5 分钟阅读(1500–3000 字)、单个功能模块 800–1200 字;按 CJK×1.5 换算,整章 5000 token 约合 3300 汉字的正文当量,是「一节一件事、一章四节左右」的自然边界。
176
+ ### 术语与验证策略
177
+
178
+ - 标题、导语与任务节只用读者在前文已认识的名字;本章新术语在首次真正使用处就近定义,不在标题里抢先出现。
179
+ - 每个任务节交付一个可观察结果;章末「本章回顾」只复述这些结果,不引入新承诺。项目有章节表时,关键、`title` 与二级标题同轮同步。
205
180
 
206
- ## 执行与例外
181
+ ### 源文件与编译顺序
207
182
 
208
- - `verifying-content.py` 第 3 查按上表逐文件、逐节、逐代码块、逐段核对,超限即失败;调限额只改该脚本常量与本表,两处同步。
209
- - 根 `index.qmd` 与 part 落地页(`body-classes: index-page`)只核对整章总量与代码块限额,不套标题与骨架限制。
210
- - 接近上限不是目标:预算是上限,内容仍按第 4 步的凑字数禁令精简。
211
- - 标题归并:两个标题讲同一件事就并成一个,下层句子跟着走,不重写。
212
- - 全章重构:第 1–2 步只定删留和标题;发现某段要重写,记下来留给第 5–7 步。
213
- - 判不准时打开 `examples/02-structure-shape.md`。
183
+ 章的关键与头文件里声明的物理顺序冲突时,以编译要求为准:被用到的类型先声明,被依赖的类型留在使用它的类前面。叙述主次由章首与横幅承担,不由声明顺序承担。
214
184
 
215
185
  ## 情况
216
186
 
217
- - 类章按「类型 → 成员变量 → 最重要函数 → 其余函数」排任务节;没有 `class` 的章按「类型 → 使用这些类型的函数」排。
218
- - 只有一项交付、没有可独立复现失败症状的工程章,可省「常见错误」,其余固定收尾保留。
219
- - 着陆页与根首页不套六段骨架,只核对整章预算、标题层级与卡片承诺。
220
- - 已有的 `.feature-grid` 内 `.landing-card` 不得删除,也不得改成普通列表;卡片数量和链接与项目章节表一致,缺了按章节表补回。
221
- - 全章重构或跨章搬迁时,先按「章节骨架」定删留,再进入标题与句子步骤。
187
+ ### 情况:part 落地页
222
188
 
223
- ## 正反例
189
+ 落地页的 title 即 part 名,不受 2–6 汉字硬限;引言的范围等于该部分全部章的 title 集合,不写 `description`,卡片标题与一句话描述跟章 title 同轮同步。
190
+
191
+ ### 情况:没有源文件的章
224
192
 
225
- 正例:章题「棋盘」→ 引言点明 3×3 棋盘 → 任务节「类型与坐标」「成员与接口」「写入与清空」「终局判定」→ 固定收尾;每个 `##` 都是「棋盘」的一部分。
193
+ 没有源文件可摘时照常走六段块序列,但不挂 blob 链接、不写摘录免责句;回顾与自测仍按本章任务节交出的结果写,不借后章内容补。
194
+
195
+ ### 情况:类章与能力章的差异
196
+
197
+ 类章按「类型、成员、最重要函数、其余函数」排任务节;能力章按读者完成任务的先后排。两者都只有一个关键,固定收尾不改名。
198
+
199
+ ## 正反例
226
200
 
227
- 反例一:章题「棋盘」,任务节却按「打开文件、修改、编译、运行」排——把读者动作当结构,偏离本章关键。反例二:标题写成「进行棋盘的初始化操作」——空动词加关系词把对象挤到末尾,读者在目录里扫不到「棋盘」。
201
+ 通用情况判得准就不打开。判不准时只开 `examples/02-structure-shape.md`,按命中的信号读其中一节。