Skip to content

Commit 725d4cb

Browse files
authored
chore(docs): add CLAUDE.md with standardized rules (#3)
1 parent 8954e9b commit 725d4cb

2 files changed

Lines changed: 380 additions & 1 deletion

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Claude
2-
.claude/settings.local.json
2+
CLAUDE.local.md
3+
.claude/
34

45
# Dependencies
56
node_modules/

CLAUDE.md

Lines changed: 378 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,378 @@
1+
# CLAUDE.md
2+
3+
Guidance for Claude Code when working with the dtvem codebase.
4+
5+
## Related Documentation
6+
7+
| Document | Purpose |
8+
|----------|---------|
9+
| [`README.md`](../README.md) | User-facing documentation, installation, usage examples |
10+
| [`CONTRIBUTING.md`](../CONTRIBUTING.md) | Contribution guidelines, PR process, development setup |
11+
| [`CODE_OF_CONDUCT.md`](../CODE_OF_CONDUCT.md) | Community standards and expected behavior |
12+
| [`GO_STYLEGUIDE.md`](GO_STYLEGUIDE.md) | Complete Go coding standards (Google style) |
13+
14+
---
15+
16+
## Critical Rules
17+
18+
**These rules override all other instructions:**
19+
20+
1. **NEVER commit directly to main** - Always create a feature branch and submit a pull request. No exceptions.
21+
2. **Follow the styleguide** - All code must comply with `GO_STYLEGUIDE.md`
22+
3. **Write tests** - All new/refactored code requires comprehensive unit tests
23+
4. **Cross-platform** - All features must work on Windows, macOS, and Linux
24+
5. **Conventional commits** - Format: `type(scope): description`
25+
6. **GitHub Issues for TODOs** - Use `gh` CLI to manage issues, no local TODO files. Use conventional commit format for issue titles
26+
7. **Pull Requests** - Use the conventional commit format for PR titles as you do for commits
27+
8. **Run validation before commits** - Run `npm run check` (format, lint, test) before committing and pushing
28+
9. **Working an issue** - When working an issue, always create a new branch from an updated main branch
29+
10. **Branch Names** - Always use the conventional commit `type` from the issue title as the first prefix, and the `scope` as the second, then a very short description, example `feat/ci/integration-tests`
30+
11. **Check branch status before pushing** - ALWAYS verify the remote tracking branch still exists before pushing. If a PR was merged/deleted, create a new branch from main instead of trying to push to the old one.
31+
12. **No co-authors** - Do not add any co-author information on commits or pull requests
32+
13. **No "generated by" statements** - Do not add generated-by statements on pull requests
33+
34+
---
35+
36+
## Quick Reference
37+
38+
### Build Commands
39+
40+
```bash
41+
# Build executables
42+
go build -o dist/dtvem.exe ./src
43+
go build -o dist/dtvem-shim.exe ./src/cmd/shim
44+
45+
# Run directly
46+
go run ./src/main.go <command>
47+
48+
# Run tests
49+
cd src && go test ./...
50+
```
51+
52+
### Deploy After Building
53+
54+
```bash
55+
cp dist/dtvem.exe ~/.dtvem/bin/dtvem.exe
56+
cp dist/dtvem-shim.exe ~/.dtvem/bin/dtvem-shim.exe
57+
~/.dtvem/bin/dtvem.exe reshim
58+
```
59+
60+
### GitHub Issues
61+
62+
```bash
63+
gh issue list # List open issues
64+
gh issue view <number> # View details
65+
gh issue create --title "..." --label enhancement --body "..."
66+
gh issue close <number>
67+
```
68+
69+
### GitHub Issue Dependencies (Blocked By / Blocking)
70+
71+
```bash
72+
# List what blocks an issue
73+
gh api repos/dtvem/dtvem/issues/<number>/dependencies/blocked_by --jq '.[] | "#\(.number) \(.title)"'
74+
75+
# List what an issue blocks
76+
gh api repos/dtvem/dtvem/issues/<number>/dependencies/blocking --jq '.[] | "#\(.number) \(.title)"'
77+
78+
# Add a blocking relationship (issue <number> is blocked by <blocker_id>)
79+
# First get the blocker's numeric ID (not issue number):
80+
gh api repos/dtvem/dtvem/issues/<blocker_number> --jq '.id'
81+
# Then add the dependency:
82+
gh api repos/dtvem/dtvem/issues/<number>/dependencies/blocked_by -X POST -F issue_id=<blocker_id>
83+
84+
# Remove a blocking relationship
85+
gh api repos/dtvem/dtvem/issues/<number>/dependencies/blocked_by/<blocker_id> -X DELETE
86+
```
87+
88+
**Note:** The API uses numeric issue IDs (not issue numbers) for POST/DELETE operations. Get the ID with `gh api repos/dtvem/dtvem/issues/<number> --jq '.id'`
89+
90+
---
91+
92+
## Project Overview
93+
94+
**dtvem** (Development Tool Virtual Environment Manager) is a cross-platform runtime version manager written in Go. Similar to `asdf` but with first-class Windows support.
95+
96+
| Attribute | Value |
97+
|-----------|-------|
98+
| Version | dev (pre-1.0) |
99+
| Runtimes | Python, Node.js, Ruby |
100+
| Tests | 160+ passing |
101+
| Style | Google Go Style Guide |
102+
103+
**Key Concept**: Shims are Go executables that intercept runtime commands (like `python`, `node`), resolve versions, and execute the appropriate binary.
104+
105+
### Available Commands
106+
107+
`init`, `install`, `uninstall`, `list`, `list-all`, `global`, `local`, `current`, `freeze`, `migrate`, `reshim`, `which`, `where`, `update`, `request`, `version`, `help`
108+
109+
---
110+
111+
## Architecture
112+
113+
### Two-Binary System
114+
115+
1. **Main CLI** (`src/main.go`) - The `dtvem` command
116+
2. **Shim Executable** (`src/cmd/shim/main.go`) - Copied/renamed for each runtime command
117+
118+
### Shim Flow
119+
120+
```
121+
User runs: python --version
122+
123+
~/.dtvem/shims/python.exe (shim)
124+
125+
Maps to runtime provider → Resolves version
126+
127+
├─ Version configured & installed? → Execute ~/.dtvem/versions/python/3.11.0/bin/python
128+
├─ Version configured but not installed? → Show error with install command
129+
└─ No version configured? → Fall back to system PATH or show install instructions
130+
```
131+
132+
### Directory Structure
133+
134+
```
135+
~/.dtvem/
136+
├── bin/ # dtvem binaries (added to PATH)
137+
│ ├── dtvem
138+
│ └── dtvem-shim
139+
├── shims/ # Runtime shims (python.exe, node.exe, etc.)
140+
├── versions/ # Installed runtimes by name/version
141+
│ ├── python/3.11.0/
142+
│ └── node/18.16.0/
143+
└── config/
144+
└── runtimes.json # Global version config
145+
```
146+
147+
### Key Packages
148+
149+
| Package | Purpose |
150+
|---------|---------|
151+
| `internal/config/` | Paths, version resolution, config file handling |
152+
| `internal/runtime/` | Provider interface, registry, version types |
153+
| `internal/shim/` | Shim lifecycle management |
154+
| `internal/path/` | PATH configuration (platform-specific) |
155+
| `internal/ui/` | Colored output, prompts, verbose/debug logging |
156+
| `internal/tui/` | Table formatting and styles |
157+
| `internal/download/` | File downloads with progress |
158+
| `internal/manifest/` | Version manifest fetching and caching |
159+
| `internal/migration/` | Migration detection and helpers |
160+
| `internal/testutil/` | Shared test utility functions |
161+
| `internal/constants/` | Platform constants |
162+
| `src/cmd/` | CLI commands (one file per command) |
163+
| `src/runtimes/` | Runtime providers (node/, python/, ruby/) |
164+
165+
---
166+
167+
## Provider System
168+
169+
All runtimes implement the `Provider` interface (`src/internal/runtime/provider.go`, 20 methods total). Providers auto-register via `init()`:
170+
171+
```go
172+
// src/runtimes/node/provider.go
173+
func init() {
174+
runtime.Register(NewProvider())
175+
}
176+
177+
// src/main.go - blank imports trigger registration
178+
import (
179+
_ "github.com/dtvem/dtvem/src/runtimes/node"
180+
_ "github.com/dtvem/dtvem/src/runtimes/python"
181+
_ "github.com/dtvem/dtvem/src/runtimes/ruby"
182+
)
183+
```
184+
185+
### Key Provider Methods
186+
187+
| Method | Purpose |
188+
|--------|---------|
189+
| `Name()` | Runtime identifier (e.g., "python") |
190+
| `DisplayName()` | Human-readable name (e.g., "Python") |
191+
| `Shims()` | Executable names (e.g., ["python", "pip"]) |
192+
| `ShouldReshimAfter()` | Detect global package installs |
193+
| `Install(version)` | Download and install a version |
194+
| `ExecutablePath(version)` | Path to versioned executable |
195+
| `GlobalPackages(path)` | Detect installed global packages |
196+
| `InstallGlobalPackages()` | Reinstall packages to new version |
197+
198+
### Adding a New Runtime
199+
200+
1. Create `src/runtimes/<name>/provider.go`
201+
2. Implement `runtime.Provider` interface (all 20 methods)
202+
3. Add `init()` function: `runtime.Register(NewProvider())`
203+
4. Import in `src/main.go`: `_ "github.com/dtvem/dtvem/src/runtimes/<name>"`
204+
5. Update `schemas/runtimes.schema.json` enum
205+
206+
The shim mappings are automatically registered via `Shims()`.
207+
208+
---
209+
210+
## Version Resolution
211+
212+
**Priority order:**
213+
1. **Local**: Walk up from `pwd` looking for `.dtvem/runtimes.json` (stops at git root)
214+
2. **Global**: `~/.dtvem/config/runtimes.json`
215+
3. **Error**: No version configured
216+
217+
**Config format** (both local and global):
218+
```json
219+
{
220+
"python": "3.11.0",
221+
"node": "18.16.0"
222+
}
223+
```
224+
225+
---
226+
227+
## Key Features
228+
229+
### Automatic Reshim Detection
230+
231+
After `npm install -g` or `pip install`, shims prompt to run `dtvem reshim` to create shims for newly installed executables.
232+
233+
### PATH Fallback
234+
235+
When no dtvem version is configured, shims fall back to system PATH (excluding the shims directory).
236+
237+
### Migration System
238+
239+
The `migrate` command detects existing installations (nvm, pyenv, fnm, system) and offers to:
240+
- Install versions via dtvem
241+
- Preserve global packages (npm packages, pip packages)
242+
- Clean up old installations (automated for version managers, manual instructions for system installs)
243+
244+
**Note**: Configuration file preservation (`.npmrc`, `pip.conf`) is not yet implemented.
245+
246+
---
247+
248+
## Coding Standards
249+
250+
All code follows `GO_STYLEGUIDE.md`. Key points:
251+
252+
- **Naming**: Avoid package/receiver repetition, no "Get" prefix
253+
- **Errors**: Use structured errors, `%w` for wrapping
254+
- **Paths**: Always use `filepath.Join()`, never hardcode `/` or `\`
255+
- **Output**: Use `internal/ui` package for user-facing messages
256+
- **Tests**: Must pass all linters (no special treatment for `*_test.go`)
257+
258+
---
259+
260+
## Testing
261+
262+
### Running Tests
263+
264+
```bash
265+
cd src && go test ./... # All tests
266+
cd src && go test ./internal/config -v # Specific package
267+
cd src && go test -cover ./... # With coverage
268+
```
269+
270+
### Test Coverage
271+
272+
- `internal/config/` - Paths, version resolution, git root detection
273+
- `internal/runtime/` - Registry, provider test harness
274+
- `internal/shim/` - Shim mapping, cache, file operations
275+
- `internal/ui/` - Output formatting functions
276+
- `internal/testutil/` - Test utility functions
277+
- `runtimes/*/` - Provider contract validation
278+
- `cmd/` - Command helpers (migrate, uninstall)
279+
280+
### Provider Test Harness
281+
282+
`internal/runtime/provider_test_harness.go` validates all Provider implementations consistently. Used by node, python, and ruby providers.
283+
284+
### Import Cycle Avoidance
285+
286+
- Runtime providers import `internal/shim`
287+
- Tests in `internal/shim/` use mock providers (not real ones)
288+
- Real providers tested via the test harness in their own packages
289+
290+
---
291+
292+
## CI/CD
293+
294+
### Workflows in This Repo (`.github/workflows/`)
295+
296+
| Workflow | Trigger | Purpose |
297+
|----------|---------|---------|
298+
| `build.yml` | PR, push to main | Lint, build, test on Windows/macOS/Linux. Posts coverage reports on PRs |
299+
| `release.yml` | Manual dispatch | Full release: validate, build 5 platforms, create GitHub Release, notify |
300+
| `commit-lint.yml` | PR | Validate PR titles follow conventional commits |
301+
| `script-lint.yml` | PR (install scripts) | Lint install.sh and install.ps1 with shellcheck/PSScriptAnalyzer |
302+
| `contributors.yml` | Push to main | Auto-update contributors section in README |
303+
| `preview-changelog.yml` | PR | Preview release notes for PRs |
304+
| `integration-test.yml` | Manual dispatch | Full integration test suite (runtimes + migrations) |
305+
| `integration-test-runtimes.yml` | Manual dispatch | Runtime install/uninstall tests only |
306+
| `integration-test-migrations.yml` | Manual dispatch | Migration tests only (all platforms/managers) |
307+
| `generate-manifests-from-r2.yml` | Manual/scheduled | Generate version manifests from R2 mirror |
308+
| `deploy-manifests.yml` | Push to main (manifests/) | Deploy manifest files to R2 |
309+
| `mirror-all.yml` | Manual dispatch | Mirror all runtime binaries to R2 |
310+
| `mirror-sync.yml` | Scheduled | Sync new versions to R2 mirror |
311+
312+
### Reusable Workflows (from `dtvem/.github` repo)
313+
314+
Integration tests and changelog generation use reusable workflows stored in the separate `dtvem/.github` repository:
315+
316+
**Runtime Tests:**
317+
- `integration-test-node.yml` - Node.js install/global/local/uninstall
318+
- `integration-test-python.yml` - Python install/global/local/uninstall
319+
- `integration-test-ruby.yml` - Ruby install/global/local/uninstall
320+
321+
**Migration Tests** (per runtime × platform × version manager):
322+
- `integration-test-migrate-{runtime}-{platform}-{manager}.yml`
323+
- Platforms: ubuntu, macos, windows
324+
- Managers: system, nvm, fnm, pyenv, rbenv, uru
325+
326+
**Utilities:**
327+
- `generate-changelog.yml` - Generate release notes from commits
328+
329+
Version injected at build time; main branch always shows `Version = "dev"`.
330+
331+
---
332+
333+
## Installation Scripts
334+
335+
### One-Command Installers
336+
337+
**Unix:**
338+
```bash
339+
curl -fsSL dtvem.io/install.sh | bash
340+
```
341+
342+
**Windows:**
343+
```powershell
344+
irm dtvem.io/install.ps1 | iex
345+
```
346+
347+
Features: Auto platform detection, PATH configuration, runs `dtvem init`.
348+
349+
---
350+
351+
## UI Output System
352+
353+
Use `internal/ui` for all user-facing output:
354+
355+
| Function | Purpose |
356+
|----------|---------|
357+
| `Success()` | Green ✓ - completed operations |
358+
| `Error()` | Red ✗ - failures |
359+
| `Warning()` | Yellow ⚠ - non-critical issues |
360+
| `Info()` | Cyan → - informational |
361+
| `Progress()` | Blue → (indented) - operation steps |
362+
| `Header()` | Bold - section titles |
363+
| `Highlight()` | Cyan bold - emphasis |
364+
| `HighlightVersion()` | Magenta bold - version numbers |
365+
| `ActiveVersion()` | Green bold - active/selected versions |
366+
| `DimText()` | Gray/faint - secondary info |
367+
| `Debug()` / `Debugf()` | Debug output (requires `DTVEM_VERBOSE`) |
368+
369+
---
370+
371+
## Important Notes
372+
373+
- **Cross-platform paths**: Use `filepath.Join()`, check `runtime.GOOS`
374+
- **Windows shims**: Must be `.exe` files
375+
- **Shim execution**: Unix uses `syscall.Exec()`; Windows uses `exec.Command()`
376+
- **Version strings**: Strip `v` prefix (e.g., "v22.0.0" → "22.0.0")
377+
- **Registry is global**: Providers auto-register on import via `init()`
378+
- **Verbose mode**: Set `DTVEM_VERBOSE=1` for debug output

0 commit comments

Comments
 (0)