autosetup: anchor on the project whose artifacts these actually are - #187
Merged
Conversation
find_build_config_dir accepts a directory once it holds a build config and its artifact directory exists. Two configs can name the same physical directory: a root config building the tree with out = 'pkg/out', and pkg/foundry.toml with the default out = 'out' that never ran. The walk stops at pkg/, and everything after it reads artifacts in the wrong frame — the extractor builds its project-file set relative to pkg/ while the artifacts record paths relative to the root, so no source path matches, every artifact is filtered out, and a build that fully succeeded ends as "no project-local logic contracts found". The artifacts themselves say which project wrote them: their recorded source paths resolve against it. A candidate now has to pass that test, so a directory holding somebody else's artifacts is skipped and the walk continues to the one they belong to. Artifacts that record no source path answer yes, leaving projects with older or stripped metadata where they were. Measured on a 399-project sweep: one project lost 110 matching artifacts this way, from a build with no errors at all.
shellygr
marked this pull request as ready for review
August 22, 2026 15:19
jar-ben
requested changes
Sep 2, 2026
| def _declared_sources(artifacts: Path, limit: int = 20) -> list[str]: | ||
| """Source paths a sample of *artifacts* say they were compiled from. | ||
|
|
||
| Foundry records them as the keys of ``metadata.settings.compilationTarget``; Hardhat as |
Collaborator
There was a problem hiding this comment.
- We support more build systems than just hardhat and foundry, so perhaps we should handle all of them also here.
- it would be better to have this as an abstract method implemented by the particular build managers
Contributor
Author
There was a problem hiding this comment.
Claude answers: Done, both points.
The ownership check read Foundry's compilationTarget and Hardhat's sourceName from a helper inside project_dir. Truffle records sourcePath and was not among them, so its artifacts yielded no recorded source and the check answered yes for every candidate. The case it exists to catch could not reach it. That sourcePath is also absolute, and joining an absolute path onto the candidate directory discards the candidate, so the relative-path comparison would have accepted any artifact whose source still existed anywhere on disk. recorded_source is now an abstract method on BuildSystemManager, implemented beside the holds_artifacts each manager already has, and the base runs the sampling walk and the containment test once for all three. project_dir locates the directory and asks whichever manager matched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…shelly/build-config-frame
jar-ben
approved these changes
Sep 2, 2026
shellygr
enabled auto-merge (squash)
September 2, 2026 16:39
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to the artifact-directory work in #169, from the same sweep that produced #186.
What goes wrong
find_build_config_dirwalks up from the contract and stops at the first directory that holds abuild config and has its artifact directory on disk. That test is satisfiable by a directory that
never built anything, because two configs can name the same physical directory:
pkg/outexists andpkg/foundry.tomlis a config, so the walk stops atpkg/. Everythingdownstream then reads those artifacts in the wrong frame: the extractor builds its project-file set
relative to
pkg/, while the artifacts recordpkg/src/Widget.solrelative to the root. No sourcepath matches, the scope filter drops every artifact, and the run dies with "No project-local logic
contracts found in FoundryManager build output" — from a build that had no errors at all.
In the sweep this cost one project 110 matching artifacts, and the class it lands in is the run's
largest.
The change
The artifacts say which project wrote them: Foundry records
metadata.settings.compilationTarget,Hardhat records
sourceName, and both are relative to the project the build ran in. A candidatedirectory now has to pass that test — at least one recorded source path has to resolve inside it —
so a directory holding somebody else's artifacts is skipped and the walk continues to the one they
belong to.
This extends #169's own principle rather than reversing it: decide from what is on disk, not from
the directory layout. A genuinely nested project that built its own artifacts records paths relative
to itself, passes, and still anchors where #169 put it. Artifacts with no recorded source (older
Foundry, stripped metadata) answer yes, so nothing moves for them.
Sampling is capped at 20 artifacts per candidate, so the check costs a few JSON reads on a path that
already walks the tree.
Tests
tests/test_project_dir.pygains three cases: the shared-out-dir layout above (fails without thechange), a nested project that really did write its own artifacts (must keep anchoring there), and
artifacts with no metadata (must not move). 24 pass in that file, 111 across the parser and
workaround suites.
The repo's full run fails 14 for me and 15 on a clean tree, all in integration tests that vary
between runs; none of them is unique to this change.
Draft, and worth reviewing alongside #186: both come out of the same sweep, and this one touches the
path #169 introduced.
Review
recorded_sourceis an abstract method onBuildSystemManagernext to the existingholds_artifacts. Each manager names its own field, and the base does the sampling walk and theownership test once. Truffle is covered, which it was not before: it records
sourcePath, so itanswered every candidate yes and the check never applied to it.
Truffle's
sourcePathis absolute where the other two are project-relative, so the base tests anabsolute path for containment rather than joining it onto the candidate. Joining discards the
candidate under pathlib and would accept any artifact whose source still existed anywhere.
The extractors parse the same three fields for their own purposes (
parsers/foundry.py,parsers/hardhat.py,parsers/truffle.py) and are left alone. They do more than read the path,and folding them in is a separate change.