// 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 keeps those files in 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. 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. A section an earlier build wrote is removed. 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 // document uses. std::string text; }; // `agentsMd` with its dependency notes section holding `notes`, in order. // A document without a section gets one appended after a blank line; one // with a section has it rewritten in place, wherever it was moved to; and // empty `notes` remove it. Removing a section that ends the document takes // the blank line in front of it too, so a document that ended in a newline // before it gained the section gets its own bytes back. The section uses // the document'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 document that ends // inside a code block: guessing which part of someone's file is generated // would overwrite their text, and missing the section would add another on // every build. With no notes it never throws, since leaving the document // alone loses nothing. Also throws for a note that would break the section // when it is read back: one with a marker line of its own, or a code block // it never closes. std::string MergeDependencyNotes(std::string_view agentsMd, std::span notes); // Refresh the dependency notes section of the AGENTS.md beside // `projectFile` from the AGENTS.consumer.md of every dependency // project.dependencyNotes selects. `project` is the Configuration that // file returned, after LoadProject: loading is what found the // dependencies. Does nothing when there is no AGENTS.md, which is never // created. 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 section that cannot be placed 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); }