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