// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® export module Crafter.Review:Inventory; import std; import :Json; // The partition of a frozen commit range — a `git format-patch` export — into // review units for the coverage contract. It is MECHANICAL: no agent decides // what the units are. Every non-blank line of the artifact belongs to // exactly one unit, and the inventory fails when that does not hold. Fixed // slots give what is NOT in the text a row of the coverage matrix too. // // Units (ids): // pK:subject commit K's subject // pK:msg commit K's message body (before the trailers), when not empty // pK:trailers commit K's trailer block, when not empty // pK:diffstat commit K's diffstat // pK:#hdr the diff header of in commit K (mode, rename, blobs) // pK:#H hunk H of in commit K; a hunk of a documentation file // that holds more than one heading-delimited part is cut into // #H.1, #H.2, ... (DocParts) // pK:outside fixed slot: the in-repository users and callers of what // commit K changes, outside its diff // pK:absent fixed slot: what a reviewer expects in commit K and does not // find // whole:* fixed slots (WholeCommits) export namespace Crafter::Review::Inventory { struct WholeSlot { std::string_view id; std::string_view description; }; constexpr std::array WholeCommits = {{ {"whole:order", "commit ordering and bisectability — each commit a self-contained step that leaves the repository consistent (no later commit in the range repairs an earlier one, nothing is used before the commit that adds it), the declared base"}, {"whole:absent", "what a reviewer of this repository expects the range to contain and it does not — a test, a version/changelog bump, a doc or packaging update, a migration for existing installs, a message for a body-less commit"}, {"whole:behaviour", "the user-visible behaviour of the range, exercised end to end in the running application — every behaviour the code and the messages promise, as a user sees it; the flows the range touches and the adjacent flows that share what it changes; what a user expects the range to deliver and does not get"}, }}; // [a, b) is one hunk of a Markdown file, lines[a] its @@ line: its // partition at the headings on the new side of the diff, outside fenced // code blocks, once the part before holds anything but headings. std::vector> DocParts(std::span lines, std::size_t a, std::size_t b); // The first non-blank line, stripped and cut to 90 code points. std::string Excerpt(std::span lines); struct Series { // Every patch's lines, one after the other: what unit lines number. std::vector lines; // Units ({id, lines, excerpt}), the whole slots not yet among them. Json units; }; Series PartitionSeries(const std::filesystem::path& dir, bool splitDocs = true); // A partition that broke the invariant. class InvariantError : public std::runtime_error { public: using std::runtime_error::runtime_error; }; struct Built { // What `inventory commits ` prints: {mode, artifact, hash8, // units, invariant, count}. Json out; std::vector lines; }; // `artifact` is kept as it is spelled: the inventory records it so. Built Build(std::string_view mode, std::string_view artifact); // `crafter-review inventory commits `: the partition as indented // JSON on stdout. Exit 1 on a broken invariant or a bad command line. std::int32_t Main(std::span args); }