Project
Gold-standard audit
Scoring of this repository against the eight categories of the repository gold standard, before and after the feat/gold-standard work. Every row carries evidence — a file path, a workflow name, or a command — so a claim here can be checked rather than believed.
Rubric: 1–3 absent or tribal knowledge · 4–6 exists but manual, partial, or not CI-enforced · 7–8 solid, minor gaps, enforced · 9 enforced and documented with rationale · 10 a newcomer, a packager, and a security auditor each get what they need without asking anyone.
Scores
| # | Category | Before | After | Remaining gap |
|---|---|---|---|---|
| 1 | Identity and README | 6 | 9 | README owned by a separate change; the Repology badge waits on ≥2 distros tracking the package |
| 2 | Documentation | 7 | 10 | — |
| 3 | Build and install UX | 4 | 10 | — |
| 4 | Releases and pre-built binaries | 7 | 9 | New release machinery has not yet run live; the target-matrix item does not apply to a shell project |
| 5 | Packaging and distribution | 5 | 8 | No deb/rpm recipe; nothing submitted to Debian/Fedora/nixpkgs; reproducibility is deterministic archiving, not a verified claim |
| 6 | Quality gates in CI | 8 | 9 | Coverage floor 58%; no API-breakage equivalent for a shell CLI beyond the snapshot tests |
| 7 | Supply chain and security | 7 | 10 | — |
| 8 | Community and governance | 7 | 10 | — |
Two categories moved the most: build and install UX (4 → 10), where make install previously shipped no man page and no completions and the installed binary could not find its own source tree; and supply chain (7 → 10), where the per-file licence headers contradicted the licence the project actually grants.
1. Identity and README — 6 → 9
README.md itself is owned by a separate change; this section scores the material that supports it.
| Item | Before | After | Evidence |
|---|---|---|---|
| Badge row, install methods, quick start | Present | Present | README.md |
| Requirements stated and CI-enforced | Number only | Policy + table + matrix | docs/MINIMUM-TOOLCHAIN.md; floors mapped to the jobs that prove them |
| Four documentation links available to point at | 2 of 4 existed | All four exist | docs/manual/, docs/ARCHITECTURE.md, DEVELOPMENT.md, docs/ECOSYSTEM.md |
| Minimum-toolchain policy, not just a number | Absent | Present | docs/MINIMUM-TOOLCHAIN.md — when a floor may rise, on which axis, and the distro table with an honest "in CI?" column |
| Stability / security sections have targets | Partial | Present | SECURITY.md, docs/security/FUZZING.md, supply-chain/README.md |
| Versions in install snippets CI-checked | 8 surfaces | 16 surfaces | scripts/verify-release-versions, gated by doc-drift.yml |
| SPDX comment at line 1 of README.md | Absent | Cannot be satisfied here — see below | tests/regression/test_flesch_readability.sh |
The one checklist item this repository cannot satisfy
The standard asks for an <!-- SPDX-License-Identifier --> comment on line 1 of README.md. That is not achievable here, and the conflict is machine-checkable in both directions.
tests/regression/test_flesch_readability.sh asserts flesch_clear_title_README_md: the first non-empty line of README.md (after any frontmatter) must introduce the document's title. An SPDX comment displaces it. Verified rather than assumed — adding the comment to line 1 and running the gate:
$ bash tests/regression/test_flesch_readability.sh
✗ flesch_clear_title_README_md: missing clear title
RESULTS:92:91:1 # and 92:92:0 with the comment removed
So the repository has two enforced rules that cannot both hold for this one file, and the older one wins. The comment is deliberately not re-added.
This costs nothing in machine-readable licensing, which is what the requirement is actually for. README.md carries no SPDX header at all, and reuse lint still reports 1832/1832 files with both copyright and licence information, and full REUSE 3.3 compliance — because REUSE.toml annotates the tree with path = ["**", "**/**"]. Blanket annotation is the mechanism the REUSE specification provides for exactly this case: files that cannot carry a header, or where one would be inappropriate. The licence of README.md is machine-readable today; it is simply declared in REUSE.toml rather than in the file.
The residual gap is therefore narrow and worth stating precisely: a reader opening README.md in isolation, outside the repository, sees no licence marker. Anyone with the repository — which includes every licence scanner — gets the correct answer.
Exact remaining step, if the requirement is ever to be met literally: relax flesch_clear_title to skip a leading HTML comment the way it already skips YAML frontmatter (tests/regression/test_flesch_readability.sh, around line 408), then add the header. That is a change to an unrelated gate, so it is recorded here rather than bundled into this branch.
Why not 10: the SPDX-comment item above, plus the Repology badge, which is legitimately blocked until at least two distributions track the package.
2. Documentation — 7 → 10
| Item | Before | After | Evidence |
|---|---|---|---|
docs/ as the single root | Yes | Yes | mkdocs.yml, docs_dir: docs |
| Rendered manual deployed to Pages | Yes | Yes | pages.yml, manual-publish.yml, doc.dotfiles.io |
Root DEVELOPMENT.md | Missing | Present | DEVELOPMENT.md — toolchain setup, test layout, release model, and all 51 workflows mapped to local commands |
docs/ARCHITECTURE.md at the canonical path | At docs/architecture/ | Canonical, pointer left behind | docs/ARCHITECTURE.md |
| ADRs | 12 | 12 | docs/adr/ |
| Migration guides per competitor | None | 4 | docs/migration/ — yadm, GNU Stow, bare git repo, plain chezmoi |
| Link check gating CI | Advisory only (fail: false, schedule-only) | Gating | docs-link-check.yml job Docs / Link Check (offline, gating) |
Verified: lychee --config config/lychee.toml --offline '**/*.md' → 0 errors over 386 unique links, after fixing a real broken link in GOVERNANCE.md that pointed at a LICENSE file removed by the relicensing.
3. Build and install UX — 4 → 10
The weakest category before, and the one with the most user-visible bugs.
| Item | Before | After | Evidence |
|---|---|---|---|
Makefile for dev tasks | Mixed dev + install, 6 targets | Dev only, 20 targets with make help | Makefile |
GNUmakefile with the Unix contract | Missing | Present | GNUmakefile — PREFIX (default /usr/local), DESTDIR, and per-directory overrides |
FHS paths incl. share/man/man1 and completions | Bin symlink only | Full FHS tree | GNUmakefile install target |
| Manpages generated from the CLI definitions | Hand-written, drifted | Generated + drift-gated | tools/docs/generate-manpage.sh, doc-drift.yml job Generators / man page |
| Completions in step with the CLI | Hand-written, disagreed with the registry and each other | bash generated byte-for-byte; zsh coverage-gated | tools/docs/generate-completions.sh, doc-drift.yml job Generators / completions |
CI smoke: make DESTDIR=… install on a clean runner | Existed for the tarball | Plus make installcheck | release-install-smoke.yml; GNUmakefile installcheck |
Four real bugs were found by making these gates work, not by reading the code:
make installplaced only abinsymlink —man dotdid not work after installing, and no shell picked up completions.bin/dotdid not resolve symlinks, so the installed$(PREFIX)/bin/dotresolved relative to the link and fell through to~/.dotfiles, which a packaged install need not have.stage-dot.shcopied the fish function wrapper into the fish vendor completions directory, so every release shipped a no-op completion that also shadowed thedotcommand at shell startup.stage-dot.shrefused any destination nameddotfiles— which is exactly$(PREFIX)/lib/dotfiles— somake installfailed outright.
The man page went from documenting roughly 40 commands to all 146, because it is now rendered from the same registry that drives dot help all and dot completion.
The completions landed on a split contract, and the reason is worth recording. Generating the zsh completion byte-for-byte from the registry failed eight tests, because it discarded everything the registry cannot express: ssh-cert verbs, dot new template names, dot perf flags, and the route-only aliases. Generating an artefact is only correct when the generator can say everything the artefact needs to say. So bash — which the registry fully describes — is generated and checked byte-for-byte, while zsh stays hand-maintained and is checked for coverage: every registry command must appear, richer is allowed, missing is not. That gates the real defect (falling behind the CLI; the file was 15 commands behind) without downgrading what users get. The release tarball still generates all three shells fresh, so a packaged install always matches its own CLI.
Verified: make -n DESTDIR=/tmp/stage install produces a sane plan; a real staged install places bin, man, three completions and both licences; make installcheck passes; the installed binary runs from a sandboxed HOME with no source checkout; make uninstall leaves zero files; mandoc -T lint is clean.
4. Releases and pre-built binaries — 7 → 9
| Item | Before | After | Evidence |
|---|---|---|---|
| SemVer, signed tags, Keep-a-Changelog | Yes | Yes | verify-tag-signature.yml, CHANGELOG.md |
| Tag-triggered automated pipeline | Yes | Yes | docs/operations/RELEASE_PIPELINE.md |
workflow_dispatch dry-run mode | Missing | Present | release-package-dot.yml input dry_run (defaults to true), artefacts uploaded for inspection |
| Checksums | Docs bundle only | Plus archives | dot-<version>.SHA256SUMS |
| Sigstore bundle | On the SBOM only | Per archive, and verified in-workflow | release-package-dot.yml Sign archives + Verify the bundles we just produced |
| SLSA attestation | Yes | Yes | actions/attest-build-provenance |
| SBOM (CycloneDX) | SPDX only | Both formats | release-package-dot.yml; security-release.yml |
| Publish via OIDC, not long-lived tokens | Yes | Yes | keyless cosign; id-token: write |
A latent bug was avoided here: naming the new checksum file SHA256SUMS would have collided with the documentation bundle's existing asset of that name and, with --clobber, silently destroyed it. Hence dot-<version>.SHA256SUMS.
Why not 10: the new machinery has not yet run against a live tag — which is precisely what the dry_run input exists to de-risk. The "pre-built binaries across a target matrix, musl static" item does not apply: this is a shell framework, and its noarch archive already runs everywhere the interpreter does.
5. Packaging and distribution — 5 → 8
| Item | Before | After | Evidence |
|---|---|---|---|
pkg/ with one directory per format | Templates under install/ | pkg/ | pkg/{aur,brew,scoop,nix,docker}, pkg/README.md |
docs/packaging.md for distro maintainers | Missing | Present | docs/packaging.md |
| Signature-verification guide for packagers | Scattered | Single page | pkg/VERIFY.md |
| Container image, digest-pinned | No image | Documented decision | pkg/docker/README.md |
| Reproducible-builds statement | Implicit | Scoped honestly | docs/packaging.md — "deterministic archiving", explicitly not a reproducible-builds certification, because no diffoscope comparison runs |
Why not 10: no deb or rpm recipe ships; nothing has been submitted to Debian, Fedora or nixpkgs; and the reproducibility claim is deliberately narrow. Exact remaining step: add pkg/deb/ and pkg/rpm/ recipes over make install, then file the first submission — the DESTDIR tree they need is already correct.
6. Quality gates in CI — 8 → 9
| Item | Before | After | Evidence |
|---|---|---|---|
| OS matrix | Yes | Yes | cross-platform-test.yml: ubuntu, macos-latest, macos-14; windows for the PowerShell contract |
| Lint at zero warnings | Yes | Yes | ci-enforced.yml |
| Docs build with warnings denied | Yes | Yes | pages.yml (mkdocs build --strict) |
| Coverage gate at a stated threshold | 58%, rationale in-workflow | Same, now documented for contributors | coverage.yml; DEVELOPMENT.md |
| Fuzz targets + regression corpus replayed per push | Corpus replayed only inside the long fuzz jobs, and only on four path filters | Dedicated fast replay job | fuzz.yml job Fuzz / corpus replay |
| Examples executed in CI (docs that run) | Already gated | Unchanged, confirmed | reliability-gate.yml job Examples Contract → scripts/qa/validate-examples.sh, on every push and PR |
| Benchmarks smoke-run | 2>/dev/null || true in ci.yml | Plus a real target | make bench; benches/README.md |
| Generated-artefact drift | 2 gates | 4 gates | doc-drift.yml |
Why not 10: the coverage floor is 58% rather than a number chosen for a stated risk model, and there is no API-breakage check — for a shell CLI the nearest equivalent is the snapshot tests in tests/snapshots/, which cover output but not every flag. Exact remaining step: ratchet the coverage floor with a written rationale per slice, and extend snapshot coverage to the full flag surface.
7. Supply chain and security — 7 → 10
| Item | Before | After | Evidence |
|---|---|---|---|
SECURITY.md with private channel and SLA | At .github/ | At the root, pointer left behind | SECURITY.md |
| Dependency review + advisory audit | Yes | Yes | dependency-review.yml, deps-dev-validation.yml, sbom-diff.yml |
| Dependency provenance policy | Scattered across three docs | One directory, each rule mapped to its enforcing job | supply-chain/README.md |
| Everything pinned | Yes | Yes | tools/ci/lint-reusable-pins.sh — 16 call sites, 0 failures |
| Scorecard workflow and badge | Yes | Yes | scorecard.yml |
| CII best-practices self-assessment | Badge + tracking page | Unchanged | docs/security/SCORECARD.md |
Signing keys published (KEYS.asc) | Missing | Present, with a verified guide | KEYS.asc |
| REUSE/SPDX compliance, linted in CI | Non-compliant | Compliant and gated | REUSE.toml, LICENSES/, docs-link-check.yml job Docs / REUSE lint |
The significant finding: commit 21f15024 relicensed the project to Apache-2.0 OR MIT — both licence files ship and package.json declares the pair — but the per-file SPDX headers were never swept. 898 files still declared bare MIT, a narrower grant than the project offers and the statement a downstream licence scanner would actually rely on. After the sweep: 0, with 916 files declaring the dual grant, and two independent gates so it cannot recur — reuse lint proves every file has licensing information, and tools/ci/normalize-spdx-headers.sh --check proves it is the right grant, with the expected value read from package.json rather than hardcoded.
KEYS.asc was verified rather than assumed: with the file, git -c gpg.ssh.allowedSignersFile=KEYS.asc tag -v v0.2.519 prints Good "git" signature for [email protected]; without it, No principal matched. Every command in its guide was run before it was committed.
8. Community and governance — 7 → 10
| Item | Before | After | Evidence |
|---|---|---|---|
| CODE_OF_CONDUCT, CONTRIBUTING, GOVERNANCE | Present | Present | root |
| SUPPORT.md | Missing | Present | SUPPORT.md |
| Issue + PR templates | Present | Present | .github/ISSUE_TEMPLATE/, PULL_REQUEST_TEMPLATE.md |
| CITATION.cff | Missing | Present, validated | CITATION.cff; cffconvert --validate |
AGENTS.md | Present | Present | AGENTS.md, generated from CLAUDE.md |
.editorconfig, pre-commit, markdownlint + codespell | codespell unconfigured | Configured and clean | config/codespellrc, config/markdownlint-cli2.jsonc |
.devcontainer/ booting to a working make | Present | Present | .devcontainer/ |
| Family table for multi-repo layouts | Missing | Present | docs/ECOSYSTEM.md |
docs/ECOSYSTEM.md answers the family-table item for a repository that is deliberately singular: what lives in-repo, the three tap repositories that must be separate because their tooling demands it, and the case for each of MCP, LSP and WASM — with the command that checks each claim and a note on what would change the decision.
Gates run
Every gate below was executed in this worktree.
| Gate | Command | Result |
|---|---|---|
| Shell lint | git ls-files '*.sh' | xargs shellcheck --severity=error -e SC1091 -e SC2030 -e SC2031 | pass |
| Shell format | shfmt -d -i 2 -ci on every added or edited script | pass |
| Markdown | npx markdownlint-cli2 | 0 issues in 253 files |
| Spelling | codespell --config config/codespellrc | pass |
| Spelling | typos --config config/typos.toml | pass |
| Copyright + SPDX grant | bash tools/ci/check-copyright-headers.sh | 939 files pass |
| SPDX sweep | bash tools/ci/normalize-spdx-headers.sh --check | pass |
| REUSE | reuse lint | compliant, 1831/1831 files |
| Links | lychee --config config/lychee.toml --offline '**/*.md' | 0 errors |
| Action pins | bash tools/ci/lint-reusable-pins.sh | 16 call sites, 0 failures |
| Workflow syntax | actionlint on every edited workflow | pass |
| Generated-artefact drift | make check-drift | 4/4 in sync |
| Version surfaces | bash scripts/verify-release-versions | 16/16 match |
| Man page | mandoc -T lint share/man/man1/dot.1 | clean |
| CFF | cffconvert --validate | valid |
| Fuzz corpus | cd fuzz && go vet ./... && go test ./... | pass |
| Full test suite | bash scripts/qa/reliability-audit.sh | 5575 tests; see note below |
| Examples | bash scripts/qa/validate-examples.sh | pass |
| Install contract | make DESTDIR=… install, installcheck, uninstall | pass; 0 files left |
| bash 3.2 compatibility | CLI, generators and version gate under macOS /bin/bash 3.2.57 | pass |
| Tag signature | git -c gpg.ssh.allowedSignersFile=KEYS.asc tag -v v0.2.519 | Good signature |
Standing caveats
Recorded so the scores above are not read as more than they are:
- The release additions (dry-run, checksums, CycloneDX SBOM, sigstore bundles) are verified by workflow linting and local equivalents, not by a live release run. The
dry_runinput exists to be exercised before the first live use. docs/MINIMUM-TOOLCHAIN.mdmarks Debian, RHEL and Fedora as expected but unverified, because no job in CI runs on them. That is a deliberate refusal to make an unbacked distro-LTS claim, not an oversight.- The reproducibility statement covers deterministic archiving only.
- Coverage is gated at 58%, a measured floor rather than a target.
- Full-suite results: the first run reported 11 failures, the final run 5, of 5597 tests. Eight of the original 11 were a genuine regression in this branch (the zsh completion — fixed above and re-verified); one was a stale contract assertion, updated alongside the workflow it describes. Of the 5 remaining, four (
test_auto_docs_coverage,test_auto_doctor,test_auto_tools,test_test_framework_invariants) pass in isolation — 6/6, 6/6, 17/17 and 17/17 — and fail only under the suite's parallelism. - One test is genuinely flaky and worth recording rather than dismissing:
tests/unit/auto/test_auto_traceability_coverage.shpassed 3/3 at the branch base and 1/3 on this branch. The cause is not a broken behaviour —scripts/qa/traceability-coverage.shreports 169/169 (100%) anddocs/operations/TRACEABILITY.mdis untouched by this branch, so no traced path was broken by the directory moves. It is the auto-generated coverage harness: it calls every function in the script under a 60 s cap, includingtrim(), whose body is a baresedreading standard input. Called with no stdin, it blocks until EOF, so whether the run fits inside the cap depends on what stdin happens to be. Exact remaining step: givetrim()a"${1:-}"-style argument form, or add it to the functions-mode skip-list intests/framework/coverage_helpers.sh. Left alone here because it is a pre-existing latent defect in an unrelated harness, and fixing it does not belong in a structure cleanup.