Guidance for Claude Code when working with the dtvem codebase.
| Document | Purpose |
|---|---|
README.md |
User-facing documentation, installation, usage examples |
CONTRIBUTING.md |
Contribution guidelines, PR process, development setup |
CODE_OF_CONDUCT.md |
Community standards and expected behavior |
GO_STYLEGUIDE.md |
Complete Go coding standards (Google style) |
These rules override all other instructions:
- NEVER commit directly to main - Always create a feature branch and submit a pull request. No exceptions.
- Follow the styleguide - All code must comply with
GO_STYLEGUIDE.md - Write tests - All new/refactored code requires comprehensive unit tests
- Cross-platform - All features must work on Windows, macOS, and Linux
- Conventional commits - Format:
type(scope): description - GitHub Issues for TODOs - Use
ghCLI to manage issues, no local TODO files. Use conventional commit format for issue titles - Pull Requests - Use the conventional commit format for PR titles as you do for commits
- Run validation before commits - Run
./rnr check(format, lint, test) before committing and pushing - Working an issue - When working an issue, always create a new branch from an updated main branch
- Branch Names - Always use the conventional commit
typefrom the issue title as the first prefix, and thescopeas the second, then a very short description, examplefeat/ci/integration-tests - 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.
- No co-authors - Do not add any co-author information on commits or pull requests
- No "generated by" statements - Do not add generated-by statements on pull requests
./rnr --list # List all available tasks
./rnr check # Format, lint, and test
./rnr build # Build both CLI and shim
./rnr test # Run tests
./rnr format # Format code
./rnr lint # Run linter
./rnr clean # Remove build artifacts
./rnr deploy-local # Build and deploy to local installation# Build executables
go build -o dist/dtvem.exe ./src
go build -o dist/dtvem-shim.exe ./src/cmd/shim
# Run directly
go run ./src/main.go <command>
# Run tests
cd src && go test ./...cp dist/dtvem.exe ~/.dtvem/bin/dtvem.exe
cp dist/dtvem-shim.exe ~/.dtvem/bin/dtvem-shim.exe
~/.dtvem/bin/dtvem.exe reshimgh issue list # List open issues
gh issue view <number> # View details
gh issue create --title "..." --label enhancement --body "..."
gh issue close <number># List what blocks an issue
gh api repos/CodingWithCalvin/dtvem.cli/issues/<number>/dependencies/blocked_by --jq '.[] | "#\(.number) \(.title)"'
# List what an issue blocks
gh api repos/CodingWithCalvin/dtvem.cli/issues/<number>/dependencies/blocking --jq '.[] | "#\(.number) \(.title)"'
# Add a blocking relationship (issue <number> is blocked by <blocker_id>)
# First get the blocker's numeric ID (not issue number):
gh api repos/CodingWithCalvin/dtvem.cli/issues/<blocker_number> --jq '.id'
# Then add the dependency:
gh api repos/CodingWithCalvin/dtvem.cli/issues/<number>/dependencies/blocked_by -X POST -F issue_id=<blocker_id>
# Remove a blocking relationship
gh api repos/CodingWithCalvin/dtvem.cli/issues/<number>/dependencies/blocked_by/<blocker_id> -X DELETENote: The API uses numeric issue IDs (not issue numbers) for POST/DELETE operations. Get the ID with gh api repos/CodingWithCalvin/dtvem.cli/issues/<number> --jq '.id'
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.
| Attribute | Value |
|---|---|
| Version | dev (pre-1.0) |
| Runtimes | Python, Node.js, Ruby |
| Tests | 160+ passing |
| Style | Google Go Style Guide |
Key Concept: Shims are Go executables that intercept runtime commands (like python, node), resolve versions, and execute the appropriate binary.
init, install, uninstall, list, list-all, global, local, current, freeze, migrate, reshim, which, where, update, request, version, help
- Main CLI (
src/main.go) - Thedtvemcommand - Shim Executable (
src/cmd/shim/main.go) - Copied/renamed for each runtime command
User runs: python --version
↓
~/.dtvem/shims/python.exe (shim)
↓
Maps to runtime provider → Resolves version
↓
├─ Version configured & installed? → Execute ~/.dtvem/versions/python/3.11.0/bin/python
├─ Version configured but not installed? → Show error with install command
└─ No version configured? → Fall back to system PATH or show install instructions
~/.dtvem/
├── bin/ # dtvem binaries (added to PATH)
│ ├── dtvem
│ └── dtvem-shim
├── shims/ # Runtime shims (python.exe, node.exe, etc.)
├── versions/ # Installed runtimes by name/version
│ ├── python/3.11.0/
│ └── node/18.16.0/
└── config/
└── runtimes.json # Global version config
| Package | Purpose |
|---|---|
internal/config/ |
Paths, version resolution, config file handling |
internal/runtime/ |
Provider interface, registry, version types |
internal/shim/ |
Shim lifecycle management |
internal/path/ |
PATH configuration (platform-specific) |
internal/ui/ |
Colored output, prompts, verbose/debug logging |
internal/tui/ |
Table formatting and styles |
internal/download/ |
File downloads with progress |
internal/manifest/ |
Version manifest fetching and caching |
internal/migration/ |
Migration detection and helpers |
internal/testutil/ |
Shared test utility functions |
internal/constants/ |
Platform constants |
src/cmd/ |
CLI commands (one file per command) |
src/runtimes/ |
Runtime providers (node/, python/, ruby/) |
All runtimes implement the Provider interface (src/internal/runtime/provider.go, 20 methods total). Providers auto-register via init():
// src/runtimes/node/provider.go
func init() {
runtime.Register(NewProvider())
}
// src/main.go - blank imports trigger registration
import (
_ "github.com/CodingWithCalvin/dtvem.cli/src/runtimes/node"
_ "github.com/CodingWithCalvin/dtvem.cli/src/runtimes/python"
_ "github.com/CodingWithCalvin/dtvem.cli/src/runtimes/ruby"
)| Method | Purpose |
|---|---|
Name() |
Runtime identifier (e.g., "python") |
DisplayName() |
Human-readable name (e.g., "Python") |
Shims() |
Executable names (e.g., ["python", "pip"]) |
ShouldReshimAfter() |
Detect global package installs |
Install(version) |
Download and install a version |
ExecutablePath(version) |
Path to versioned executable |
GlobalPackages(path) |
Detect installed global packages |
InstallGlobalPackages() |
Reinstall packages to new version |
- Create
src/runtimes/<name>/provider.go - Implement
runtime.Providerinterface (all 20 methods) - Add
init()function:runtime.Register(NewProvider()) - Import in
src/main.go:_ "github.com/CodingWithCalvin/dtvem.cli/src/runtimes/<name>" - Update
schemas/runtimes.schema.jsonenum
The shim mappings are automatically registered via Shims().
Priority order:
- Local: Walk up from
pwdlooking for.dtvem/runtimes.json(stops at filesystem root) - Global:
~/.dtvem/config/runtimes.json - Error: No version configured
Config format (both local and global):
{
"python": "3.11.0",
"node": "18.16.0"
}After npm install -g or pip install, shims prompt to run dtvem reshim to create shims for newly installed executables.
When no dtvem version is configured, shims fall back to system PATH (excluding the shims directory).
The migrate command detects existing installations (nvm, pyenv, fnm, system) and offers to:
- Install versions via dtvem
- Preserve global packages (npm packages, pip packages)
- Clean up old installations (automated for version managers, manual instructions for system installs)
Note: Configuration file preservation (.npmrc, pip.conf) is not yet implemented.
All code follows GO_STYLEGUIDE.md. Key points:
- Naming: Avoid package/receiver repetition, no "Get" prefix
- Errors: Use structured errors,
%wfor wrapping - Paths: Always use
filepath.Join(), never hardcode/or\ - Output: Use
internal/uipackage for user-facing messages - Tests: Must pass all linters (no special treatment for
*_test.go)
cd src && go test ./... # All tests
cd src && go test ./internal/config -v # Specific package
cd src && go test -cover ./... # With coverageinternal/config/- Paths, version resolution, directory traversalinternal/runtime/- Registry, provider test harnessinternal/shim/- Shim mapping, cache, file operationsinternal/ui/- Output formatting functionsinternal/testutil/- Test utility functionsruntimes/*/- Provider contract validationcmd/- Command helpers (migrate, uninstall)
internal/runtime/provider_test_harness.go validates all Provider implementations consistently. Used by node, python, and ruby providers.
- Runtime providers import
internal/shim - Tests in
internal/shim/use mock providers (not real ones) - Real providers tested via the test harness in their own packages
| Workflow | Trigger | Purpose |
|---|---|---|
build.yml |
PR, push to main | Lint, build, test on Windows/macOS/Linux. Posts coverage reports on PRs |
release.yml |
Manual dispatch | Full release: validate, build 5 platforms, create GitHub Release, notify |
commit-lint.yml |
PR | Validate PR titles follow conventional commits |
script-lint.yml |
PR (install scripts) | Lint install.sh and install.ps1 with shellcheck/PSScriptAnalyzer |
contributors.yml |
Push to main | Auto-update contributors section in README |
preview-changelog.yml |
PR | Preview release notes for PRs |
integration-test.yml |
Manual dispatch | Full integration test suite (runtimes + migrations) |
integration-test-runtimes.yml |
Manual dispatch | Runtime install/uninstall tests only |
integration-test-migrations.yml |
Manual dispatch | Migration tests only (all platforms/managers) |
generate-manifests-from-r2.yml |
Manual/scheduled | Generate version manifests from R2 mirror |
deploy-manifests.yml |
Push to main (manifests/) | Deploy manifest files to R2 |
mirror-all.yml |
Manual dispatch | Mirror all runtime binaries to R2 |
mirror-sync.yml |
Scheduled | Sync new versions to R2 mirror |
Integration tests and changelog generation use reusable workflows stored in the separate dtvem/.github repository:
Runtime Tests:
integration-test-node.yml- Node.js install/global/local/uninstallintegration-test-python.yml- Python install/global/local/uninstallintegration-test-ruby.yml- Ruby install/global/local/uninstall
Migration Tests (per runtime × platform × version manager):
integration-test-migrate-{runtime}-{platform}-{manager}.yml- Platforms: ubuntu, macos, windows
- Managers: system, nvm, fnm, pyenv, rbenv, uru
Utilities:
generate-changelog.yml- Generate release notes from commits
Version injected at build time; main branch always shows Version = "dev".
Unix:
curl -fsSL dtvem.io/install.sh | bashWindows:
irm dtvem.io/install.ps1 | iexFeatures: Auto platform detection, PATH configuration, runs dtvem init.
Use internal/ui for all user-facing output:
| Function | Purpose |
|---|---|
Success() |
Green ✓ - completed operations |
Error() |
Red ✗ - failures |
Warning() |
Yellow ⚠ - non-critical issues |
Info() |
Cyan → - informational |
Progress() |
Blue → (indented) - operation steps |
Header() |
Bold - section titles |
Highlight() |
Cyan bold - emphasis |
HighlightVersion() |
Magenta bold - version numbers |
ActiveVersion() |
Green bold - active/selected versions |
DimText() |
Gray/faint - secondary info |
Debug() / Debugf() |
Debug output (requires DTVEM_VERBOSE) |
- Cross-platform paths: Use
filepath.Join(), checkruntime.GOOS - Windows shims: Must be
.exefiles - Shim execution: Unix uses
syscall.Exec(); Windows usesexec.Command() - Version strings: Strip
vprefix (e.g., "v22.0.0" → "22.0.0") - Registry is global: Providers auto-register on import via
init() - Verbose mode: Set
DTVEM_VERBOSE=1for debug output