Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
141 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
9953ccd
docs(specs): move specs/ to docs/design/
cpplain Mar 14, 2026
0878407
docs(design): make source-dir a required positional arg for all commands
cpplain Mar 14, 2026
143c610
docs(design): remove .lnkconfig, simplify config to .lnkignore only
cpplain Mar 14, 2026
03c0084
docs(design): remove TargetDir from adopt/orphan, restrict paths to h…
cpplain Mar 14, 2026
1c0fffb
docs: update docs for subcommand-based CLI refactor
cpplain Mar 14, 2026
7a75f28
docs(design): fix ln command syntax in glossary
cpplain Mar 14, 2026
c2920d0
docs(design): add CleanEmptyDirs spec to internals, remove, prune, an…
cpplain Mar 14, 2026
764e654
docs(design): refine adopt, orphan, remove, prune, and status specs
cpplain Mar 14, 2026
d5b68d5
docs(design): refine internals, orphan, output, status, config, and e…
cpplain Mar 14, 2026
619b3b0
docs(design): remove target-dir arg and quiet flag from specs
cpplain Mar 14, 2026
7b8c730
docs(design): add stdlib spec and refine remove/internals traversal s…
cpplain Mar 15, 2026
5afc08a
docs(design): add autonomous agent loop for iterative doc improvement
cpplain Mar 15, 2026
faaf2e9
docs(design): specify file mode capture before move in orphan execute…
cpplain Mar 15, 2026
6d882f6
docs(design): add ✓ icon to status Total line examples to match Print…
cpplain Mar 15, 2026
647ebd8
docs(design): remove extra blank line step from dry-run output flow i…
cpplain Mar 15, 2026
f404968
docs(design): clarify partial-success output and suppress next-step h…
cpplain Mar 15, 2026
f640e2e
docs(design): include ValidateSymlinkCreation (step 8) for directory-…
cpplain Mar 15, 2026
650a499
docs(design): add file mode preservation steps to MoveFile in adopt.md
cpplain Mar 15, 2026
b68764d
docs(design): specify I/O error propagation for LoadIgnoreFile read f…
cpplain Mar 15, 2026
9003ffd
docs(design): specify combined error for rollback failure in adopt Ph…
cpplain Mar 15, 2026
168b460
docs(design): specify multi-source iteration in FindManagedLinks brok…
cpplain Mar 15, 2026
7f58c64
docs(design): fix ValidateSymlinkCreation argument order in adopt Pha…
cpplain Mar 15, 2026
ee78275
docs(design): show complete dry-run output in adopt.md example
cpplain Mar 15, 2026
8aa2c1d
docs(design): fix EvalSymlinks error handling in remove.md managed li…
cpplain Mar 15, 2026
1f97d48
docs(design): fix startup sequence step 4 ambiguity for no-command in…
cpplain Mar 15, 2026
702ee5b
docs(design): specify symlink-relative resolution for relative target…
cpplain Mar 16, 2026
108d97c
docs(design): specify regular-files-only collection in adopt.md Phase…
cpplain Mar 16, 2026
fbad716
docs(design): specify regular-files-only collection in create.md Phase 1
cpplain Mar 16, 2026
f8a1570
docs(design): specify symlink-relative resolution in validateAdoptSou…
cpplain Mar 16, 2026
4e0b845
docs(design): specify dst cleanup on copy or verification failure in …
cpplain Mar 16, 2026
78ec92f
docs(design): filter broken links in orphan.md Phase 1 directory step
cpplain Mar 16, 2026
b1b566e
docs(design): fix directory collision case in create.md Phase 3 Execu…
cpplain Mar 16, 2026
ba38d50
docs(design): add rel \!= "." guard to managed-link check in remove.m…
cpplain Mar 16, 2026
5766c7c
docs(design): clarify ManagedLink.Target holds absolute resolved path…
cpplain Mar 16, 2026
782bcfc
docs(design): fix ManagedLink.Target comment in status.md to match in…
cpplain Mar 16, 2026
5fb4ed0
docs(design): add rel \!= "." guard to orphan.md Phase 1 containment …
cpplain Mar 16, 2026
ee27c80
docs(design): add step 1 to orphan.md Execute Mode rollback trigger
cpplain Mar 16, 2026
eadf3b3
docs(design): clarify orphan.md rollback includes the failing orphan …
cpplain Mar 16, 2026
9ce8298
docs(design): add rel \!= "." guard to stdlib.md containment check pa…
cpplain Mar 16, 2026
f1c0574
docs(design): add failing adoption and (if moved) guard to adopt.md r…
cpplain Mar 16, 2026
081e182
docs(design): specify os.Chmod failure is non-fatal in MoveFile cross…
cpplain Mar 16, 2026
5486759
docs(design): specify active-before-broken ordering for piped output …
cpplain Mar 16, 2026
7486b6f
docs(design): add Source field assignment to FindManagedLinks Behavio…
cpplain Mar 16, 2026
095d483
docs(design): fix FindManagedLinks step ordering so macOS Library/.Tr…
cpplain Mar 16, 2026
9e4887d
docs(design): add missing command header to create.md and adopt.md dr…
cpplain Mar 16, 2026
db27dca
docs(design): clarify next-step hint condition requires created > 0 i…
cpplain Mar 16, 2026
f409886
docs(design): fix PrintCommandHeader to emit no output in piped mode
cpplain Mar 16, 2026
689f670
docs(design): specify that PrintNextStep contracts sourceDir via Cont…
cpplain Mar 16, 2026
c128815
docs(design): add step 2 to orphan rollback trigger to prevent strand…
cpplain Mar 16, 2026
8e6329e
docs(design): fix Section 3 output examples to show active links in l…
cpplain Mar 16, 2026
5161e72
docs(design): add validation to reject non-adopted symlinks in adopt …
cpplain Mar 16, 2026
0e9d15b
docs(design): add PrintActiveLink and PrintBrokenLink to output.md
cpplain Mar 16, 2026
68f4de0
docs(design): add os.Stat check in Broken Link Handling to prevent mi…
cpplain Mar 16, 2026
183a4e3
docs(design): add PrintWarningWithHint to output.md Specialized Funct…
cpplain Mar 16, 2026
124631b
docs(design): add PrintVerbose to orphan chmod failure handling
cpplain Mar 16, 2026
862f204
docs(design): fix partial success example to show regular file collis…
cpplain Mar 16, 2026
a07b869
docs(design): use PrintWarningWithHint in create Phase 3 Execute step 5
cpplain Mar 16, 2026
304d683
docs(design): use PrintWarningWithHint in remove and prune execute fa…
cpplain Mar 16, 2026
2efb0ee
docs(design): gate status piped mode Total line with ShouldSimplifyOu…
cpplain Mar 16, 2026
0e0778f
docs(design): use ExpandPath in LoadIgnoreFile step 1 to handle ~/...…
cpplain Mar 16, 2026
c2e5d2c
Revert "docs(design): add PrintActiveLink and PrintBrokenLink to outp…
cpplain Mar 16, 2026
2d710c4
Revert "docs(design): gate status piped mode Total line with ShouldSi…
cpplain Mar 16, 2026
89d6bc0
chore: remove lorah settings and design improvement workflow files
cpplain Mar 17, 2026
be75b28
docs: update documentation to reflect subcommand interface
cpplain Mar 17, 2026
b013e1c
docs(design): clarify implementation details across design docs
cpplain Mar 22, 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
214 changes: 214 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
# 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

**Commands (`main.go` and `lnk/`):**

- **main.go**: CLI entry point with subcommand-based interface (`lnk <command> [flags] <source-dir>`). Commands: `create`, `remove`, `status`, `prune`, `adopt`, `orphan`. For all commands, `source-dir` is the first required positional argument. For `adopt`/`orphan`: one or more file paths are required as additional positional arguments (`<source-dir> <path...>`). Uses stdlib `flag` package with `extractCommand()` to support flags before or after the command name.
- **lnk/create.go**: 3-phase execution: collect (walk source dir, apply `PatternMatcher`), validate all targets via `ValidateSymlinkCreation` (all-or-nothing), execute via `CreateSymlink`. Continue-on-failure during execution.
- **lnk/remove.go**: Walks source dir to compute expected symlink paths, verifies each is a managed symlink, removes matches. Continue-on-failure. Calls `CleanEmptyDirs` on parent dirs afterward.
- **lnk/status.go**: Calls `FindManagedLinks`, categorizes links as active/broken, reports results. Read-only.
- **lnk/prune.go**: Calls `FindManagedLinks`, filters to broken-only, removes them. Continue-on-failure. Calls `CleanEmptyDirs`.
- **lnk/adopt.go**: 2-phase transactional: validate all paths first, then execute with full rollback on any failure. Uses `MoveFile`, `CreateSymlink`, `validateAdoptSource`.
- **lnk/orphan.go**: 2-phase transactional (inverse of adopt): validate, then execute with rollback. Uses `FindManagedLinks`, `RemoveSymlink`, `MoveFile`, `CleanEmptyDirs`. Permission restoration is best-effort.

**Configuration (`lnk/config.go`):**

Loads and merges configuration from all sources. `LoadIgnoreFile(sourceDir)` parses `<sourceDir>/.lnkignore`. `LoadConfig(sourceDir, cliIgnorePatterns)` merges: built-in defaults + `.lnkignore` patterns + CLI `--ignore` patterns, in that order (later patterns can negate earlier ones with `!pattern`).

**Shared internals:**

- **lnk/symlink.go**: `ManagedLink` struct (`Path`, `Target`, `IsBroken`, `Source`), `FindManagedLinks(startPath, sources)`, `CreateSymlink`, `RemoveSymlink`
- **lnk/file_ops.go**: `MoveFile` (`os.Rename` fast path, cross-device copy+verify+delete fallback), `CleanEmptyDirs(dirs, boundaryDir)`
- **lnk/patterns.go**: `PatternMatcher` with gitignore-style matching (`**`, `!` negation, trailing `/` for directories)
- **lnk/validation.go**: `ValidateSymlinkCreation(source, target)` (checks same-path, circular reference, overlapping paths)

**Infrastructure:**

- **lnk/errors.go**: Custom error types (see Error Handling)
- **lnk/exit_codes.go**: `ExitError` (1), `ExitUsage` (2)
- **lnk/output.go**: All print functions (see Output System)
- **lnk/terminal.go**: `isTerminal()`, `ShouldSimplifyOutput()`
- **lnk/color.go**: ANSI color functions (`Red`, `Green`, `Yellow`, `Cyan`, `Bold`), lazy init via `sync.Once`
- **lnk/verbosity.go**: `VerbosityNormal`, `VerbosityVerbose`
- **lnk/constants.go**: Shared constants (skip dirs, icons, formatting)

### 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

**Traversal Strategies**: Two approaches depending on the command:

- **Source walk** (`create`, `remove`): Walk `SourceDir` with `filepath.WalkDir`, compute expected target paths. Efficient because source directories are small.
- **Target walk** via `FindManagedLinks` (`status`, `prune`, `orphan`): Walk `TargetDir` to discover all symlinks pointing into `SourceDir`. Necessary when the source files may no longer exist. Skips `Library` and `.Trash` on macOS.

**Error Handling**: Uses custom error types in `lnk/errors.go`, all implementing the `HintableError` interface (`GetHint() string`):

- `PathError`: filesystem path failures (file not found, permission denied)
- `LinkError`: symlink operation failures with source and target
- `ValidationError`: invalid configuration or arguments
- `HintedError`: wraps any arbitrary error with a hint via `WithHint(err, hint)`
- `LinkExistsError`: non-fatal signal that symlink already points to correct target; caller skips silently
- Sentinel errors: `ErrNotSymlink`, `ErrAlreadyAdopted` (used with `errors.Is`)
- **Error propagation models:**
- Continue-on-failure (`create`, `remove`, `prune`): validation is all-or-nothing; execution continues on per-item failure, counts failures, returns aggregate error
- Transactional rollback (`adopt`, `orphan`): all validations pass before any changes; any execution failure triggers reverse-order rollback
- **Exit codes**: 0 (success), 1 (`ExitError` — runtime error), 2 (`ExitUsage` — bad flags, missing args, unknown command)

**Output System**: Centralized in `lnk/output.go`:

- **Print functions**: `PrintSuccess`, `PrintError`, `PrintWarning`, `PrintSkip`, `PrintDryRun`, `PrintInfo`, `PrintDetail`, `PrintVerbose`, `PrintCommandHeader`, `PrintErrorWithHint(err)`
- **Specialized**: `PrintSummary(format, args...)`, `PrintNextStep(command, sourceDir, description)`, `PrintDryRunSummary()`, `PrintEmptyResult(itemType)`
- **Terminal vs piped**: `ShouldSimplifyOutput()` gates icons and colors. Piped output uses plain prefixes (`success`, `error:`, `warning:`, `dry-run:`). `PrintCommandHeader` outputs nothing when piped.
- **Streams**: stdout for normal output; stderr for errors and warnings
- **Color**: enabled when no `--no-color`, no `NO_COLOR` env var, and stdout is a TTY. Colors computed lazily via `sync.Once` in `color.go`.
- **Verbosity**: two levels — `VerbosityNormal` (default) and `VerbosityVerbose` (`-v`). Only `PrintVerbose` is suppressed at normal level.

**Terminal Detection**: `terminal.go` detects TTY for conditional formatting (colors, piped output simplification)

### Configuration Structure

**`LoadConfig(sourceDir, cliIgnorePatterns)` algorithm** (from `docs/design/config.md`):
1. Call `LoadIgnoreFile(sourceDir)` to parse `<sourceDir>/.lnkignore` (if present)
2. Combine: `getBuiltInIgnorePatterns()` + `.lnkignore` patterns + CLI `--ignore` patterns
3. Expand `~` to home directory via `ExpandPath("~")`
4. Return `Config{SourceDir, TargetDir, IgnorePatterns}`

**Path helpers** (in `lnk/config.go`):
- `ExpandPath(path)`: expands `~` to home directory; other paths returned unchanged
- `ContractPath(path)`: contracts home directory to `~` for display output

```go
// Final resolved configuration used by all operations
type Config struct {
SourceDir string // Source directory (from CLI positional arg)
TargetDir string // Target directory (always ~; configurable in tests)
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 (always ~; configurable in tests)
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 (always ~; configurable in tests)
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 (always ~; configurable in tests)
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
- Use `PrintWarningWithHint()` for non-fatal per-item errors in continue-on-failure commands
- Expand paths with `ExpandPath()` to handle `~/` notation
- Use `ContractPath()` for all user-facing display paths
- Validate paths early using functions in `validation.go`
- Return typed errors with hints from library functions; let `main` handle display

## Common Tasks

### Adding a New Operation

1. Add new subcommand case to the `switch` in `main.go` and write a `handleX()` function
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 the command name to `suggestCommand()` valid commands list in `main.go`
5. Add `printCommandHelp()` case for the new command in `main.go`
6. Add tests in `lnk/xxx_test.go`
7. 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()`
- Ignore files use gitignore-style format: one pattern per line (e.g., `local/`, `*.secret`)

### 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
19 changes: 12 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

### Changed

### Deprecated
- **BREAKING:** Replaced action flags (`-C`, `-R`, `-S`, `-P`, `-A`, `-O`) with subcommands (`create`, `remove`, `status`, `prune`, `adopt`, `orphan`)
- **BREAKING:** Replaced hand-rolled flag parser with stdlib `flag` package
- **BREAKING:** `adopt`/`orphan` now use positional arguments (`<source-dir> <path...>`) instead of explicit flags
- **BREAKING:** Target directory is always `~`; removed `--target`/`-t` flag
- **BREAKING:** Simplified to two verbosity levels: normal and verbose (removed quiet level)

### Removed

### Fixed

### Security
- **BREAKING:** `--quiet` / `-q` flag
- **BREAKING:** `--yes` flag and confirmation prompts
- **BREAKING:** `--output` flag and JSON output format
- **BREAKING:** `--source` / `-s` flag (replaced by required positional argument)
- **BREAKING:** `.lnkconfig` config file support (use `.lnkignore` for ignore patterns)
- Progress indicators

## [0.5.0] - 2026-02-16

Expand All @@ -38,7 +43,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
Loading