Thanks for the detailed report, @amarcadet. I can confirm the analysis lines up with the code.
The root cause is that FileSystem.glob unconditionally excludes **/.gitkeep and **/.DS_Store at the lowest level [1]. The BuildableFolderException.excluded patterns are resolved by calling that same glob [2], so patterns like **/.gitkeep match nothing and no membershipExceptions are emitted to the .pbxproj [3]. Meanwhile, Xcode’s PBXFileSystemSynchronizedRootGroup reads the directory directly from disk — it doesn’t consult Tuist’s resolved file list — so .gitkeep / .DS_Store are still bundled.
In short: Tuist’s file resolution pipeline can’t “see” these files, but Xcode’s synchronized group can, creating the inconsistency you described.
A fix would likely need to either:
- Unconditionally emit
.gitkeepand.DS_Storeas membership exceptions on every synchronized root group (matching the original intent of the FileSystem filter), or - Bypass the glob exclusion when resolving
BuildableFolderException.excludedpatterns, so users can target these files explicitly.
If you’d like to dig deeper or work on a fix, I’d suggest installing this skill following the steps in this repo and using a coding agent (Claude, Codex, etc.) to prototype the change. If that doesn’t yield a solution, a minimal reproducible project attached here would help the maintainers prioritize it.
To reply, just mention @dosu.
Docs are dead. Just use Dosu.