docs(contributing): add CONTRIBUTING.md formalizing repo standards
Documents the branching/PR workflow, commit conventions, fish function doc-header and colored --help conventions, the docs generation pipeline, testing, and the secrets/machine-config placement rule, so these practices live somewhere durable instead of only in commit history and conversation memory.
This commit is contained in:
+276
@@ -0,0 +1,276 @@
|
|||||||
|
# Contributing to fish-config
|
||||||
|
|
||||||
|
This is a personal dotfiles repo, but it's run with practices meant to scale
|
||||||
|
to a team of contributors, not just one person's solo habits. This document
|
||||||
|
formalizes those practices so they live somewhere durable instead of only in
|
||||||
|
commit history and conversation memory. It will grow as the project does —
|
||||||
|
treat it as a living document, not a final word.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Getting Started](#getting-started)
|
||||||
|
- [Branching & Pull Requests](#branching--pull-requests)
|
||||||
|
- [Commit Conventions](#commit-conventions)
|
||||||
|
- [Fish Coding Standards](#fish-coding-standards)
|
||||||
|
- [Opinionated Components](#opinionated-components)
|
||||||
|
- [Documentation Pipeline](#documentation-pipeline)
|
||||||
|
- [Testing](#testing)
|
||||||
|
- [Secrets & Machine-Specific Config](#secrets--machine-specific-config)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
You'll need [fish 4.x](https://fishshell.com/). Clone the repo and run the
|
||||||
|
test suite to confirm your environment is sane:
|
||||||
|
|
||||||
|
```fish
|
||||||
|
fish tests/run-tests.fish
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're touching anything under `docs/manual/`, you'll also want `pandoc`,
|
||||||
|
`python3-yaml`, and Node 24+ to exercise the full doc-build pipeline locally
|
||||||
|
(see [Documentation Pipeline](#documentation-pipeline)) — otherwise CI will
|
||||||
|
catch problems on push.
|
||||||
|
|
||||||
|
## Branching & Pull Requests
|
||||||
|
|
||||||
|
- **Branch off `main` before starting work.** Don't accumulate uncommitted
|
||||||
|
changes directly on `main`. (If you already started editing before
|
||||||
|
branching, that's fine — create the branch now, before your first commit;
|
||||||
|
branching doesn't touch the working tree.)
|
||||||
|
- **Merge target is `main`, via PR.** Contributors open the PR; the repo
|
||||||
|
owner merges it. Don't merge your own PR.
|
||||||
|
- **Keep feature branches focused.** If you stumble onto something unrelated
|
||||||
|
to your current task while working (a pre-existing bug, a stray cleanup),
|
||||||
|
don't fold it into the same commit or PR. Handle it with one of these,
|
||||||
|
in order of preference:
|
||||||
|
1. **Separate branch, separate PR, merged to `main` independently.** The
|
||||||
|
default for anything that doesn't overlap the code your current branch
|
||||||
|
touches. The two PRs review and merge independently, in either order.
|
||||||
|
2. **Separate branch and PR, then sync `main` back into your feature
|
||||||
|
branch** once it merges. Use this only when the unrelated fix actually
|
||||||
|
touches the same file/function as your feature branch, or your feature
|
||||||
|
branch depends on the corrected behavior to work or test correctly.
|
||||||
|
3. **Commit directly to `main`.** Reserved for changes too small to
|
||||||
|
justify a branch, or genuinely urgent fixes. **Always ask for explicit
|
||||||
|
approval before doing this** — there's no standing exception, no matter
|
||||||
|
how trivial the change looks.
|
||||||
|
|
||||||
|
## Commit Conventions
|
||||||
|
|
||||||
|
Commit subjects follow [Conventional Commits](https://www.conventionalcommits.org/):
|
||||||
|
|
||||||
|
```
|
||||||
|
type(scope): description
|
||||||
|
```
|
||||||
|
|
||||||
|
Types currently in use in this repo: `feat`, `fix`, `docs`, `test`, `chore`,
|
||||||
|
`perf`. The scope is usually the function, component, or subsystem touched
|
||||||
|
(e.g. `feat(help): ...`, `fix(scrub): ...`, `chore(docs): ...`). Look at
|
||||||
|
`git log` for recent examples before picking a type/scope for something
|
||||||
|
novel.
|
||||||
|
|
||||||
|
## Fish Coding Standards
|
||||||
|
|
||||||
|
### File header
|
||||||
|
|
||||||
|
Every hand-authored `.fish` file starts with:
|
||||||
|
|
||||||
|
```fish
|
||||||
|
# Copyright (C) 2026 Rootiest
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
```
|
||||||
|
|
||||||
|
(A few completion scripts adapted directly from a tool's own upstream
|
||||||
|
completions keep that tool's original attribution comment instead of this
|
||||||
|
header — match whichever convention the specific file already follows. New
|
||||||
|
files use the standard header above.)
|
||||||
|
|
||||||
|
### Public function documentation header
|
||||||
|
|
||||||
|
Every user-facing function gets a machine-parsed comment header directly
|
||||||
|
above its `function` line. This header is the single source of truth for
|
||||||
|
the generated manual (`docs/fish-config.md` / the man page / the docs
|
||||||
|
site) — see [Documentation Pipeline](#documentation-pipeline) for how it
|
||||||
|
gets consumed. The parser (`docs/manualtools.py`) recognizes these labels,
|
||||||
|
all optional except where noted:
|
||||||
|
|
||||||
|
| Label | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `CATEGORY` | **Required to appear in the manual at all** — see below. |
|
||||||
|
| `COMPONENT` | Only for functions gated by the [opinionated-component system](#opinionated-components). |
|
||||||
|
| `DEPENDENCIES` | Other functions this one calls that a reader may want to look up. |
|
||||||
|
| `SYNOPSIS` | One-line usage form. |
|
||||||
|
| `DESCRIPTION` | Prose description; can span multiple paragraphs. |
|
||||||
|
| `ARGUMENTS` | Flags/positional args, one per line. |
|
||||||
|
| `EXIT STATUS` | Exit codes and what they mean. |
|
||||||
|
| `RETURNS` | For functions used for their output/return value rather than exit status. |
|
||||||
|
| `EXAMPLE` | One or more realistic invocations. |
|
||||||
|
| `NOTES` | Anything else worth flagging (fallback behavior, caveats, gotchas). |
|
||||||
|
|
||||||
|
A full example (`functions/claude.fish`):
|
||||||
|
|
||||||
|
```fish
|
||||||
|
# Copyright (C) 2026 Rootiest
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
# CATEGORY
|
||||||
|
# 12-ai-and-developer-tools
|
||||||
|
#
|
||||||
|
# COMPONENT
|
||||||
|
# aliases/dev-tools
|
||||||
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# agents-init
|
||||||
|
#
|
||||||
|
# SYNOPSIS
|
||||||
|
# claude [ARGS...]
|
||||||
|
#
|
||||||
|
# DESCRIPTION
|
||||||
|
# Wrapper for the claude CLI that ensures the AGENTS/ sub-repository is
|
||||||
|
# initialized and any agent-made changes are committed before launch.
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
**`CATEGORY` is the opt-in gate for the manual:** a function with no
|
||||||
|
`# CATEGORY` line produces no manual entry at all — this is how bundled
|
||||||
|
plugin internals and prompt guts stay out of user-facing docs without an
|
||||||
|
exclusion list. `CATEGORY` must exactly match one of the existing
|
||||||
|
`docs/manual/05-functions/NN-*.md` stubs:
|
||||||
|
|
||||||
|
```
|
||||||
|
01-file-and-directory 08-terminal-management
|
||||||
|
02-navigation 09-clipboard
|
||||||
|
03-editors-and-viewers 10-network
|
||||||
|
04-git-and-version-control 11-pager-and-logging
|
||||||
|
05-package-management 12-ai-and-developer-tools
|
||||||
|
06-dependency-management 13-media-and-utilities
|
||||||
|
07-system-and-monitoring 14-miscellaneous
|
||||||
|
```
|
||||||
|
|
||||||
|
If your function genuinely doesn't fit any of these, add a new
|
||||||
|
`docs/manual/05-functions/NN-your-category.md` stub (with frontmatter
|
||||||
|
matching its siblings) rather than force-fitting it into an existing one.
|
||||||
|
|
||||||
|
### Private/internal helper functions
|
||||||
|
|
||||||
|
Functions named with a leading `_` (e.g. `_agents_init_ensure_gitignore`,
|
||||||
|
`__fish_config_op_enabled`) are excluded from the manual unconditionally,
|
||||||
|
regardless of whether they carry a `CATEGORY` line — so they generally don't
|
||||||
|
have one. They should still carry the standard file header, and a lighter
|
||||||
|
`SYNOPSIS`/`DESCRIPTION` comment is encouraged wherever the function's
|
||||||
|
purpose or calling convention isn't obvious from its body, matching the
|
||||||
|
convention already used across `__fish_config_op_*.fish` and similar files.
|
||||||
|
|
||||||
|
### Function declaration
|
||||||
|
|
||||||
|
Give every function a `--description`, since it's what shows up in `fish -c
|
||||||
|
'functions'`/completions and other introspection:
|
||||||
|
|
||||||
|
```fish
|
||||||
|
function my_function --description 'Short, imperative description'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Colored `--help` output
|
||||||
|
|
||||||
|
Every function with a `-h`/`--help` flag uses this standardized color
|
||||||
|
palette (established across the codebase 2026-08-22):
|
||||||
|
|
||||||
|
```fish
|
||||||
|
set -l c_head (set_color --bold cyan) # section headers
|
||||||
|
set -l c_cmd (set_color --bold) # the command name itself (theme-adaptive)
|
||||||
|
set -l c_flag (set_color yellow) # flags
|
||||||
|
set -l c_arg (set_color cyan) # required-argument placeholders
|
||||||
|
set -l c_dim (set_color brblack) # optional-argument placeholders
|
||||||
|
set -l c_ok (set_color green) # success/positive status
|
||||||
|
set -l c_warn (set_color yellow) # warnings
|
||||||
|
set -l c_err (set_color red) # errors
|
||||||
|
set -l c_reset (set_color normal)
|
||||||
|
```
|
||||||
|
|
||||||
|
Not every function needs every variable — pull in only the ones your help
|
||||||
|
text actually uses. Structure the usage block as `Usage:`, then sections
|
||||||
|
for arguments/flags/examples as needed; see `functions/rand_string.fish` or
|
||||||
|
any recently-touched function for a full worked example.
|
||||||
|
|
||||||
|
## Opinionated Components
|
||||||
|
|
||||||
|
Some functionality in this config is classified into one of six toggleable
|
||||||
|
categories (C1–C6: aliases, autoexec, overrides, integrations, logging,
|
||||||
|
greeting) plus sub-categories, so users can disable pieces of it via
|
||||||
|
`config-settings` or universal variables. If you're adding something that
|
||||||
|
shadows a builtin, runs at startup, overrides a key binding/environment
|
||||||
|
variable, or otherwise falls under an existing category, tag it with a
|
||||||
|
`# COMPONENT` header (`<category>/<subcategory>`, e.g.
|
||||||
|
`overrides/key-bindings`) and guard it with `__fish_config_op_enabled`. See
|
||||||
|
the [README's Minimal Mode section](README.md#minimal-mode) for the full
|
||||||
|
category list and toggle semantics, and
|
||||||
|
`AGENTS/specs/2026-08-17-opinionated-component-subcategories-design.md` for
|
||||||
|
the underlying design. Most new functions are *not* opinionated components
|
||||||
|
— only tag something if it fits an existing category; this isn't something
|
||||||
|
to force.
|
||||||
|
|
||||||
|
## Documentation Pipeline
|
||||||
|
|
||||||
|
`docs/manual/` is the single source of truth for the user manual, man page,
|
||||||
|
and documentation site. **Never hand-edit `docs/fish-config.md` or
|
||||||
|
`docs/fish-config.1`** — they're generated by `docs/build-manual.py` from
|
||||||
|
`docs/manual/**` plus every function's doc-header (see above), verified by
|
||||||
|
`docs/verify-manual.py`, and auto-committed by CI on push to `main`
|
||||||
|
(`chore(docs): regenerate manual, man page, and component registry`).
|
||||||
|
|
||||||
|
If you're changing something under `docs/manual/` directly, or adding a
|
||||||
|
function whose `CATEGORY`/`COMPONENT` header should surface new content, you
|
||||||
|
can build and check it locally before pushing:
|
||||||
|
|
||||||
|
```fish
|
||||||
|
python3 docs/build-manual.py --concat -o docs/fish-config.md
|
||||||
|
python3 docs/verify-manual.py
|
||||||
|
```
|
||||||
|
|
||||||
|
CI runs the same verification and regenerates the site/man page — a broken
|
||||||
|
manual won't get published, but running it locally saves a round trip.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```fish
|
||||||
|
fish tests/run-tests.fish
|
||||||
|
```
|
||||||
|
|
||||||
|
Runs before every push and gates the documentation build in CI. Two phases:
|
||||||
|
|
||||||
|
1. **Syntax lint** — every tracked `.fish` file (`config.fish`, `functions/`,
|
||||||
|
`conf.d/`, `completions/`, `integrations/`) is checked with `fish -n`.
|
||||||
|
2. **Sandboxed functional checks** — the config is copied into a throwaway
|
||||||
|
`HOME`/`XDG_CONFIG_HOME` sandbox (never this checkout itself, since it
|
||||||
|
doubles as a real `~/.config/fish`) and loaded as an isolated interactive
|
||||||
|
session. `tests/functional.fish` then runs against foundational behavior:
|
||||||
|
XDG/PATH/CDPATH setup, key bindings, abbreviations, core functions, the
|
||||||
|
opinionated-component registry, and more.
|
||||||
|
|
||||||
|
To add a new functional check, add a `test_*` function to
|
||||||
|
`tests/functional.fish` — it's picked up automatically by
|
||||||
|
`functional_test_main`, no registration needed. Return 0 on pass, non-zero
|
||||||
|
on fail; print a short diagnostic on failure.
|
||||||
|
|
||||||
|
## Secrets & Machine-Specific Config
|
||||||
|
|
||||||
|
**Nothing containing credentials, tokens, personal identifiers, or a
|
||||||
|
specific machine's paths belongs in this repo — not even in a PR.** That
|
||||||
|
kind of thing lives in each user's own private overlay
|
||||||
|
(`~/.config/.user-dots/fish/secrets.fish` and `local.fish`), which is
|
||||||
|
git-ignored by design. If you're writing something that needs a secret or a
|
||||||
|
machine-specific path, source it from there rather than hardcoding it.
|
||||||
|
Everything else — general-purpose, reusable across machines — belongs in
|
||||||
|
the tracked repo as normal. See [README's Personalization
|
||||||
|
section](README.md#personalization) and
|
||||||
|
[`docs/manual/07-customization.md`](docs/manual/07-customization.md) for the
|
||||||
|
full mechanics.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under AGPL-3.0-or-later (see `LICENSE`). Every new
|
||||||
|
hand-authored file needs the SPDX header shown in [File
|
||||||
|
header](#file-header) above.
|
||||||
Reference in New Issue
Block a user