Skip to main content
Kosh is a high-performance static site generator built in Go. We welcome contributions that improve performance, add features, or fix bugs.

Getting Started

Installation

1

Install Kosh

# Install via go install (recommended)
go install github.com/Kush-Singh-26/kosh/cmd/kosh@latest

# Verify installation
kosh version
2

Clone the repository

git clone https://github.com/Kush-Singh-26/kosh.git
cd kosh
3

Build from source

# Build CLI binary
go build -o kosh.exe ./cmd/kosh

# Run tests
go test ./...

Development Workflow

Before You Start

Always run tests and linter before committing:
  • go test ./... - Run all tests
  • golangci-lint run - Run static analysis

Making Changes

Follow these practices for all contributions:
  1. Small, atomic commits - Make focused commits for easier rollback and review
  2. Feature branches - Create a branch for each feature or bugfix
  3. Code review - All changes require review before merging
  4. Tests first - Write or update tests for your changes
  5. Documentation - Update README.md and AGENTS.md as needed

Adding a New Feature

1

Define the interface (if needed)

Add service contracts to builder/services/interfaces.go:
builder/services/interfaces.go
// AssetService handles static asset processing
type AssetService interface {
    Build(ctx context.Context) error
}
2

Implement the service

Create or modify service in builder/services/:
builder/services/asset_service.go
type assetServiceImpl struct {
    cfg      *config.Config
    logger   *slog.Logger
    sourceFs afero.Fs
    destFs   afero.Fs
}

func NewAssetService(cfg *config.Config, logger *slog.Logger, 
                     sourceFs, destFs afero.Fs) AssetService {
    return &assetServiceImpl{
        cfg:      cfg,
        logger:   logger,
        sourceFs: sourceFs,
        destFs:   destFs,
    }
}
3

Wire dependencies

Update builder/run/builder.go in the NewBuilder() function:
builder/run/builder.go
// Initialize services with dependency injection
assetService := services.NewAssetService(cfg, logger, sourceFs, destFs)
renderService := services.NewRenderService(cfg, logger, destFs)
postService := services.NewPostService(
    cfg, cacheService, renderService, logger, 
    metrics, md, nativeRenderer, sourceFs, destFs, diagramAdapter,
)
4

Add tests

Write tests following existing patterns:
builder/services/asset_service_test.go
func TestAssetService_Build(t *testing.T) {
    // Setup
    cfg := &config.Config{...}
    logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
    
    // Test
    svc := NewAssetService(cfg, logger, sourceFs, destFs)
    err := svc.Build(context.Background())
    
    // Assert
    if err != nil {
        t.Errorf("Build() error = %v", err)
    }
}
5

Update documentation

Document your feature in:
  • README.md - User-facing documentation
  • AGENTS.md - Developer guidelines
  • This docs site - Comprehensive guides

Architecture Overview

Service Layer Pattern

Kosh follows a clean architecture with separated concerns:
cmd/kosh/                    # CLI entry point
    └── main.go              # Command routing

builder/
├── services/                # Business Logic Layer
│   ├── interfaces.go        # Service contracts
│   ├── post_service.go      # Markdown parsing & indexing
│   ├── cache_service.go     # Thread-safe cache wrapper
│   ├── asset_service.go     # Static asset processing
│   └── render_service.go    # HTML template rendering
├── run/                     # Orchestration Layer
│   ├── builder.go           # Builder initialization (DI container)
│   ├── build.go             # Main build orchestration
│   └── incremental.go       # Watch mode & fast rebuilds
├── cache/                   # Data Access Layer
│   ├── cache.go             # BoltDB operations with generics
│   ├── types.go             # Data structures
│   └── adapter.go           # Diagram cache adapter
└── utils/                   # Utilities
    ├── pools.go             # Object pooling (BufferPool)
    └── worker_pool.go       # Generic worker pool

Dependency Injection

The Builder struct acts as a composition root:
builder/run/builder.go
type Builder struct {
    cacheService  services.CacheService
    postService   services.PostService
    assetService  services.AssetService
    renderService services.RenderService
    // ... other dependencies
}
Services are injected via constructors:
func NewPostService(cfg *config.Config, cache CacheService, 
                    renderer RenderService, ...) PostService {
    return &postServiceImpl{
        cfg:      cfg,
        cache:    cache,
        renderer: renderer,
    }
}
Benefits:
  • Testability - Easy mocking of dependencies
  • Separation of Concerns - Each service has a single responsibility
  • Flexibility - Swap implementations without changing business logic

Testing Strategy

Kosh uses multiple testing approaches:
  1. Unit Tests - Test individual functions and methods
  2. Integration Tests - Test service interactions
  3. Performance Tests - Benchmark before/after changes
  4. Race Detection - Detect concurrent access issues
  5. End-to-End - Full build pipeline validation

Running Tests

# Run all tests
go test ./...

# Run specific package
go test ./builder/search -v

# Run specific test
go test ./builder/search -run TestStemmer -v

# Run with race detection
go test -race ./...

# Run benchmarks
go test -bench=. -benchmem ./builder/benchmarks/

Writing Tests

Follow existing test patterns:
builder/search/search_test.go
func TestStemmer(t *testing.T) {
    tests := []struct {
        input    string
        expected string
    }{
        {"running", "run"},
        {"easily", "easili"},
        {"processing", "process"},
    }

    for _, tt := range tests {
        t.Run(tt.input, func(t *testing.T) {
            result := Stem(tt.input)
            if result != tt.expected {
                t.Errorf("Stem(%q) = %q, want %q", 
                         tt.input, result, tt.expected)
            }
        })
    }
}

WASM Compilation

The search engine compiles to WebAssembly for browser execution.
Only recompile WASM when you modify search-related code:
  • cmd/search/main.go
  • builder/search/*.go
  • builder/models/models.go (SearchIndex struct)
  • Serialization format changes

Full Compilation Process

1

Compile WASM binary

GOOS=js GOARCH=wasm go build -o internal/build/wasm/search.wasm ./cmd/search
PowerShell:
$env:GOOS="js"; $env:GOARCH="wasm"
go build -o internal/build/wasm/search.wasm ./cmd/search
Remove-Item Env:GOOS; Remove-Item Env:GOARCH
2

Rebuild CLI

The CLI embeds the WASM binary:
go build -ldflags="-s -w" -o kosh.exe ./cmd/kosh
3

Clear cache

./kosh.exe clean --cache
4

Rebuild site

./kosh.exe build
Output files:
  • internal/build/wasm/search.wasm - WASM source (~4.2 MB)
  • public/static/wasm/search.wasm - Deployed WASM (extracted from CLI)
  • public/search.bin - Search index (~200 KB, msgpack + gzip)

Release Checklist

Before releasing a new version:
  • All tests pass: go test ./...
  • Linter passes: golangci-lint run
  • Binary builds: go build -o kosh.exe ./cmd/kosh
  • Version command works: ./kosh version
  • README.md is up to date
  • AGENTS.md is up to date
  • WASM recompiled if search changed
  • CHANGELOG.md updated

Getting Help

If you need help:
  • Read the AGENTS.md developer guide
  • Check existing issues and pull requests
  • Open a new issue with details about your contribution
  • Join discussions in pull requests

Code Review Process

All contributions go through code review:
  1. Submit PR - Create a pull request with clear description
  2. Automated checks - Tests and linter must pass
  3. Review - Maintainers review code quality and design
  4. Revisions - Address feedback and update PR
  5. Merge - Once approved, PR is merged to main

Build docs developers (and LLMs) love