We should stop project-root pollution

09 October 2024

Not too long ago people realized that enough is enough and that programs should not be writing stuff directly to $HOME as they please. The battle is far from over, as many programs still hard-code their settings path, but the XDG Base Directory Spec has gained enough supported to at least enable tools that automate the cleanup process.

What about our repositories, though?

A quick glance at BigQuery’s public datasets for GitHub data shows that many files at the project root are just configuration for some vendor or tool:

/* most common files at the root of GitHub repositories */
SELECT
  path,
  COUNT(*) as count
FROM `bigquery-public-data.github_repos.sample_files`
GROUP BY path
-- only top-level files, i.e. paths which don't contain slashes
HAVING path NOT LIKE '%/%'
ORDER BY count DESC
LIMIT 1000
path count
README.md 147824
.gitignore 138027
LICENSE 122677
.travis.yml 52847
package.json 37508
LICENSE.txt 15942
Makefile 14351
CHANGELOG.md 13315
setup.py 12636
LICENSE.md 12480
.gitattributes 12360
CONTRIBUTING.md 10582
.editorconfig 10164
index.js 10125
Gemfile 9997
Rakefile 9503
bower.json 9297
composer.json 8382
COPYING 7956
.npmignore 7770
MANIFEST.in 7237
README.rst 7099
.jshintrc 6922
build.gradle 6846
index.html 6824

(Remaining 975 rows at the bottom of the article.)

Some of those are understandably relevant to occupy real estate at the project root (README, LICENSE, CHANGELOG).

But many are just tool-specific configuration files, most of which rarely gets touched.

“Why do you care?”

Hey, I’m not the only one. I care because it gets noisy and cluttered. It makes even simple tasks, like finding the main package declaration, become frustrating for users who aren’t familiar with the language.

The project root is the living room, and we should keep it tidy to make guests feel comfortable.

Remaining 975 rows
path count
README 6738
requirements.txt 6676
pom.xml 6269
.gitmodules 6251
gradlew 6073
gradlew.bat 5926
settings.gradle 5785
AUTHORS 5679
Gruntfile.js 5146
gulpfile.js 5127
.eslintrc 4882
setup.cfg 4670
gradle.properties 4310
Dockerfile 3808
readme.md 3716
.babelrc 3586
Gemfile.lock 3529
tox.ini 3528
CMakeLists.txt 3480
.rspec 3135
appveyor.yml 2988
.bowerrc 2810
configure.ac 2578
phpunit.xml.dist 2490
ChangeLog 2489
Makefile.am 2446
TODO 2441
INSTALL 2337
license.txt 2332
.project 2202
NEWS 2045
karma.conf.js 2043
Procfile 2016
webpack.config.js 2008
composer.lock 1959
circle.yml 1958
phpunit.xml 1955
CHANGELOG 1931
Vagrantfile 1914
.coveragerc 1857
VERSION 1852
NOTICE 1847
README.markdown 1805
.eslintignore 1792
test.js 1630
index.php 1620
main.go 1615
autogen.sh 1535
build.xml 1495
screenshot.png 1479
build.sh 1470
README.txt 1359
.rubocop.yml 1354
.classpath 1344
server.js 1338
.jscsrc 1330
MIT-LICENSE 1321
.ruby-version 1303
LICENCE 1286
LICENSE-MIT 1272
build.sbt 1264
Podfile 1228
configure 1209
.scrutinizer.yml 1197
app.js 1178
config.ru 1177
TODO.md 1168
Cargo.toml 1164
favicon.ico 1145
Readme.md 1123
Setup.hs 1119
.codeclimate.yml 1110
.dockerignore 1096
_config.yml 1071
docker-compose.yml 1058
license 1049
project.clj 1044
.mailmap 1026
CODE_OF_CONDUCT.md 1024
Guardfile 1018
CHANGES.md 999
CHANGES 996
CONTRIBUTING.rst 980
Podfile.lock 972
tsconfig.json 964
manage.py 959
.coveralls.yml 937
CNAME 936
CONTRIBUTORS 865
.DS_Store 860
.jshintignore 859
__init__.py 831
License.txt 821
AndroidManifest.xml 799
install.sh 786
.htaccess 784
CREDITS 777
MAINTAINERS 777
.gitreview 760
.watchmanconfig 748
Makefile.in 731
README.rdoc 731
readme.txt 726
mix.exs 716
project.properties 705
.ember-cli 699
doc.go 691
style.css 676
test-requirements.txt 660
stack.yaml 648
.hgignore 646
testem.json 633
ember-cli-build.js 627
AUTHORS.rst 619
app.json 614
changelog.md 613
makefile 613
CHANGES.txt 612
.yardopts 609
manifest.json 602
license.md 594
Doxyfile 592
config.js 590
main.js 580
logo.png 574
NOTICE.txt 572
HISTORY.md 567
mix.lock 566
install-sh 549
MIT-LICENSE.txt 547
rebar.config 546
icon.png 540
History.md 538
package.js 534
typings.json 533
CHANGELOG.rst 531
COPYRIGHT 530
INSTALL.md 511
.eslintrc.json 503
REQUIRE 500
_Pods.xcodeproj 494
COPYING.txt 490
CHANGES.rst 489
Package.swift 486
changelog.txt 480
component.json 479
tslint.json 478
runtests.py 471
.clang-format 466
.ruby-gemset 460
mkdocs.yml 457
HISTORY.rst 450
AUTHORS.md 450
MANIFEST 449
robots.txt 449
CONTRIBUTORS.md 448
aclocal.m4 448
proguard-project.txt 447
config.h.in 446
.document 443
config.json 442
test.sh 441
main.cpp 440
CHANGELOG.txt 435
main.py 434
.php_cs 434
requirements-dev.txt 432
build.cmd 427
THANKS 424
run.sh 423
Gruntfile.coffee 414
.env.example 397
TODO.txt 396
test.py 395
demo.gif 394
feed.xml 393
gruntfile.js 393
contributing.md 388
config.guess 387
config.sub 384
.flowconfig 377
Cartfile.resolved 376
Kconfig 376
missing 375
404.html 375
REPORTING-BUGS 374
UNLICENSE 373
Kbuild 372
plugin.xml 370
main.c 370
build.bat 369
Cartfile 360
LICENCE.txt 358
cli.js 351
npm-shrinkwrap.json 349
example.js 348
release.sh 347
AUTHORS.txt 346
COPYING.LESSER 340
ISSUE_TEMPLATE.md 340
LICENSE-APACHE 339
gulpfile.babel.js 337
.nvmrc 337
README.org 337
init.rb 333
deploy.sh 331
Changelog.md 327
.testr.conf 327
config.go 324
README.adoc 324
binding.gyp 322
coffeelint.json 321
depcomp 321
DESCRIPTION 312
wercker.yml 307
.hgtags 307
pytest.ini 306
bootstrap.sh 304
CONTRIBUTING 302
License.md 299
tests.py 298
version.sbt 295
.hound.yml 294
config.py 294
demo.html 292
util.go 291
LICENSE.TXT 289
Changelog 287
ChangeLog.md 286
build.properties 286
HACKING 285
metadata.json 282
.eslintrc.js 280
NEWS.md 276
checkstyle.xml 274
server.php 273
dev-requirements.txt 272
.versions 271
artisan 271
ic_launcher-web.png 269
PATENTS 268
README.textile 268
bootstrap 268
setup.sh 266
example_test.go 266
configure.in 264
fabfile.py 263
HACKING.rst 262
client.go 261
readme.markdown 261
.rubocop_todo.yml 260
config.rb 260
NAMESPACE 256
tsd.json 255
License 255
BUGS 254
Appraisals 254
server.go 254
functions.php 253
LICENCE.md 252
acinclude.m4 250
config.xml 249
Cargo.lock 249
babel.cfg 247
test.html 246
.styleci.yml 246
RELEASE_NOTES.md 245
utils.go 245
global.json 244
testem.js 243
Gulpfile.js 240
start.sh 239
pylintrc 239
.pylintrc 235
pubspec.yaml 235
messages.json 229
about.md 228
Capfile 228
Berksfile 227
.dir-locals.el 226
.bumpversion.cfg 225
jsconfig.json 225
.drone.yml 225
Info.plist 224
run_tests.sh 224
COPYING.LIB 223
INSTALL.txt 222
ROADMAP.md 221
compile 219
build.fsx 218
Main.sublime-menu 217
.jscs.json 216
phpspec.yml 216
.csslintrc 216
.yo-rc.json 216
metadata.rb 216
LICENSE-2.0.txt 213
ez_setup.py 212
run.py 211
.env 210
.kitchen.yml 210
rebar 210
.npmrc 208
header.php 207
protractor.conf.js 207
build.js 205
glide.yaml 204
footer.php 204
favicon.png 203
build 201
example.html 200
version.go 199
.Rbuildignore 197
example.py 197
Android.mk 196
keywords.txt 196
index.md 195
wscript 194
CONDUCT.md 193
elm-package.json 193
SUMMARY.md 193
.jsbeautifyrc 192
library.properties 191
config.h 189
about.html 188
todo.txt 188
app.py 188
Cakefile 186
ltmain.sh 184
API.md 184
version.txt 183
paket.dependencies 182
app.yaml 182
.cproject 180
requirements-test.txt 180
ansible.cfg 180
.gitlab-ci.yml 179
gulpfile.coffee 179
.pydevproject 178
paket.lock 177
settings.py 176
utils.py 175
.istanbul.yml 174
manifest.yml 174
mkinstalldirs 173
errors.go 173
book.json 173
Setup.lhs 172
index.coffee 171
main.m 171
opam 168
config.php 168
_tags 168
config 168
codecov.yml 168
.ghci 167
glide.lock 166
main_test.go 166
params.json 165
index.ios.js 165
package.xml 165
build.ps1 164
screenshot.jpg 164
Cartfile.private 164
README.MD 164
phpcs.xml 164
RELEASE.md 164
FAQ.md 163
.vimrc 162
History.txt 161
packages.config 160
util.h 159
.rvmrc 159
runtime.txt 158
sitemap.xml 157
RELEASING.md 156
local.properties 155
version 155
AndroidKernel.mk 153
SConstruct 152
.zuul.yml 151
LICENSE.rst 151
search.php 151
runtests.sh 151
init.lua 151
404.php 151
util.c 151
update.sh 151
changelog 149
CONTRIBUTORS.txt 149
ReadMe.md 148
chefignore 147
install 147
lint.xml 147
build.py 146
build.rs 146
page.php 145
.arcconfig 144
requirements_dev.txt 144
.scss-lint.yml 143
COPYRIGHT.txt 143
server.py 142
conftest.py 141
README.mdown 140
scalastyle-config.xml 140
logo.svg 140
Doxyfile.in 139
Changes 138
Default.sublime-commands 137
.appveyor.yml 137
Screenshot.png 137
haxelib.json 137
index.android.js 136
single.php 136
browser.js 136
UPGRADE.md 135
.merlin 135
proguard.cfg 135
error.go 134
myocamlbuild.ml 134
.swiftlint.yml 134
clean.sh 134
openstack-common.conf 133
HACKING.md 131
_oasis 131
Default (Windows).sublime-keymap 130
example.png 130
addon.xml 130
.slather.yml 130
Default (OSX).sublime-keymap 129
comments.php 129
install.php 128
CHANGELOG.markdown 128
TESTING.md 128
README.mkd 127
run 127
.reviewboardrc 127
webpack.config.dev.js 126
index.css 126
utils.js 126
version.sh 126
common.h 126
ChangeLog.txt 126
CREDITS.md 126
Brocfile.js 125
test 125
index.hbs 124
uninstall.php 124
.cvsignore 124
Default (Linux).sublime-keymap 123
shard.yml 123
test_requirements.txt 123
background.js 123
.buildpacks 123
bootstrap.php 122
FAQ 122
PULL_REQUEST_TEMPLATE.md 122
.landscape.yaml 121
autoload.php 120
tasks.py 120
preview.png 120
nginx.conf 120
GNUmakefile 119
Contributing.md 119
utils.h 119
default.hbs 118
sbt 118
HISTORY 118
rebar.lock 118
post.hbs 118
sidebar.php 117
.vscodeignore 117
maven_push.gradle 117
Default-568h@2x.png 117
versioneer.py 117
webpack.config.prod.js 116
screenshot.gif 116
.fixtures.yml 116
ionic.project 116
.eslintrc.yml 115
404.md 115
run_tests.py 115
styles.css 114
contributors.txt 114
types.go 114
.nojekyll 114
setup.ml 112
travis.sh 112
licence.txt 112
composer.phar 111
.tern-project 111
TODO.rst 111
.verb.md 111
versions.json 110
README.asciidoc 110
client_test.go 110
logger.go 110
humans.txt 110
Cask 109
Module.php 109
archive.php 108
webpack.config.production.js 108
login.php 108
VERSION.txt 108
.simplecov 108
npm-debug.log 108
bootstrap.py 107
todo.md 106
example.php 105
web.config 105
atom.xml 104
.swift-version 104
_config.php 103
manifest.mf 103
nodemon.json 102
default.nix 102
.csscomb.json 102
codeception.yml 101
version.h 101
rollup.config.js 100
config.m4 100
CTestConfig.cmake 100
context.go 100
box.json 100
codereview.settings 99
util_test.go 99
index.less 99
mvnw 99
DOCUMENTATION.md 98
wsgi.py 97
tests.webpack.js 97
history.md 97
config.yaml 97
.stylelintrc 96
NOTES 96
make.bat 96
make.sh 96
popup.html 95
log.go 95
run-tests.sh 95
modman 95
config.h.cmake 95
test.c 94
.bithoundrc 94
README.html 94
api.go 93
uninstall.sh 93
Makefile.PL 93
requirements.php 92
config.mk 92
chrome.manifest 92
RELEASENOTES.md 92
MAINTAINERS.md 92
BUILD.md 92
options.html 92
.landscape.yml 92
CREDITS.txt 91
install.rb 91
plugin.yml 91
compile.sh 90
parser.go 90
yii.bat 90
Manifest.txt 90
Readme.markdown 89
.env.sample 89
RELEASE 89
utils_test.go 88
util.py 88
util.js 88
config.rpath 88
Program.cs 87
NOTICE.md 87
PKG-INFO 87
import-summary.txt 87
Makefile.common 87
CHANGELOG.rdoc 87
config.ini 87
demo.js 86
erlang.mk 86
NOTES.md 86
readme.rst 86
logout.php 86
.gitconfig 86
.slugignore 86
code_of_conduct.md 86
.cfignore 86
Readme.txt 85
buildout.cfg 85
package.lisp 85
page.hbs 85
script.js 85
.autotest 85
system.properties 84
package.sh 84
UPGRADING.md 84
example.gif 84
main.lua 84
.bzrignore 83
config_test.go 83
proguard-rules.pro 83
init.sh 83
config.yml 82
phpunit.php 82
client.js 82
hosts 81
response.go 81
app.rb 81
nbactions.xml 80
ant.properties 80
webpack.production.config.js 80
sache.json 80
ABOUT-NLS 80
notes.txt 80
request.go 79
.ycm_extra_conf.py 79
firebase.json 79
tests.js 79
jsdoc.json 79
main.css 79
.coffeelintignore 79
apple-touch-icon.png 78
CONTRIB.md 78
webpack.config.development.js 78
docker-entrypoint.sh 78
esdoc.json 77
appinfo.json 77
build.boot 77
message.go 76
utils.c 76
demo.png 76
install.rdf 76
handler.go 76
supervisord.conf 75
mainwindow.h 75
webpack.config.babel.js 75
DEVELOPMENT.md 75
urls.py 75
yuidoc.json 75
Changelog.txt 75
LICENSE-MIT.txt 75
BUILDING.md 74
GPL-LICENSE.txt 74
publish.sh 74
git.mk 74
screenshot-1.png 74
Settings.StyleCop 74
webpack.config.base.js 73
rebar.config.script 73
index.d.ts 73
test.lua 73
conn.go 73
Config.in 73
ReleaseNotes.md 73
version.py 73
mainwindow.cpp 73
http.go 73
tags.html 73
entrypoint.sh 73
.no-sublime-package 73
fanart.jpg 72
test.php 72
activator.properties 72
bench_test.go 72
.pre-commit-config.yaml 72
commands.go 72
mvnw.cmd 72
screenshot2.png 72
code-of-conduct.md 71
CONTRIBUTE.md 71
requirements_test.txt 71
resources.qrc 71
launch.sh 71
NuGet.Config 71
Icon.png 71
router.go 71
dub.json 71
tag.hbs 71
LICENSE.MIT 71
NuGet.config 70
ivy.xml 70
main.h 70
screenshot-2.png 70
schema.sql 69
OSSMETADATA 69
rules.mk 69
common.mk 69
dev_requirements.txt 69
INSTALL.rst 69
KEYS 69
theme.toml 69
shell.nix 68
init.php 68
cover.jpg 68
server_test.go 67
conf.py 67
helpers.go 67
options.js 67
rustfmt.toml 66
cmake_uninstall.cmake.in 66
stamp-h.in 66
LICENSE.GPL 66
vimrc 66
init 66
PKGBUILD 66
resource.h 66
BUILD 65
session.go 65
nb-configuration.xml 65
bin.js 65
.ackrc 65
auth.go 65
waf 65
CHANGE.md 64
RELEASE-NOTES.md 64
options.go 64
TODO.org 64
mainwindow.ui 64
.gemtest 64
blueprints.yaml 64
sonar-project.properties 64
event.go 64
models.py 64
Main.hs 64
.sass-lint.yml 63
packages.json 63
install.py 63
.gdbinit 63
.get_maintainer.ignore 63
searchform.php 63
CONTRIBUTORS.rst 63
types.h 63
findbugs-exclude.xml 63
configure.sh 63
log.c 63
admin.php 63
behat.yml 62
.travis.sh 62
default.ps1 62
DISCLAIMER 62
common.go 62
.drone.sec 62
contact.html 62
yii 62
screen.png 62
test.yml 62
Thorfile 61
coverage.sh 61
MANIFEST.SKIP 61
Context.sublime-menu 61
uninstall.rb 61
.analysis_options 61
BSDmakefile 61
webpack.prod.config.js 61
karma.config.js 61
library.json 61
grunt.js 61
.python-version 61
default.py 61
DESIGN.md 61
App.config 60
cron.php 60
default.properties 60
api.php 60
OWNERS 60
debug.h 60
LICENSE.MD 60
site.yml 60
data.json 60
screenshot1.png 60
settings.xml 60
NEWS.txt 60
.pep8 59
manifest 59
metadata.yaml 59
plugin.json 59
install.bat 59
author.hbs 59
readme.html 58
release-notes.md 58
config.c 58
app.config 58
.goxc.json 57
template.html 57
provision.sh 57
.semver 57
Default.sublime-keymap 57
cache.go 57
phpdoc.dist.xml 57
config.w32 57
Jenkinsfile 57
wallaby.js 56
index.yaml 56
.bashrc 56
control 56
log.h 56
db.go 56
index.rst 56
node.go 56
run.bat 56
setup.rb 56
connection.go 56
other-requirements.txt 55
start.js 55
xmlrpc.php 55
test.txt 55
file.go 55
environment_config.yaml 55
rebar3 55
.remarkrc 54
shippable.yml 54
LICENSE.LGPL 54
routes.js 54
info.plist 54
todo 54
DCO 54
.pullapprove.yml 54
test_settings.py 54
parser_test.go 54
demo.py 54
cucumber.yml 53
.buckconfig 53
LICENSE.html 53
CONTRIBUTING.markdown 53
config.lua 53
pre-commit 53
icon.ico 53
api.js 53
Messages.sh 53
.directory 53
brunch-config.js 53
init.bat 53
README.TXT 53
issue_template.md 53
apiary.apib 53
clean.bat 53
META 53
debug.go 53
fig.yml 53
ReadMe.txt 52
THANKS.md 52
README_CN.md 52
LICENSE-GPL 52
compat.h 52
COPYING.LGPL 52
command.go 52
.buildpath 52
pavement.py 52
DESCRIPTION.rst 52
content.php 52
benchmark.js 52
reader.go 52
.kitchen.docker.yml 51
karma-e2e.conf.js 51
linter.py 51
notes.md 51
uncrustify.cfg 51
licence 51
template.go 51
writer.go 51
docs 51
context_test.go 51
DEPS 51
deploy 51
config.toml 51
COPYING.DOC 50
.sublimelinterrc 50
stats.go 50
update.php 50
common.py 50
preview.gif 50
pre_build_hook 50
cleanup.sh 50
RELEASE-NOTES.txt 50
CONTRIBUTION.md 50
settings.json 50
releases.json 50
RELEASE_NOTES 50
Web.config 49
Rakefile.rb 49
nuget.config 49
Procfile.dev 49
rakefile.rb 49
list.h 49
cli.go 49
stdeb.cfg 49
config.json.example 49
devServer.js 49
HEADER.txt 49
ofxaddons_thumbnail.png 48
build.cake 48
.rultor.yml 48
environment.yml 48
extension.js 48
build.config 48
changes.txt 48
.cocoadocs.yml 48
.checkignore 47
index.ts 47
.checkstyle 47
version.properties 47
install.js 47
newrelic.js 47
MAINTAINERS.toml 47
test-main.js 47
LICENSE.BSD 47
crossdomain.xml 47
Changes.md 47
release 47
gradle-mvn-push.gradle 46
icon.svg 46
COPYING.MIT 46
screenshot-3.png 46
ReleaseNotes.txt 46
.node-version 46
requirements.pip 46
store.go 46
SECURITY.md 46
webpack.dev.config.js 46
background.html 46
browserconfig.xml 46
.godir 45
readthedocs.yml 45
feeds.conf.default 45
register.php 45
.isort.cfg 45
constants.go 45
ylwrap 45
Local.testsettings 45
README.md~ 44
WORKSPACE 44
generate.sh 44
SETUP.md 44
model.py 44
header.txt 44
bundle.js 44
GruntFile.js 44
settings.php 44
common.c 44
benchmark_test.go 44
.agignore 44
.deployment 44
env.sh 44
apple-touch-icon-precomposed.png 44
header.png 44
distribute_setup.py 44
version.c 44
common.js 44
Plugin.php 44
router_test.go 44
handlers.go 44
parse.go 43
typedoc.json 43
HISTORY.txt 43
run.js 43
phpmd.xml 43
filter.go 43
USAGE.md 43
cookiecutter.json 43
oca_dependencies.txt 43
.envrc 43
build.proj 43
VERSION.yml 43
smart.json 43
requirements-tests.txt 43
Brewfile 42
CommonAssemblyInfo.cs 42
gitconfig 42
proxy.go 42
authors.txt 42
ISSUE_TEMPLATE 42
.tm_properties 42
test.cpp 42
debug.c 42
premake4.lua 42
jquery.js 42