Skip to content

Commit 9463bfe

Browse files
committed
docs: limits go last, the emit/parse mismatch is recorded, and links are checked
Appending [hooks] to 21 had put it after that chapter's 'Current limitations', which is the one section that has to be last. Both languages reordered. `mcpp emit xpkg` produces a descriptor that `mcpp xpkg parse` rejects for a package that keeps its own mcpp.toml: the emitted mcpp segment carries `manifest = "mcpp.toml"` and no `sources` list, and the validator requires one. Isolated by adding the list by hand, which makes it validate. Recorded as a current limitation in 21 rather than fixed here. Rule 9: every relative Markdown link in docs/ and examples/ resolves. Rule 3 catches a chapter path named from anywhere including source comments; this catches a link whose target is not there. A chapter moved in this batch would satisfy one and break the other.
1 parent 3d75224 commit 9463bfe

3 files changed

Lines changed: 72 additions & 27 deletions

File tree

‎.github/tools/check_docs_structure.sh‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# 6. no emoji under docs/ or in a top-level README
1313
# 7. the generated design-record index is current
1414
# 8. a new design record declares its subject and status
15+
# 9. every relative link in docs/ and examples/ resolves
1516
#
1617
# What it deliberately does NOT check: whether a chapter documents what is
1718
# implemented, whether an assertion's strength matches its evidence, or whether
@@ -139,6 +140,26 @@ for f in .agents/docs/[0-9]*.md; do
139140
|| bad "$f: front matter declares no valid \`status\` (active | landed | superseded | abandoned)"
140141
done
141142

143+
# ── 9. every relative link in docs/ and examples/ resolves ───────────────
144+
#
145+
# Rule 3 catches `docs/NN-*.md` named anywhere, including from source comments.
146+
# This is the other half: a Markdown link in a document that points at a file
147+
# which is not there. Both halves are needed -- a chapter moved in this batch
148+
# would satisfy one and break the other.
149+
python3 - <<'PYCHECK' || fail=1
150+
import re, pathlib, sys
151+
bad = 0
152+
for f in list(pathlib.Path("docs").rglob("*.md")) + list(pathlib.Path("examples").rglob("*.md")):
153+
for m in re.finditer(r"\]\(([^)#]+?)(?:#[^)]*)?\)", f.read_text(errors="ignore")):
154+
t = m.group(1)
155+
if t.startswith(("http", "mailto:")):
156+
continue
157+
if not (f.parent / t).exists():
158+
print(f"FAIL: {f}: link to `{t}` does not resolve")
159+
bad += 1
160+
sys.exit(1 if bad else 0)
161+
PYCHECK
162+
142163
if [[ "$fail" -eq 0 ]]; then
143164
echo "OK: docs structure checks pass"
144165
fi

‎docs/21-commands-by-scenario.md‎

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -195,21 +195,6 @@ included. `mcpp self config --mirror CN|GLOBAL` selects the download mirror;
195195
mcpp and xlings hold this setting separately, so selecting it for one does not
196196
select it for the other.
197197

198-
## Current limitations
199-
200-
- `mcpp why --format json` is defined for the `toolchain` topic only. The other
201-
topics report `'<topic>' has no machine-readable shape yet` and exit non-zero.
202-
- `mcpp search` matches a substring; there is no field selector, and no way to
203-
restrict a search to one namespace.
204-
- `mcpp clean --stale` reads `target/.build_cache`, which holds a bounded number
205-
of recent entries. A project built across more (target, profile) pairs than it
206-
holds loses its oldest entries, and a directory whose entry has been evicted
207-
is then treated as unrecorded — kept while it is newer than `--older-than`,
208-
removed after that.
209-
- `mcpp cache gc --older-than 0` is rejected with `bad --older-than value '0'
210-
(expected <N>{s,m,h,d})`, while `mcpp clean --stale --older-than 0` accepts it.
211-
The two options share a parser but not this case.
212-
213198
## `[hooks]` — Project Build Lifecycle Commands (experimental)
214199

215200
> **Experimental.** A hook cannot currently decide whether a build succeeded.
@@ -370,3 +355,30 @@ A different sound for a successful or failed build. `side_effect = false` is
370355
written out rather than left to the default: it is the value this manifest
371356
wants on its own terms — a missing audio device should never fail a build — so
372357
it will still say so once the key has more than one accepted value.
358+
359+
## Current limitations
360+
361+
- `mcpp why --format json` is defined for the `toolchain` topic only. The other
362+
topics report `'<topic>' has no machine-readable shape yet` and exit non-zero.
363+
- `mcpp search` matches a substring; there is no field selector, and no way to
364+
restrict a search to one namespace.
365+
- `mcpp clean --stale` reads `target/.build_cache`, which holds a bounded number
366+
of recent entries. A project built across more (target, profile) pairs than it
367+
holds loses its oldest entries, and a directory whose entry has been evicted
368+
is then treated as unrecorded — kept while it is newer than `--older-than`,
369+
removed after that.
370+
- `mcpp cache gc --older-than 0` is rejected with `bad --older-than value '0'
371+
(expected <N>{s,m,h,d})`, while `mcpp clean --stale --older-than 0` accepts it.
372+
The two options share a parser but not this case.
373+
374+
`mcpp emit xpkg` produces a descriptor that `mcpp xpkg parse` rejects for a
375+
package that keeps its own `mcpp.toml`. The emitted `mcpp` segment carries
376+
`manifest = "mcpp.toml"` and no `sources` list, and the validator requires one:
377+
378+
```
379+
error: d.lua: xpkg-lua://example.mathkit@0.1.0: error: synthesised manifest
380+
missing sources (mcpp segment must declare `sources = { ... }`)
381+
```
382+
383+
Adding `sources = { "src/*.cppm" }` to the emitted `mcpp` segment makes it
384+
validate. Measured on 2026.9.8.1.

‎docs/zh/21-commands-by-scenario.md‎

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -170,18 +170,6 @@ warning: pre-v1 cache at '/home/speak/.mcpp/bmi' occupies 167.5 MiB and is no lo
170170
`mcpp self config --mirror CN|GLOBAL` 选择下载镜像;mcpp 与 xlings 各自持有这个设置,
171171
为其中一个选定不会为另一个选定。
172172

173-
## 当前边界
174-
175-
- `mcpp why --format json` 只对 `toolchain` 话题有定义。其余话题报
176-
`'<topic>' has no machine-readable shape yet` 并以非零退出。
177-
- `mcpp search` 按子串匹配;没有字段选择器,也没有把搜索限定到单个命名空间的方式。
178-
- `mcpp clean --stale` 读 `target/.build_cache`,它保存的近期条目数量有上限。一个工程
179-
如果构建过的 (目标, profile) 组合多于这个上限,最旧的条目会被挤掉;条目被挤掉的目录
180-
随后按未记录处理 —— 在 `--older-than` 之内保留,超出则删除。
181-
- `mcpp cache gc --older-than 0` 以 `bad --older-than value '0'
182-
(expected <N>{s,m,h,d})` 被拒绝,而 `mcpp clean --stale --older-than 0` 接受。
183-
两个选项共用一个 parser,但这一种取值上不一致。
184-
185173
## `[hooks]` —— 项目构建生命周期命令(实验性)
186174

187175
> **实验性。** Hook 目前**不能**决定一次构建成功与否。所有 Hook 失败都以
@@ -313,3 +301,27 @@ deps = ["xim:mcpp-hooks-audioplayer@0.0.1"]
313301
根据构建成功或失败播放不同提示音。`side_effect = false` 写出来而不是靠默认值:它是
314302
这份 manifest 自己就想要的值——缺个音频设备不该让构建失败——所以等这个键有了不止
315303
一个可接受的值之后,它仍然会这么写。
304+
305+
## 当前边界
306+
307+
- `mcpp why --format json` 只对 `toolchain` 话题有定义。其余话题报
308+
`'<topic>' has no machine-readable shape yet` 并以非零退出。
309+
- `mcpp search` 按子串匹配;没有字段选择器,也没有把搜索限定到单个命名空间的方式。
310+
- `mcpp clean --stale` 读 `target/.build_cache`,它保存的近期条目数量有上限。一个工程
311+
如果构建过的 (目标, profile) 组合多于这个上限,最旧的条目会被挤掉;条目被挤掉的目录
312+
随后按未记录处理 —— 在 `--older-than` 之内保留,超出则删除。
313+
- `mcpp cache gc --older-than 0` 以 `bad --older-than value '0'
314+
(expected <N>{s,m,h,d})` 被拒绝,而 `mcpp clean --stale --older-than 0` 接受。
315+
两个选项共用一个 parser,但这一种取值上不一致。
316+
317+
对一个自带 `mcpp.toml` 的包,`mcpp emit xpkg` 产出的描述符会被 `mcpp xpkg parse`
318+
拒绝。产出的 `mcpp` 段带 `manifest = "mcpp.toml"` 而没有 `sources` 列表,而校验器
319+
要求有一个:
320+
321+
```
322+
error: d.lua: xpkg-lua://example.mathkit@0.1.0: error: synthesised manifest
323+
missing sources (mcpp segment must declare `sources = { ... }`)
324+
```
325+
326+
给产出的 `mcpp` 段补上 `sources = { "src/*.cppm" }` 即可通过校验。实测于
327+
2026.9.8.1。

0 commit comments

Comments
 (0)