Thank you for your interest in contributing to Gokin!
- Getting Started
- Development Setup
- Code Style
- Testing
- Benchmarking
- Making Changes
- Commit Messages
- Pull Requests
- Adding New Tools
- Reporting Issues
- Code of Conduct
- Go 1.25+ (check with
go version) - Git (for version control)
# Clone the repository
git clone https://github.com/ginkida/gokin.git
cd gokin
# Install dependencies
go mod download
# Verify installation
go mod verify
# Build
go build -o gokin ./cmd/gokin
# Run
./gokin# Development build with version info
go build -ldflags "-s -w -X main.version=$(git describe --tags --always)" -o gokin ./cmd/gokin
# Run all tests with race detector
go test -race ./...
# Run tests with coverage
go test -coverprofile=coverage.out ./...
# View coverage report
go tool cover -html=coverage.out- We use
gofmtfor code formatting - 120 character line limit
- Use
goimportsfor import management
# Format all code
gofmt -w .
# Format specific file
gofmt -w internal/agent/agent.goWe use golangci-lint for linting:
# Install golangci-lint
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin
# Run linter
golangci-lint run ./...
# Run with timeout
golangci-lint run ./... --timeout=5m# Run go vet
go vet ./...
# Run with all checks
go vet -all ./...| Type | Convention | Example |
|---|---|---|
| Packages | lowercase, no underscores | internal/agent |
| Types | PascalCase | Agent, TokenCounter |
| Variables | camelCase | maxTokens, isReady |
| Constants | PascalCase or camelCase | MaxHistorySize or maxHistorySize |
| Functions (exported) | PascalCase | NewAgent, LoadConfig |
| Functions (unexported) | camelCase | calculateTokens, parseMessage |
| Interfaces | PascalCase with "er" suffix | Reader, Writer |
- Always handle errors explicitly
- Use
fmt.Errorfwith%wfor wrapped errors - Never use
log.Printfor errors in libraries - Return meaningful error messages
// Good
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
// Bad
if err != nil {
log.Printf("error: %v", err)
return err
}- Use table-driven tests for multiple test cases
- Name test functions:
Test<Unit>_<Scenario> - Use descriptive subtest names
func TestTokenCounter_CountTokens(t *testing.T) {
tests := []struct {
name string
text string
want int
}{
{
name: "empty string",
text: "",
want: 0,
},
{
name: "simple text",
text: "Hello, world!",
want: 4,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := EstimateTokens(tt.text)
if got != tt.want {
t.Errorf("EstimateTokens() = %v, want %v", got, tt.want)
}
})
}
}# Run all tests
go test ./...
# Run tests with verbose output
go test -v ./...
# Run tests with race detector
go test -race ./...
# Run specific package tests
go test -v ./internal/mcp/...
# Run tests matching pattern
go test -v -run "TestToken" ./internal/context/...
# Skip slow tests
go test -short ./...
# Run with coverage
go test -coverprofile=coverage.out -covermode=atomic ./...We aim for meaningful test coverage, not arbitrary percentages:
# Get coverage for all packages
go test -coverprofile=coverage.out ./...
# View coverage in browser
go tool cover -html=coverage.out
# Get summary
go tool cover -func=coverage.out- Use
Benchmarkprefix for benchmark functions - Use
-benchtimefor longer runs - Measure memory with
-benchmem
func BenchmarkTokenCounting_SimpleText(b *testing.B) {
text := "This is a simple test message with some words."
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = EstimateTokens(text)
}
}# Run all benchmarks
go test -bench=. ./...
# Run with memory statistics
go test -bench=. -benchmem ./...
# Run specific benchmark
go test -bench=BenchmarkTokenCounting -benchmem ./internal/context/...
# Run with longer duration
go test -bench=. -benchtime=5s ./...
# Compare benchmarks (before/after)
benchstat old.txt new.txtBenchmarks are automatically run in CI and results are uploaded as artifacts.
- Fork the repository on GitHub
- Clone your fork locally
- Create a feature branch:
git checkout -b feature/my-feature # or git checkout -b fix/my-fix - Make your changes
- Test thoroughly:
go test -race ./... go vet ./... golangci-lint run ./... - Commit with clear messages (see below)
- Push to your fork
- Open a Pull Request
| Type | Prefix | Example |
|---|---|---|
| Feature | feature/ |
feature/add-semantic-search |
| Bug Fix | fix/ |
fix/token-counting-bug |
| Refactor | refactor/ |
refactor/context-manager |
| Documentation | docs/ |
docs/update-readme |
| Performance | perf/ |
perf/optimize-token-counter |
| Chore | chore/ |
chore/update-dependencies |
<type>: <short description>
<longer description (optional)>
<footer (optional)>
| Type | Description |
|---|---|
feat |
New feature |
fix |
Bug fix |
docs |
Documentation changes |
style |
Code style changes (formatting, etc.) |
refactor |
Code refactoring |
perf |
Performance improvements |
test |
Adding or updating tests |
chore |
Maintenance tasks |
revert |
Reverting a previous commit |
feat: add semantic search support for code exploration
This adds a new semantic search tool that uses embeddings to find
relevant code snippets based on natural language queries.
Closes #123
fix: resolve token counting issue with large code blocks
The token counter was incorrectly estimating tokens for code blocks
containing many identifiers. This fix adjusts the estimation algorithm.
test: add MCP transport tests
Added comprehensive tests for JSON-RPC message parsing, HTTP transport,
and concurrent send/receive operations.
When opening a PR, please fill out the template:
## Description
Brief description of changes
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Testing
How was this tested?
## Checklist
- [ ] Code follows style guidelines
- [ ] Self-review completed
- [ ] Comments added for complex code
- [ ] Documentation updated
- [ ] Tests added/updated
- [ ] All tests pass- Maintainers will review your PR
- Address any feedback
- Once approved, maintainers will merge
// internal/tools/my_tool.go
package tools
type MyTool struct {
// Configuration
}
func (t *MyTool) Name() string {
return "my_tool"
}
func (t *MyTool) Description() string {
return "Description of what the tool does"
}
func (t *MyTool) Declaration() *genai.FunctionDeclaration {
return &genai.FunctionDeclaration{
Name: t.Name(),
Description: t.Description(),
Parameters: &genai.Schema{
Type: "object",
Properties: map[string]*genai.Schema{
"arg1": {Type: "STRING", Description: "First argument"},
},
Required: []string{"arg1"},
},
}
}
func (t *MyTool) Validate(args map[string]any) error {
// Validate arguments
if _, ok := args["arg1"]; !ok {
return NewValidationError("arg1", "is required")
}
return nil
}
func (t *MyTool) Execute(ctx context.Context, args map[string]any) (ToolResult, error) {
// Implementation
return NewSuccessResult("result"), nil
}// internal/tools/registry.go
func (r *Registry) registerBuiltInTools() {
// ... existing tools
r.tools["my_tool"] = &MyTool{}
}// internal/tools/my_tool_test.go
package tools
func TestMyTool_Validate(t *testing.T) { /* ... */ }
func TestMyTool_Execute(t *testing.T) { /* ... */ }When reporting bugs, please include:
- Clear, descriptive title
- Steps to reproduce
- Expected vs actual behavior
- System information:
- OS and version
- Go version (
go version) - Gokin version (
gokin version) - AI provider (Gemini, GLM, etc.)
- Logs (use
--verboseor--debugflag)
- Describe the feature
- Explain the use case
- Provide examples
Please DO NOT report security vulnerabilities in public issues.
Instead, email: ya-ginkida@yandex.kz
Please note that this project adheres to a Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to ya-ginkida@yandex.kz.
Feel free to open an issue for questions or discussions. We welcome:
- Bug reports
- Feature requests
- Questions
- Documentation improvements
By contributing to Gokin, you agree that your contributions will be licensed under the same license as the project.