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>
This commit is contained in:
parent
c2e8bf0a93
commit
7815856d60
5 changed files with 446 additions and 194 deletions
|
|
@ -360,6 +360,36 @@ namespace {
|
|||
for (Shader& s : cfg.shaders) s.path = abs(s.path);
|
||||
return cfg;
|
||||
}
|
||||
|
||||
// Into ProjectCache under `fullHash`, with `loaded` recorded beside it.
|
||||
// When another thread stored the same spec first, its Configuration
|
||||
// wins and `cfg` is dropped.
|
||||
Configuration* StoreProject(std::string fullHash, Configuration cfg, LoadedProject loaded) {
|
||||
std::lock_guard lock(ProjectCacheMutex);
|
||||
if (auto it = ProjectCache.find(fullHash); it != ProjectCache.end()) {
|
||||
return it->second.get();
|
||||
}
|
||||
auto stored = std::make_unique<Configuration>(std::move(cfg));
|
||||
Configuration* ptr = stored.get();
|
||||
ProjectCache.emplace(std::move(fullHash), std::move(stored));
|
||||
LoadedProjects.emplace(ptr, std::move(loaded));
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// `url` as it names a repository: without user info, which is where a
|
||||
// token goes, and without trailing slashes or ".git", so specs that only
|
||||
// spell the repository differently name the same one.
|
||||
std::string RepositoryKey(std::string_view url) {
|
||||
std::string key(url);
|
||||
if (const std::size_t scheme = key.find("://"); scheme != std::string::npos) {
|
||||
const std::size_t host = scheme + 3;
|
||||
const std::size_t at = key.rfind('@', key.find('/', host));
|
||||
if (at != std::string::npos && at >= host) key.erase(host, at + 1 - host);
|
||||
}
|
||||
while (key.ends_with('/')) key.pop_back();
|
||||
if (key.ends_with(".git")) key.resize(key.size() - 4);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
Configuration* Crafter::GitProject(const GitProjectSpec& spec) {
|
||||
|
|
@ -412,15 +442,13 @@ Configuration* Crafter::GitProject(const GitProjectSpec& spec) {
|
|||
std::vector<std::string_view> argViews(spec.args.begin(), spec.args.end());
|
||||
Configuration cfg = LoadProjectFromRoot(projectPath, cloneDir, argViews);
|
||||
|
||||
std::lock_guard lock(ProjectCacheMutex);
|
||||
if (auto it = ProjectCache.find(fullHash); it != ProjectCache.end()) {
|
||||
return it->second.get();
|
||||
}
|
||||
auto stored = std::make_unique<Configuration>(std::move(cfg));
|
||||
Configuration* ptr = stored.get();
|
||||
ProjectCache.emplace(std::move(fullHash), std::move(stored));
|
||||
LoadedProjects.emplace(ptr, LoadedProject{ .projectFile = projectPath.lexically_normal(), .url = spec.source.url });
|
||||
return ptr;
|
||||
const fs::path projectInRepo = spec.projectFile.lexically_normal();
|
||||
const fs::path below = projectInRepo.parent_path();
|
||||
return StoreProject(std::move(fullHash), std::move(cfg), {
|
||||
.projectFile = projectPath.lexically_normal(),
|
||||
.name = below.empty() ? name : std::format("{}/{}", name, below.generic_string()),
|
||||
.identity = std::format("git|{}|{}", RepositoryKey(spec.source.url), projectInRepo.generic_string()),
|
||||
});
|
||||
}
|
||||
|
||||
fs::path Crafter::GitFetch(const GitSource& source) {
|
||||
|
|
@ -475,15 +503,11 @@ Configuration* Crafter::LocalProject(const LocalProjectSpec& spec) {
|
|||
std::vector<std::string_view> argViews(spec.args.begin(), spec.args.end());
|
||||
Configuration cfg = LoadProjectFromRoot(canonical, depRoot, argViews);
|
||||
|
||||
std::lock_guard lock(ProjectCacheMutex);
|
||||
if (auto it = ProjectCache.find(fullHash); it != ProjectCache.end()) {
|
||||
return it->second.get();
|
||||
}
|
||||
auto stored = std::make_unique<Configuration>(std::move(cfg));
|
||||
Configuration* ptr = stored.get();
|
||||
ProjectCache.emplace(std::move(fullHash), std::move(stored));
|
||||
LoadedProjects.emplace(ptr, LoadedProject{ .projectFile = canonical, .url = "" });
|
||||
return ptr;
|
||||
return StoreProject(std::move(fullHash), std::move(cfg), {
|
||||
.projectFile = canonical,
|
||||
.name = depRoot.filename().string(),
|
||||
.identity = std::format("local|{}", canonical.string()),
|
||||
});
|
||||
}
|
||||
|
||||
std::optional<LoadedProject> Crafter::LoadedProjectOf(const Configuration& cfg) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue