feat(agents): keep dependencies' AGENTS.consumer.md in AGENTS.md
An agent working on a crafter-build project reads its AGENTS.md, and learns nothing there about the libraries the project builds against unless someone copied that in by hand. This repository shows the cost: the code style section of its AGENTS.md restates what Crafter.LintRules enforces, and goes stale whenever the rules change. A library now ships those notes itself, as AGENTS.consumer.md beside its project.cpp; nothing in its project.cpp changes. When a consumer has an AGENTS.md beside its own project.cpp, `crafter-build` and `crafter-build test` keep a section in it, between two marker comments, holding its dependencies' notes. The section is appended the first time and rewritten in place after that. The file is written only when the section changes, and an AGENTS.md is never created. lint, format, run and clean leave it alone. The update runs after loading, which has just fetched every dependency, and before compiling, so a build that fails still has the notes. cfg.dependencyNotes picks the dependencies. Transitive, the default, follows `dependencies` all the way down from the project's own configurations, its tests and its `#pragma crafter dependency` tooling; Direct stops at the first loaded project on each path; None removes the section. A dependency's own pragma tooling never counts: it is about how that dependency is built, not how it is used, and being no `dependencies` edge it is never reached. Nor are a dependency's tests: nothing builds them for the consumer, and their configurations can point at a local their project file has since moved out of. The walk has to know which Configurations belong to another project, and where that project lives, and cfg.path cannot say: only the Configuration GitProject and LocalProject return is rebased, so a dependency's internal configurations keep "./", which resolves to the consumer. The loader records it instead, LoadedProjectOf beside the project cache, and keeps what the root's pragmas resolved to past the frame LoadProject pops, PragmaDependencies. Neither is exported. The markers count only as whole lines outside fenced code blocks, so documentation can quote them. Unbalanced markers and a document ending inside a code block leave the file alone with a warning: guessing which part of someone's file is generated would overwrite their text, and missing the section would add another one on every build. A note that would break the section when it is read back, through a marker line of its own or a code block it never closes, is left out with a warning. The file is replaced through a temporary renamed onto the symlink's target, so a linked AGENTS.md stays a link and a write cut short leaves the old file rather than part of the new one. The code is a new partition, Crafter.Build:Agents, listed in build.sh, CrafterBuildModules, project.cpp and the umbrella. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0cbded4a3d
commit
e87fb0cc12
12 changed files with 475 additions and 4 deletions
65
interfaces/Crafter.Build-Agents.cppm
Normal file
65
interfaces/Crafter.Build-Agents.cppm
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// 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, tests 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 {
|
||||
// Where it came from, written into the comment above it: the git URL,
|
||||
// or for a local project the file's path relative to the consumer.
|
||||
std::string source;
|
||||
// The file's content, without leading blank lines or trailing
|
||||
// whitespace.
|
||||
std::string text;
|
||||
};
|
||||
|
||||
// `agentsMd` with its dependency notes section holding `notes`, in order.
|
||||
// A file 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 together with that blank line, so a file
|
||||
// that only ever gained the section gets its own bytes back.
|
||||
//
|
||||
// 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 the markers are unbalanced or the document ends inside a
|
||||
// code block: guessing which part of someone's file is generated would
|
||||
// overwrite their text, and missing the section would add a second one
|
||||
// on every build. 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, and returns whether it
|
||||
// did. An unreadable file, a failed write, malformed markers and a
|
||||
// dependency's unusable notes are warnings: the notes never fail a build.
|
||||
bool UpdateAgentsMd(const Configuration& project, const fs::path& projectFile);
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -97,3 +97,20 @@ 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
|
||||
std::string url; // the git source; empty for a LocalProject
|
||||
};
|
||||
// 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue