π€ Contributing to ZSHANDΒΆ
π This guide covers how to get going (cloner first), when to use config vs the repo, conventions, workflow, and quality requirements for contributing.
π§ Philosophy
Every contribution should leave the codebase faster, better documented, and better tested than before. Headers are not optional β they're how we communicate intent to humans, AI, and tooling alike.
π Quick start: use the installer (cloner)ΒΆ
The recommended way to get a working ZSHAND environment is the cloner. It installs the framework, sets up your config directory, and runs the platform installer so you have a working shell and (optionally) dev tools.
Run the clonerΒΆ
The cloner will: check prerequisites (git, curl, gum), detect your OS (Debian, Ubuntu, Manjaro, macOS, WSL), prompt for install path (default ~/code/zshand), clone the repo, run setup.zsh (creates ~/.config/zshand/, installs packages, compiles bundles), and offer to set up ~/.zshenv (e.g. ZSHAND_AUTO_FULL=1). After that, open a new terminal or exec zsh.
Cloner optionsΒΆ
| Option | Purpose |
|---|---|
--dry-run | Show what would be done, no changes |
--non-interactive | No prompts (CI / scripting) |
--update | Update an existing clone (pull + setup) |
--uninstall | Remove symlinks and optional cleanup |
--quiet | Less output |
--help | Full list |
Example: bash <(curl -fsSL .../cloner.sh) --dry-run to preview.
The installer (cloner + setup) is what gives you a working environment.
π Prerequisites (what the installer uses)ΒΆ
If you use the cloner (recommended), it and setup.zsh handle OS detection, package installs, and config creation. You only need git and curl to run the cloner; the installer will install gum if missing and run the platform script (Debian, Ubuntu, Manjaro, macOS, WSL).
For local development (running just test, just lint, contributing patches), you need these tools. The Dev Container installs them via mise; otherwise install mise and run mise install in the repo.
| Tool | Purpose |
|---|---|
| zsh 5.0+ | Shell runtime |
| git 2.20+ | Version control |
| just | Task runner (just test, just lint) |
| ShellSpec | BDD test framework |
| Trunk | Linting (ShellCheck, shfmt, etc.) |
| mise | Optional; installs just, shellspec, etc. |
| Hyperfine | Optional; for benchmarking |
See Installers for how the cloner and setup work.
π Config vs main repo: where to add thingsΒΆ
Use your config directory when itβs only for you or experimental.
Contribute to the main repo when itβs a bug fix, feature for everyone, or new framework piece.
| Add in ~/.config/zshand/ | Add in main repo (PR) |
|---|---|
| Your own functions, aliases, widgets | Bug fixes in framework code |
Your own hooks (e.g. 60_my_tool.zsh) | New features used by many users |
Overrides (e.g. rc.d/14_aliases.zsh) | New core modules, hooks, or bin tools |
Personal init.d/ / post.d/ scripts | Docs, tests, tooling improvements |
Keybindings in keybindings.toml | Performance or compatibility improvements |
Why config first? Framework updates never overwrite your config; you can try ideas without forking. Same basename in config overrides the framework file.
When to move to the repo? When the change is generally useful, fits the existing structure, and youβre willing to maintain tests and docs (headers, spec file). See Config for the full layout.
π ConventionsΒΆ
π Header standardΒΆ
Every framework file must have a documentation header per HEADER_STANDARD.md. No exceptions.
| Location | Tier | Title format |
|---|---|---|
build/*.zsh (complex) | Full | βββ box |
core/, functions/, hooks/, bin/ | Standard | ββ name β Description ββ |
shared_functions/ | Compact | ββ name β Description ββ |
config/*.conf, .zshand-deps | Minimal | Short # block |
Checklist: Title Β· USAGE Β· DEPENDENCIES Β· EXIT CODES Β· EXAMPLES Β· TESTING (spec path) Β· SEE ALSO Β· 78βchar dividers.
π’ File namingΒΆ
| Directory | Pattern | Example |
|---|---|---|
core/ | NN_name.zsh (even numbers) | 08_audit.zsh |
shared_functions/, startup/, hooks/ | NN_name.zsh | 01_stderr_error.zsh, 24_az_safe_mode.zsh |
functions/, widgets/ | name.zsh (no number) | dcopy.zsh, aicmit.zsh |
bin/ | name (no extension) | aicontext |
private_functions/ | _name.zsh | _zshand_network_monitor.zsh |
Numbering: Even = framework. Odd = user insertion (e.g. rc.d/03_my.zsh between 02_vars and 04_path).
π·οΈ Naming and behaviorΒΆ
| Scope | Convention | Example |
|---|---|---|
| Public (user-facing) | Short, memorable | zcheck, dcopy, zprime |
| Private (framework) | _az_ or _zshand_ prefix | _az_safe_mode_enter |
| Shared utilities | Descriptive, lowercase | stderr_error, load_file_timed |
π Other conventionsΒΆ
- TOML for framework knobs β Use
config.tomlfor dev_mode, quiet, perf_budget, keybindings; donβt invent new env vars for behavior. Config - Secrets only in
secrets.zshβ Never inenv.zshor committed code. SECURITY - Load order β New
core/orhooks/use the next free even number in the right range. ARCHITECTURE - Overrides β Same basename in config replaces framework; different name = both load. CORE
π οΈ Dev setup (for hacking on the framework)ΒΆ
If youβre contributing code to the repo, you need a dev environment. The cloner or Dev Container gets you most of the way.
Option 1: Dev Container (recommended) β Open repo in VS Code/Cursor with Dev Containers, click Reopen in Container. The container installs zsh, mise, and project tools. See π³ Using Dev Containers.
Option 2: Local (after cloner or manual clone)
# If you didnβt use the cloner:
git clone https://github.com/presempathy/zshand.git ~/code/zshand
cd ~/code/zshand
zsh setup.zsh
mise install
export ZSHAND_DEV_MODE=1
exec zsh
just test
just lint
zcheck
Dev mode
With ZSHAND_DEV_MODE=1, the framework loads individual files instead of bundles. Use it while developing so changes apply without recompiling.
π Using Just (Task Runner)ΒΆ
Just is the project's task runner. It provides a consistent interface for common development tasks.
π Quick StartΒΆ
π Common TasksΒΆ
| Task | Description | Usage |
|---|---|---|
just test | Run all ShellSpec tests | just test or just test spec/path/ |
just lint | Run all linters (Trunk) | just lint |
just format | Auto-format code | just format |
just build | Rebuild all bundles | just build |
just check | Run full quality gate | just check |
β οΈ Important NotesΒΆ
- Always run
just testbefore committing to ensure tests pass - Run
just lintto catch formatting and linting issues early - Use
just checkbefore opening a PR to run the full quality gate - If a task fails, fix the errors before proceeding β don't skip tests or linting
Don't break the build
Never commit code that fails just test or just lint. These checks run in CI/CD and will block your PR. If tests are failing, fix them locally first.
π³ Using Dev ContainersΒΆ
Dev Containers provide a consistent development environment across all contributors, ensuring everyone has the same tools and dependencies.
π Quick StartΒΆ
- Open in VS Code/Cursor with the Dev Containers extension installed
- Click "Reopen in Container" when prompted, or use Command Palette:
F1βDev Containers: Reopen in Container- Wait for setup β the container will build and install dependencies automatically
π¦ What's IncludedΒΆ
The dev container automatically installs:
- zsh and zshdb (for debugging)
- mise (tool version manager)
- All development tools via mise (node, go, rust, uv, shellspec, just)
- Git and GitHub CLI
π§ ConfigurationΒΆ
The dev container configuration is in .devcontainer/devcontainer.json:
- Base image:
mcr.microsoft.com/devcontainers/base:ubuntu - Features: Common utilities, Git, Python
- Post-create: Installs zsh and zshdb
π Debugging in Dev ContainerΒΆ
The dev container includes zshdb for debugging zsh scripts:
- Set breakpoints in your
.zshfiles - Use VS Code's Debug panel (F5)
- Select "Debug Zsh Script" configuration
- Step through code with full debugging support
β οΈ Important NotesΒΆ
- All development happens inside the container β your local files are mounted
- Changes persist β files are synced between container and host
- Run
just testinside the container to ensure compatibility - Don't install tools manually β use mise or the container's package manager
First-time setup
The first time you open the dev container, it may take a few minutes to build. Subsequent opens are much faster thanks to Docker layer caching.
π§ͺ Using ShellSpecΒΆ
ShellSpec is the BDD-style testing framework used for all test suites. It provides descriptive test syntax and comprehensive assertion matchers.
π Quick StartΒΆ
# Run all tests
shellspec
# Run specific spec file
shellspec spec/functions/dcopy_spec.sh
# Run tests in a directory
shellspec spec/core/
# Run with verbose output
shellspec --format documentation
# Run with coverage (requires kcov)
shellspec --kcov
π Writing ShellSpec TestsΒΆ
Basic StructureΒΆ
#!/usr/bin/env shellspec
# ββ function_name_spec β Tests for function_name ββββββββββββββββββββββββββ
Describe "function_name"
# Setup (runs before each test)
BeforeAll 'source "${PROJECT_ROOT}/functions/function_name.zsh"'
It "returns success on valid input"
When call function_name "valid_arg"
The status should be success
The output should include "expected text"
End
It "exits with error when argument missing"
When call function_name
The status should be failure
The stderr should include "ERROR:"
End
It "handles edge cases correctly"
When call function_name ""
The status should be failure
End
End
Common MatchersΒΆ
| Matcher | Purpose | Example |
|---|---|---|
The status should be success | Check exit code 0 | The status should be success |
The status should be failure | Check non-zero exit | The status should be failure |
The output should include | Check stdout contains | The output should include "text" |
The stderr should include | Check stderr contains | The stderr should include "ERROR" |
The output should eq | Exact match | The output should eq "exact" |
The path should exist | File exists | The path "file.txt" should exist |
Test OrganizationΒΆ
spec/
βββ functions/
β βββ dcopy_spec.sh # Tests for functions/dcopy.zsh
βββ core/
β βββ 08_audit_spec.sh # Tests for core/08_audit.zsh
βββ shared_functions/
βββ 01_stderr_error_spec.sh # Tests for shared_functions/01_stderr_error.zsh
Rule: Spec files mirror the source tree structure.
β οΈ Important NotesΒΆ
- Always write tests for new functions or behavior changes
- Run tests before committing β use
just testorshellspec - Keep tests fast β avoid slow operations (network, file I/O) unless testing them
- Test edge cases β empty strings, missing args, invalid input
- Use descriptive test names β
It "does something specific"notIt "works"
Don't skip tests
All tests must pass before committing. If a test fails: 1. Read the error message carefully 2. Fix the code (not the test, unless the test is wrong) 3. Re-run the test to verify the fix 4. Run the full suite with just test before committing
π Coverage ExpectationsΒΆ
| Change Type | Minimum Coverage |
|---|---|
| New function | β Happy path + β¬ error paths + β¬ edge cases |
| Bug fix | β Regression test proving the fix |
| Refactor | β Existing tests still pass (no weakening) |
| Performance | β Behavioral tests + β‘ benchmark comparison |
π WorkflowΒΆ
πΏ Branch StrategyΒΆ
main β stable, always passes tests
βββ feat/name β feature branches (short-lived)
βββ fix/name β bug fix branches
βββ docs/name β documentation-only changes
β Before SubmittingΒΆ
Run the full quality gate:
# 1. Lint (fixes formatting automatically)
just lint # or: trunk check
# 2. Test (all tests must pass)
just test # Full ShellSpec suite
shellspec spec/core/ # Specific directory
# 3. Verify headers
grep -c '# ββ' <your-file> # Count section dividers
# 4. Benchmark (if touching startup path)
hyperfine --warmup 3 'zsh -ic exit'
# 5. Compile and verify
zprime # Rebuild all bundles
zcheck # Framework integrity
# Or run everything at once:
just check # Runs lint + test + build
Don't break the build
All tests (just test) and linters (just lint) must pass before submitting. These checks run automatically in CI/CD and will block your PR if they fail.
π Commit MessagesΒΆ
Follow conventional commit format:
| Type | When |
|---|---|
feat | New feature or function |
fix | Bug fix |
docs | Documentation only |
perf | Performance improvement |
refactor | Code restructure (no behavior change) |
test | Adding or updating tests |
chore | Build system, deps, tooling |
π§ͺ Testing RequirementsΒΆ
Every contribution that adds or modifies behavior must include tests using ShellSpec.
See ShellSpec section above
For detailed information on writing and running ShellSpec tests, see the π§ͺ Using ShellSpec section above.
π Where Tests GoΒΆ
Spec files mirror the source tree:
functions/dcopy.zsh β spec/functions/dcopy_spec.sh
core/08_audit.zsh β spec/core/08_audit_spec.sh
shared_functions/01_stderr_error.zsh β spec/shared_functions/01_stderr_error_spec.sh
π Running TestsΒΆ
# Run all tests (recommended)
just test
# Run specific spec file
shellspec spec/functions/dcopy_spec.sh
# Run tests in a directory
shellspec spec/core/
# Run with verbose output
shellspec --format documentation
π Coverage ExpectationsΒΆ
| Change Type | Minimum Coverage |
|---|---|
| New function | β Happy path + β¬ error paths + β¬ edge cases |
| Bug fix | β Regression test proving the fix |
| Refactor | β Existing tests still pass (no weakening) |
| Performance | β Behavioral tests + β‘ benchmark comparison |
Never weaken tests
Do not delete or relax existing test assertions without explicit approval. If a test needs updating, explain why in the commit message.
Tests must pass before committing
Always run just test before committing. Failing tests will block your PR in CI/CD.
β¨ Linting & FormattingΒΆ
The framework uses Trunk to manage all linters:
| Linter | Checks | Config |
|---|---|---|
| π ShellCheck | Shell script correctness | .trunk/configs/.shellcheckrc |
| π shfmt | Shell formatting | .editorconfig |
| π markdownlint | Markdown formatting | .trunk/configs/.markdownlint.yaml |
| π gitleaks | Secret detection | Trunk default |
| π cspell | Spelling | .cspell.json |
| π prettier | Markdown/JSON formatting | .prettierrc.yaml |
π Running LintersΒΆ
trunk check # Check all changed files
trunk check --all # Check everything
trunk check <file> # Check specific file
trunk fmt # Auto-fix formatting issues
β οΈ ShellCheck OverridesΒΆ
If you must disable a ShellCheck rule:
- Always add a comment explaining why
- Prefer per-line disables over per-file
- If the same disable appears in 3+ files, add it to
.shellcheckrcinstead
β‘ Performance guidelinesΒΆ
The framework has strict performance targets. Profiling shows where time is spent so you can stay under budget.
zprofile and the profile report help you achieveThe framework has strict performance targets:
| Metric | Target | Measured By |
|---|---|---|
| β‘ Cold start | <50ms | hyperfine 'zsh -ic exit' |
| β‘ Warm start (P10k) | <10ms | Instant prompt to interactive |
| π Per-file budget | <100ms | ZSHAND_PERF_BUDGET enforcement |
π RulesΒΆ
- π« No network calls in startup path (hooks can defer)
- π« No subprocess spawning unless absolutely necessary
- β Use zsh builtins over external commands where possible
- β Lazy-load heavy integrations (Docker, cloud CLIs)
- β Cache results that don't change per-session
- β
Profile before and after with
ZSHAND_PROFILE_BUNDLE=1
π Benchmarking a ChangeΒΆ
# Before your change
hyperfine --warmup 3 'zsh -ic exit' --export-json before.json
# Make your change, rebuild
zprime
# After your change
hyperfine --warmup 3 'zsh -ic exit' --export-json after.json
# Compare (manual or via script)
π Adding a New FileΒΆ
Step-by-stepΒΆ
- Choose the right directory (see ARCHITECTURE.md)
- Pick the correct number prefix (even = framework, odd = user slot)
- Write the header per HEADER_STANDARD.md
- Implement the functionality
- Write tests in the mirrored spec location
- Run the quality gate (lint, test, benchmark)
- Rebuild bundles with
zprime - Verify with
zcheck
ποΈ Example: Adding a new functionΒΆ
# 1. Create the function
vi functions/my_function.zsh
# 2. Write header (Standard tier for functions/)
# Include: USAGE, DEPENDENCIES, EXIT CODES, EXAMPLES, TESTING, SEE ALSO
# 3. Create the spec
vi spec/functions/my_function_spec.sh
# 4. Verify
just test spec/functions/my_function_spec.sh
trunk check functions/my_function.zsh
zprime
π Related DocumentsΒΆ
| Document | Purpose |
|---|---|
| π₯ INSTALLERS.md | Cloner and setup (how the installer works) |
| ποΈ ARCHITECTURE.md | Framework structure and boot sequence |
| π HEADER_STANDARD.md | Documentation header conventions |
| π§ͺ TESTING.md | Detailed testing guide |
| β‘ PERFORMANCE.md | Performance profiling and optimization |
| π¨ STYLE_GUIDE.md | Code style conventions |
| π Documentation Index | Full doc nav |