xff v0.8.0

C++ Style

The C++ coding style for MBO Works repositories. It sits on top of two
machine-enforced config files and adds the human conventions below. When in doubt
the config files win; this document explains and extends them so a contributor (or
an AI assistant) can follow them without reverse-engineering the tooling.

Toolchain (machine-enforced)

What .clang-format decides (do not fight it)

Source text uses ASCII except for the explicitly allowed graphical glyphs listed in RULES.md.
Spell other intentional Unicode string/character data with escapes and explain the
purpose in English. Raw strings do not decode escapes: construct Unicode expectations explicitly
when their exact code points matter. Dedicated Unicode test files may use literal visible samples.

Unicode is not inherently unwanted; unnecessary mixing of test purposes is. Tests specifically
about Unicode handling belong in separate Unicode-specific files explicitly excluded from the
ASCII-source check. Ordinary functional fixtures stay ASCII unless an approved glyph is the
actual output under test: histogram blocks and tree connectors belong in their functional tests.
Visible Unicode samples may be literal in dedicated test files; retain escapes for invisible
combining marks, joiners, or exact byte sequences, and explain the property each sample exercises.

Naming (enforced by .clang-tidy readability-identifier-naming)

Code organization

Formatting conventions on top of clang-format

clang-format picks a layout per line; these habits steer it toward the readable one.

  1. No comment at the end of a long line. Put the comment on its own line above
    the element. A trailing // ... that pushes a line past 120 makes clang-format
    explode the element across several lines.

    // -exec run in the matched entry's directory
    {.name = "-execdir", .kind = Kind::kAction, .arity = -1},
  2. Trailing comma on the last field of a complex aggregate breaks it one field per
    line. Because InsertTrailingCommas is off, the comma is your per-aggregate opt-in.
    Use it for long/complex initializers; a short one that reads on a single line stays.
    This applies to named variables, return values, and structs passed directly as call arguments.
    Each multiline designated initializer needs its own trailing comma inside its closing brace.
    Review nested initializers independently: a comma after an inner closing brace separates outer
    elements or arguments and does not replace the inner trailing comma.

    Emit(Render(
        {
            .name = name,
            .options = {
                .width = width,
                .enabled = enabled,
            },
        },
        format));
    const Drop drop{
        .line = line,
        .layer = Source::kProject,
        .safety = Safety::kSecurity,
    };

    In a long array of struct literals - a registry-style table such as kGlobals or
    kDescriptors - put the trailing comma on every element
    , even the short ones that
    would fit on a single line, so clang-format expands the whole table uniformly, one
    field per line. A consistent table you scroll through reads better than a mix of
    one-liners and exploded rows packed to save height. This stays a deliberate, per-table
    choice made element by element: InsertTrailingCommas is off (we do not always want
    trailing commas), so clang-format never forces it for you.

  3. Force a line break with a comment rather than let clang-format cram a value at the right
    margin.
    A long argument - especially a raw string such as a proto R"pb(...)pb" - otherwise
    gets packed onto the call line and shoved against the 120 column, unreadable. A trailing
    comment makes clang-format keep the element on its own line. Mark it // NL ("new line"):

    EXPECT_THAT(  // NL
        message,
        EqualsProto(R"pb(name: "n" value: 1)pb"));

    When there is a relevant reason, keep the prefix and add it: // NL: <short reason>, preferred
    over a bare // NL. Always keep the NL prefix - do not drop to a bare // or an unprefixed
    comment - for two reasons: it marks the comment as load-bearing for layout, so a reader knows
    that removing it re-crams the line; and the consistent marker is machine-checkable, so a
    pre-commit rule can verify these lines stay broken.

    Reserve // clang-format off / on for a genuine table or hand-aligned expression that
    clang-format cannot lay out - // NL only inserts breaks, so it cannot keep an over-120
    line whole or stop a reflow (e.g. a one-line-per-case test table, or a multi-clause
    requires(...)). Do not use it to hand-place ordinary layout, and keep the off/on pair
    tight and adjacent so the on is never forgotten: everything between the two loses every
    formatting guarantee above.

Idioms

Error handling: absl::Status and the MBO status macros

Propagate errors with the macros from mbo/status/status_macros.h
(@mboworks_mbo//mbo/status:status_macros_cc), not a hand-written
if (!x.ok()) return x.status();.

absl::StatusOr<Report> Build(std::string_view path) {
  MBO_ASSIGN_OR_RETURN(const Config config, LoadConfig(path));  // value, or return status
  Report report = Analyze(config);
  MBO_RETURN_IF_ERROR(Persist(report));                         // Status guard
  return report;
}

Output, logging, and AbslStringify

Concurrency and thread safety

These are the baseline for any multi-threaded code, not extra credit; threading bugs are
silent and data-dependent, so the annotations and the sanitizer are the standing guard.
Most of this repo is single-threaded today, so this section is the standard to apply when
you add
multi-threaded code, not a description of current breadth.

Protocol Buffers

Testing (GoogleTest / GoogleMock)

All exported code must be tested, at every level (unit, integration, and end-to-end where
it applies). A one-shot manual check or a script you ran once is planning input, never a
substitute for a committed test. Tests use GoogleTest + GoogleMock with these conventions.

Structure

Assertions: matchers only; convenience macros are forbidden

Status matchers (mbo/testing)

Protocol-buffer fixtures and matchers

Build proto test data with mbo::proto::ParseTextProtoOrDie(R"pb(...)pb") and assert with
mbo::proto::EqualsProto / Partially(EqualsProto(...)) - never imperative setters or
serialized-string comparison. See the Protocol Buffers section.

Shell / binary-level tests