Introduce new `grokkit lint` command for automatic language detection, linting, and AI-suggested fixes. Supports 9 languages including Go, Python, JavaScript, TypeScript, Rust, Ruby, Java, C/C++, and Shell. - Add cmd/lint.go for command implementation - Create internal/linter package with detection and execution logic - Update README.md with usage examples and workflows - Enhance docs/ARCHITECTURE.md and docs/TROUBLESHOOTING.md - Add comprehensive tests for linter functionality
598 lines
15 KiB
Markdown
598 lines
15 KiB
Markdown
# 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**
|
|
- File backups before any modification
|
|
- 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
|
|
├─→ Create backup (.bak)
|
|
├─→ 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
|
|
└─→ Keep backup
|
|
```
|
|
|
|
### 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
|
|
↓
|
|
Create backup (.bak)
|
|
↓
|
|
(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
|
|
- Backup files 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)
|