Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
8de9a51
chore: add Lorah configuration for CLI refactor
cpplain Feb 21, 2026
5303903
chore(lorah): initialize CLI refactor task list and progress tracking
cpplain Feb 21, 2026
7abaa84
feat(config): add flag-based config and ignore file support
cpplain Feb 21, 2026
c01de9f
feat(config): implement config merging with CLI precedence
cpplain Feb 21, 2026
31533bf
feat(linker): add LinkOptions struct for package-based API
cpplain Feb 21, 2026
c43e330
feat(linker): implement CreateLinksWithOptions for package-based linking
cpplain Feb 21, 2026
53a97fb
feat(linker): implement RemoveLinksWithOptions for package-based removal
cpplain Feb 21, 2026
22d2360
feat(status): implement StatusWithOptions for package-based status
cpplain Feb 21, 2026
418d308
feat(linker): implement PruneWithOptions for package-based pruning
cpplain Feb 21, 2026
17bbfdd
refactor(linker): decouple collectPlannedLinks from Config dependency
cpplain Feb 21, 2026
4cc61d4
feat(link-utils): implement FindManagedLinksForSources for package-ba…
cpplain Feb 21, 2026
f638830
feat(cli)\!: rewrite CLI to use flag-based interface
cpplain Feb 21, 2026
0ae3372
feat(adopt): implement AdoptWithOptions for package-based adoption
cpplain Feb 21, 2026
d8c1998
feat(orphan): implement OrphanWithOptions for package-based orphaning
cpplain Feb 21, 2026
5b975fb
test(e2e): rewrite tests for new flag-based CLI interface
cpplain Feb 21, 2026
41a6e15
chore: complete verification examples and all refactoring tasks
cpplain Feb 21, 2026
181f3ea
chore(lorah): update tracking for legacy cleanup phase
cpplain Feb 22, 2026
b037d9c
chore(lorah): initialize legacy cleanup with 34-task breakdown
cpplain Feb 22, 2026
7746849
refactor: remove legacy config and adopt functions
cpplain Feb 22, 2026
bb84808
refactor: remove legacy linker functions
cpplain Feb 22, 2026
506123f
refactor: remove legacy Status function
cpplain Feb 22, 2026
41cd9e9
refactor: remove legacy Adopt function
cpplain Feb 22, 2026
f945e8d
refactor: remove legacy Orphan function
cpplain Feb 22, 2026
331e19f
refactor: remove legacy link utils functions
cpplain Feb 22, 2026
8eee89a
refactor: remove legacy config types
cpplain Feb 22, 2026
abf2017
refactor: remove legacy error constant
cpplain Feb 22, 2026
95ed4f6
refactor: remove legacy ConfigFileName constant
cpplain Feb 22, 2026
4977ca7
test: remove legacy ErrNoLinkMappings test
cpplain Feb 22, 2026
5577181
test: remove legacy config tests
cpplain Feb 22, 2026
ccf5fb5
test: remove legacy linker tests
cpplain Feb 22, 2026
f883be9
test: remove legacy status tests
cpplain Feb 22, 2026
552dec3
test: remove legacy adopt tests
cpplain Feb 22, 2026
1f07b2e
test: remove legacy orphan tests
cpplain Feb 22, 2026
61a85e9
test: remove legacy link utils tests
cpplain Feb 22, 2026
13574d9
refactor: rename CreateLinksWithOptions to CreateLinks
cpplain Feb 22, 2026
d37bd5c
refactor: rename RemoveLinksWithOptions to RemoveLinks
cpplain Feb 22, 2026
6896dd7
refactor: rename StatusWithOptions to Status
cpplain Feb 22, 2026
66e34cb
refactor: rename PruneWithOptions to Prune
cpplain Feb 22, 2026
361aa15
refactor: rename AdoptWithOptions to Adopt
cpplain Feb 22, 2026
5b09f0b
refactor: rename OrphanWithOptions to Orphan
cpplain Feb 22, 2026
fad3e4d
refactor: rename FindManagedLinksForSources to FindManagedLinks
cpplain Feb 22, 2026
7253ed9
refactor: rename MergeFlagConfig to LoadConfig
cpplain Feb 22, 2026
d161cfc
refactor: rename LoadFlagConfig to loadConfigFile
cpplain Feb 22, 2026
6d44f9f
refactor: rename parseFlagConfigFile to parseConfigFile
cpplain Feb 22, 2026
7aa8e37
chore: update progress tracking for parseFlagConfigFile rename
cpplain Feb 22, 2026
b61d10b
refactor: rename FlagConfig to FileConfig
cpplain Feb 22, 2026
397e797
refactor: rename MergedConfig to Config
cpplain Feb 22, 2026
72ba860
chore: update progress tracking for MergedConfig rename
cpplain Feb 22, 2026
b19ba76
refactor: rename FlagConfigFileName to ConfigFileName
cpplain Feb 22, 2026
32ba893
chore: update progress tracking for FlagConfigFileName rename
cpplain Feb 22, 2026
1fcf563
chore: complete verification tasks (30-32)
cpplain Feb 22, 2026
e0bdf61
docs: rewrite README for flag-based CLI
cpplain Feb 22, 2026
edd38fc
docs: update CLAUDE.md for flag-based CLI
cpplain Feb 22, 2026
c523d7d
style: remove extra blank lines and align comments
cpplain Feb 22, 2026
1b3f153
chore: remove legacy terminology from tests and docs
cpplain Feb 22, 2026
8c25eae
chore: remove .lorah project management directory
cpplain Feb 22, 2026
248613e
refactor: remove package concept and simplify CLI
cpplain Feb 22, 2026
0fd441a
refactor: remove unused code and functions
cpplain Feb 23, 2026
a3301e0
refactor: remove unused error helpers and color function
cpplain Feb 23, 2026
4659b2b
fix: improve error handling and path expansion
cpplain Feb 23, 2026
b735c58
fix(validation): handle filepath.Abs errors consistently
cpplain Feb 23, 2026
8fc3067
fix: improve error recovery and resource cleanup
cpplain Feb 23, 2026
b8a3281
fix: return errors on partial operation failures
cpplain Feb 23, 2026
c79184b
refactor: simplify code by removing unnecessary abstractions
cpplain Feb 23, 2026
be0711e
refactor: eliminate duplicate code and single-use abstractions
cpplain Feb 23, 2026
2ff8a5e
refactor(patterns): remove single-use MatchesPattern wrapper
cpplain Feb 23, 2026
2632339
refactor: remove git operations from orphan workflow
cpplain Feb 23, 2026
6189930
refactor(config): remove JSON config file remnants
cpplain Feb 23, 2026
50a4701
refactor: remove non-functional JSON output infrastructure
cpplain Feb 23, 2026
d94b24a
refactor: flatten project structure and simplify package layout
cpplain Feb 23, 2026
3e59484
chore: remove orphaned examples directory
cpplain Feb 23, 2026
cd602c5
docs: symlink CLAUDE.md to AGENTS.md for multi-agent support
cpplain Mar 6, 2026
424a6e3
refactor(cli): extract duplicate action flag validation
cpplain Mar 6, 2026
3967b64
refactor(cli): simplify help text with single multiline string
cpplain Mar 6, 2026
0d45d21
chore(scripts): update testdata paths from e2e/ to test/
cpplain Mar 7, 2026
79eefb0
docs(specs): add initial specification suite as work in progress
cpplain Mar 8, 2026
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
175 changes: 175 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# CLAUDE.md

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

## Project Overview

`lnk` is an opinionated symlink manager for dotfiles written in Go. It recursively traverses source directories and creates individual symlinks for each file (not directories), allowing mixed file sources in the same target directory.

## Development Commands

```bash
# Build
make build # Build binary to bin/lnk with version from git tags

# Testing
make test # Run all tests (unit + e2e)
make test-unit # Run unit tests only (lnk/)
make test-e2e # Run e2e tests only (test/)
make test-coverage # Generate coverage report (coverage.html)

# Code Quality
make fmt # Format code (prefers goimports, falls back to gofmt)
make lint # Run go vet
make check # Run fmt, test, and lint in sequence
```

## Architecture

### Core Components

- **main.go**: CLI entry point with POSIX-style flag parsing. Action flags (-C/--create, -R/--remove, -S/--status, -P/--prune, -A/--adopt, -O/--orphan) determine the operation. For C/R/S operations, the positional argument specifies the source directory. For A/O operations, positional arguments specify files to manage.

- **lnk/config.go**: Configuration system with `.lnkconfig` file support. Config files can specify target directory and ignore patterns using stow-style format (one flag per line). CLI flags override config file values. Config file search locations:
1. `.lnkconfig` in source directory
2. `.lnkconfig` in home directory (~/.lnkconfig)
3. Built-in defaults if no config found

- **lnk/create.go, lnk/remove.go**: Symlink operations with 3-phase execution:
1. Collect planned links (recursive file traversal)
2. Validate all targets
3. Execute or show dry-run

- **lnk/adopt.go**: Moves files from target to source directory and creates symlinks

- **lnk/orphan.go**: Removes symlinks and restores actual files to target locations

### Key Design Patterns

**Recursive File Linking**: lnk creates symlinks for individual files, NOT directories. This allows:

- Multiple source directories can map to the same target
- Local-only files can coexist with managed configs
- Parent directories are created as regular directories, never symlinks

**Error Handling**: Uses custom error types in `errors.go`:

- `PathError`: for file operation errors
- `ValidationError`: for validation failures
- `WithHint()`: adds actionable hints to errors

**Output System**: Centralized in `output.go` with support for:

- Text format (default, colorized)
- Verbosity levels: quiet, normal, verbose

**Terminal Detection**: `terminal.go` detects TTY for conditional formatting (colors, progress bars)

### Configuration Structure

```go
// Config loaded from .lnkconfig file
type FileConfig struct {
Target string // Target directory (default: ~)
IgnorePatterns []string // Ignore patterns from config file
}

// Final resolved configuration
type Config struct {
SourceDir string // Source directory (from CLI)
TargetDir string // Target directory (CLI > config > default)
IgnorePatterns []string // Combined ignore patterns from all sources
}

// Options for linking operations
type LinkOptions struct {
SourceDir string // source directory - what to link from (e.g., ~/git/dotfiles)
TargetDir string // where to create links (default: ~)
IgnorePatterns []string // combined ignore patterns from all sources
DryRun bool // preview mode without making changes
}

// Options for adopt operations
type AdoptOptions struct {
SourceDir string // base directory for dotfiles (e.g., ~/git/dotfiles)
TargetDir string // where files currently are (default: ~)
Paths []string // files to adopt (e.g., ["~/.bashrc", "~/.vimrc"])
DryRun bool // preview mode
}

// Options for orphan operations
type OrphanOptions struct {
SourceDir string // base directory for dotfiles (e.g., ~/git/dotfiles)
TargetDir string // where symlinks are (default: ~)
Paths []string // symlink paths to orphan (e.g., ["~/.bashrc", "~/.vimrc"])
DryRun bool // preview mode
}
```

### Testing Structure

- **Unit tests**: `lnk/*_test.go` - use `testutil_test.go` helpers for temp dirs
- **E2E tests**: `test/e2e_test.go` - full workflow testing
- Test data: Use `test/helpers_test.go` for creating test repositories

## Development Guidelines

### Commit Messages

Follow [Conventional Commits](https://www.conventionalcommits.org/):

- `feat:` - new feature
- `fix:` - bug fix
- `docs:` - documentation only
- `refactor:` - code restructuring
- `test:` - adding/updating tests
- `chore:` - build/tooling changes

Breaking changes use `!` suffix: `feat!:` or `BREAKING CHANGE:` in footer.

### CLI Design Principles

From [cpplain/cli-design](https://github.com/cpplain/cli-design):

- **Obvious Over Clever**: Make intuitive paths easiest
- **Helpful Over Minimal**: Provide clear guidance and error messages
- **Consistent Over Special**: Follow CLI conventions
- All destructive operations support `--dry-run`

### Code Standards

- Use `PrintVerbose()` for debug output (hidden unless --verbose)
- Use `PrintErrorWithHint()` for user-facing errors with actionable hints
- Expand paths with `ExpandPath()` to handle `~/` notation
- Validate paths early using `validation.go` functions

## Common Tasks

### Adding a New Operation

1. Add new action flag to `main.go` (e.g., `-X/--new-operation`)
2. Create options struct in `lnk/` following the pattern (e.g., `NewOperationOptions`)
3. Implement operation function in `lnk/` (e.g., `func NewOperation(opts NewOperationOptions) error`)
4. Add case in `main.go` to handle the new flag and construct options
5. Add tests in `lnk/xxx_test.go`
6. Add e2e test if appropriate

### Modifying Configuration

- Config types in `config.go` are simple structs for holding configuration
- Add validation with helpful hints using `NewValidationErrorWithHint()`
- Config files use stow-style format: one flag per line (e.g., `--target=~`)

### Running Single Test

```bash
go test -v ./lnk -run TestFunctionName
go test -v ./test -run TestE2EName
```

## Technical Notes

- Version info injected via ldflags during build (version, commit, date)
- No external dependencies - stdlib only
- Git operations are optional (detected at runtime)
- Uses stdlib `flag` package for command-line parsing
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Comprehensive end-to-end testing suite
- Zero-configuration defaults with flexible config discovery (`~/.config/lnk/config.json`, `~/.lnk.json`)
- Zero-configuration defaults with flexible config discovery (`.lnkconfig` in source dir, `~/.config/lnk/config`, `~/.lnkconfig`)
- Global flags: `--verbose`, `--quiet`, `--yes`, `--no-color`, `--output`
- Command suggestions for typos, progress indicators, confirmation prompts
- JSON output and specific exit codes for scripting
Expand Down
154 changes: 0 additions & 154 deletions CLAUDE.md

This file was deleted.

1 change: 1 addition & 0 deletions CLAUDE.md
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,17 @@ type[(optional scope)]: description
#### Examples

```
feat: add support for multiple link mappings
feat: add support for multiple packages

fix: prevent race condition during link creation

docs: add examples to README

feat(adopt): allow adopting entire directories

fix!: change config file format to JSON
fix!: change default target directory

BREAKING CHANGE: config files must now use .lnk.json extension
BREAKING CHANGE: target directory now defaults to home instead of current directory
```

### CLI Design Guidelines
Expand Down
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ build:
@# Generate dev+timestamp for local builds (releases override via ldflags)
@VERSION=$$(date -u '+dev+%Y%m%d%H%M%S'); \
echo "Building lnk $$VERSION..."; \
go build -ldflags "-X 'main.version=$$VERSION'" -o bin/lnk cmd/lnk/main.go
go build -ldflags "-X 'main.version=$$VERSION'" -o bin/lnk .

# Clean build artifacts
clean:
Expand All @@ -34,7 +34,7 @@ clean:

# Clean test artifacts
clean-test:
rm -rf e2e/testdata/
rm -rf test/testdata/
@echo "Test data cleaned. Run 'scripts/setup-testdata.sh' to recreate."

# Run tests
Expand All @@ -43,11 +43,11 @@ test:

# Run unit tests only
test-unit:
go test -v ./internal/...
go test -v ./lnk/...

# Run E2E tests only
test-e2e:
go test -v ./e2e/...
go test -v ./test/...

# Run tests with coverage
test-coverage:
Expand Down
Loading