Must the `config/` directory be hidden?
In Rails and Hanami applications, the framework itself uses a config/ directory (unhidden) in the application root. While these applications don't force one to use that directory, it is the default directory either by convention, configuration, or generated from templates. I would imagine it to be exceedingly common for such a directory to be used in many frameworks across a multitude of languages, not just Ruby. (Phoenix, Laravel, and EmberJS are other examples)
Would it not make sense to lean on the prevalence of these common config/ directories rather than creating yet-another config directory as its sibling? I can see this leading to confusion.
"Where should X file be placed? .config/ or config/?" - proceeds to place the file in the wrong directory and it does not get picked up.
Alternatively, developer Sally uses her editor or IDE's fuzzy finder to search for configs. Now there are two directories that fuzzy match against "config".
In the "olden" days, the leading dot was a hack to "hide" a file or directory from the default listing. It made perfect sense to hide user-specific RC files (.npmrc, .vimrc, .irbrc) from the rest of the project. What's more, that collection of files being user specific and not project specific, would align well with the files being excluded from version control. I recall in the early days of the BabelJS project, for example, the relevant discussions around .babelrc vs babel.json. A key factor in the debate was whether to use "user-specific vs project-specific" as the deciding factor in whether the file should be hidden (user) or visible (project), which would closely correlate to whether the file should be gitignored (user local only) or versioned (shared with the team).
Consider the different use cases and dimensions around files like Makefile, Rakefile, Procfile, package.json, Gemfile, config.ru and friends are versioned & unhidden; whereas other tool files such as (.vimrc, .vscode, .irb-history, .history, .cache, .bundle, and a dozen IDE config dirs) are effectively user specific preferences, gitignored, and hidden.
I recognize that in the intervening years we have seen an explosion in tools that expect to see project specific (ie, shared team) files that are both hidden and version controlled. (Which seems contradictory to me. If the file is intended to be shared and version controlled among the team, why is it also hidden?) But in the absence of any evidence, I would attribute most of those tools' shared-but-hidden files to arbitrary 🤷 decisions; not intentional, well-reasoned choices.
So if we do not have a rule of thumb heuristic such as "user vs project" (aka, gitignored vs version-controlled) to help us decide between hidden vs visible files, what other rationale do we have to suggest .config vs config?
This echoes some of our earlier discussion regarding XDG and Runcom which allows XDG-like directory structures in your project. See here and here.
Structure
I tend to approach the folder/file structure from an XDG mindset and making the classification between project and user is a good way to think about this. For context, this is what I mean by XDG structure (I detail this further here in case it helps):
XDG Global Structure
# Cache
$HOME/.cache
# Configuration
$HOME/.config
# Data
$HOME/.local/share
# State
$HOME/.local/state
The above can be applied to a specific project if you swap $HOME with <project> (whatever that might be).
With the above in mind, it might help to look at an actual project. For instance, here's what the Terminus project structure looks like which I'm currently working on (truncated for brevity):
Terminus (Hanami) Directory Structure
.
├── .circleci # Should be: `.config/circleci/config.yml`.
│ └── config.yml
├── .config # XDG Configuration (managed by Runcom).
│ ├── milestoner
│ │ └── configuration.yml
│ └── rubocop
│ └── config.yml
├── .github # Should be in `.config/github/*.{yml,md}`
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE.md
│ └── PULL_REQUEST_TEMPLATE.md
├── config # Project specific.
│ ├── app.rb
│ ├── assets.js
│ ├── puma.rb
│ ├── routes.rb
│ └── settings.rb
├── .dockerignore # Should be: `.config/docker/ignore.txt`
├── .env # Hidden by my global Git ignore configuration.
├── .envrc # Hidden by my global Git ignore configuration.
├── .env.development # Hidden by my global Git ignore configuration.
├── .env.test # Hidden by my global Git ignore configuration.
├── .gitignore # Should be: `.config/git/ignore.txt`
├── .node-version # Should be: `.config/node/version.txt`
├── .overmind.sock # Hidden by my global Git ignore configuration.
├── .reek.yml # Should be: `.config/reek/config.yml`
├── .ruby-version # Should be: `.config/ruby/version.txt`
├── compose.yaml # Should be: `.config/docker/compose.yml`
├── config.ru
├── Dockerfile
├── Gemfile
├── Gemfile.lock
├── LICENSE.adoc
├── package-lock.json
├── package.json
├── Procfile
├── Procfile.dev # Hidden by my global Git ignore configuration.
├── Rakefile
└── README.adoc
My preference, as you can see from the comments, is that all of this configuration logic should be in the XDG-like directory structures because you might have an associated global configuration that you want the local (project specific) configuration to override. This would not only clean up the file structure but provide better organization and reduce some of the confusion. There's a lot of power in this because it allows team members to have more control over how their app is configured (especially for people with different workflows and tooling -- me being one of them) with nice fallbacks when project specific configurations are not present.
Visibility
In terms of visibility, this gets tricky because, if we only think about configuration, you would want config/ to always be public but .config could be fully public or partially public. I says this because I've worked on projects where some parts of .config needed to be private because of actual security concerns and in other cases for political reasons. The way to handle this is to leverage your Git ignore configuration (more context is provide here in case it helps):
# Global default as specified by the XDG specification.
$HOME/.config/git/ignore
# Local and unique per project.
<your project path>/.gitignore
# Local and unique per project but specific to you alone!
<your project path>/.git/info/exclude
The above allows you to control visibility to a fine level of detail which can be quite powerful in serving everyone's needs.
There's one last thing to note, which isn't mentioned above, because not only can you control visibility through Git but you can also control visibility at an IDE level. For example, here's my Sublime Text Settings which prevents various files and folders from showing when searching through my project. I bring this up because this would resolve the scenerio you point out here:
[D]eveloper Sally uses her editor or IDE's fuzzy finder to search for configs. Now there are two directories that fuzzy match against "config".
Conclusion
To reference your final point:
[I]f we do not have a rule of thumb heuristic such as "user vs project" (aka, gitignored vs version-controlled) to help us decide between hidden vs visible files, what other rationale do we have to suggest .config vs config?
I'll admit -- while not satisfying -- this all depends on context because projects, teams, and individuals are complex. That said, I definitely think that leveraging both a .config structure (i.e. XDG mindset) and config/ (project specific) leads to better organization. The fact that the only difference between the two is whether you have the dot (.) prefix or not is unfortunate but not sure it can be avoided.