Crafter.Build/interfaces/Crafter.Build-Agents.cppm

72 lines
3.7 KiB
Text
Raw Permalink Normal View History

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>
2026-10-31 03:18:06 +01:00
// 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,
fix(agents): what the review of dependency notes found A review of the feature turned up the following, fixed here. Writing. AGENTS.md was replaced through a temporary renamed over it, which swaps in another file: a read-only AGENTS.md was rewritten anyway, a hard link such as a CLAUDE.md beside it kept the old text, ACLs and ownership were dropped, and without an fsync a power cut could still leave it empty. The temporary was named after the PID, which every Flatpak instance shares as PID 2. The file is now written in place, from the first byte that differs, then truncated and synced, under an flock on the file itself. A write that fails partway is undone where the disk allows; with the section last, where it is appended, only the section can be damaged. A read-only file is opened for writing only when something would change, so it is refused then and silent otherwise. Reading. libc++'s filebuf reports a failed read as the end of the file, so an I/O error came back as a short file and the section was written over what was left. Reads go through read(2). Code fences. ```make``` was taken for a fence, and a fence in a list item was missed while its indented closer opened one, so markers a user quoted in a code block could be taken for the section, or the real one hidden and appended again. Backtick info strings, block quotes and list items are now followed, and a block ends with its container. Warnings with nothing to write. With no notes, nothing short of a well-formed section is reported, so an AGENTS.md that ends in an open code block, or a project set to None, no longer warns on every build. An unclosed fence after a section no longer stops it being rewritten in place. Labels. The `from` line carried the git URL, credentials and all, or a path relative to the consumer that changed with the machine, the cache location and --local. It is now the repository's or the directory's name. Notes are deduplicated by repository rather than by clone, so one library pinned two ways is described once, as the nearer spec has it. The walk. It no longer looks at the project's tests: a plain build never did, and a test's dependency can point at the Configuration the project file returned by value. It goes ring by ring over projects rather than configurations, so what the project names itself comes first even with the library/executable split. The project's own AGENTS.consumer.md is excluded by path, also when a dependency's project file sits beside the project's. Removing a section dropped a blank line even from between two paragraphs, joining them; only a section that ends the file takes it now. A CRLF file gets a CRLF section. A note was checked on its raw bytes and again, under other whitespace rules, on the trimmed ones, so it could pass the first check and fail the merge, which then dropped every note. It is normalised once and checked on exactly what is merged. `test --list` only lists, and no longer writes AGENTS.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:02:28 +01:00
// Only the projects this one's own configurations and
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>
2026-10-31 03:18:06 +01:00
// `#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 {
fix(agents): what the review of dependency notes found A review of the feature turned up the following, fixed here. Writing. AGENTS.md was replaced through a temporary renamed over it, which swaps in another file: a read-only AGENTS.md was rewritten anyway, a hard link such as a CLAUDE.md beside it kept the old text, ACLs and ownership were dropped, and without an fsync a power cut could still leave it empty. The temporary was named after the PID, which every Flatpak instance shares as PID 2. The file is now written in place, from the first byte that differs, then truncated and synced, under an flock on the file itself. A write that fails partway is undone where the disk allows; with the section last, where it is appended, only the section can be damaged. A read-only file is opened for writing only when something would change, so it is refused then and silent otherwise. Reading. libc++'s filebuf reports a failed read as the end of the file, so an I/O error came back as a short file and the section was written over what was left. Reads go through read(2). Code fences. ```make``` was taken for a fence, and a fence in a list item was missed while its indented closer opened one, so markers a user quoted in a code block could be taken for the section, or the real one hidden and appended again. Backtick info strings, block quotes and list items are now followed, and a block ends with its container. Warnings with nothing to write. With no notes, nothing short of a well-formed section is reported, so an AGENTS.md that ends in an open code block, or a project set to None, no longer warns on every build. An unclosed fence after a section no longer stops it being rewritten in place. Labels. The `from` line carried the git URL, credentials and all, or a path relative to the consumer that changed with the machine, the cache location and --local. It is now the repository's or the directory's name. Notes are deduplicated by repository rather than by clone, so one library pinned two ways is described once, as the nearer spec has it. The walk. It no longer looks at the project's tests: a plain build never did, and a test's dependency can point at the Configuration the project file returned by value. It goes ring by ring over projects rather than configurations, so what the project names itself comes first even with the library/executable split. The project's own AGENTS.consumer.md is excluded by path, also when a dependency's project file sits beside the project's. Removing a section dropped a blank line even from between two paragraphs, joining them; only a section that ends the file takes it now. A CRLF file gets a CRLF section. A note was checked on its raw bytes and again, under other whitespace rules, on the trimmed ones, so it could pass the first check and fail the merge, which then dropped every note. It is normalised once and checked on exactly what is merged. `test --list` only lists, and no longer writes AGENTS.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:02:28 +01:00
// What the comment above the note calls the dependency: its
// repository's name, or a local project's directory name.
std::string name;
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>
2026-10-31 03:18:06 +01:00
// The file's content, without leading blank lines or trailing
fix(agents): what the review of dependency notes found A review of the feature turned up the following, fixed here. Writing. AGENTS.md was replaced through a temporary renamed over it, which swaps in another file: a read-only AGENTS.md was rewritten anyway, a hard link such as a CLAUDE.md beside it kept the old text, ACLs and ownership were dropped, and without an fsync a power cut could still leave it empty. The temporary was named after the PID, which every Flatpak instance shares as PID 2. The file is now written in place, from the first byte that differs, then truncated and synced, under an flock on the file itself. A write that fails partway is undone where the disk allows; with the section last, where it is appended, only the section can be damaged. A read-only file is opened for writing only when something would change, so it is refused then and silent otherwise. Reading. libc++'s filebuf reports a failed read as the end of the file, so an I/O error came back as a short file and the section was written over what was left. Reads go through read(2). Code fences. ```make``` was taken for a fence, and a fence in a list item was missed while its indented closer opened one, so markers a user quoted in a code block could be taken for the section, or the real one hidden and appended again. Backtick info strings, block quotes and list items are now followed, and a block ends with its container. Warnings with nothing to write. With no notes, nothing short of a well-formed section is reported, so an AGENTS.md that ends in an open code block, or a project set to None, no longer warns on every build. An unclosed fence after a section no longer stops it being rewritten in place. Labels. The `from` line carried the git URL, credentials and all, or a path relative to the consumer that changed with the machine, the cache location and --local. It is now the repository's or the directory's name. Notes are deduplicated by repository rather than by clone, so one library pinned two ways is described once, as the nearer spec has it. The walk. It no longer looks at the project's tests: a plain build never did, and a test's dependency can point at the Configuration the project file returned by value. It goes ring by ring over projects rather than configurations, so what the project names itself comes first even with the library/executable split. The project's own AGENTS.consumer.md is excluded by path, also when a dependency's project file sits beside the project's. Removing a section dropped a blank line even from between two paragraphs, joining them; only a section that ends the file takes it now. A CRLF file gets a CRLF section. A note was checked on its raw bytes and again, under other whitespace rules, on the trimmed ones, so it could pass the first check and fail the merge, which then dropped every note. It is normalised once and checked on exactly what is merged. `test --list` only lists, and no longer writes AGENTS.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:02:28 +01:00
// whitespace. Lines end in LF; the merge writes the line ending the
// document uses.
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>
2026-10-31 03:18:06 +01:00
std::string text;
};
// `agentsMd` with its dependency notes section holding `notes`, in order.
fix(agents): what the review of dependency notes found A review of the feature turned up the following, fixed here. Writing. AGENTS.md was replaced through a temporary renamed over it, which swaps in another file: a read-only AGENTS.md was rewritten anyway, a hard link such as a CLAUDE.md beside it kept the old text, ACLs and ownership were dropped, and without an fsync a power cut could still leave it empty. The temporary was named after the PID, which every Flatpak instance shares as PID 2. The file is now written in place, from the first byte that differs, then truncated and synced, under an flock on the file itself. A write that fails partway is undone where the disk allows; with the section last, where it is appended, only the section can be damaged. A read-only file is opened for writing only when something would change, so it is refused then and silent otherwise. Reading. libc++'s filebuf reports a failed read as the end of the file, so an I/O error came back as a short file and the section was written over what was left. Reads go through read(2). Code fences. ```make``` was taken for a fence, and a fence in a list item was missed while its indented closer opened one, so markers a user quoted in a code block could be taken for the section, or the real one hidden and appended again. Backtick info strings, block quotes and list items are now followed, and a block ends with its container. Warnings with nothing to write. With no notes, nothing short of a well-formed section is reported, so an AGENTS.md that ends in an open code block, or a project set to None, no longer warns on every build. An unclosed fence after a section no longer stops it being rewritten in place. Labels. The `from` line carried the git URL, credentials and all, or a path relative to the consumer that changed with the machine, the cache location and --local. It is now the repository's or the directory's name. Notes are deduplicated by repository rather than by clone, so one library pinned two ways is described once, as the nearer spec has it. The walk. It no longer looks at the project's tests: a plain build never did, and a test's dependency can point at the Configuration the project file returned by value. It goes ring by ring over projects rather than configurations, so what the project names itself comes first even with the library/executable split. The project's own AGENTS.consumer.md is excluded by path, also when a dependency's project file sits beside the project's. Removing a section dropped a blank line even from between two paragraphs, joining them; only a section that ends the file takes it now. A CRLF file gets a CRLF section. A note was checked on its raw bytes and again, under other whitespace rules, on the trimmed ones, so it could pass the first check and fail the merge, which then dropped every note. It is normalised once and checked on exactly what is merged. `test --list` only lists, and no longer writes AGENTS.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:02:28 +01:00
// 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.
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>
2026-10-31 03:18:06 +01:00
//
// The two marker lines count only as whole lines outside fenced code
// blocks, which is what lets a document quote them. Throws, naming the
fix(agents): what the review of dependency notes found A review of the feature turned up the following, fixed here. Writing. AGENTS.md was replaced through a temporary renamed over it, which swaps in another file: a read-only AGENTS.md was rewritten anyway, a hard link such as a CLAUDE.md beside it kept the old text, ACLs and ownership were dropped, and without an fsync a power cut could still leave it empty. The temporary was named after the PID, which every Flatpak instance shares as PID 2. The file is now written in place, from the first byte that differs, then truncated and synced, under an flock on the file itself. A write that fails partway is undone where the disk allows; with the section last, where it is appended, only the section can be damaged. A read-only file is opened for writing only when something would change, so it is refused then and silent otherwise. Reading. libc++'s filebuf reports a failed read as the end of the file, so an I/O error came back as a short file and the section was written over what was left. Reads go through read(2). Code fences. ```make``` was taken for a fence, and a fence in a list item was missed while its indented closer opened one, so markers a user quoted in a code block could be taken for the section, or the real one hidden and appended again. Backtick info strings, block quotes and list items are now followed, and a block ends with its container. Warnings with nothing to write. With no notes, nothing short of a well-formed section is reported, so an AGENTS.md that ends in an open code block, or a project set to None, no longer warns on every build. An unclosed fence after a section no longer stops it being rewritten in place. Labels. The `from` line carried the git URL, credentials and all, or a path relative to the consumer that changed with the machine, the cache location and --local. It is now the repository's or the directory's name. Notes are deduplicated by repository rather than by clone, so one library pinned two ways is described once, as the nearer spec has it. The walk. It no longer looks at the project's tests: a plain build never did, and a test's dependency can point at the Configuration the project file returned by value. It goes ring by ring over projects rather than configurations, so what the project names itself comes first even with the library/executable split. The project's own AGENTS.consumer.md is excluded by path, also when a dependency's project file sits beside the project's. Removing a section dropped a blank line even from between two paragraphs, joining them; only a section that ends the file takes it now. A CRLF file gets a CRLF section. A note was checked on its raw bytes and again, under other whitespace rules, on the trimmed ones, so it could pass the first check and fail the merge, which then dropped every note. It is normalised once and checked on exactly what is merged. `test --list` only lists, and no longer writes AGENTS.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:02:28 +01:00
// 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.
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>
2026-10-31 03:18:06 +01:00
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
fix(agents): what the review of dependency notes found A review of the feature turned up the following, fixed here. Writing. AGENTS.md was replaced through a temporary renamed over it, which swaps in another file: a read-only AGENTS.md was rewritten anyway, a hard link such as a CLAUDE.md beside it kept the old text, ACLs and ownership were dropped, and without an fsync a power cut could still leave it empty. The temporary was named after the PID, which every Flatpak instance shares as PID 2. The file is now written in place, from the first byte that differs, then truncated and synced, under an flock on the file itself. A write that fails partway is undone where the disk allows; with the section last, where it is appended, only the section can be damaged. A read-only file is opened for writing only when something would change, so it is refused then and silent otherwise. Reading. libc++'s filebuf reports a failed read as the end of the file, so an I/O error came back as a short file and the section was written over what was left. Reads go through read(2). Code fences. ```make``` was taken for a fence, and a fence in a list item was missed while its indented closer opened one, so markers a user quoted in a code block could be taken for the section, or the real one hidden and appended again. Backtick info strings, block quotes and list items are now followed, and a block ends with its container. Warnings with nothing to write. With no notes, nothing short of a well-formed section is reported, so an AGENTS.md that ends in an open code block, or a project set to None, no longer warns on every build. An unclosed fence after a section no longer stops it being rewritten in place. Labels. The `from` line carried the git URL, credentials and all, or a path relative to the consumer that changed with the machine, the cache location and --local. It is now the repository's or the directory's name. Notes are deduplicated by repository rather than by clone, so one library pinned two ways is described once, as the nearer spec has it. The walk. It no longer looks at the project's tests: a plain build never did, and a test's dependency can point at the Configuration the project file returned by value. It goes ring by ring over projects rather than configurations, so what the project names itself comes first even with the library/executable split. The project's own AGENTS.consumer.md is excluded by path, also when a dependency's project file sits beside the project's. Removing a section dropped a blank line even from between two paragraphs, joining them; only a section that ends the file takes it now. A CRLF file gets a CRLF section. A note was checked on its raw bytes and again, under other whitespace rules, on the trimmed ones, so it could pass the first check and fail the merge, which then dropped every note. It is normalised once and checked on exactly what is merged. `test --list` only lists, and no longer writes AGENTS.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:02:28 +01:00
// 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.
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>
2026-10-31 03:18:06 +01:00
bool UpdateAgentsMd(const Configuration& project, const fs::path& projectFile);
}