Skip to content

Development Guide

Library Orientation

This guide covers how to contribute to GTB itself, set up a development environment, run tests, and understand the internal architecture. If you are developing an application using GTB, some sections may differ.

Development Setup

Prerequisites

  • Go 1.26 or later
  • Git configured for private repositories
  • golangci-lint for code quality checks
  • mockery for generating mocks (if modifying interfaces)
  • just (for running build scripts)

Clone and Setup

git clone https://github.com/phpboyscout/gtb.git
cd gtb

# Install dependencies
go mod download

# Verify setup
just build
# Or manually:
go build ./...
go test ./...

Environment Configuration

Set up your environment for private module development:

# Configure Git authentication
git config --global --replace-all url."https://${GITHUB_USERNAME}:${GITHUB_TOKEN}@github.com".insteadOf "https://github.com"

# Set Go private modules
go env -w GOPRIVATE=github.com

Project Structure

Understanding the codebase organization:

gtb/
โ”œโ”€โ”€ .github/                # CI/CD workflows and GitHub templates
โ”œโ”€โ”€ .gtb/          # Configuration and manifest for the project
โ”œโ”€โ”€ cmd/                    # Built-in shareable command implementations (docs, init, root, update, version)
โ”œโ”€โ”€ docs/                   # Project documentation
โ”œโ”€โ”€ internal/               # Internal components used exclusively by the CLI and not intended for public consumption
โ”‚   โ””โ”€โ”€ agent/              # AI Agent components used by the CLI
โ”‚   โ””โ”€โ”€ cmd/                # Cobra command logic use by the CLI
โ”‚   โ””โ”€โ”€ generator/          # Template generation logic use by the CLI to create CLI projects
โ”œโ”€โ”€ pkg/                    # Public library packages
โ”‚   โ”œโ”€โ”€ chat/               # AI provider integrations (OpenAI, Gemini, Claude)
โ”‚   โ”œโ”€โ”€ config/             # Configuration management (Viper wrapper)
โ”‚   โ”œโ”€โ”€ controls/           # Service lifecycle management
โ”‚   โ”œโ”€โ”€ docs/               # TUI documentation browser and AI Q&A
โ”‚   โ”œโ”€โ”€ errorhandling/      # Custom error types and reporting
โ”‚   โ”œโ”€โ”€ props/              # Application-wide properties and dependency container
โ”‚   โ”œโ”€โ”€ setup/              # Initialization and project setup
โ”‚   โ”œโ”€โ”€ utils/              # Common utility functions
โ”‚   โ””โ”€โ”€ vcs/                # Version control (Git) utilities
โ”œโ”€โ”€ main.go                 # CLI entry point
โ””โ”€โ”€ justfile                # Build and automation tasks

Development Workflow

Significant Feature Lifecycle

Non-trivial features follow a spec-first, branch-per-feature workflow. The spec and the implementation share a single branch and PR, ensuring the design is reviewed and approved before code is written.

1. Create a Feature Branch

git checkout -b feature/my-new-feature

2. Draft a Specification

Using an AI assistant (Cursor Agent, Claude, etc.), draft a feature spec following the Feature Specifications guide. The spec captures requirements, public API design, internal implementation, testing strategy, and implementation phases.

Save it to docs/development/specs/YYYY-MM-DD-<feature-name>.md with status DRAFT.

3. Push and Raise a PR

Push your branch and open a PR containing only the spec. This is the review gate -- no implementation code yet.

git push -u origin feature/my-new-feature

Mark the PR as a spec review (e.g. prefix the title with [SPEC]) so reviewers know what to expect.

4. Request SME Review

Request review from a Subject Matter Expert (SME). The reviewer should:

  • Read the spec and assess feasibility, design decisions, and scope.
  • If approved: update the spec's status to APPROVED (in both frontmatter and the document header) and push the change.
  • If changes are needed: use GitHub's PR commenting facility to communicate required changes. The spec author iterates until the reviewer is satisfied.
  • If rejected: update the spec's status to REJECTED, add a ## Rejection Rationale section (see Handling Rejections), and merge the PR to preserve the historical record.

Warning

Do not begin implementation until the spec status is APPROVED.

5. Implement the Feature

Once approved, develop the feature in the same branch. Use the spec as the primary reference -- it defines the implementation phases, public API, and acceptance criteria. AI agents can read the spec directly from the repository for full context.

Update the spec status to IN PROGRESS when development begins.

# Implement in phases as defined by the spec
# Verify as you go
go test ./...
golangci-lint run

6. Code Review, Approval, and Merge

Once implementation is complete:

  1. Update the spec status to IMPLEMENTED.
  2. Ensure all CI checks pass and the Verification Checklists are satisfied.
  3. Request code review following standard project conventions.
  4. Merge the PR once approved.

Tip

Keep the spec and implementation in the same PR. This ensures the design rationale is always co-located with the code it describes in the git history.

Quick Fixes and Minor Changes

Not every change needs a spec. For bug fixes, small enhancements, and refactors that don't alter the public API or architecture, follow the standard branch-and-PR workflow without a spec.

Testing

The project uses comprehensive testing with mocks:

# Run all tests
go test ./...

# Run tests with coverage
go test -cover ./...

# Run specific package tests
go test ./config -v

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

3. Mock Generation

If you modify interfaces, regenerate mocks:

# Install mockery if not already installed
go install github.com/vektra/mockery/v2@latest

# generate all mocks for the project
mockery

4. Code Quality

The project enforces code quality standards:

# Run linter
golangci-lint run

# Apply Go fixes
go fix ./...

# Auto-fix issues where possible
golangci-lint run --fix

# Format code
go fmt ./...

# Check for common issues
go vet ./...

Testing Philosophy

Unit Testing

  • High Coverage: Aim for >90% test coverage
  • Mocked Dependencies: Use generated mocks for external dependencies
  • Table-Driven Tests: Use table-driven patterns for multiple scenarios
  • Parallel Execution: Tests should run in parallel where possible

Example test structure:

func TestLoadEmbed(t *testing.T) {
    t.Parallel()

    tests := []struct {
        name          string
        paths         []string
        setup         func(*configMocks.MockEmbeddedFileReader)
        expectError   bool
        expectedFiles int
    }{
        {
            name:  "successful single file",
            paths: []string{"config.yaml"},
            setup: func(m *configMocks.MockEmbeddedFileReader) {
                m.EXPECT().ReadFile("config.yaml").Return([]byte("test"), nil)
            },
            expectError:   false,
            expectedFiles: 1,
        },
        // ... more test cases
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            t.Parallel()
            // Test implementation
        })
    }
}

Integration Testing

  • Test command execution end-to-end
  • Verify configuration loading and merging
  • Test error handling paths

Architecture Principles

For a visual overview of component relationships and core workflows, see the Architectural Overview.

1. Dependency Injection

All components receive their dependencies via constructor functions:

func NewCmdRoot(ver string, commit string, date string) *cobra.Command {
    // Dependencies are injected, and root initialization handles built-in commands
}

2. Interface Segregation

Small, focused interfaces for better testability:

type EmbeddedFileReader interface {
    ReadFile(name string) ([]byte, error)
}

3. Error Wrapping

Consistent error handling with context:

if err != nil {
    return nil, errors.WrapPrefix(err, "failed to load config", 0)
}

4. Configuration Abstraction

Flexible configuration system supporting multiple sources:

  • File-based configuration
  • Embedded configuration
  • Environment variables
  • Command-line flags

Adding New Components

1. Creating a New Package

When adding a new package:

mkdir newpackage
cd newpackage

interface.go:

package newpackage

// Define interfaces first
type NewService interface {
    DoSomething(ctx context.Context, input string) error
}

implementation.go:

package newpackage

import "context"

type service struct {
    logger *log.Logger
    config Configurable
}

func NewService(logger *log.Logger, config Configurable) NewService {
    return &service{
        logger: logger,
        config: config,
    }
}

func (s *service) DoSomething(ctx context.Context, input string) error {
    s.logger.Debug("doing something", "input", input)
    // Implementation
    return nil
}

2. Adding Tests

implementation_test.go:

package newpackage_test

import (
    "context"
    "testing"

    "github.com/phpboyscout/gtb/newpackage"
    "github.com/phpboyscout/gtb/mocks/pkg/config"
    "github.com/charmbracelet/log"
    "github.com/stretchr/testify/assert"
)

func TestService_DoSomething(t *testing.T) {
    t.Parallel()

    logger := log.New(io.Discard)
    mockConfig := configMocks.NewMockConfigurable(t)
    service := newpackage.NewService(logger, mockConfig)

    err := service.DoSomething(context.Background(), "test")
    assert.NoError(t, err)
}

3. Generate Mocks

Add mockery directive to interface:

type NewService interface {
    DoSomething(ctx context.Context, input string) error
}

Debugging Tips

1. Verbose Logging

Enable debug logging during development:

logger := log.NewWithOptions(os.Stderr, log.Options{
    Level: log.DebugLevel,
    ReportCaller: true,
})

2. Test Debugging

Run specific tests with verbose output:

go test -v ./config -run TestLoadEmbed

3. Race Condition Detection

Run tests with race detection:

go test -race ./...

Contributing Guidelines

Pull Requests

For significant features, follow the full Significant Feature Lifecycle above. For all PRs:

  1. Create a feature branch from main.
  2. Add comprehensive tests.
  3. Update documentation.
  4. Ensure all CI checks pass.
  5. Submit PR with a clear description of the impact, linking to the spec if applicable.

Code Review Checklist

  • Feature spec exists and is APPROVED (for non-trivial features)
  • Spec status updated to reflect current state (IN PROGRESS / IMPLEMENTED)
  • Tests added/updated
  • Documentation updated
  • Code follows project conventions
  • No breaking changes (or properly documented)
  • Error handling follows project patterns
  • Interfaces are properly mocked

Release Process

Releases are automated via CI/CD, but follow semantic versioning:

  • Major: Breaking changes
  • Minor: New features, backward compatible
  • Patch: Bug fixes, backward compatible

Common Development Tasks

Adding a New Built-in Command

  1. Create package in cmd/
  2. Implement command following existing patterns
  3. Add to root command registration in pkg/cmd/root/root.go:
    rootCmd.AddCommand(newbuiltin.NewCmdBuiltin(p))
    
  4. Add comprehensive tests
  5. Update documentation

Modifying Configuration System

  1. Update interfaces in config/
  2. Regenerate mocks
  3. Update tests
  4. Verify backward compatibility
  5. Update examples

Detailed Development Guides

For specialized information on library-level development and architectural standards, see:

This development guide should help you contribute effectively to GTB!