Skip to content

Commit 7d0c7cb

Browse files
committed
docs(style): 判据是疑问词,不是问号 —— 而列头也是标题
第 0 章的两个小节标题「背景:模块到了,工具链没跟上」「谁在为这个落差付账」与 类比表的列头「大致相当于谁的活」都是口语,而三者都通过了 check_docs_style.sh。 原因是两处:register 规则只匹配 `?` / 吗 / 呢,而疑问词本身不带问号;它又只读 以 `#` 开头的行,而列头按每一条要紧的性质都是标题。 - 第 0 章重写。背景的范围由问题决定,不由方案决定:C++ 工程侧要四样东西同时 成立,而没有任何一个工具同时负责它们;CMake 是其中一样的事实标准,而事实标准 陈述的是采用率;最常失效的是环境层,因为只有它没有任何东西在检查。模块是加在 这个结构上的第四条约束,不是问题本身。 - 100 处标题与 57 处列头改为名词短语,中英成对。 - check_docs_style.sh:register 判据改为疑问词集合(谁、哪、什么、多少、为何、 如何、怎样、怎么),并新增第 4 条覆盖列头。第 1 章标题按名字豁免并写明理由。 - check_docs_structure.sh 第 9 条原先把 fragment 丢掉,于是指向已改名小节的链接 解析到文件就算通过。现在校验 fragment,实现 GitHub 的 slug 规则。它立刻报出 chapter 30 里两个手写就写错的锚点,与这批改名无关。 - skill:R5(背景的范围由问题决定)、判据是疑问词、列头与标题同规,各附本次的 原句作为例子。 四条新判据逐条证伪过:不带问号的疑问句标题、中文列头、英文列头、以及反引号内的 疑问词不得误报。
1 parent c3cebe8 commit 7d0c7cb

57 files changed

Lines changed: 467 additions & 307 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/mcpp-docs-style/SKILL.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,18 +95,19 @@ docs/specs/ ──▶ docs/ 允许
9595

9696
### 四条产生每个决定的规则
9797

98-
| | 规则 | 它排除了什么 |
98+
| | 规则 | 排除的形态 |
9999
|---|---|---|
100100
| **R1** | **一个主题一个拥有者。** 恰好一章拥有一个主题;其余每一章只写一句话并链接过去 | 同一件事被解释两遍,然后各自过期 |
101101
| **R2** | **一章为**有任务的读者**而存在,不为**有名字的机制**而存在** | 按机制建目录,于是「测试」「依赖解析」这类**任务**没有家 |
102102
| **R3** | 每章在**前 15 行**内写明读者、它回答的那一个问题、以及它**排除**什么 | 排除是承重的:它是防止这一章重新吸收 R1 已经分配出去的主题的闸 |
103103
| **R4** | 一个部分是**某类读者的一段弧**,其内部顺序是那类读者需要它的顺序 | 字母序、按特性发布时间排序 |
104+
| **R5** | **背景一节的范围由问题决定,不由方案决定。** 先把读者实际面对的那个问题写完整,再写本工具触及其中的哪一部分 | 只写与本工具最近的那一条成因,读者据此以为问题就这么大 |
104105

105106
### 每章的设计规格
106107

107108
动一章之前,先把这张表填出来。填不出来的那一格,就是还没设计的地方。
108109

109-
|| 要回答什么 |
110+
|| 填写要求 |
110111
|---|---|
111112
| 读者 | 谁在读它。一句话说不出来就是没定位 |
112113
| 那一个问题 | 它存在的理由,一个问句 |
@@ -159,10 +160,24 @@ docs/specs/ ──▶ docs/ 允许
159160
| What you may rely on, and what changes | Stability guarantees |
160161
| 0x —— 人人都需要 | 0x —— 基础 |
161162
| 0x — Everyone | 0x — Fundamentals |
163+
| 背景:模块到了,工具链没跟上 | 背景:C++ 工程侧的工具现状 |
164+
| 谁在为这个落差付账 | 这一现状的代价 |
165+
| 大致相当于谁的活 | 可对照的工具 |
166+
| 长什么样 | 形式 |
167+
| 一个 flag 由哪根轴决定 | 决定一个 flag 的轴 |
168+
| 一条运行时搜索路径可以住在哪里 | 运行时搜索路径的允许位置 |
162169

163170
「…的原因」「…的依据」「…的范围」是把 why 型标题转成名词短语的常用形。
164171
**保留 why 本身,去掉疑问语气。**
165172

173+
**判据是疑问词,不是问号。**「谁在为这个落差付账」「打什么由谁决定」都不带问号,
174+
都是疑问句。检查脚本第一版只匹配 `?` / 吗 / 呢,两句全部通过。判据是这一组词:
175+
谁、哪、什么、多少、为何、如何、怎样、怎么。
176+
177+
**表头单元格与标题同规。** 一个列头按每一条要紧的性质都是标题:它命名一个主题、
178+
被跳读、并且是读者扫描时看的那一行。`| 部分 | 大致相当于谁的活 |` 通过了当时
179+
全部的检查,而它是全树最直白的一处违规。
180+
166181
这条同样管**部分名与段位名**,不只管章节标题。「人人都需要」描述的是受众、
167182
是一个句子片段;「基础」是这一段**是什么**。受众写在每章开头的「读者」那一行,
168183
不写在目录的骨架上。

.github/tools/check_docs_structure.sh

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -144,23 +144,78 @@ for f in .agents/docs/[0-9]*.md; do
144144
|| bad "$f: front matter declares no valid \`status\` (active | landed | superseded | abandoned)"
145145
done
146146

147-
# ── 9. every relative link in docs/ and examples/ resolves ──────────────
147+
# ── 9. every relative link in docs/ and examples/ resolves, fragment included
148148
#
149149
# Rule 3 catches `docs/NN-*.md` named anywhere, including from source comments.
150150
# This is the other half: a Markdown link in a document that points at a file
151151
# which is not there. Both halves are needed -- a chapter moved in this batch
152152
# would satisfy one and break the other.
153+
#
154+
# THE FRAGMENT IS PART OF THE LINK. The first version of this rule discarded
155+
# it (`(?:#[^)]*)?`), so a link to a section that had been renamed resolved to
156+
# the file and was reported correct. Renaming 100 headings for register in one
157+
# batch is exactly the change that produces those, and two hand-written anchors
158+
# in chapter 30 were already wrong before the renames began.
153159
python3 - <<'PYCHECK' || fail=1
154-
import re, pathlib, sys
160+
import re, pathlib, sys, unicodedata
161+
162+
def slug(heading):
163+
"""GitHub's heading slug: lowercase, drop punctuation and symbols, spaces to hyphens.
164+
165+
Category P* and S* covers what github-slugger removes -- ASCII punctuation,
166+
the em dash, the backticks around inline code, and the full-width comma and
167+
colon the Chinese chapters use -- while `-` and `_` are kept because an
168+
anchor is allowed to contain them.
169+
"""
170+
t = re.sub(r"^#+\s+", "", heading).strip().lower()
171+
keep = []
172+
for ch in t:
173+
if ch in "-_":
174+
keep.append(ch)
175+
elif ch.isspace():
176+
keep.append(" ")
177+
elif unicodedata.category(ch)[0] in "PS":
178+
continue
179+
else:
180+
keep.append(ch)
181+
return "".join(keep).replace(" ", "-")
182+
183+
def anchors_of(path):
184+
"""Every anchor the file defines, with GitHub's -1/-2 suffix for repeats."""
185+
seen, out, infence = {}, set(), False
186+
for line in path.read_text(errors="ignore").splitlines():
187+
if line.startswith("```"):
188+
infence = not infence
189+
continue
190+
if infence or not re.match(r"^#{1,6} ", line):
191+
continue
192+
s = slug(line)
193+
n = seen.get(s, 0)
194+
seen[s] = n + 1
195+
out.add(s if n == 0 else f"{s}-{n}")
196+
return out
197+
198+
files = list(pathlib.Path("docs").rglob("*.md")) + list(pathlib.Path("examples").rglob("*.md"))
199+
cache = {}
155200
bad = 0
156-
for f in list(pathlib.Path("docs").rglob("*.md")) + list(pathlib.Path("examples").rglob("*.md")):
157-
for m in re.finditer(r"\]\(([^)#]+?)(?:#[^)]*)?\)", f.read_text(errors="ignore")):
158-
t = m.group(1)
201+
for f in files:
202+
for m in re.finditer(r"\]\(([^)\s]*?)(?:#([^)\s]+))?\)", f.read_text(errors="ignore")):
203+
t, frag = m.group(1), m.group(2)
159204
if t.startswith(("http", "mailto:")):
160205
continue
161-
if not (f.parent / t).exists():
206+
target = (f.parent / t) if t else f
207+
if t and not target.exists():
162208
print(f"FAIL: {f}: link to `{t}` does not resolve")
163209
bad += 1
210+
continue
211+
if not frag or target.suffix != ".md" or not target.is_file():
212+
continue
213+
key = target.resolve()
214+
if key not in cache:
215+
cache[key] = anchors_of(target)
216+
if frag not in cache[key]:
217+
print(f"FAIL: {f}: `#{frag}` is not a heading in {target.as_posix()}")
218+
bad += 1
164219
sys.exit(1 if bad else 0)
165220
PYCHECK
166221

.github/tools/check_docs_style.sh

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
88
# 2. reference docs do not address the reader in the second person
99
# (tutorials do — they are listed below, not inferred)
1010
# 3. docs/X.md and docs/zh/X.md have the same heading structure
11+
# 4. a table's header cells follow the same register as a heading
12+
#
13+
# Rule 4 exists because rule 1 read only lines beginning with `#`, and a column
14+
# header is a heading by every property that matters: it names a topic, it is
15+
# read out of order, and it is what a reader scans. `| 部分 | 大致相当于谁的活 |`
16+
# passed every check in this file while being the plainest register violation
17+
# in the tree.
1118
#
1219
# What it deliberately does NOT check: whether a claim's strength matches its
1320
# evidence. That is the most important rule in the skill and it needs a reader.
@@ -43,19 +50,69 @@ for f in docs/*.md docs/zh/*.md docs/specs/*.md; do
4350
base="$(basename "$f")"
4451

4552
# ── 1. heading register ───────────────────────────────────────────────
53+
#
54+
# An interrogative WORD is the test, not a question mark. `谁在为这个落差付账`
55+
# and `打什么由谁决定` carry no `?` and are questions all the same, and the
56+
# first version of this rule matched `?`/`吗`/`呢` and passed both.
57+
#
58+
# THE ONE EXEMPTION IS BY NAME. `# 00 —— mcpp 是什么` mirrors the English
59+
# `What mcpp Is`, which is a noun clause rather than a question, and a chapter
60+
# title is the chapter's registered name: it appears in three indexes and in
61+
# every citation of the chapter. Exempting it here states the decision;
62+
# narrowing the rule to `##` and deeper would have hidden it.
4663
while IFS= read -r h; do
47-
case "$h" in
48-
*"?"*|*""*|*""*)
64+
if [[ "$f" == "docs/zh/00-what-mcpp-is.md" && "$h" == "# 00 —— mcpp 是什么" ]]; then
65+
continue
66+
fi
67+
# Inline code is quoted material: `cxx_stdlib` and `mcpp.why.toolchain`
68+
# must not be read for register.
69+
bare="$(sed -E 's/`[^`]*`//g' <<< "$h")"
70+
case "$bare" in
71+
*"?"*|*""*|*""*|*""*)
4972
bad "$f: question heading — use a noun phrase: $h" ;;
73+
*""*|*""*|*"如何"*|*"为何"*|*"什么"*|*"多少"*|*"怎样"*|*"怎么"*)
74+
bad "$f: interrogative heading — use a noun phrase (…的原因 / …的依据 / …的范围): $h" ;;
5075
esac
51-
case "$h" in
52-
*"一段话"*|*"讲完"*|*"姊妹篇"*|*"干活"*|*"怎么"*|*"会怎样"*|*"不许"*)
76+
case "$bare" in
77+
*"一段话"*|*"讲完"*|*"姊妹篇"*|*"干活"*|*"会怎样"*|*"不许"*|*"跟上"*|*"付账"*|*"长什么样"*)
5378
bad "$f: conversational heading: $h" ;;
5479
*"The whole idea"*|*"in one paragraph"*|*"Consuming one"*|*"the thing that"*)
5580
bad "$f: conversational heading: $h" ;;
5681
esac
5782
done < <(headings "$f")
5883

84+
# ── 4. table header register ──────────────────────────────────────────
85+
#
86+
# A header row is the row directly above the `|---|---|` separator, so the
87+
# separator is what identifies it; matching every `|` line would read data.
88+
while IFS= read -r cell; do
89+
case "$cell" in
90+
*"?"*|*""*|*""*|*""*)
91+
bad "$f: question in a table header — use a noun phrase: $cell" ;;
92+
*""*|*""*|*"如何"*|*"为何"*|*"什么"*|*"多少"*|*"怎样"*|*"怎么"*)
93+
bad "$f: interrogative table header — use a noun phrase: $cell" ;;
94+
esac
95+
shopt -s nocasematch
96+
if [[ "$cell" =~ ^(what|which|who|how|why|where|whether)([[:space:]]|$) ]]; then
97+
bad "$f: interrogative table header — use a noun phrase: $cell"
98+
fi
99+
shopt -u nocasematch
100+
done < <(awk '
101+
/^```/ { infence = !infence; prev = ""; next }
102+
infence { next }
103+
/^\|[[:space:]:|-]+\|[[:space:]]*$/ && prev ~ /^\|/ {
104+
line = prev
105+
gsub(/`[^`]*`/, "", line) # quoted material, as above
106+
n = split(line, cells, "|")
107+
for (i = 2; i < n; i++) {
108+
c = cells[i]
109+
gsub(/^[[:space:]]+|[[:space:]]+$/, "", c)
110+
if (c != "") print c
111+
}
112+
}
113+
{ prev = $0 }
114+
' "$f")
115+
59116
# ── 2. second person in reference docs ────────────────────────────────
60117
case " $TUTORIALS " in
61118
*" $base "*) ;;

0 commit comments

Comments
 (0)