Skip to content

Commit 667c9b4

Browse files
FelixIsaacclaude
andcommitted
docs: add CLAUDE.md for future Claude Code instances
- Project overview and architecture - Build, test, and development commands - Core architectural decisions (git wrapper, native age, pattern matching) - Push/pull data flow diagrams - Critical implementation details (error handling, cross-platform paths) - How to add new commands and release process 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 3b0c710 commit 667c9b4

1 file changed

Lines changed: 163 additions & 0 deletions

File tree

CLAUDE.md

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
`claude-code-sync` is a CLI tool for syncing Claude Code configs (`~/.claude/`) across machines via GitHub with age encryption. Core functionality: selective encryption (sensitive files like `settings.json` encrypted, non-sensitive like `CLAUDE.md` plain text), cross-platform single binary (Go), native age encryption (no external CLI).
8+
9+
## Build Commands
10+
11+
```bash
12+
# Build for current platform
13+
go build -o claude-code-sync.exe ./cmd/claude-code-sync/ # Windows
14+
go build -o claude-code-sync ./cmd/claude-code-sync/ # Unix
15+
16+
# Build with version (for development)
17+
go build -ldflags="-X main.version=0.2.1-dev" -o claude-code-sync.exe ./cmd/claude-code-sync/
18+
19+
# Install to system PATH
20+
cp claude-code-sync.exe C:\Users\<user>\.local\bin\ # Windows
21+
sudo mv claude-code-sync /usr/local/bin/ # Unix
22+
23+
# Run tests
24+
go test ./...
25+
go test -v ./internal/config/ # Specific package
26+
27+
# Format and lint
28+
go fmt ./...
29+
go vet ./...
30+
```
31+
32+
## Architecture
33+
34+
### Entry Point & Version Injection
35+
36+
- `cmd/claude-code-sync/main.go`: Entry point with `var version = "dev"` (overridden by goreleaser's `-ldflags "-X main.version={{.Version}}"`)
37+
- Calls `cmd.SetVersion(version)` then `cmd.Execute()`
38+
39+
### Core Layers
40+
41+
1. **CLI Layer** (`internal/cmd/`): Cobra commands (root.go registers all subcommands)
42+
2. **Business Logic**: Config patterns (`internal/config/`), crypto (`internal/crypto/`), git wrapper (`internal/git/`), sync engine (`internal/sync/`)
43+
3. **External**: Shells out to `git` CLI, native age lib for encryption
44+
45+
### Key Architectural Decisions
46+
47+
**Git wrapper vs go-git**: Shells out to `git` CLI for simplicity (users already have it, easier debugging). All git ops in `internal/git/git.go` with `exec.Command("git", "-C", repoDir, args...)`.
48+
49+
**Native age encryption**: Uses `filippo.io/age` Go library directly (no external `age` CLI). Streaming I/O via `age.Encrypt()`/`age.Decrypt()` - no full files in memory.
50+
51+
**Pattern matching**: Two systems:
52+
- **Encrypt patterns** (`internal/config/config.go:ShouldEncrypt`): Filename match (e.g., `settings.json`) or path wildcard (e.g., `skills/*/resources/*`)
53+
- **Exclude patterns** (`internal/config/config.go:ShouldExclude`): Directory prefix (e.g., `plans/` matches `plans/foo/bar.md`) or filename wildcard (e.g., `*.log`)
54+
55+
**Data flow (Push)**:
56+
1. Walk `~/.claude/` (`internal/sync/sync.go`)
57+
2. For each file: check exclude → check encrypt → copy plain or encrypt to `~/.claude-sync/repo/`
58+
3. Generate `.sync-manifest` (SHA256 checksums)
59+
4. `git add -A && git commit && git push` (`internal/git/git.go`)
60+
61+
**Data flow (Pull)**:
62+
1. Backup current `~/.claude/` to `~/.claude-sync/backups/TIMESTAMP/`
63+
2. `git pull` (retries with `--allow-unrelated-histories` if needed)
64+
3. Walk repo, decrypt `.age` files or copy plain to `~/.claude/`
65+
4. Verify SHA256 checksums
66+
67+
### File Locations
68+
69+
```
70+
~/.claude-sync/
71+
├── config # Repo URL (plain text)
72+
├── identity.key # age private key (chmod 600)
73+
├── backups/ # Auto backups before pull
74+
└── repo/ # Git clone
75+
├── CLAUDE.md # Plain
76+
├── commands/, agents/, skills/ # Plain (except skills/*/resources/* encrypted)
77+
├── settings.json.age # Encrypted
78+
└── .sync-manifest # SHA256 checksums
79+
```
80+
81+
### Patterns (internal/config/config.go)
82+
83+
**DefaultEncryptPatterns**:
84+
```go
85+
"settings.json", "settings.local.json", "claude.json",
86+
".credentials.json", "client_secret_*.json",
87+
"skills/*/resources/*"
88+
```
89+
90+
**DefaultExcludePatterns** (note specific exclusions):
91+
```go
92+
"plans", "projects", "local", "statsig", "todos", "debug",
93+
"file-history", "ide", "shell-snapshots", "telemetry", "sessionStorage",
94+
"plugins/cache", "plugins/marketplaces", // But NOT plugins/ itself
95+
"history.jsonl", "stats-cache.json",
96+
"*.log", "*.tmp", "*.cache", "*.local-backup-*", ".git"
97+
```
98+
99+
## Critical Implementation Details
100+
101+
### Error Handling
102+
103+
Return errors up the stack (don't panic), wrap with context: `fmt.Errorf("context: %w", err)`. User-facing errors via `logError()` in `internal/cmd/root.go`.
104+
105+
### Cross-Platform Paths
106+
107+
`toUnixPath()` in `internal/cmd/init.go` converts backslashes to forward slashes for Git Bash compatibility. Always use `filepath.ToSlash()` when displaying paths.
108+
109+
### Git Operations
110+
111+
All git commands in `internal/git/git.go`:
112+
- `run()`: Executes git, returns stderr on error
113+
- `Pull()`: Auto-retries with `--allow-unrelated-histories` if unrelated histories error
114+
- `IsValidRepoURL()`, `CheckRemote()`: Validate before cloning
115+
116+
### Encryption
117+
118+
- Key generation: `age.GenerateX25519Identity()` returns identity with both private key and public recipient
119+
- Public key derivation: `identity.Recipient().String()`
120+
- Key file format: Comment lines + `AGE-SECRET-KEY-...`
121+
- Encrypt: Stream `io.Copy(age.Encrypt(out, recipient), in)`
122+
- Decrypt: Stream `io.Copy(out, age.Decrypt(in, identity))`
123+
124+
## Adding New Commands
125+
126+
1. Create `internal/cmd/newcommand.go`:
127+
```go
128+
var newCmd = &cobra.Command{
129+
Use: "new",
130+
Short: "Description",
131+
RunE: runNew,
132+
}
133+
134+
func runNew(cmd *cobra.Command, args []string) error {
135+
// Implementation
136+
}
137+
```
138+
139+
2. Register in `internal/cmd/root.go` init():
140+
```go
141+
rootCmd.AddCommand(newCmd)
142+
```
143+
144+
## Release Process
145+
146+
Triggered by git tag push:
147+
```bash
148+
git tag -a v0.3.0 -m "Release v0.3.0"
149+
git push origin v0.3.0
150+
```
151+
152+
GitHub Actions (`.github/workflows/release.yaml`) runs goreleaser:
153+
- Builds for Windows/macOS/Linux (amd64/arm64)
154+
- Injects version via `-ldflags "-X main.version={{.Version}}"`
155+
- Creates GitHub release
156+
- Updates Homebrew tap and Scoop bucket
157+
158+
## Known Limitations
159+
160+
- No tests yet (see ARCHITECTURE.md technical debt)
161+
- No custom encrypt/exclude patterns (hardcoded in `config.go`)
162+
- No lock file (concurrent operations possible but risky)
163+
- Shells out to git (requires git in PATH)

0 commit comments

Comments
 (0)