Merge branch 'claude/dependency-notes': a library's AGENTS.consumer.md in its consumers' AGENTS.md
All checks were successful
Flatpak / flatpak (push) Successful in 12m18s

Brings in dependency notes: a crafter-build project that ships an
AGENTS.consumer.md beside its project.cpp has it kept in a marked
section of each consumer's AGENTS.md, refreshed by `crafter-build` and
`crafter-build test`, chosen by cfg.dependencyNotes (Transitive,
Direct, None). The new partition is Crafter.Build:Agents; the loader
records where each GitProject and LocalProject came from and what a
project file's pragmas resolved to.

The branch started before master's four external-fetch commits. Both
touch External.cpp, the Clang partition, project.cpp, README.md and
AGENTS.md, but in different places, so the merge needed no hand
reconciliation; the merged tree passes build.sh, the self-host build,
format --check and the test suite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-10-31 05:05:07 +01:00
commit cc0a6aed8c
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
29 changed files with 1238 additions and 19 deletions

View file

@ -0,0 +1,72 @@
// 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<const DependencyNote> 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);
}

View file

@ -8,6 +8,7 @@ import :Interface;
import :Implementation;
import :External;
import :Toolchain;
import :Agents;
namespace fs = std::filesystem;
export namespace Crafter {
@ -494,6 +495,14 @@ export namespace Crafter {
// }});
// Not part of VariantId: a script changes nothing that is compiled.
std::vector<Script> scripts;
// Which dependencies' AGENTS.consumer.md `crafter-build` and
// `crafter-build test` keep in the AGENTS.md beside project.cpp, when
// there is one (see Crafter.Build:Agents):
// cfg.dependencyNotes = DependencyNotes::Direct;
// Only read on the Configuration project.cpp returns; a dependency's
// setting decides nothing for its consumers. Not part of VariantId:
// the notes change nothing that is compiled.
DependencyNotes dependencyNotes = DependencyNotes::Transitive;
void GetInterfacesAndImplementations(std::span<fs::path> interfaces, std::span<fs::path> implementations);
// Retry the `import X;` names GetInterfacesAndImplementations could not
// place, against the dependency DAG as it stands now. Sources are

View file

@ -114,3 +114,30 @@ export namespace Crafter {
// SHA for reproducible, offline-after-first-fetch builds.
fs::path GitFetch(const GitSource& source);
}
namespace Crafter {
// Where GitProject or LocalProject loaded a Configuration from. The
// loader keeps this beside the cache that owns the Configuration, since
// the Configuration itself cannot say: only the one they return has its
// path rebased, and the configurations a dependency's project file makes
// for itself keep a path such as "./", which resolves against the
// consumer's directory.
struct LoadedProject {
fs::path projectFile; // absolute
// What a consumer's AGENTS.md calls it: the repository's name for a
// git project, with the path below the clone's root for one that is
// not at it, and the directory's name for a local one. The same on
// every machine and for a --local checkout and its clone alike,
// which a path into the cache or a sibling checkout is not, and free
// of what a URL can carry, credentials included.
std::string name;
// Which library this is, whatever the revision: the repository and
// the project file in it for git, the project file for a local one.
// Two specs that pin one library differently are one library.
std::string identity;
};
// The LoadedProject behind a Configuration that GitProject or
// LocalProject returned; nullopt for any other: the project crafter-build
// was pointed at, and every configuration a project file makes itself.
std::optional<LoadedProject> LoadedProjectOf(const Configuration& cfg);
}

View file

@ -75,6 +75,13 @@ namespace Crafter {
// that library as a dependency, the test's GitProject loads crafter-build's
// project file in turn and the two recurse until the cycle guard throws.
export bool IsTopLevelProject();
// What the `#pragma crafter dependency` lines of `projectFile` resolved
// to the last time LoadProject loaded it in this process, in file order;
// empty when it has none or was never loaded. Unlike ProjectDependency
// this still answers once LoadProject has returned, which is when the
// dependency notes of the project crafter-build was pointed at are
// gathered (Crafter.Build:Agents).
std::vector<Configuration*> PragmaDependencies(const fs::path& projectFile);
// Resolves the directory holding distributed runtime assets (Crafter.Build
// module sources, wasi-runtime/, etc). Honors CRAFTER_BUILD_HOME; otherwise
// derives <prefix>/share/crafter-build from the running executable's path.

View file

@ -8,6 +8,7 @@ export import :Platform;
export import :Implementation;
export import :Shader;
export import :External;
export import :Agents;
export import :Test;
export import :Lint;
export import :Script;