# Architecture Overview This document describes the design principles, architecture patterns, and implementation details of Grokkit. ## Table of Contents - [Design Philosophy](#design-philosophy) - [Project Structure](#project-structure) - [Core Components](#core-components) - [Data Flow](#data-flow) - [Testing Strategy](#testing-strategy) - [Design Patterns](#design-patterns) - [Future Considerations](#future-considerations) ## Design Philosophy Grokkit follows these core principles: ### 1. **Simplicity First** - Minimal dependencies (stdlib + well-known libs) - Minimal interface surface (CLI commands over TUI interactions) - Clear, readable code over clever solutions - Single responsibility per package ### 2. **Safety by Default** - Git-based version control for change management - Confirmation prompts for destructive actions - Comprehensive error handling ### 3. **Observability** - Structured logging for all operations - Request/response timing - Contextual error messages ### 4. **Testability** - Interface-based design - Dependency injection where needed - Unit tests for pure functions ### 5. **User Experience** - Streaming responses for immediate feedback - Persistent history - Shell completions ## Project Structure ``` grokkit/ ├── main.go # Entry point ├── cmd/ # CLI commands (Cobra) │ ├── root.go # Root command + flags │ ├── chat.go # Chat command │ ├── edit.go # Edit command │ ├── review.go # Review command │ ├── commit.go # Commit commands │ ├── completion.go # Shell completions │ └── *_test.go # Command tests ├── config/ # Configuration (Viper) │ ├── config.go # Config loading + getters │ └── config_test.go # Config tests ├── internal/ # Private packages │ ├── errors/ # Custom error types │ │ ├── errors.go │ │ └── errors_test.go │ ├── git/ # Git operations wrapper │ │ ├── git.go │ │ ├── interface.go # GitRunner interface │ │ └── git_test.go │ ├── grok/ # Grok API client │ │ ├── client.go # HTTP client + streaming │ │ ├── interface.go # AIClient interface │ │ └── client_test.go │ ├── linter/ # Multi-language linting │ │ ├── linter.go # Language detection + linter execution │ │ └── linter_test.go │ └── logger/ # Structured logging (slog) │ ├── logger.go # Logger setup + helpers │ └── logger_test.go ├── docs/ # Documentation │ ├── TROUBLESHOOTING.md │ ├── ARCHITECTURE.md # This file │ └── CONFIGURATION.md ├── .gitea/workflows/ # CI/CD │ ├── ci.yml # Test + lint + build │ └── release.yml # Multi-platform releases └── Makefile # Build automation ``` ### Package Organization - **`cmd/`**: Cobra commands, minimal logic (orchestration only) - **`config/`**: Viper integration, configuration management - **`internal/`**: Core business logic (not importable outside project) - **`internal/errors/`**: Domain-specific error types - **`internal/git/`**: Git command abstraction - **`internal/grok/`**: API client with streaming support - **`internal/linter/`**: Language detection and linter execution - **`internal/logger/`**: Structured logging facade ## Core Components ### 1. CLI Layer (`cmd/`) **Responsibility:** Command parsing, user interaction, orchestration **Pattern:** Command pattern (Cobra) ```go var chatCmd = &cobra.Command{ Use: "chat", Short: "Interactive chat", Run: func(cmd *cobra.Command, args []string) { // 1. Parse flags // 2. Load config // 3. Call business logic // 4. Handle errors // 5. Display output }, } ``` **Key characteristics:** - Thin orchestration layer - No business logic - Delegates to internal packages - Handles user I/O only ### 2. Configuration Layer (`config/`) **Responsibility:** Load and manage configuration from multiple sources **Pattern:** Singleton (via Viper) ```go // Priority: CLI flag > env var > config file > default func GetModel(flagModel string) string { if flagModel != "" { return resolveAlias(flagModel) } return viper.GetString("default_model") } ``` **Configuration sources (in order):** 1. CLI flags (`--model grok-4`) 2. Environment variables (`XAI_API_KEY`) 3. Config file (`~/.config/grokkit/config.toml`) 4. Defaults (hardcoded in `config.Load()`) ### 3. API Client (`internal/grok/`) **Responsibility:** HTTP communication with Grok API **Pattern:** Client pattern with streaming support ```go type Client struct { APIKey string BaseURL string } // Public API func (c *Client) Stream(messages, model) string func (c *Client) StreamSilent(messages, model) string func (c *Client) StreamWithTemp(messages, model, temp) string ``` **Key features:** - Context-aware requests (60s timeout) - Server-Sent Events (SSE) streaming - Silent vs live output modes - Comprehensive logging **Request flow:** 1. Marshal request → JSON 2. Create HTTP request with context 3. Set headers (Authorization, Content-Type) 4. Send request 5. Stream response line-by-line 6. Parse SSE chunks 7. Build full response 8. Log metrics ### 4. Git Integration (`internal/git/`) **Responsibility:** Abstract git command execution **Pattern:** Command wrapper + Interface ```go type GitRunner interface { Run(args []string) (string, error) IsRepo() bool } // Implementation func Run(args []string) (string, error) { out, err := exec.Command("git", args...).Output() // Log execution // Wrap errors return string(out), err } ``` **Why wrapped:** - Logging of all git operations - Consistent error handling - Testability via interface - Future: git library instead of exec ### 5. Linter (`internal/linter/`) **Responsibility:** Language detection and linter orchestration **Pattern:** Strategy pattern (multiple linters per language) ```go type Language struct { Name string Extensions []string Linters []Linter // Multiple options per language } type Linter struct { Name string Command string Args []string InstallInfo string } // Auto-detect language and find available linter func LintFile(filePath string) (*LintResult, error) { lang := DetectLanguage(filePath) // By extension linter := FindAvailableLinter(lang) // Check PATH return RunLinter(filePath, linter) // Execute } ``` **Supported languages:** - Go (golangci-lint, go vet) - Python (pylint, flake8, ruff) - JavaScript/TypeScript (eslint, tsc) - Rust (clippy) - Ruby (rubocop) - Java (checkstyle) - C/C++ (clang-tidy) - Shell (shellcheck) **Design features:** - Fallback linters (tries multiple options) - Tool availability checking - Helpful install instructions - Exit code interpretation ### 6. Logging (`internal/logger/`) **Responsibility:** Structured logging with context **Pattern:** Facade over stdlib `log/slog` ```go // Structured logging with key-value pairs logger.Info("API request completed", "model", model, "duration_ms", duration, "response_length", length) ``` **Features:** - JSON output format - Dynamic log levels - Multi-writer (file + stderr in debug) - Context enrichment **Log levels:** - `DEBUG`: Detailed info for troubleshooting - `INFO`: Normal operations - `WARN`: Potential issues - `ERROR`: Failures requiring attention ### 6. Error Handling (`internal/errors/`) **Responsibility:** Domain-specific error types **Pattern:** Custom error types with context ```go type GitError struct { Command string Err error } type APIError struct { StatusCode int Message string Err error } ``` **Benefits:** - Type-safe error handling - Rich error context - Error wrapping (`errors.Is`, `errors.As`) ## Data Flow ### Chat Command Flow ``` User Input ↓ chatCmd.Run() ↓ config.GetModel() # Resolve model ↓ loadChatHistory() # Load previous messages ↓ grok.Client.Stream() # API call ├─→ logger.Info() # Log request ├─→ HTTP POST # Send to API ├─→ Stream chunks # Receive SSE └─→ logger.Info() # Log completion ↓ saveChatHistory() # Persist conversation ↓ Display to user ``` ### Edit Command Flow ``` User: grokkit edit file.go "instruction" ↓ editCmd.Run() ├─→ Validate file exists ├─→ Read original content ├─→ Clean "Last modified" comments ↓ grok.Client.StreamSilent() # Get AI response ├─→ [Same as chat, but silent] ↓ grok.CleanCodeResponse() # Remove markdown fences ↓ Display preview # Show diff-style output ↓ Prompt user (y/n) ↓ Write file (if confirmed) └─→ logger.Info() # Log changes ``` ### Commit Command Flow ``` User: grokkit commit ↓ commitCmd.Run() ↓ git.Run(["diff", "--cached"]) # Get staged changes ├─→ logger.Debug() # Log git command ↓ grok.Client.Stream() # Generate commit message ↓ Display message + prompt ↓ exec.Command("git", "commit") # Execute commit ├─→ logger.Info() # Log result ``` ### Lint Command Flow ``` User: grokkit lint file.py [--dry-run] [--auto-fix] ↓ lintCmd.Run() ├─→ Validate file exists ↓ linter.LintFile() ├─→ DetectLanguage() # By file extension ├─→ FindAvailableLinter() # Check PATH ├─→ RunLinter() # Execute linter ↓ Display linter output ↓ (if --dry-run) → Exit ↓ (if issues found) → Request AI fixes ↓ grok.Client.StreamSilent() # Get fixed code ├─→ Build prompt with linter output + original code ├─→ Stream API response ↓ grok.CleanCodeResponse() # Remove markdown ↓ (if not --auto-fix) → Show preview + prompt ↓ Write fixed file ↓ linter.LintFile() # Verify fixes ├─→ Display verification result ``` ## Testing Strategy ### Unit Tests **Coverage goals:** - Internal packages: >70% - Helper functions: 100% - Commands: Test helpers, not Cobra execution **Approach:** - Table-driven tests - Isolated environments (temp directories) - No external dependencies in unit tests **Example:** ```go func TestRemoveLastModifiedComments(t *testing.T) { tests := []struct { name string input string expected string }{ {"removes comment", "// Last modified\ncode", "code"}, {"preserves other", "// Comment\ncode", "// Comment\ncode"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := removeLastModifiedComments(tt.input) assert.Equal(t, tt.expected, result) }) } } ``` ### What's NOT tested (and why) ❌ **Cobra command execution** - Requires CLI integration tests ❌ **User input (Scanln)** - Requires terminal mocking ❌ **API streaming** - Would need mock HTTP server ❌ **os.Exit() paths** - Can't test process termination These require **integration/E2E tests**, which are a different testing layer. ### Test Organization ``` package/ ├── implementation.go # Production code └── implementation_test.go # Tests (same package) ``` **Benefits:** - Tests can access private functions - Clear mapping between code and tests - Go convention ## Design Patterns ### 1. Interface Segregation ```go // Small, focused interfaces type AIClient interface { Stream(messages []map[string]string, model string) string StreamSilent(messages []map[string]string, model string) string } type GitRunner interface { Run(args []string) (string, error) IsRepo() bool } ``` **Benefits:** - Easy to mock for testing - Flexibility to swap implementations - Clear contracts ### 2. Facade Pattern (Logger) ```go // Simple API hides slog complexity logger.Info("message", "key", "value") logger.Error("error", "context", ctx) ``` ### 3. Command Pattern (CLI) ```go // Each command is independent var chatCmd = &cobra.Command{...} var editCmd = &cobra.Command{...} ``` ### 4. Strategy Pattern (Streaming modes) ```go // Different behaviors via parameters func streamInternal(messages, model, temp, printLive bool) { if printLive { fmt.Print(content) // Live streaming } // Silent streaming builds full response } ``` ### 5. Template Method (Git operations) ```go // Base wrapper with logging func Run(args []string) (string, error) { logger.Debug("executing", "command", args) out, err := exec.Command("git", args...).Output() if err != nil { logger.Error("failed", "command", args, "error", err) } return string(out), err } ``` ## Future Considerations ### Scalability **Current limitations:** - Single API key (no multi-user support) - No request queuing - No rate limiting client-side - Chat history grows unbounded **Future improvements:** - Rate limit handling with backoff - History trimming/rotation - Concurrent request support - Request caching ### Extensibility **Easy to add:** - New commands (add to `cmd/`) - New models (config aliases) - New output formats (change display logic) - New logging destinations (slog handlers) **Harder to add:** - Multiple AI providers (requires abstraction) - Non-streaming APIs (requires client refactor) - GUI (CLI-focused architecture) ### Performance **Current bottlenecks:** - Network latency (streaming helps with perception) - API response time (model-dependent) - File I/O for large history (rare) **Optimization opportunities:** - Request caching (for repeated queries) - Parallel git operations (if multiple files) - Lazy loading of history (if very large) ### Security **Current measures:** - API key via environment variable - No credential storage - Git-based rollbacks for safety **Future considerations:** - Encrypted config storage - API key rotation support - Audit logging for file changes - Sandboxed file operations ## Contributing When contributing, maintain these principles: 1. **Keep packages small and focused** 2. **Write tests for pure functions** 3. **Log all external operations** 4. **Use interfaces for testability** 5. **Handle errors explicitly** 6. **Document non-obvious behavior** --- **See also:** - [Configuration Guide](CONFIGURATION.md) - [Troubleshooting](TROUBLESHOOTING.md)