Why seniority
seniority against cosmiconfig, lilconfig, dotenv and rc, one capability per row, every cell linked to the test, grade or source that proves it — including YAML, which it deliberately leaves to a loader you pass.
cosmiconfig and lilconfig find a config file. dotenv loads a .env. rc merges files,
variables and argv. Each one answers part of "what is this option set to?". seniority answers
all of it, in one declared order, and adds the part none of them has: where every value came
from, and what it beat. Its drop-in paths are graded by each incumbent's own suite. It has no
dependencies.
Every mark links to its evidence. For ours, that is a test in this repository. For theirs, it
is the published source of the exact version compat-oracle grades, or that package's own test
suite. None of the four incumbents is installed in this repository at its graded version, so
their cells link the file on jsDelivr. scripts/capabilities-lock.test.ts fails the build when
a cited test no longer contains the title it is cited for, or a link is not pinned to a version.
✓ yes · ◐ partial, with what is missing · ✗ no · — does not apply. Every mark links to its evidence: our test or grade, or the incumbent’s source at the version compat-oracle grades.
Precedence and provenance
One precedence: flag, env, config, package.json, default
resolve()merges every source a CLI reads in one declared order,ORDER, so the same option cannot be decided differently in two places.seniority- seniority: yes
Provenance for every value:
--explainEach resolved value says which source set it, where (file and line, or the variable), and every candidate it beat, so "why is this set?" has an answer.
seniority- seniority: yes
A pure
resolve: the environment is an argumentresolve(specs, layers)reads no file and noprocess.env, so the same inputs give the same answer anywhere, and a test passes the environment instead of editing it.
Discovery
A bounded, symlink-safe find-up
The upward walk stops at
stopAt, the filesystem root or a depth limit, whichever comes first, and a symlink ring ends it rather than looping, so a start-up cannot hang on a strange directory.seniority- seniority: yes
Bring your own format
Loaders are injected per extension, so a TOML, YAML or JSON5 parser is the caller's choice and the caller's dependency, and an extension with no loader is a usage error naming the option that would supply one.
seniority- seniority: yes
cosmiconfig- cosmiconfig: yes
loaders, over its defaults
YAML with no parser supplied
seniority bundles no format parser: its own loaders have no
.yamlentry, and the cosmiconfig drop-in reads only the YAML that is also JSON and refuses the rest by name, so a YAML config needsloaders: { '.yaml': yaml.load }.
Environment variables
Env reaches declared options only, by their own names
A variable sets an option only if the running command declares it, under the prefix in SCREAMING_SNAKE (
MYTOOL_DRY_RUN), so a stray variable cannot inject a key nobody asked for.seniority- seniority: yes
Booleans from env: one spelling each way
true,1andyesorfalse,0andno(and nothing else) set a boolean from the environment, and any other value is a CONFIG error naming the fix, rather than a truthy string.seniority- seniority: yes
Validation and plugins
Every violation names the file, the line and the value
validate()returns every violation at once, each naming where the bad value came from, so a config with three mistakes is fixed in one pass.seniority- seniority: yes
Source plugins, ranked between the built-ins
A plugin adds a source (a vault, a remote config) at a rank between two built-ins, never above the flag the user typed, and
--explainnames it without seniority knowing it.seniority- seniority: yes
Zero runtime dependencies
seniority installs one package; rc brings four, and cosmiconfig brings js-yaml and env-paths.
seniority- seniority: yes
cosmiconfig- cosmiconfig: noenv-paths and js-yaml
lilconfig- lilconfig: yes
dotenv- dotenv: yes
Compatibility
Passes lilconfig's own test suite
seniority/lilconfigis graded by lilconfig 3.1.3's own tests, all 77, the same count the real lilconfig gets here.cosmiconfig- cosmiconfig: does not applya different API
Passes rc's own test
seniority/rcruns rc 1.2.8's test script, one script of bare assertions with no reporter, and exits 0: the grade is one bit, not 100% of anything.seniority- seniority: yes1 / 1 of its own tests
cosmiconfig- cosmiconfig: does not applya different API
Passes cosmiconfig's own test suite, bar YAML
senioritypasses 186 of the 243 cases of cosmiconfig 10.0.1's own suite; 54 of the rest are YAML, which seniority deliberately does not parse without a loader.Passes dotenv's own test suite, bar the vault
seniority/dotenvpasses 106 of the 141 assertions of dotenv 17.4.2's own suite (node-tap counts assertions); the encrypted.env.vaultand the dotenvx tips are declined, not missing.cosmiconfig- cosmiconfig: does not applya different API
Reading it
- "ours" is
resolve,discoverand the rest of seniority's own API. The drop-ins keep their incumbents' behaviour, which is what their grades measure.seniority/rcstill turns every<name>_variable into a key, because rc does. - rc has a precedence too, and the table says so: defaults, files, variables, argv. What it does not have is the record of which one won.
- — does not apply is not a soft ✗. cosmiconfig reads no environment, so the environment rows do not apply to it; the cell says why.
What it leaves out on purpose
- YAML. seniority bundles no format parser; that is a product decision, not a missing
feature. cosmiconfig's suite grades
seniority186 of 243, and 54 of the 57 it misses are YAML fixtures. Passloaders: { '.yaml': yaml.load }and you have cosmiconfig's behaviour, with the parser as your dependency rather than everyone's. - dotenv's vault and dotenvx. The encrypted
.env.vault,decrypt, and the dotenvx tips are declined. They account for 29 of the 35 assertionsseniority/dotenvdoes not pass. - Rearranging the order. A precedence a program can reorder is one nobody can reason about from outside. A plugin can add a source between two built-ins, never reorder them.
What is not in the table
- Weight. Zero dependencies is a row. The byte figures are on Benchmarks, measured the same way on both sides, because they are figures rather than a yes or no.
- Speed. Nothing measures how fast a config is found against lilconfig, so there is no speed claim.
- Symlink handling in the incumbents. seniority's walk is proven to end on a symlink ring. Whether the others loop depends on their filesystem calls, and no test here runs them on one, so the find-up row is about the bounds each one has, not about symlinks.
Source plugins
seniority/plugin: add a configuration source — a vault, a CI variable set, a remote config — at a rank between two built-ins, named in --explain, and checked by npx seniority check before it ships.
Compatibility
How seniority's four drop-ins are graded by their incumbents' own suites — lilconfig 77 / 77, rc 1 / 1 (one bit), cosmiconfig 186 / 243 (YAML), dotenv 106 / 141 (the vault) — and the differences that remain.