Thank you for your interest in contributing to proj! This document provides guidelines and information for contributors.
Please be respectful and considerate in all interactions. We're all here to build something useful together.
- Go 1.24 or later
- Make
- Git
-
Fork and clone the repository:
git clone https://github.com/YOUR_USERNAME/proj.git cd proj -
Install dependencies:
make deps
-
Build the project:
make build
-
Run tests:
make test -
Run in development mode:
make dev
main- Stable release branch- Feature branches -
feat/description - Bug fix branches -
fix/description - Documentation -
docs/description
-
Create a feature branch:
git checkout -b feat/my-feature
-
Make your changes following the coding guidelines below.
-
Run tests and linting:
make test make lint make fmt -
Commit your changes:
git add . git commit -m "feat: add my new feature"
-
Push and create a pull request:
git push origin feat/my-feature
We follow Conventional Commits:
<type>: <description>
[optional body]
[optional footer]
Types:
feat- New featurefix- Bug fixdocs- Documentation changesstyle- Code style changes (formatting, etc.)refactor- Code refactoringtest- Adding or updating testschore- Maintenance tasks
Examples:
feat: add support for Ruby language detection
fix: resolve crash when git directory is missing
docs: update installation instructions
refactor: simplify project scanning logic
proj/
├── cmd/proj/ # CLI entrypoint
│ └── main.go # Main function, CLI parsing
├── internal/ # Private application code
│ ├── app/ # Main TUI application model
│ ├── actions/ # Built-in action implementations
│ ├── config/ # Configuration loading/saving
│ ├── git/ # Git operations
│ ├── language/ # Language detection
│ ├── project/ # Project scanning
│ └── tui/ # TUI components
│ ├── styles.go # Lip Gloss styles
│ ├── keys.go # Keyboard shortcuts
│ └── views/ # View components
├── pkg/ # Public packages (importable by others)
│ └── plugin/ # Plugin system
├── plugins/ # Example plugins
│ └── example/ # Example plugin implementation
├── scripts/ # Build and installation scripts
├── docs/ # Documentation
├── Makefile # Build automation
└── go.mod # Go module definition
- Follow Effective Go
- Use
gofmtfor formatting - Use meaningful variable and function names
- Add comments for exported functions and types
- One primary type per file when possible
- Tests in
*_test.gofiles alongside source - Keep files focused and under 500 lines when reasonable
- Always handle errors explicitly
- Use descriptive error messages
- Wrap errors with context using
fmt.Errorf("context: %w", err)
result, err := doSomething()
if err != nil {
return fmt.Errorf("failed to do something: %w", err)
}- Write tests for new functionality
- Use table-driven tests where appropriate
- Aim for meaningful coverage, not 100%
func TestMyFunction(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{"empty input", "", ""},
{"normal input", "hello", "HELLO"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := MyFunction(tt.input)
if result != tt.expected {
t.Errorf("got %q, want %q", result, tt.expected)
}
})
}
}- Edit
internal/language/detect.go - Add detection logic in
Detect()function - Add to the
languagesslice if using marker files - Add tests in
detect_test.go
// In detect.go
{name: "MyLang", markers: []string{"mylang.config", "*.mylang"}},- Edit
internal/config/config.go - Add to
DefaultConfig()aliases - Update
docs/CONFIG.md
// In config.go, DefaultConfig()
"myeditor": {"myeditor", "--flag"},- Edit
internal/actions/actions.go - Add action handler in
Execute()method - Add to
internal/tui/views/action_menu.goinDefaultActions() - Add tests
- Edit
pkg/plugin/types.gofor new types - Update
pkg/plugin/loader.gofor new methods - Update
docs/PLUGINS.md - Add example in
plugins/example/main.go
# All tests
make test
# Specific package
go test ./internal/language/
# With verbose output
go test -v ./...
# With coverage
make test-coverage- Place tests in
*_test.gofiles - Use
t.Run()for subtests - Use
t.Helper()in helper functions - Use
t.TempDir()for temporary directories
- Update
README.mdfor user-facing changes - Update relevant docs in
docs/directory - Add comments to exported types and functions
- Include examples where helpful
proj supports shell integration to enable directory changing from the TUI. We welcome contributions for additional shell support!
Shell integration uses a wrapper function that:
- Runs the actual
projbinary with user arguments - Checks for a temporary file at
~/.config/proj/.proj_last_dir - Changes to the directory specified in that file (if it exists)
- Cleans up the temporary file
The Go binary writes to this file when a project is selected in the TUI.
To add support for a new shell:
-
Create the integration script:
touch scripts/shells/yourshell.ext
-
Implement the wrapper function following this pattern:
# Your shell's syntax for defining functions proj() { # Store original directory original_dir=$(pwd) # or your shell's equivalent # Run the actual proj binary command proj "$@" # or your shell's equivalent for passing args # Check for directory change file proj_dir_file="$HOME/.config/proj/.proj_last_dir" if [ -f "$proj_dir_file" ]; then target_dir=$(cat "$proj_dir_file") if [ -d "$target_dir" ] && [ "$target_dir" != "$original_dir" ]; then echo "Changing to: $target_dir" cd "$target_dir" fi rm -f "$proj_dir_file" fi }
-
Add auto-setup detection for when the script is sourced:
# Your shell's method for detecting if sourced vs executed if [[ sourced_condition ]]; then setup_function_or_direct_call fi
-
Update the installer script in
scripts/install.sh:- Add your shell to the case statement in
setup_shell_integration() - Create a
setup_yourshell_integration()function - Follow the pattern used by bash/zsh/fish functions
- Add your shell to the case statement in
-
Test your integration:
# Source your script manually source scripts/shells/yourshell.ext # Test the proj function proj # Navigate to a project and verify directory changes work
-
Add examples to documentation:
- Add manual setup instructions to
docs/INSTALL.md - Include your shell in supported shells list
- Add manual setup instructions to
Currently supported:
- bash (
scripts/shells/bash.sh) - zsh (
scripts/shells/zsh.sh) - fish (
scripts/shells/fish.fish)
Requested shells (contributions welcome):
- PowerShell
- Nushell
- Elvish
- Oil
- Ion
- Xonsh
See existing implementations for reference:
When contributing shell support, please ensure:
- The wrapper function preserves all
projarguments - Error handling doesn't break the shell session
- The integration works in both interactive and non-interactive modes
- Clean up temporary files properly
- Follow your shell's best practices for function definitions
-
Ensure all tests pass:
make test -
Run the linter:
make lint
-
Update documentation if needed.
-
Create the pull request with:
- Clear title describing the change
- Description of what and why
- Reference to any related issues
-
Respond to feedback and make requested changes.
- Tests pass (
make test) - Linter passes (
make lint) - Code is formatted (
make fmt) - Documentation updated if needed
- Commit messages follow convention
- No unrelated changes included
Include:
- proj version (
proj --version) - Go version (
go version) - Operating system and version
- Steps to reproduce
- Expected vs actual behavior
- Error messages or logs
Include:
- Clear description of the feature
- Use case / why it would be helpful
- Any implementation ideas (optional)
Maintainers: See RELEASE.md for the release process and version management guidelines.
- Open an issue for questions
- Check existing issues and documentation first
- Be patient and respectful
By contributing, you agree that your contributions will be licensed under the MIT License.
Thank you for contributing to proj!