STYLE-GUIDE.md
In docs/
In docs/
The mkit documentation is the single source of truth for the on-disk and wire formats, the CLI, the Rust crates, and the attestation subsystem. It evolves continuously with new features and crates.
This style guide provides editorial guidelines for writing clear and consistent mkit documentation. Aim for clarity, accuracy, and completeness when contributing improvements. Use this guide as a reference document for specific questions.
This page is divided into two main sections:
The "general" documentation covers the mechanics and formatting
guidelines that apply across mkit prose — the README, docs/,
CONTRIBUTING.md, advisories, release notes, and any other narrative
text in the repository.
All mkit prose documentation is written in Markdown. Rust API docs are written as rustdoc comments inside the crate sources; see Writing API documentation.
Developers reading the mkit docs shouldn't trip over obvious mistakes and lose confidence in the project. Write with correct grammar, punctuation, and spelling.
Always show and don't tell. Instead of explaining a concept to the reader, give them an example. Providing examples helps grab the reader's attention.
Some words have multiple legitimate spellings. Prefer the shorter,
single-l American forms: "canceled" and "canceling", but
"cancellation". Prefer "behavior" over "behaviour".
Developers read documentation to find answers to their problems. Documentation exists because it can translate complex information into easily digestible pieces. Voice and tone directly influence that translation and remove friction.
Write clearly and concisely using plain American English. Aim for clarity for all English speakers, not only native ones.
Generally, write in the second rather than the first person. Use "you" instead of "we".
Reserve "we" for the rare cases when the mkit maintainers are directly addressing the reader to convey an important message (for example, a security advisory or a deprecation notice).
Writing in the present tense tells the reader what the system does right now. Developers already weigh complex tradeoffs when choosing a VCS or a signing stack; the docs should not add tense ambiguity to that list.
Avoid the passive voice. Common passive constructions use "was" or "by". Use a tool such as Hemingway or Grammarly to spot passive voice in your drafts.
Write short sentences. One thought per sentence is punchier and pithier. Cramming multiple thoughts into one sentence makes the copy painful to read.
Use action verbs and subject-verb-object construction, cut clunky phrases, and avoid jargon. Remove any adjectives or adverbs that don't change the meaning of a sentence.
If you have to write a long sentence, follow it up with a short one. The contrast snaps the reader back to attention. Don't repeat the same word inside a single sentence, and don't start or end a sentence with the same word you used to start or end the previous one.
Use "they" as a singular pronoun. When necessary, address a group of readers as "developers", "operators", "maintainers", or "callers" (for an API).
When it is correct to use words instead of symbols:
mkit targets Linux and macOS (Windows is not a supported target —
MKIT-6; see docs/INVARIANTS.md). When you list
supported platforms, order them alphabetically: "Linux and macOS".
Capitalize platform names as the vendors do: Linux, macOS, Windows, FreeBSD. Spell "macOS" with a lowercase "m" and capital "OS".
mkit ships several transport schemes — mkit+file://, mkit+https://,
mkit+s3://, mkit+ssh://, mkit+enc://. Reference them by their full scheme,
formatted as inline code, the first time they appear on a page. After
that, "the SSH transport" or "the S3 transport" is acceptable.
Before a working tree has any mkit state, it is a directory. Once
mkit init runs, it is a repository, and its state lives in the
.mkit/ directory. The CLI also speaks of objects, packs,
refs, the index, and the working tree; use those terms
consistently and link to the relevant SPEC document on first use.
mkit uses BLAKE3 as its content-address primitive. Refer to a full
hash as a digest or object ID, and to a shortened form as a
short hash. Object IDs are bare 64-character lowercase hex with no
prefix — when showing examples, use a realistic-looking 64-hex digest,
not xxxxxxxx.
On first use on an individual documentation page, expand an abbreviation in parentheses. For example, "Distinguished Encoding Rules (DER)" or "Trusted Platform Module (TPM)".
The following abbreviations are acceptable in their shortened form without expansion:
Avoid Latin abbreviations such as "i.e." or "e.g.". Spell them out: "that is" or "for example".
Use external product names the same way the industry does. For example: BLAKE3, Ed25519, secp256k1, P-256, SHA-256, in-toto, DSSE, SLSA, libgit2, GitHub, GitLab, S3, MinIO, Linux, macOS, Windows, Rust, Cargo, rustup, Clippy, rustdoc, Tokio, Hyper, OpenSSH, PKCS#11, TPM 2.0, WebAssembly.
Always use a capital "B" for bytes. Write "bit" or a lowercase "b" for bits. For example:
For an in-depth reference, see Kilobytes, kibibytes, and kilobits in the API documentation section.
Generally, use Oxford commas. If a sentence ends up with too many commas, refactor it — split it in two, swap a comma for an em dash or a colon, or simplify the structure — rather than dropping the Oxford comma.
One exception is in section headings, where you may omit it for brevity.
The possessive of a singular noun is formed by adding an apostrophe
and an s. This holds no matter the final consonant. The
possessive of a plural noun that ends in s is formed by adding
just the apostrophe.
Exceptions:
Do not pad a slash with extra space for emphasis.
Prefer splitting a phrase into two separate sentences. The goal is documentation that reads easily.
In rare cases when you need to keep the structure, use em dashes (—) or connecting words ("then", "however", "so") rather than commas to glue two independent clauses together.
Guidelines for formatting in different situations, such as file names, inline code, and headings.
On a page, top-level section headers are H2 (##). The page title
is the H1 (#). Do not skip heading levels just to emphasize a
sub-section.
Use sentence case for section and sub-section headings, except where a heading begins with a product or proper name (mkit, BLAKE3, in-toto, DSSE, GitHub). Developers are used to those names; keeping their canonical casing makes the docs more scannable.
If a heading refers to a product or component name, capitalize that name as the vendor does:
Use title case for any text rendered as a button or other clickable UI element. The rule of thumb is to not capitalize articles, prepositions, or conjunctions inside a title-cased phrase.
File names, directory names, and file extensions are rendered as bold text in Markdown.
.mkit/..cbor.rust/fuzz/.Do not use capitalized words for emphasis.
Exception: always capitalize product phrases as the project does.
Link the appropriate noun phrase rather than using the word "here". The linked text should describe the destination and act as a Call to Action (CTA).
Use relative links when referencing another file in the
repository (./specs/SPEC-OBJECTS.md, ../README.md). When linking from
prose into the Rust API docs, prefer a stable docs.rs URL or an
intra-doc rustdoc link inside the source — not a path into
rust/target/doc/.
An accessible document is created to be as easily readable by a sighted reader as by a low-vision or non-sighted one. The most common thing to get right is alternative ("alt") text for images and videos.
When embedding an image or video in Markdown, add a meaningful alt text inside the square brackets.
Alt text should describe what the image conveys, not what it looks like. "Hash throughput chart" is fine; "blue and orange bar chart" is not.
Apply inline code formatting (back-ticks) only to programming words, identifiers, paths inside source trees, command invocations, or literal CLI output. Do not use inline code as a substitute for emphasis.
--porcelain to make mkit status write to stdout.File menu, then Save As.—When you want an em dash, write — instead of -, --, or
the literal — character. Markdown renders the entity reliably; the
hyphen variants render as plain hyphens, and the literal —
character is the most common variant inserted by AI-assisted
writing — flag it during review.
Render keyboard shortcuts with the <kbd> element. Wrap each key in
its own tag, and keep the plus sign outside the tags.
⌘+t or ctrl+t.Rules:
Cmd.Ctrl,
Alt, Shift).Do not use emojis in the documentation. This includes prose, headings, callouts, and changelog entries.
When referencing how to install or run a Rust binary, use Cargo as
the canonical entry point. For mkit specifically, prefer the
documented install channels (cargo install, Homebrew, the
install.sh script, or a release binary) and link to
docs/INSTALL.md for the full set.
cargo install --git https://github.com/officialunofficial/mkit mkit-cliWhen a collapsible component holds a single item or a single paragraph, do not wrap it in a bullet point. The bullet adds no information.
Avoid terms that describe deprecated or removed workflows. If a command, flag, or format was renamed, use the current name and link to the change in the CHANGELOG only when the deprecation window is still active.
For instructions that require manually editing files outside the mkit-managed area (for example, hand-editing keys or tweaking transport-specific configuration), put those instructions inside a collapsible block labeled "Manual setup" or "Advanced".
Any numbered list starts at 1, not 0. This keeps numbering
consistent across the docs.
When a feature is gated by a specific crate version, use later or earlier to describe the range. Use the crate name and a SemVer number.
Any string a person reads is user-facing copy: CLI stdout and stderr,
error and progress messages, --help text, the manpage,
shell-completion descriptions, prompts, the web demo, and the docs
site. A dynamic format! string is still user-facing copy.
Every string earns its place by doing at least one of: saying what happened, saying what happens next, or telling the reader what to do. Get straight to the point.
non-fast-forward push rejected: fetch and retry, not
Sorry, we couldn't push your changes. Please try again.sysexits(3), see CLI.md); the message carries the
specifics and, when one exists, the recovery step.message field). Route a
repeated string through one helper instead of hand-writing it per
call site.COMMIT_EDITMSG template, porcelain formats — see
PARITY.md) keeps git's wording even where it breaks
these rules.Before opening a PR that touches copy, grep the diff for please,
sorry, unfortunately, simply, and easy, and check whether an
existing helper already words the state you are adding.
Apple's Human Interface Guidelines on writing is the reference for tone: clear, direct, specific.
When a topic is easier to grasp visually or interactively, reach for the right tool:
README.md charts and the SPEC-* docs.sh or console, and prefix interactive commands with $ .These terms are used consistently throughout the mkit
documentation. When in doubt, link to the canonical definition (the
relevant SPEC-*.md page) on first use.
mkit+file, mkit+https,
mkit+s3, mkit+ssh, and mkit+enc (see
SPEC-TRANSPORT).mkit-attest;
external signers (TPM, secure element, CTAP, file) live under
contrib/signers/ (see SPEC-EXTERNAL-SIGNER).rust/crates/..tar.gz,
.zip, and so on). mkit release artifacts are archives.rust/tests/golden/ and
changing one is a breaking format change.Writing API documentation accurately and precisely helps callers use mkit's crates correctly. The following sections cover the mechanics of rustdoc, the tags to prefer, and the topics that often trip up authors.
/// for items, //! for modules and crates.pub item in rust/crates/mkit-*. CI enforces
#![warn(missing_docs)] on the public crates.cargo
doc, docs.rs, and IDE tooltips.| Section | Usage |
|---|---|
# Examples | A self-contained code block, ideally as a doctest. Doctests run in CI; broken examples fail the build. |
# Errors | Required on any function that returns a Result. Describes the error variants and the conditions that produce them. |
# Panics | Required on any function that can panic. Describes the precondition the caller must uphold. |
# Safety | Required on every unsafe fn. Describes the invariants the caller must uphold to avoid undefined behavior. |
# Returns | Optional. Use only when the return value is non-obvious from the signature. |
mkit-specific conventions on top of stock rustdoc:
[`Type`] syntax (or [`crate::path::Type`]). Do not
write raw https://docs.rs/... URLs when an intra-doc link
works — they outlive renames and cargo doc validates them.[`mkit_core::Object`]). rustdoc
resolves these at build time.#[doc(hidden)] or
pub(crate) rather than /// comments saying "do not use".#[deprecated(since = "...", note =
"...")] attribute over prose in the docstring. The attribute
shows up in rustdoc, in compiler warnings, and in IDE tooltips.> **Experimental:** .... Do not invent
custom badges.#[cfg(...)],
use #[doc(cfg(...))] so rustdoc renders the platform marker.A few formatting notes:
[`super::Foo`],
[`crate::pack::Header`]) over an absolute path./// H~2~O — the hydrogen oxide molecule
/// 21^st^ century[link][name] syntax works, but intra-doc
[`name`] is preferred for cross-references inside the
workspace.These are topics that often come up in mkit development and need precise wording.
Concurrency describes two tasks that logically run together. Two concurrent tasks may each start before the other finishes.
Parallelism describes two tasks that physically run at the same time. Two network requests, two threads on two CPU cores, or two BLAKE3 chunk hashes on two cores are examples of parallelism.
It is possible, and very common with Rust async, to have
concurrency without parallelism: two async fns polled on the
same single-threaded executor run concurrently but not in
parallel. Spawning them onto a multi-threaded runtime (for
example, Tokio's default runtime) lets them also run in parallel,
provided they are Send.
BLAKE3 is itself parallelizable: chunks of a large input can be
hashed on multiple cores. The benches in
benchmarks/charts/hashing-*.svg reflect single-core throughput
unless the chart caption says otherwise.
In Rust, the unit of async work is a future. A future is either:
On the receiving side:
poll method returns
Poll::Ready(value).await on a future blocks the current async task until the
future is ready, then yields the inner value.On the producing side:
async fn is inert: it does
nothing until polled by an executor.tokio::spawn) starts driving it concurrently with the current
task.For API documentation, the caller usually wants to know:
Send/Sync bounds it carries, if relevant.Typically write:
Returns a future that resolves to the parsed [
Commit].
Avoid:
Returns a
Future<Output = Commit>.
The signature already says that; the prose should say something useful on top.
When Result is involved, document the error cases under a
# Errors section rather than embedding them in the description.
Unless you have a specific reason to use "URI", use "URL" everywhere. This follows the goals of the WHATWG URL specification.
In mkit, transports are identified by URLs of the form
mkit+<scheme>://.... Refer to the full string as a transport
URL, and to the part after mkit+ as the inner scheme.
kB) and other
times refers to 1,024 bytes. Be explicit.kiB.kiB, MiB, and
GiB to communicate clearly to callers.kB, MB, and GB
and be explicit that the number refers to 1,000 bytes,
1,000,000 bytes, and so on.kbit, Mbit, and Gbit when referring to
bits to remove the ambiguity between bits and bytes. Both
kbit/s and kbps are acceptable for rates.B for bytes. Write bit or a lowercase
b for bits.10 MiB.Be precise when describing cryptographic primitives.
Result<(), VerifyError>, not bool,
so the caller can distinguish "definitely invalid" from "could
not verify".Use /// for item-level doc comments and //! for
module/crate-level ones. Format docstrings to fit the column
width of the file at hand; for most mkit Rust sources that is
100 columns. The rustfmt configuration in rustfmt.toml
governs the formatter; rustdoc itself does not reflow text.
Write descriptions in the third-person declarative, not the second-person imperative.
Explain the behavior of a function beyond its parameters and return value. Those are easy to read off the signature; what's less clear is the failure modes, the side effects, the preconditions, and the concurrency safety. Document the parts of the iceberg below the surface.
Write parameter and field descriptions that teach the caller something. If you have nothing useful to add, leave the field undocumented and rely on the name and type. Quality beats quantity.
pub struct CommitMeta {
// CORRECT:
/// Total uncompressed size, in bytes, of all trees and blobs
/// reachable from this commit. Computed lazily by
/// [`Commit::stats`]; cached after the first call.
pub reachable_bytes: u64,
// INCORRECT:
/// Total size
pub reachable_bytes: u64,
// ACCEPTABLE WHEN THE NAME AND TYPE SUFFICE:
pub reachable_bytes: u64,
}Leave off a period if the description of a function, parameter, or return value is a single phrase. Use periods when writing multiple sentences.
Example:
/// Stages the file at `path` for the next commit and returns its
/// BLAKE3 digest. The file is hashed and written to the object
/// store immediately; the index is updated to point at the new
/// object.
///
/// If `path` is outside the working tree, this returns
/// [`StageError::OutsideWorkingTree`] and leaves the index
/// untouched.
///
/// # Errors
///
/// Returns an error if the file cannot be read, if it is a
/// symlink that resolves outside the working tree, or if writing
/// the object to the store fails.
///
/// # Examples
///
/// ```no_run
/// # use mkit_core::Repository;
/// # fn main() -> anyhow::Result<()> {
/// let repo = Repository::open(".")?;
/// let digest = repo.stage("README.md")?;
/// println!("staged {digest}");
/// # Ok(()) }
/// ```
pub fn stage(&self, path: &str) -> Result<Digest, StageError> { /* ... */ }SPEC-*.md files in this directory for the on-disk and
wire formats; link to them from prose when defining a term.