Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build and Test Commands

### Building
```bash
# Build the executable
go build -o dave.exe

# Download dependencies
go mod download
```

### Testing
```bash
# Run all tests
go test ./tests/...

# Run a specific test file
go test ./tests/calculator_test.go

# Run a specific test function
go test -run TestCalculateMonthlyInterest ./tests/
```

### Running
```bash
# Run the built executable
./dave.exe

# Or run directly with go
go run main.go
```

## Project Architecture

Dave is a CLI debt tracking tool built with Go using the Cobra command framework and SQLite for persistence.

### Core Package Structure

- **`main.go`**: Entry point that delegates to `cmd.Execute()`
- **`cmd/`**: Cobra command definitions (add, pay, remove, adjust-*, mode, snowball, reset, show)
- `root.go`: Base command setup, database initialization lifecycle, and command registration
- Each command file defines a single Cobra command
- **`internal/models/`**: Data models and database operations
- `debt.go`: Debt CRUD operations with support for position-based (#) or name-based lookup
- `payment.go`: Payment history tracking
- `settings.go`: User settings (sort mode, snowball amount)
- **`internal/database/`**: SQLite database wrapper and schema management
- `db.go`: Database connection wrapper
- `schema.go`: Table creation and schema initialization
- **`internal/calculator/`**: Financial calculation engine
- `interest.go`: Monthly interest calculations
- `projections.go`: Simulates monthly payments with compound interest to project payoff timelines
- **`internal/display/`**: Terminal UI rendering with Lipgloss
- `table.go`: Debt table formatting with ASCII art header
- `formatter.go`: Currency, date, and percentage formatting utilities
- `styles.go`: Lipgloss style definitions
- **`internal/config/`**: Configuration and path management
- `paths.go`: Database path resolution (`~/.dave/debts.db`)

### Key Architecture Patterns

**Database Lifecycle**: The database connection is initialized in `cmd/root.go`'s `init()` via Cobra's `OnInitialize` hook and closed in `PersistentPostRun`. All commands access the shared DB via `GetDB()`.

**Dual Identifier System**: Most commands accept either a position number (1, 2, 3...) or creditor name. The `GetDebtByIndexOrName()` function handles this by attempting integer parsing first, then falling back to name lookup.

**Sort Modes**: Three modes control debt ordering:
- `snowball`: Smallest balance first (psychological wins)
- `avalanche`: Highest APR first (mathematically optimal)
- `manual`: Custom ordering via `custom_order` column

Mode changes are handled in `models/settings.go` and affect how `GetAllDebts()` sorts results.

**Projection Algorithm** (`calculator/projections.go`):
The `ProjectPayoffTimeline()` function simulates month-by-month payments:
1. Apply monthly interest to all active debts
2. Apply minimum payments to all active debts
3. Apply snowball amount to highest-priority unpaid debt
4. When a debt is paid off, add its minimum payment to the snowball (auto-snowball)
5. Repeat for up to 600 months (50 years) or until all debts are paid

This simulation accounts for compound interest and the cascading snowball effect.

**Hidden Paid Debts**: Debts with `current_balance = 0` are automatically filtered out by `GetAllDebts()` (via `WHERE current_balance > 0`), but remain in the database for historical tracking.

**Payment History**: The `payments` table tracks all payments with interest/principal breakdown. When recording a payment, the debt balance is updated and a payment record is inserted.

## Data Storage

SQLite database at `~/.dave/debts.db` with three tables:
- **debts**: id, creditor, original_balance, current_balance, apr, minimum_payment, custom_order (nullable), created_at, updated_at
- **payments**: id, debt_id (FK), amount, interest_paid, principal_paid, payment_date, created_at
- **settings**: key-value pairs (sort_mode, snowball_amount)

## Dependencies

- **Cobra**: CLI framework for command structure
- **Lipgloss**: Terminal UI styling and table rendering
- **modernc.org/sqlite**: Pure-Go SQLite driver (no CGo)
6 changes: 4 additions & 2 deletions internal/display/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ package display
import (
"fmt"
"time"

"github.com/dustin/go-humanize"
)

// FormatCurrency formats a float as currency with comma separators
func FormatCurrency(amount float64) string {
if amount < 0 {
return fmt.Sprintf("-$%.2f", -amount)
return "-$" + humanize.CommafWithDigits(amount*-1, 2)
}
return fmt.Sprintf("$%.2f", amount)
return "$" + humanize.CommafWithDigits(amount, 2)
}

// FormatPercent formats a float as a percentage
Expand Down
10 changes: 9 additions & 1 deletion internal/display/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,15 @@ func RenderDebtsTable(debts []models.Debt, projections []calculator.DebtProjecti
t := table.New().
Border(lipgloss.NormalBorder()).
BorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("8"))).
Headers("#", "Creditor", "Original", "Current", "Rate", "Payment", "Interest", "Payoff", "Months", "Years")
Headers("#", "Creditor", "Original", "Current", "Rate", "Payment", "Interest", "Payoff", "Months", "Years").
StyleFunc(func(row, col int) lipgloss.Style {
style := lipgloss.NewStyle()
// Right-align numeric columns (all except #=0 and Creditor=1)
if col >= 2 {
style = style.Align(lipgloss.Right)
}
return style
})

// Add data rows
for i, debt := range debts {
Expand Down