Skip to content

Commit 7cb5334

Browse files
committed
docs: adopt a documentation style guide and bring the docs into compliance
Corosio's documentation had no stated standard and no way to check one. This adds both, and applies them. **The standard.** `doc/STYLE_GUIDE.md` defines five axes -- Structure, Accuracy, Wording, Completeness, Presentation -- over the Diátaxis modes, a single-source-of-truth rule for code and signatures, a controlled vocabulary, and the enforcement tiers that say which rules block a merge. **The machinery.** `doc/lint/` holds nine checks: structural AsciiDoc and nav rules, sentence length over both corpora, docstring extraction so prose rules reach the headers, tagged-include resolution, MrDocs reference warnings, a self-test that mutates the linters and asserts they notice, and a gate that compares a run against `baseline.json`. `doc/.vale/` adds the Corosio styles and vocabulary. The Documentation workflow runs all of it, with the structural and sentence-length rules blocking and the reference surface reporting. **The compliance work.** Every page declares its Diátaxis mode; listing blocks are tagged and sourced from compiled snippets rather than pasted; prose links the reference through `cpp:` macros instead of restating signatures; tense, fluff, and terminology follow the guide. The reference side documents every parameter, return value, and backend trait the generator asked for, and MrDocs now builds the public headers with warnings enabled and reports none. **One API change.** The awaitables returned by the I/O initiators were plain structs whose captured arguments, out-parameters, constructor, and CRTP dispatch hook were all public, committing the library to a surface it never intended to offer. Only `await_ready`, `await_suspend`, and `await_resume` are the interface, and the compiler is what calls those; everything else is private now. Member-initialisation order is unchanged. **Repairs the audit turned up.** `message_flags::dont_route` does not exist and five docstrings cited it; `native_tcp`/`native_udp` claimed `family()` was a compile-time constant when only `type()` and `protocol()` are; `local_stream.hpp` referenced a symbol that exists nowhere, which MrDocs renders as nothing rather than a broken link; `io_context.hpp` pointed at a `post()` overload that no longer exists; `shutdown_type` omitted `udp_socket` from the types that use it; `4a.tcp-networking.adoc` re-taught TCP internals the networking tutorial owns and now cross-links them. **Cancellation and message-flag claims.** The cancellation wording `289c5d2c` introduced generalises too far. `decode_io_result` lets a decided result outrank the cancellation flag only when `bytes > 0`, so "an operation whose result is already decided reports that result" holds for byte-transferring operations and fails for zero-byte ones. Narrowed on `tcp_acceptor::cancel`, its `implementation::cancel`, and `resolver::cancel`, whose operations -- accept, wait, resolve -- never transfer a byte, so a racing cancel always reports `operation_canceled`. Left as it was on `tcp_socket`, `local_stream_socket`, `local_datagram_socket`, `udp_socket` and `stream_file`, which do transfer bytes. `close()` in `random_access_file.hpp` and `stream_file.hpp` still carried the pre-`289c5d2c` unconditional claim although it tears down through the same path `cancel()` uses; both now defer to `cancel`'s contract. `udp_socket::implementation`'s four operations described `flags` as "Platform message flags (e.g. `MSG_DONTWAIT`)". At that layer the parameter still carries the portable `message_flags` bit pattern -- the public overloads forward `static_cast<int>(flags)` unchanged and `to_native_msg_flags` translates inside the backend -- and `MSG_DONTWAIT` is not in that mapping at all, which maps only peek, out_of_band and do_not_route. Also: `@return` added to four non-void functions that documented only `@throws` (`random_access_file::size`, `stream_file::implementation::size` and `::release`, `native_resolver::operator=`); `@tparam Ex` added to the executor constructors of `local_stream_socket` and `local_datagram_socket`, matching `tcp_socket`; and `basic_mocket::write_some`'s `@return` now states the partial-write contract `7bc2fa76` introduced. `doc/prompts/` carries the audit and repair tooling that found these, and `doc/design/style-guide-compliance.md` records the adoption plan.
1 parent b2f2937 commit 7cb5334

148 files changed

Lines changed: 9243 additions & 1909 deletions

File tree

Some content is hidden

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

‎.github/workflows/docs.yml‎

Lines changed: 346 additions & 1 deletion
Large diffs are not rendered by default.

‎doc/.gitignore‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
node_modules/
22
build/
3+
lint/.docstrings/
4+
# Vale-managed style packages (regenerated by `vale sync`); Corosio/ is ours and is tracked.
5+
.vale/styles/Google/
6+
.vale/styles/Vale/

‎doc/.vale.ini‎

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
StylesPath = .vale/styles
2+
MinAlertLevel = warning
3+
; Pinned deliberately. A bare `Packages = Google` resolves to whatever release the
4+
; feed serves at `vale sync` time, so the corpus moves underneath baseline.json and
5+
; findings appear as NEW without a word of prose changing. Note the project also
6+
; moved errata-ai -> vale-cli. Bump this URL on purpose, and reseed in the same change.
7+
Packages = https://github.com/vale-cli/Google/releases/download/v0.7.1/Google.zip
8+
; Corosio domain vocabulary: .vale/styles/config/vocabularies/Corosio/accept.txt.
9+
; It holds genuine prose words and proper nouns ONLY. Bare C++ identifiers used as
10+
; running text stay unlisted on purpose — they are style-guide B1 defects and must
11+
; keep showing up as Vale.Spelling alerts until the prose is fixed.
12+
Vocab = Corosio
13+
[*.adoc]
14+
BasedOnStyles = Vale, Google, Corosio
15+
; NO BlockIgnores here, on purpose. Vale's native AsciiDoc handling (it shells out to
16+
; asciidoctor and only lints extracted prose nodes) already excludes delimited listing
17+
; blocks — `[source,cpp]`/`----`, bare `----`, `....` literal blocks, blocks nested in
18+
; list items or admonitions, `role=pseudocode`/`role=external`, and callout markers are
19+
; all skipped natively. A `BlockIgnores = (?s) *(\[source.*?----.*?----)` substitution
20+
; does NOT additionally protect anything — it destroys the `----` delimiters before
21+
; asciidoctor sees them, which corrupts the block structure and hands the code inside to
22+
; the linter as if it were a paragraph. That was measured on Capy's corpus, where
23+
; removing the line dropped `.adoc` warning-level alerts 503 -> 376; Corosio inherits the
24+
; decision rather than re-deriving it. If you are tempted to add a BlockIgnores line for
25+
; source blocks: don't. Confirm first, with an isolated fixture, that Vale is actually
26+
; failing to skip something.
27+
;
28+
; One Vale/asciidoctor artifact to know about before chasing a missing alert: a
29+
; correctly-excluded code block can suppress an UNRELATED, later Vale.Spelling or
30+
; Google.Colons alert when the block's own text shares a SUBSTRING with the flagged word,
31+
; and only when the block sits BEFORE the flagged prose in the file. Reduced fixture:
32+
; `// token` before a paragraph containing `foo_token` suppresses the alert; `// hello`
33+
; before it does not. This is a position-resolution artifact, not something any
34+
; BlockIgnores/TokenIgnores value controls — do not try to "fix" it with a config change
35+
; without a bite-tested fixture proving the change does something.
36+
;
37+
; Ignore inline code spans (backticks) AND `cpp:target[...]` reference macros: the B1
38+
; conversion replaces backtick symbol spans with cpp: macros, and their symbol text must
39+
; stay unlinted, exactly as the backtick spans were.
40+
;
41+
; The third clause is the fixed label of the boost-wide thread-safety idiom
42+
; ("Distinct objects: Safe." / "Shared objects: Unsafe."). Each instance is a genuine
43+
; Google.Colons hit, but the form is boost-wide and Corosio does not get to rewrite it;
44+
; `grep -ro '\(Distinct\|Shared\) objects:' --include='*.hpp' include` reports 72, spread
45+
; over io_context, resolver_results, endpoint, signal_set, tcp_acceptor, the TLS streams
46+
; and others. Only the LABEL and its colon are blanked, so whatever follows stays fully
47+
; linted by every other rule; the pattern deliberately does NOT spell out "Safe."/
48+
; "Unsafe." because instances continue into longer clauses that a phrase-exact form would
49+
; leave exposed while suppressing its siblings.
50+
; This is NOT a Google.Colons demotion, on purpose: demoting the rule would also hide the
51+
; genuine non-idiom Colons hits.
52+
TokenIgnores = (\x60[^\x60]+\x60), (cpp:[^\s\[]*\[[^\]]*\]), ((?:Distinct|Shared) objects:)
53+
54+
; --- Google house-style pack: deliberately demoted, not abandoned ----------------
55+
; These eight rules encode GOOGLE's house style, not Corosio defects, and are scoped out
56+
; of the "vale clean" criterion. They are demoted to `suggestion` (below MinAlertLevel)
57+
; rather than removed, so a curious reader can still run
58+
; `vale --minAlertLevel=suggestion` and see them. Counts below are measured on Corosio's
59+
; two corpora at the port commit — `.adoc` = doc/modules, docstrings =
60+
; lint/.docstrings — and are the same ruling Capy made, re-measured here rather than
61+
; inherited.
62+
;
63+
; Google.Headings — Corosio writes Title Case section headings; Google style mandates
64+
; sentence case. Retitling every heading is a user-visible house-style change, not a
65+
; defect fix.
66+
; Google.WordListCase — Google's capitalisation list for words like "Internet"/"email";
67+
; disagrees with Boost usage, not with Part C. Note Corosio's networking tutorial uses
68+
; "Internet" heavily, so this rule is noisier here than in Capy.
69+
; Google.EmDash — bans spaced em dashes. Corosio uses ` -- ` (AsciiDoc's em-dash form) as
70+
; a deliberate typographic convention.
71+
; Google.We / Google.FirstPerson — ban first-person. The tutorial and design prose
72+
; address the reader directly by design (D1/D3).
73+
; Google.Latin — bans "e.g."/"i.e."; both are standard in Boost reference documentation.
74+
; Google.Quotes — demands commas and periods inside quotation marks (US convention).
75+
; Corosio quotes code-like strings, where moving punctuation inside the quotes would
76+
; misstate the string's contents.
77+
; Google.Spacing — flags spacing around punctuation in prose that is mostly quoted code.
78+
;
79+
; NOT demoted, on purpose: Google.Will (a genuine C4 signal, and C4 is gated),
80+
; Google.Colons, Google.OxfordComma, Google.LyHyphens, Google.Units, Google.Ordinal.
81+
;
82+
; Google.LyHyphens misfires on Corosio's `family-*` compounds -- `family-neutral`,
83+
; `family-sensitive`, `family-generic`, `family-specific`. The rule targets adverb
84+
; hyphenation (`newly-created`) and matches these only because "family" ends in "ly".
85+
; The hyphens are correct: they are compound adjectives, not adverbs. The rule stays
86+
; un-demoted because it catches the real thing elsewhere, so this bounded set of false
87+
; positives is carried in baseline.json instead -- grandfathered on purpose, not by
88+
; accident. Re-check it if the `family-*` vocabulary grows.
89+
Google.Headings = suggestion
90+
Google.WordListCase = suggestion
91+
Google.EmDash = suggestion
92+
Google.We = suggestion
93+
Google.FirstPerson = suggestion
94+
Google.Latin = suggestion
95+
Google.Quotes = suggestion
96+
Google.Spacing = suggestion
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
extends: existence
2+
message: "Filler/fluff — delete or rewrite (style guide C5/C9): '%s'."
3+
level: warning
4+
ignorecase: true
5+
tokens:
6+
- simply
7+
- basically
8+
- essentially
9+
- obviously
10+
- of course
11+
- note that
12+
- in order to
13+
- due to the fact that
14+
- utilize
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
extends: existence
2+
message: "Heading uses 'Part N' ceremony instead of a descriptive title (style guide A7): '%s'."
3+
level: warning
4+
scope: heading
5+
ignorecase: true
6+
raw:
7+
- '^Part\s+([0-9]+|[IVXLC]+)\b'
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# RETIRED AS AN AUTHORITY — demoted rather than deleted, the same treatment
2+
# .vale.ini gives the Google house-style pack. `doc/lint/sentence-length.mjs` is
3+
# the authority for C2 on both surfaces; this rule is kept only so that
4+
# `vale --minAlertLevel=suggestion` can still show what Vale made of a page.
5+
# `suggestion` is below .vale.ini's `MinAlertLevel = warning`, so it no longer
6+
# reaches baseline.json, the gate, or a default `vale modules` run.
7+
#
8+
# ============================================================================
9+
# DO NOT GATE THIS RULE. `--gate 'vale_adoc:Corosio\.SentenceLength$'` — the
10+
# obvious spec, by analogy with the working `vale_adoc:Corosio\.PartHeadings$` —
11+
# is VACUOUS. At `suggestion` this rule never enters a Vale fingerprint set, so
12+
# the spec matches nothing and the gate reports `gated: true, gatedNew: 0` and
13+
# exits 0 while checking NOTHING. Measured, exactly that. That is the same
14+
# fail-open shape as the vacuous `Corosio.PartHeadings` rule this branch already
15+
# had to fix. Gate the script instead:
16+
#
17+
# --gate 'sentence_length:^C2:'
18+
#
19+
# which is live in docs.yml since the Phase-4 exit. Re-measured at the Phase-4
20+
# final fix wave: EXIT=1 / gatedNew=2, both findings the grandfathered
21+
# `when_any.hpp` refusals, so it still needs a baseline reseed before it can go
22+
# green. (An earlier version of this comment said gatedNew=135, the figure from
23+
# before the .adoc hard slice was worked to zero; `doc/lint/README.md` carried
24+
# the identical staleness and was corrected, this copy was missed.)
25+
# `^C2:` binds the HARD slice only; the design-essay
26+
# findings are keyed `advisory-C2` and are deliberately unreachable from a
27+
# `C2`-prefixed spec (doc/STYLE_GUIDE.md Part C2: hard in API docs, soft in
28+
# essays).
29+
# ============================================================================
30+
#
31+
# Three measured reasons this rule cannot be the authority, none fixable inside
32+
# a Vale rule (all `cwd=doc`, vale 3.15.1, target `modules`):
33+
#
34+
# 1. UNDER-COUNTS. It runs after .vale.ini's `TokenIgnores` blanks inline code
35+
# spans, so a span contributes ZERO words where a reader counts one. Three
36+
# measurements of the size of that blind spot, all agreeing:
37+
# * task P4-prereq, Vale with rewritten TokenIgnores, on the
38+
# pre-BlockIgnores-fix config: 140 -> 170 (+30)
39+
# * this task, Vale with the committed TokenIgnores, every backtick span
40+
# (2115) and `cpp:` macro (557) outside code blocks replaced by one
41+
# word, `--minAlertLevel=suggestion`: 135 -> 164 (+29)
42+
# * sentence-length.mjs, spans blanked versus spans as one word, all
43+
# slices of `.adoc`: 125 -> 152 (+27)
44+
# An earlier version of this comment claimed "+35, measured twice" by
45+
# substituting today's 135 for prereq's 140 and by counting words with
46+
# Vale's tokenizer. Both were wrong; the real figure is +27 to +30.
47+
# 2. MIS-ATTRIBUTES, which is worse: the missed block produces no alert to
48+
# chase, so re-running to a fixpoint never finds it. The blanking corrupts
49+
# Vale's position mapping for `scope: sentence` rules. Hand-verified case:
50+
# `5.buffers/5b.types.adoc` holds two over-limit sentences in list items
51+
# (27 and 34 words) and Vale reports NONE. The artifact is not confined to
52+
# `scope: sentence` either — a `Corosio.Terminology` alert on
53+
# `4.coroutines/4b.launching.adoc` is reported at line 25, an `include::`
54+
# line inside a `[source,cpp]` block, when the text that matched is at
55+
# line 68.
56+
# 3. FALSE-POSITIVES on under-segmentation. `4.coroutines/4f.composition.adoc:93`
57+
# is flagged as one sentence; its four real sentences are 23, 14, 11 and 6
58+
# words. Reproduced with that paragraph alone in a file.
59+
#
60+
# Do NOT re-promote this to warning/error without first showing, on a fixture,
61+
# that Vale's position mapping for `scope: sentence` is fixed. Two checkers
62+
# reporting different C2 numbers is how a gate loses credibility.
63+
extends: occurrence
64+
message: "Sentence over 25 words — split it (style guide C1/C2)."
65+
level: suggestion
66+
scope: sentence
67+
token: \b(\w+)\b
68+
max: 25
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Deliberately scoped to the two literal tokens doc/STYLE_GUIDE.md Part C4 names
2+
# ("Avoid needless 'will' and 'has been'") — NOT extended to every perfect-tense
3+
# form. What this rule does and does not see, re-measured at commit 620fdf2c with
4+
# cwd=doc and PATH including node_modules/.bin (Vale shells out to asciidoctor for
5+
# .adoc, and the extracted docstrings are .adoc too):
6+
#
7+
# `will\s` IS wrap-tolerant, and that is deliberate. Commit 30464086 changed the
8+
# token from the space-literal `'will '` to `'will\s'`, so a `will` at the end of a
9+
# wrapped source line now matches. Fixture proof: `The buffer will\nbe consumed.`
10+
# is FLAGGED, Match `"will\n"`. Do not "simplify" this back to a literal space.
11+
#
12+
# `has been` is NOT wrap-tolerant — a known, open hole. Fixture proof: `The buffer
13+
# has\nbeen consumed.` is NOT flagged, while the same text on one line is. It costs
14+
# nothing today (0 occurrences of `has\s*\n\s*been` in either corpus at 620fdf2c),
15+
# so it is recorded rather than fixed; the fix is the same one-character change
16+
# (`has\sbeen`) if an instance ever appears.
17+
#
18+
# The perfect-tense family this rule deliberately excludes — 17 occurrences on the
19+
# docstring corpus (`doc/lint/.docstrings/`), plus 4 on the `.adoc` pages:
20+
# have been x12 (ex/this_coro.hpp x4, read_at_least.hpp x3,
21+
# write_at_least.hpp x3, ex/thread_pool.hpp,
22+
# write.hpp)
23+
# has already been x3 (when_any.hpp x2, ex/async_mutex.hpp)
24+
# has not been x1 (write_at_least.hpp)
25+
# has now<newline>been x1 (ex/async_waker.hpp — invisible for BOTH reasons:
26+
# an intervening adverb and a line wrap)
27+
# (.adoc: have been x2 in 4h.lambda-captures, 9l.RunApi; has <adv> been x2 in
28+
# 9b.Separation, why-corosio)
29+
# Maintainer ruling: these stay excluded. Part C4 names only "will" and "has been",
30+
# so the rule is FAITHFUL to the guide as written; extending `tokens` would be a
31+
# style-guide change smuggled in as a lint fix, and would surface ~17 new prose
32+
# findings at once. A future editor who wants broader coverage changes Part C4
33+
# first, then this file.
34+
extends: existence
35+
message: "Avoid needless future/perfect tense — prefer present simple (style guide C4): '%s'."
36+
level: warning
37+
ignorecase: true
38+
# `have been` and `had been` are the same present/past-perfect construct as
39+
# `has been`, which C4 names explicitly. Matching only the singular left a blind
40+
# spot a future author could write into freely: a raw grep found 8 live sites the
41+
# rule could not see (6 in published docstrings, 2 on pages), including one CI
42+
# surfaced only because a long single-line paragraph parses differently there.
43+
# All 8 were fixed when these tokens were added, so the rule stays at zero.
44+
tokens:
45+
- 'will\s'
46+
- 'has been'
47+
- 'have been'
48+
- 'had been'
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# C10 / C.1 one-term-per-concept. Two things this rule has to get right at once,
2+
# and it got both wrong before: it must catch the verb in every form a writer
3+
# actually uses, and it must NOT touch API identifiers or the noun `launcher`.
4+
#
5+
# `ignorecase: false` plus bare stems meant only the exact lowercase stem was
6+
# seen. Measured on a fixture: `Launch the task for execution.`, `launches it on
7+
# the executor`, `launched`, `launching`, `Spawn`, `spawns`, `spawned`,
8+
# `spawning`, `Fire off`, `fires off`, `Kick off`, `kicked off` and `Boxed` all
9+
# passed; only `launch`, `cancellation token`, `cancel token` and `boxed` were
10+
# caught. That left the majority of real C10 prose invisible to the C10 checker.
11+
#
12+
# `ignorecase: true` is safe here, MEASURED rather than assumed. Every
13+
# `launch`/`spawn`-bearing identifier in the library and the pages is either
14+
# snake_case (`launch_one`, `launch_all`, `spawn_work`, `co_spawn`,
15+
# `launch_policies`, the snippet tag `4b_launching`) or a `launcher` compound
16+
# (`when_any_io_launcher`, `launchable`, `relaunch`, `launchers`). `_` is a word
17+
# character, so the `\b` after the stem already excludes the snake_case forms,
18+
# and the inflection list below is deliberately limited to VERB endings so it
19+
# cannot reach `launcher`. On top of that, `.vale.ini`'s `TokenIgnores` blanks
20+
# backtick spans and `cpp:` macros, so an identifier written as code is invisible
21+
# to this rule anyway.
22+
#
23+
# The noun `launcher` STAYS, and must not be flagged: it is the role name of the
24+
# `run_async` wrapper object, and plan Task 11's approved brief for all 18
25+
# overloads uses it — "Bind <options> to produce a launcher; invoke the launcher
26+
# with a task to start it." The verb `launch` is the violation, the noun
27+
# `launcher` is not. Verify both directions if you touch the pattern.
28+
extends: substitution
29+
message: "Use '%s' for one-term-per-concept consistency (style guide C.1)."
30+
level: warning
31+
ignorecase: true
32+
swap:
33+
'\b(launch(?:es|ed|ing)?|spawn(?:s|ed|ing)?|fire[sd]? off|firing off|kick(?:s|ed)? off|kicking off)\b': start
34+
'\bcancellation token\b': stop token
35+
'\bcancel token\b': stop token
36+
'\bboxed\b': type-erased

0 commit comments

Comments
 (0)