# AGENTS.md - Development Guidelines for my-log-wynter ## Project Overview my-log-wynter is a Go CLI application that extends the `codeberg.org/danjones000/my-log` CLI library with custom commands. It integrates with youtube-dl/yt-dlp for video metadata fetching. It uses and `github.com/lrstanley/go-ytdlp` for yt-dlp integration. ## Build/Lint/Test Commands ### Building ```bash # Build all packages go build ./... # Build the CLI binary go build -o my-log ./cmd/my-log # Run the CLI go run ./cmd/my-log ``` ### Testing ```bash # Run all tests go test ./... # Run a single test by name go test -run ./... # Run tests with verbose output go test -v ./... # Run tests with coverage go test -cover ./... # Run benchmarks go test -bench=. ./... ``` ### Linting ```bash # Run golangci-lint (comprehensive linter) golangci-lint run ./... # Run golangci-lint with auto-fix golangci-lint run ./... --fix # Run go vet go vet ./... # Format code (gofmt) gofmt -w . gofmt -d . # Check for unused imports and other issues goimports -w . ``` ### Dependencies ```bash # Update dependencies go get -u ./... # Tidy go.mod go mod tidy # List dependencies go list -m all ``` ## Code Style Guidelines ### General Conventions - Follow [Effective Go](https://go.dev/doc/effective_go) and [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) - Use Go 1.26.0 or later (as specified in go.mod) - Run `go fmt` and `gofmt` before committing - Run `golangci-lint run ./...` before committing ### Naming Conventions - **Packages**: Use short, lowercase names (e.g., `ytdlp`, not `yt-dlp` or `youtube_dlp`) - **Variables/Functions**: Use camelCase (e.g., `fetchURL`, not `fetch_url`) - **Exported Functions/Types**: Use PascalCase (e.g., `Fetch`, not `fetch`) - **Constants**: Use PascalCase (e.g., `MaxRetries`) or camelCase for unexported (e.g., `maxRetries`) - **Acronyms**: Keep original casing (e.g., `URL`, not `Url`; `ID`, not `Id`) - **Files**: Use lowercase with underscores for multiple words (e.g., `fetch_video.go`) ### Import Organization Imports should be organized in three groups separated by blank lines: 1. Standard library packages 2. External/third-party packages 3. Internal packages (if applicable) ```go import ( "context" "fmt" "os" "codeberg.org/danjones000/my-log/cli" "github.com/lrstanley/go-ytdlp" ) ``` ### Error Handling - Return errors where possible; avoid panics except for truly unrecoverable conditions - Wrap errors with context using `fmt.Errorf("description: %w", err)` or `%v` - Handle errors explicitly; don't ignore them with `_` - Place error checks immediately after the operation that can fail ```go // Good result, err := fetchURL(ctx, url) if err != nil { return nil, fmt.Errorf("failed to fetch %s: %w", url, err) } // Avoid result, _ := fetchURL(ctx, url) // Don't ignore errors ``` ### Context Usage - Pass `context.Context` as the first parameter for functions that may timeout or be cancelled - Use `context.Background()` for top-level operations and `context.WithTimeout()` for bounded operations - Check for context cancellation with `ctx.Err()` when appropriate ```go func Fetch(ctx context.Context, url string) (*Result, error) { // ... } ``` ### Types and Interfaces - Define interfaces close to where they are used - Prefer concrete types unless interface polymorphism is needed - Use pointer receivers (`*T`) for methods that modify the receiver or when nil is meaningful ### Testing Conventions - Test files should be named `*_test.go` - Test functions should start with `Test` (e.g., `TestFetch`) - Benchmark functions should start with `Benchmark` (e.g., `BenchmarkFetch`) - Use the github.com/nalgeon/be for assertions: `be.Equal`, `be.Err`, and `be.True` - Use table-driven tests when testing multiple cases: ```go func TestFetch(t *testing.T) { tests := []struct { name string url string wantErr bool }{ {"valid youtube", "https://youtube.com/watch?v=xxx", false}, {"invalid url", "not-a-url", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // test logic be.Equal(t, 2*5, 10) be.True(t, !false) be.Err(t, err, nil) // No error be.Err(t, ioErr, io.EOF) // ioErr should be an io.EOF be.Err(t, otherErr, "bad stuff") // error message contains "bad stuff" }) } } ``` ### Concurrency - Use goroutines with `go` keyword for concurrent operations - Use `sync.WaitGroup` or channels for synchronization - Prefer `errgroup` for coordinating multiple goroutines with error handling - Never leak goroutines; ensure they can complete or be cancelled ### Documentation - Document exported functions, types, and constants with doc comments - Comments should start with the identifier name (no need to repeat the name) - Keep comments concise but explanatory ```go // Fetch retrieves video metadata from yt-dlp compatible sources. func Fetch(ctx context.Context, url string) (*Info, error) { // ... } ``` ### Logging - Print user-friendly messages to stderr ## Project Structure ``` my-log-wynter/ ├── cmd/my-log/ # CLI entry point │ └── main.go ├── ytdlp/ # yt-dlp integration package │ └── fetch.go ├── go.mod ├── go.sum └── AGENTS.md # This file ``` ## Common Tasks ### Adding a new command 1. Add the command to the my-log library (this project imports it) 2. Update this repository's code as needed ### Adding a new yt-dlp feature 1. Modify `ytdlp/fetch.go` 2. Add tests in `ytdlp/fetch_test.go` 3. Run tests with `go test -v ./ytdlp/` ## Pre-commit Checklist - [ ] Code is formatted: `gofmt -w .` - [ ] Imports are organized: `goimports -w .` - [ ] No lint errors: `golangci-lint run ./...` - [ ] Tests pass: `go test ./...` - [ ] Code builds: `go build ./...` ## Git Commit Guidelines - **Format**: Prepend commit messages with a gitmoji emoji (see https://gitmoji.dev) - **Style**: Write detailed commit messages that explain what changed and why - **Examples**: `✨ Add JSON export functionality for log entries`, `🐛 Fix date parsing for RFC3339 timestamps`, `📝 Update README with configuration examples` - Do not commit files not yet staged unless explicitly asked to do so.