| subject | docs |
|---|---|
| status | active |
This restructures mcpp's documentation as a whole: the user documentation
(docs/), the specifications (docs/specs/), and the design records
(.agents/docs/). Each serves a different reader, so each gets its own
admission criterion, register, language policy, stability promise and
lifecycle — and the relation between them becomes a rule that a check can
enforce rather than a habit.
It supersedes the documentation half of
.agents/docs/2026-09-08-examples-curriculum-and-documentation-plan.md. That
plan's example curriculum stands and is referenced here rather than restated;
its chapter-level proposals (splitting 05, adding chapters 22 and 23) are
carried in unchanged and placed inside the architecture this document defines.
Measured 2026-09-08 against origin/main at 6e1c65c6.
| tree | files | lines | index |
|---|---|---|---|
docs/ chapters (English) |
23 | 11,313 | docs/README.md, 35 lines |
docs/zh/ |
23 | — | docs/zh/README.md |
docs/specs/ |
5 | 1,156 | docs/specs/README.md, 42 lines |
.agents/docs/ |
268 | 109,146 | .agents/docs/README.md, one heading, no body |
.agents/skills/ |
4 | 997 | none |
The design records are 8.8 times the entire user-facing documentation
(11,313 + 1,156 lines), and their whole index is the single line
# 开发/方案文档目录.
-
The architecture is stated exactly once, in a leaf. The three-way division of labour — user docs / specs / design docs, with an audience named for each — exists as a table in
docs/specs/README.md.docs/README.md, which is where a reader arrives, does not mention.agents/docsat all. -
The specifications are written in Chinese, inside the English tree, with no
docs/zh/counterpart. Every chapter underdocs/has adocs/zh/mirror whose heading structure CI compares.docs/specs/has neither: it is Chinese-primary and unmirrored. -
The style checker's scope is a glob, and the glob is why.
.github/tools/check_docs_style.shiteratesdocs/*.md docs/zh/*.md. That pattern does not descend, sodocs/specs/is exempt from the register rules and from the parity loop — by accident rather than by decision. Defect 2 is the visible consequence. -
docs/README.mdlists two of four specifications. SPEC-002 (target-side.md) and SPEC-003 (exit-codes.md) exist, are indexed indocs/specs/README.md, and are absent from the tree's front page. -
User documentation cites design records. Five chapters send a reader to
.agents/docs/…(05,08twice,11,20), and fourdocs/zh/chapters do the same. A design record carries no stability promise and describes a moment; a user chapter that ends in one has delegated a question it should have answered. -
Six code comments cite chapters that do not exist.
docs/35-pack-design.md,docs/04-schema-xpkg-extension.mdanddocs/34-release-readiness.mdare named frommodules/manifest/src/,src/pack/,src/pm/andsrc/publish/— survivors of an earlier numbering. Nothing checks that a cited document exists. -
The design tree has no taxonomy and no status. Filenames carry an implicit one — 92 contain
design, 60plan, 20analysis, 7review, and 65 contain none of the sixteen classifier words in use — and nothing records whether a record is being executed, has shipped, was superseded, or was abandoned.2026-09-05-heterogeneous-build-ecosystem-design-v2.mdand2026-09-06-ecosystem-plan-v3.mdencode that in the filename, which works for the two documents whose authors thought of it.
| cited as | files citing | where |
|---|---|---|
docs/NN-… |
103 | .agents/, docs/, src/, modules/ |
examples/NN-… |
59 | .agents/ 17, docs/ 18, examples/ 9, tests/ 7, .github/ 4, src/ 3 |
docs/05-mcpp-toml alone is cited 90 times.
Yesterday's plan proposed renumbering five example directories, estimating the cost as "external links". The measurement says otherwise: the example numbers are cited from seven test files, four CI workflows and three source files, where a stale path is a broken job rather than a broken link. Both trees are therefore treated the same way in §6: numbers are stable; grouping happens in the index.
docs/specs/README.md already says what the three trees are for, and its table
is correct. The defect is placement: it sits two levels down, in the tree whose
audience is the narrowest of the three, in a language the enclosing tree does
not use. A contributor who wants to know where a document belongs will not find
it, so documents land where the last similar one landed.
That is the whole mechanism behind defects 1, 5 and 7. Nothing enforced the division because nothing published it.
The numbered sequence reads as one audience and is not:
| chapters | audience |
|---|---|
| 00, 01, 02, 03, 05, 06, 07, 10, 12–18, 20, 21 | people using mcpp |
| 08 (toolchain internals), 11 (machine output) | people writing against a mechanism |
| 04 (build from source), 09 (releasing mcpp) | people changing mcpp |
09-release.md documents how a maintainer cuts a release. It is chapter nine of
a sequence whose first three chapters are hello-world, dependencies and
packaging. Nothing marks the transition.
Four directions are in use and only one of them is examined:
- spec → design record, in a metadata row: correct, and it is the
convention
docs/specs/README.mdalready prescribes. - user chapter → spec: correct and underused.
- user chapter → design record: wrong, and present five times.
- code comment → chapter: unchecked, and wrong six times.
Each class is defined by its reader. Everything else — register, language, stability, lifecycle — follows from that and is stated so the answer to "where does this belong" is mechanical.
用户文档 docs/ |
规范 docs/specs/ |
设计记录 .agents/docs/ |
|
|---|---|---|---|
| reader | someone with a task in hand | someone implementing against the mechanism: index authors, downstream tools, contributors | whoever works on that change, and whoever later asks why it is like this |
| question it answers | how do I do X | what exactly is guaranteed, and is it implemented yet | why is it this way, and what was refuted |
| emphasis | completing the task; the shortest correct path | precision; every rule tagged with implementation status | the reasoning and the measurements, including the ones that overturned the plan |
| register | declarative reference; tutorials may address the reader | RFC 2119 (必须 / 应当 / 可以) | narrative permitted; "why" is the content |
| language | English + docs/zh/ parity, CI-checked |
decision in §6.2 | the language of the round; new writing in English academic register |
| stability | additive; a spelling is kept as an alias | numbered, versioned, state machine | immutable once the change lands |
| lifecycle | kept current with the implementation | Draft → Review → Accepted → Superseded | active → landed → superseded / abandoned |
| history | none — a chapter describes today | a change record at the end | it is history |
| admission criterion | a reader with this task cannot finish without it | two independent implementations could disagree without it | a decision was made whose reasoning would otherwise be lost |
| who reviews | anyone who has done the task | whoever owns the mechanism | whoever did the work |
Three consequences worth naming because they are the ones that get violated:
A user chapter never records history. "This was a bug until 2026.8.16" is a design record's sentence. A chapter states what is true and, where a version matters, states the floor: "2026.9.6.5+".
A design record is never edited after its change lands — except to add a status line or a correction block that says what later measurement overturned. The alternative is a document that silently becomes a claim about the present, and the repository has already met the failure that produces: a decision written a second time without reading the first.
A specification is the only tree with a normative voice. If a user chapter finds itself writing 必须, the content belongs in a spec and the chapter should cite it.
.agents/skills/ holds four procedure documents for agents:
mcpp-usage, mcpp-contributing, mcpp-release, mcpp-docs-style. They are
not a fourth class of documentation; they are executable procedure —
ordered steps with criteria, addressed to an agent rather than a reader.
The rule that keeps them from becoming a fourth copy: a skill states the
procedure and cites the chapter for the explanation; it does not restate the
explanation. mcpp-release and docs/09-release.md are the pair to watch —
the skill is the checklist, the chapter is the reasoning, and the version-number
rules must exist in exactly one of them.
Six edges; four allowed, two forbidden.
docs/ ────────────────▶ docs/specs/ allowed (cite for exact semantics)
docs/ ─ ─ ─ ─ ─ ─ ─ ▶ .agents/docs/ FORBIDDEN
docs/specs/ ──────────────▶ .agents/docs/ allowed, metadata row only (provenance)
docs/specs/ ──────────────▶ docs/ allowed (point at the how-to)
.agents/docs/ ────────────▶ anything allowed
code ──────────────▶ docs/ or docs/specs/ allowed, and the target must exist
Why the forbidden edge is forbidden. A design record describes a moment and carries no stability promise. Sending a user into one means either the chapter is incomplete, or the record holds something that has become normative. Both have a fix, and neither is a link:
When a user chapter wants to cite a design record, the content is promoted — into the chapter if it is how-to, into a spec if it is a guarantee. The design record is then cited by the spec's metadata row, where provenance belongs.
The five existing leak sites are the promotion worklist, and each names what it
would promote: schema ownership (→ SPEC-004), toolchain naming and the hermetic
link model (→ 08 or a spec), the machine-output protocol's design (→ 11,
which already has SPEC-003 beside it), the heterogeneous design v2 (→ 20).
Code comments cite documents, and the document must exist. Six do not today. The check is one line and is listed in §7.
Renumbering is refused on the measurement in §1.3. docs/README.md becomes a
grouped index over the numbers that exist, and gains the two new chapters at the
next free numbers.
| part | chapters | reader |
|---|---|---|
| I — Using mcpp | 00, 01, 05, 06, 07, 21, 22 (new) | someone building something |
| II — Shipping what you built | 02, 10, 12 | someone publishing |
| III — Toolchains and targets | 03, 16, 14, 15, 13, 17 | someone whose target is not the host |
| IV — Devices and accelerators | 18, 20 | someone with a GPU or a board |
| V — Extending mcpp from outside | 23 (new) | rule-package and index authors |
| VI — Machine interfaces and compatibility | 11, 19, SPEC-003 | tool authors, release engineers |
| VII — Contributing to mcpp itself | 04, 08, 09 | maintainers |
Part VII is the change that fixes §2.2: 04, 08 and 09 keep their numbers
and stop appearing to be step four, step eight and step nine of a user's path.
Before the parts, one table mapping an intention to a path across all three teaching surfaces — chapters, examples and templates. This is the table yesterday's plan specified; it is unchanged and belongs here because it is the entry point for the whole documentation set, not for the examples alone.
Carried unchanged from the earlier plan, restated here as a table only:
moved out of 05 |
lines | to |
|---|---|---|
| features and capabilities (§2.8–2.8.2) | 394 | 22 — Features and capabilities (new) |
[xlings], the project environment (§2.13) |
317 | 17 |
the C++ runtime contract (§2.3 cxx_runtime) |
242 | 03 |
[target.*] conditioning (§2.7.1) |
138 | 14 |
| host tools from a dependency (§2.14) | 274 | 07 |
[hooks] (§2.16) |
192 | 21 |
05 lands at about 1,570 lines. 23 — Authoring a rule package is assembled
from docs/07 §"Writing a rule package", the mcpp::action roles, the island
ladder's four rungs, device_extensions / rule_module, and the probe and
advisory channels.
SPEC-001 identity, SPEC-002 target side, SPEC-003 exit codes, SPEC-004 manifest
semantics. Two are missing from docs/README.md (defect 4) — a one-line fix.
Two candidates for SPEC-005 and SPEC-006 emerge from §4's promotion worklist and from the accelerator work:
- The artifact compatibility tag, including the
accelfield's grammar, the coverage relation (family targets, portable-form floors) and the matching algorithm. It is normative, it has a second implementer today (mcpp-indexdescriptors are written by hand against it), and it currently lives indocs/20§"What a prebuilt artifact states" — a user chapter writing rules. - The
build.mcppdirective protocol, whose version number already functions as a normative contract (kProtocolVersion; an engine refuses a program declaring a higher one) and whose only description is a section ofdocs/07.
Both are promotions of existing text rather than new writing, and both are listed as staged work rather than decided here.
Today: Chinese, unmirrored, unchecked. Three options.
| cost | consequence | |
|---|---|---|
(a) English primary + docs/zh/specs/ mirror |
translate 1,156 lines once, then parity forever | uniform with the enclosing tree; reachable by the downstream tool authors the specs name as their audience |
(b) move to a top-level specs/, keep Chinese |
rewrite paths in ~15 citation sites | admits that specs are not user docs, and abandons the audience that cannot read them |
| (c) English only, no mirror | translate once | breaks the tree's own parity policy in the other direction |
Recommendation: (a). The audience the specs themselves name — index authors and downstream tooling — is the least likely of the three audiences to be Chinese-reading, and the specs are the documents where a misreading is most expensive.
docs/specs/README.md already requires a metadata table and a change record in
every spec. Nothing checks it. §7 adds that, and the same check verifies that
every spec in the directory appears in both indexes.
268 files and 110,143 lines are not rewritten. Three additions, all of which apply to new documents and are backfilled only where a reader needs them.
---
subject: heterogeneous # one of a short controlled list
status: landed # active | landed | superseded | abandoned
superseded_by: 2026-09-07-module-first-heterogeneous-surface.md
implements: [docs/20-heterogeneous-builds.md, docs/specs/SPEC-005]
---status is what the tree lacks most. A reader opening
2026-09-05-accelerator-support-design.md today cannot tell from the document
that …-v2 and then ecosystem-plan-v3 moved past it; the filename carries it
for the two authors who thought of the convention.
.agents/docs/README.md is generated: grouped by subject, ordered newest
first within each, showing status and title. For the 268 existing records the
title comes from the first heading and the status is landed unless the
document says otherwise — a mechanical default that is right for almost all of
them, since they describe changes that shipped.
The generator is a script, and the index is checked in so that reading the tree on GitHub works.
Derived from the filenames rather than invented: toolchain, packaging,
resolution, target, freestanding, heterogeneous, ecosystem,
performance, platform, docs, process. Eleven. A twelfth is added when a
document does not fit, which is a decision someone makes rather than a field
someone fills in freely — a free-text subject would reproduce the
sixty-five-uncategorised state in a new column.
Four records carry no date prefix (fix-xlings-package-home-detection.md,
llvm-install-failure-analysis.md, platform-abstraction-plan.md,
platform-remaining-ifdefs-report.md) and todos/ holds four more under a
directory the convention does not mention. Both are absorbed by the front matter
and the generated index without moving a file.
.github/tools/check_docs_style.sh keeps its three rules and gains a scope; a
new check_docs_structure.sh carries the rules that are about the architecture
rather than the prose.
| # | rule | reads |
|---|---|---|
| 1 | the existing three: heading register, second person in reference docs, bilingual heading parity | text |
| 2 | scope extended to docs/specs/ |
the glob, corrected to descend |
| 3 | no docs/** file cites .agents/ |
the forbidden edge in §4 |
| 4 | a spec cites .agents/ only inside its metadata table |
line position |
| 5 | every docs/…md path named anywhere in the repository resolves |
catches defect 6, in code as well as prose |
| 6 | every spec in docs/specs/ appears in docs/README.md and in docs/specs/README.md |
catches defect 4 |
| 7 | every spec has a metadata table and a change record | the contract docs/specs/README.md already states |
| 8 | a new .agents/docs/*.md has front matter with a known subject and status |
new files only, by comparing against the merge base |
| 9 | .agents/docs/README.md matches what the generator would produce |
the index cannot drift |
Rule 5 is the one with reach beyond documentation: a comment in src/ naming a
chapter is a citation, and today six of them are stale. Rule 8 is scoped to new
files on purpose — a rule that demanded front matter on 268 existing records
would be satisfied by a mechanical pass that adds a field nobody chose.
What none of them check. Whether a chapter is right, whether a spec's rules are complete, or whether a design record's reasoning holds. The checks establish that each document is in the tree its content belongs to and is reachable from that tree's index; a reader establishes the rest.
| stage | content | independent? |
|---|---|---|
| 1 | publish the architecture: the three-way table moves to docs/README.md; role index; parts I–VII; the two missing specs listed |
yes — index only, no content moves |
| 2 | close defects 4, 5 and 6: promote the five leak sites, fix the six code citations | yes |
| 3 | checks 2–7 (scope, citation direction, resolvable paths, spec index and metadata) | after stage 2, so the tree is already clean when the check turns on |
| 4 | translate the specs, mirror to docs/zh/specs/, put them under the parity loop |
yes; the largest single translation cost |
| 5 | front matter, subject list, generated index, checks 8–9 | yes |
| 6 | split 05; add chapters 22 and 23 |
after stage 1, which is where their entries in the index go |
| 7 | SPEC-005 (compatibility tag) and SPEC-006 (directive protocol), each a promotion | last; each is its own review |
Stages 1–3 are a week's worth of work and close five of the seven defects. Stage 4 is the largest and is separable. Stage 6 is the chapter work carried from the earlier plan and is where its example work rejoins.
- It does not renumber anything. §1.3 measured the cost in test and CI files, not link rot, and this reverses a recommendation made yesterday on an estimate.
- It does not rewrite the 268 design records. Their value is that they were written when the reasoning was fresh; a pass to normalise them would edit documents whose defining property is that they are not edited.
- It does not merge
.agents/skills/into any tree. They are procedure, and §3.1 states the rule that keeps them from duplicating explanation. - It does not introduce a documentation site generator. Every tree stays readable as Markdown on GitHub, which is where its readers are; a generated site is a separate decision with its own hosting and staleness questions.
- It does not change what is in a chapter beyond the moves listed. The register work landed in #452 and the content is largely correct; this is an architecture change, not a rewrite.
-
The specs' language (§6.2). Recommendation is English primary with a
docs/zh/specs/mirror, at 1,156 lines of translation. The alternative that is cheapest — leave them Chinese and exempt them explicitly rather than by glob accident — is defensible if the specs' real audience is this ecosystem's own contributors rather than the downstream tool authors they name. -
Whether
08-toolchain-internalsbelongs in part VII. It documents mechanism for people writing against it as much as for people changing mcpp. Placing it under "Contributing" may hide it from the first group. -
SPEC-005's scope. The compatibility tag alone, or the tag plus the prebuilt-artifact selection algorithm? The algorithm is implemented once, in
src/pack/prebuilt.cppm, and has no second implementer today — which is the admission criterion in §3 arguing against including it. -
Whether
status: landedis the right default for the backfill. It is right for almost all of the 268 and wrong for the handful that were written and never executed. The alternative isunknown, which is honest and makes the generated index less useful on its first day. -
Whether design records should carry a correction block. §3 says a record is immutable except for a status line. Several records in the tree have been corrected in place by later measurement, which is how their own value was preserved. Making that a named, dated block at the end — rather than an edit in the body — would keep both properties.
Shipped as PR #590 on docs/architecture-three-trees. Stages 1, 2, 3, 5 and 6
of §8 are done; stage 4 (translating the specifications) and stage 7 (SPEC-005 /
SPEC-006) are not, and §12.4 says why.
| surface | before | after |
|---|---|---|
docs/05-mcpp-toml.md |
3,129 lines | 1,626 |
| largest chapter | 3,129 | 1,218 (07) |
.agents/docs/README.md |
one heading | generated, 313 lines over 269 records |
| user chapters citing a design record | 9 | 0 |
| specifications listed on the front page | 2 of 4 | 4 of 4 |
stale docs/NN-*.md citations |
6 | 0 |
| manifest keys with no example | 13 | 7 |
build.mcpp API names used by an example |
10 of 38 | 12 of 38 |
| structure rules enforced in CI | 0 | 10 |
The seven manifest keys still uncovered are the ones §3.2 classifies as a code
block rather than an example — [hooks], scan_overrides, cxx_runtime,
module_extensions, platforms, [feature-xlings] — plus two the plan
intended to cover and did not, for reasons worth stating rather than
carrying forward as debt:
[resources]compiles on PE targets only, and a declared file must exist on every target. An example for it would add a binary.icoto the tree and then assert nothing on the two platforms most of this repository's CI runs on. It belongs to a Windows-facing example, which does not exist yet.[runtime]is the provider-neutral runtime contract a prebuilt library declares. §4.2 folded it into the publishing example, which is blocked (§12.4).
Read as rendered text rather than as a diff, which is the rule the skill states for exactly this reason:
Fourteen cross-references, in both languages, left pointing at section numbers
that had moved with their sections. §2.8.1 cited from inside the chapter
that now contains it; §2.13 cited from the chapter that now is it; §2.14
cited from the chapter it moved to. Every heading structure stayed valid and the
bilingual parity check stayed green throughout, because neither of those is a
check about meaning.
A mechanical move keeps every structure valid and leaves the prose pointing at the old shape. The checks cannot see it; a reader can.
Chapter 17's "Related chapters" ended up mid-chapter, and its own bullet for
05 said "every manifest key, including [xlings]" — the one claim the move
made false.
Four translations were missing content that every check called equal. The style checker compares heading structure, which is what catches a page that has fallen a section behind. It cannot see a table row or a fenced block that never crossed. Found while comparing the two languages by hand after the split, then by the rule written for it:
| where | what was missing |
|---|---|
docs/zh/22 (from 05) |
the [features] section had no body: the TOML example that defines the syntax, and all three bullets |
docs/zh/05 §2.11 |
the identity verdict table and the two paragraphs around it |
docs/zh/08 §7.4 |
the shared-library format table, Mach-O's install name, and the refusal of an unservable target |
docs/zh/16 |
seven target rows — the 简体中文 support matrix listed 14 targets where the English listed 21 |
None was introduced by this batch. The last one is the argument for rule 10 on its own: a bilingual pair can agree on every heading and disagree about which targets the tool supports.
Two rules were written in the skill and not applied. Chapter 22 had no
"Current limitations" while the skill calls that section mandatory. And "every
document states which rung it is on" was followed by no chapter and is not
useful to a reader; it became "a Related documents line naming the rung above
and below", which is what the chapters actually do and what carries the
gradient. A rule nothing follows is worse than no rule, because it makes the
skill unfalsifiable.
Three defects met while building 12-a-new-device-language, each now in its
README because a rule author will meet all three:
- An action's command runs from the build directory, so a rule joins
manifest_dir()to the package-root-relative pathsdevice_sources()gives. - A pipeline's exit status is its last command's. The first
toycsummed with… | paste -sd+ - | bc; when an earlier stage produced nothingbcstill exited 0,set -enever fired, and the program compiled, linked, ran and printed0. - The compiler is a declared input. Without it, editing
toyc.shleft every edge clean and the artifact kept the previous compiler's bytes.
And one in the tooling, whose first description in this document was wrong in a
way worth keeping. mcpp emit xpkg writes manifest = "mcpp.toml" into the
mcpp segment; mcpp xpkg parse reports that key as unknown and exits 1,
and the "missing sources" error follows from it — the ignored key means nothing
derives sources from the manifest it names.
The first reading said "adding
sourcesby hand makes it validate". It does not. It removes the second error, the summary prints, and the command still exits 1. The conclusion came from reading the OUTPUT rather than the STATUS, which is the failure this repository already has a memory for.
mcpp-index uses that key in 0 of 218 descriptors: a package keeping its own
mcpp.toml omits the mcpp field entirely and mcpp looks the manifest up under
the version directory. So the defect is not a missing list — it is that emit
writes a descriptor shape the resolver in the same release does not implement
and no published descriptor uses. Recorded in docs/21; not fixed in a
documentation change.
build_examples.sh refused all four new example roots for being in neither
BUILD nor SKIP. That is the denominator discipline working, and it is the
same shape §7 gives the coverage check.
The specifications' language (§6.2, §11.1). English primary with a 简体中文
mirror is the recommendation and is 1,156 lines of translation. It is an open
question in this document, and translating before it is answered is the wrong
order. What did ship is the half that is not a question: the register rules now
cover docs/specs/, which they were exempt from by a glob rather than by a
decision.
SPEC-005 and SPEC-006 (§6.1). Each is a promotion of text that exists and each is its own review.
A publishing round-trip example (§4.3, B3). Blocked on the emit/parse
mismatch above: the loop it would teach cannot be shown end to end while the
descriptor mcpp emit xpkg writes is one mcpp xpkg parse refuses.
[resources] and [runtime] were to be folded into that track and are
therefore still uncovered.
14.7's --no-accel diagnostic and L0 of the rule ladder are unaffected by
this batch and remain where the heterogeneous plan records them.