mbo 0.15.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)

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.

    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

Exception policy

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

Bazel test sizing

Assertions: gmock matchers, never comparison macros

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