// SPDX-License-Identifier: LGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® export module Crafter.Build:Agents; import std; namespace fs = std::filesystem; // Dependency notes. A crafter-build project ships an AGENTS.consumer.md beside // its project.cpp, written for the agents of the projects that depend on it, // and crafter-build merges those files into each consumer's AGENTS.md. Without // that, an agent working on a consumer knows nothing about the libraries it // builds against unless someone copied the knowledge in by hand, and a copy // goes stale the moment the library changes. // // The consumer writes its own instructions in an AGENTS.template.md beside its // project.cpp, and every build writes AGENTS.md from that, so AGENTS.md is // build output and stays out of version control. Kept in a committed // AGENTS.md instead, as they once were, the notes changed that file whenever // a dependency changed its own, on builds that changed nothing else, and // every diff and rebase carried changes nobody had made. export namespace Crafter { struct Configuration; // Which dependencies' notes a project's AGENTS.md carries: the value of // Configuration::dependencyNotes. enum class DependencyNotes { // The projects this one builds against, and the projects those build // against, all the way down. Transitive, // Only the projects this one's own configurations and // `#pragma crafter dependency` lines name. Direct, // None: AGENTS.md holds the template's text alone. None, }; // One dependency's AGENTS.consumer.md, ready to merge. struct DependencyNote { // What the comment above the note calls the dependency: its // repository's name, or a local project's directory name. std::string name; // The file's content, without leading blank lines or trailing // whitespace. Lines end in LF; the merge writes the line ending the // template uses. std::string text; }; // `templateMd` with its dependency notes section holding `notes`, in // order: AGENTS.md as it reads below the line that says a build wrote it. // A template without a section gets one appended after a blank line; one // with a section, which is how a template places it, has it rewritten // where it stands, whatever it held; and empty `notes` remove it. // Removing a section that ends the template takes the blank line in front // of it too, so the result ends as the text before it did. The section // uses the template's line ending, so a CRLF file stays CRLF. // // The two marker lines count only as whole lines outside fenced code // blocks, which is what lets a document quote them. Throws, naming the // line, when there are notes to write and the markers are unbalanced, or // when the section would have to be appended to a template that ends // inside a code block: a lone or doubled marker is a slip in the // template, and any guess at what it meant would put the notes, or cut // the text, where its author did not, while notes appended inside a code // block would read as code. With no notes it never throws, since nothing // has to be placed. Also throws for a note that would break the section // for whoever reads it: one with a marker line of its own, or a code // block it never closes. std::string MergeDependencyNotes(std::string_view templateMd, std::span notes); // Write the AGENTS.md beside `projectFile` from the AGENTS.template.md // beside it, with the AGENTS.consumer.md of every dependency // project.dependencyNotes selects merged in. `project` is the // Configuration that file returned, after LoadProject: loading is what // found the dependencies. Creates AGENTS.md when it is missing, and // replaces only one a build wrote: an AGENTS.md someone wrote is left // alone, with a warning. Without a template it writes nothing, and only // warns about a section an earlier crafter-build left in AGENTS.md. // Writes only when the text changes, in place, so hard links, ownership // and a read-only AGENTS.md are respected; returns whether it wrote. A // read or write that fails, a template the notes cannot be placed in and // a dependency's unusable notes are warnings: the notes never fail a // build, and nothing is reported when nothing would have been written. bool UpdateAgentsMd(const Configuration& project, const fs::path& projectFile); }