Skip to content

🀝 Contributing to ZSHAND¢

πŸ“‹ This guide covers dev setup, workflow, conventions, and quality requirements for contributing to the ZSHAND framework.

🧠 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.


πŸš€ Getting StartedΒΆ

πŸ“‹ PrerequisitesΒΆ

Tool Min Version Purpose Install
🐚 zsh 5.0+ Shell runtime System package manager
πŸ“¦ git 2.20+ Version control sudo apt install git
πŸ”‘ direnv β€” Secrets & env management mise install direnv
πŸ“‹ just β€” Task runner / orchestration mise install just
πŸ§ͺ ShellSpec β€” BDD test framework mise install shellspec
✨ Trunk 1.0+ Linting & formatting trunk.io
⚑ Hyperfine β€” Benchmarking mise install hyperfine
πŸ”§ mise 1.0+ Polyglot runtime manager mise.jdx.dev

πŸ› οΈ Dev SetupΒΆ

Option 1: Dev Container (Recommended for new contributors)

# 1. Open in VS Code/Cursor with Dev Containers extension
# 2. Click "Reopen in Container" when prompted
# 3. Everything is set up automatically!
#    - All tools installed via mise
#    - zsh and zshdb ready for debugging
#    - Consistent environment for all contributors

See the 🐳 Using Dev Containers section for details.

Option 2: Local Setup

# 1. Clone the repo
git clone https://github.com/presempathy/zshand.git ~/code/zshand
cd ~/code/zshand

# 2. Install tools via mise
mise install

# 3. Run first-time setup (installs deps, creates user config dir)
zsh setup.zsh

# 4. Enable dev mode (disables bundles, enables debug output)
export ZSHAND_DEV_MODE=1
exec zsh

# 5. Verify everything works
just test          # Run full test suite
just lint          # Run linters
zcheck             # Framework integrity audit

Dev mode vs production

With ZSHAND_DEV_MODE=1, the framework loads individual files instead of compiled bundles, and enables debug output. Always develop in this mode so you see changes immediately without recompiling.


πŸ“‹ Using Just (Task Runner)ΒΆ

Just is the project's task runner. It provides a consistent interface for common development tasks.

πŸš€ Quick StartΒΆ

# List all available tasks
just --list

# Run a task
just test
just lint
just build

πŸ“ 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 test before committing to ensure tests pass
  • Run just lint to catch formatting and linting issues early
  • Use just check before 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ΒΆ

  1. Open in VS Code/Cursor with the Dev Containers extension installed
  2. Click "Reopen in Container" when prompted, or use Command Palette:
  3. F1 β†’ Dev Containers: Reopen in Container
  4. 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:

  1. Set breakpoints in your .zsh files
  2. Use VS Code's Debug panel (F5)
  3. Select "Debug Zsh Script" configuration
  4. 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 test inside 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 test or shellspec
  • 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" not It "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

πŸ“ ConventionsΒΆ

πŸ“ Header StandardΒΆ

Every file must have a documentation header following HEADER_STANDARD.md. This is non-negotiable.

File Location Tier Title Format
build/*.zsh (complex) πŸ‘‘ Full ╔═╗ box
core/*.zsh, functions/*.zsh, hooks/*.zsh, bin/* πŸ“˜ Standard ── name β€” Description ──
shared_functions/*.zsh πŸ“¦ Compact ── name β€” Description ──
config/*.conf, .zshand-deps 🏷️ Minimal Brief # comment block

Quick checklist for every new file:

  • βœ… Title line or box at correct tier
  • βœ… USAGE section with signature and arguments
  • βœ… DEPENDENCIES section (even if "None")
  • βœ… EXIT CODES section
  • βœ… EXAMPLES section (copy-pasteable)
  • βœ… TESTING section (spec path, status, must-cover list)
  • βœ… SEE ALSO section (related files)
  • βœ… Closing rule (# ──...──)
  • βœ… All dividers exactly 78 chars after #

πŸ”’ File NamingΒΆ

Directory Pattern Example
core/ NN_name.zsh (even numbers) 08_audit.zsh
shared_functions/ NN_name.zsh 01_stderr_error.zsh
startup/ NN_az_name.zsh 24_az_safe_mode.zsh
hooks/ NN_name.zsh 01_mise.zsh
functions/ name.zsh (no prefix) dcopy.zsh
widgets/ name.zsh (no prefix) aicmit.zsh
bin/ name (no extension) aicontext
private_functions/ _name.zsh (underscore prefix) _zshand_network_monitor.zsh

🏷️ Function Naming¢

Scope Convention Example
🌐 Public (user-facing) Short, memorable zcheck, dcopy, zprime
πŸ”’ Private (framework-internal) _az_ or _zshand_ prefix _az_safe_mode_enter
🧱 Shared utility Descriptive, lowercase stderr_error, load_file_timed
⌨️ Widget Descriptive, lowercase aicmit, bufcopy

πŸ”„ 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(scope): short description

Optional longer description explaining why.

Closes #123
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:

  1. Always add a comment explaining why
  2. Prefer per-line disables over per-file
  3. If the same disable appears in 3+ files, add it to .shellcheckrc instead
# shellcheck disable=SC2034  β€” Variable exported for use by sourced modules
local MY_VAR="value"

⚑ Performance Guidelines¢

The 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ΒΆ

  1. Choose the right directory (see ARCHITECTURE.md)
  2. Pick the correct number prefix (even = framework, odd = user slot)
  3. Write the header per HEADER_STANDARD.md
  4. Implement the functionality
  5. Write tests in the mirrored spec location
  6. Run the quality gate (lint, test, benchmark)
  7. Rebuild bundles with zprime
  8. 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

Document Purpose
πŸ—οΈ 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