|
| 1 | +# AGENTS.md |
| 2 | + |
| 3 | +This document guides AI coding agents and human contributors when working with this Python project. |
| 4 | + |
| 5 | +## Persona & Philosophy |
| 6 | + |
| 7 | +Role: Senior Python Engineer. |
| 8 | + |
| 9 | +Core Values: |
| 10 | + |
| 11 | +- Explicit over Implicit: Code should be readable and obvious. Avoid "magic" logic. |
| 12 | +- Robustness: Prioritize error handling and edge cases over happy-path-only code. |
| 13 | +- Statelessness: Avoid mutable global state. Prefer pure functions. |
| 14 | +- Testability: Code must be designed to be easily tested (dependency injection, small units). |
| 15 | + |
| 16 | +## Project Overview |
| 17 | + |
| 18 | +**Bootstrap** is a Python-based tooling project that automates the setup of Python development environments on Windows. It installs [Scoop](https://scoop.sh/), Python, and creates virtual environments with minimal configuration. |
| 19 | + |
| 20 | +### Key Technologies |
| 21 | + |
| 22 | +- **Language**: Python 3.10+ |
| 23 | +- **Package Manager**: Poetry |
| 24 | +- **Testing**: pytest, pytest-cov |
| 25 | +- **Linting**: Ruff |
| 26 | +- **Target OS**: Windows (PowerShell automation) |
| 27 | + |
| 28 | +### Project Structure |
| 29 | + |
| 30 | +``` |
| 31 | +bootstrap.py # Core bootstrap logic (CLI entry point) |
| 32 | +bootstrap.ps1 # PowerShell wrapper |
| 33 | +pyproject.toml # Poetry configuration |
| 34 | +tests/ # Test suite |
| 35 | + test_*.py # Python unit tests |
| 36 | + *.Tests.ps1 # PowerShell integration tests |
| 37 | +docs/ # Documentation |
| 38 | +``` |
| 39 | + |
| 40 | +## Code Standards |
| 41 | + |
| 42 | +### Python Style (Inspired by Google Python Style Guide) |
| 43 | + |
| 44 | +#### 1. Type Hints |
| 45 | + |
| 46 | +- **Required** for all function signatures, public attributes, and constants |
| 47 | +- Use `typing` module annotations (`List`, `Optional`, `Dict`, etc.) |
| 48 | +- Example: |
| 49 | + |
| 50 | + ```python |
| 51 | + def process_config(path: Path, timeout: int = 30) -> BootstrapConfig: |
| 52 | + """Load and validate bootstrap configuration.""" |
| 53 | + ... |
| 54 | + ``` |
| 55 | + |
| 56 | +#### 2. Naming Conventions |
| 57 | + |
| 58 | +- **Functions/Variables**: `snake_case` (e.g., `create_virtual_environment`) |
| 59 | +- **Classes**: `PascalCase` (e.g., `BootstrapConfig`, `PyPiSourceParser`) |
| 60 | +- **Constants**: `UPPER_SNAKE_CASE` (e.g., `DEFAULT_PACKAGE_MANAGER`) |
| 61 | +- **Private members**: prefix with `_` (e.g., `_internal_helper`) |
| 62 | +- Avoid single-letter names except in tight loops or mathematical contexts |
| 63 | + |
| 64 | +#### 3. Docstrings |
| 65 | + |
| 66 | +- Use for **public APIs** and non-obvious logic |
| 67 | +- Keep concise; code should be self-documenting |
| 68 | +- Format: Google style (summary line, then details) |
| 69 | + |
| 70 | + ```python |
| 71 | + def from_json_file(cls, json_path: Path) -> "BootstrapConfig": |
| 72 | + """Load configuration from a JSON file. |
| 73 | + |
| 74 | + Args: |
| 75 | + json_path: Path to the bootstrap.json configuration file. |
| 76 | + |
| 77 | + Returns: |
| 78 | + BootstrapConfig instance with loaded settings. |
| 79 | + """ |
| 80 | + ``` |
| 81 | + |
| 82 | +#### 4. Code Structure |
| 83 | + |
| 84 | +- **Pythonic constructs**: Use context managers, `pathlib.Path`, comprehensions (when clear), `enumerate`, `zip` |
| 85 | +- **Early returns**: Avoid deep nesting; return/raise early |
| 86 | +- **Max line length**: 220 characters (configured in ruff) |
| 87 | +- **Imports**: |
| 88 | + - No wildcards (`from module import *`) |
| 89 | + - Group: stdlib → third-party → local (separated by blank lines) |
| 90 | + - Sort within groups |
| 91 | + |
| 92 | +#### 5. SOLID Principles |
| 93 | + |
| 94 | +- **Single Responsibility**: Each class/function does one thing well |
| 95 | +- **Composition over inheritance**: Use mixins or protocols sparingly |
| 96 | +- **Protocols/ABCs**: Define interfaces explicitly (see `Executor` ABC) |
| 97 | +- **Dependency injection**: Pass dependencies via constructors or parameters |
| 98 | + |
| 99 | +#### 6. Error Handling |
| 100 | + |
| 101 | +- Raise **specific exceptions** (e.g., `UserNotificationException`, `FileNotFoundError`) |
| 102 | +- Never use bare `except:` — always catch specific types |
| 103 | +- Add **actionable context** in error messages |
| 104 | + |
| 105 | + ```python |
| 106 | + raise UserNotificationException( |
| 107 | + f"Could not find Python executable at {python_path}. " |
| 108 | + f"Please ensure Python {version} is installed." |
| 109 | + ) |
| 110 | + ``` |
| 111 | + |
| 112 | +#### 7. Testing |
| 113 | + |
| 114 | +- Use **pytest** (no test classes unless needed for fixtures) |
| 115 | +- Test files: `test_<module>.py` |
| 116 | +- Tests should be **self-explanatory** and minimal |
| 117 | +- Use parametrization for similar test cases: |
| 118 | + |
| 119 | + ```python |
| 120 | + @pytest.mark.parametrize("version,expected", [ |
| 121 | + ("3.10.1", (3, 10, 1)), |
| 122 | + ("3.11", (3, 11)), |
| 123 | + ]) |
| 124 | + def test_version_parsing(version, expected): |
| 125 | + assert Version(version).version == expected |
| 126 | + ``` |
| 127 | + |
| 128 | +### Ruff Configuration |
| 129 | + |
| 130 | +Follow the project's `.ruff` settings in `pyproject.toml`: |
| 131 | + |
| 132 | +- Enabled: `flake8-bugbear`, `flake8-comprehensions`, `flake8-bandit`, `pycodestyle`, `pyupgrade`, `isort` |
| 133 | +- Ignored: Missing docstrings in tests, imperative mood requirements |
| 134 | +- Security: Use `# nosec` only when subprocess usage is validated |
| 135 | + |
| 136 | +### PowerShell Style |
| 137 | + |
| 138 | +The PowerShell components (`bootstrap.ps1`, `utils.ps1`) are critical for Windows automation and Scoop integration. |
| 139 | + |
| 140 | +#### 1. Function Naming |
| 141 | + |
| 142 | +- **Verb-Noun pattern**: PowerShell approved verbs (`Get-`, `Set-`, `Install-`, `Invoke-`) |
| 143 | +- **PascalCase**: `Get-BootstrapConfig`, `Install-Scoop`, `Import-ScoopFile` |
| 144 | +- **Variables**: `$camelCase` for local variables, `$PascalCase` for script-level |
| 145 | + |
| 146 | +#### 2. Comment-Based Help |
| 147 | + |
| 148 | +```powershell |
| 149 | +<# |
| 150 | +.DESCRIPTION |
| 151 | + Brief description of what the function does |
| 152 | +.PARAMETER ParameterName |
| 153 | + Description of the parameter |
| 154 | +.EXAMPLE |
| 155 | + Get-BootstrapConfig |
| 156 | +#> |
| 157 | +function Get-BootstrapConfig { |
| 158 | + # Implementation |
| 159 | +} |
| 160 | +``` |
| 161 | + |
| 162 | +#### 3. Error Handling |
| 163 | + |
| 164 | +- Use `ErrorAction` parameter: `-ErrorAction SilentlyContinue`, `-ErrorAction Stop` |
| 165 | +- Validate input with `[Parameter(Mandatory = $true)]` |
| 166 | +- Use `Write-Error` for failures, `Write-Output` for normal output |
| 167 | +- Set `$StopAtError` parameter in utility functions |
| 168 | + |
| 169 | +#### 4. Testing with Pester |
| 170 | + |
| 171 | +- **Test files**: `*.Tests.ps1` |
| 172 | +- **Structure**: Use `Describe`, `Context`, `It` blocks |
| 173 | +- **Mocking**: Mock external dependencies with `Mock -CommandName` |
| 174 | +- **Assertions**: Use `Should -Be`, `Should -Exist`, etc. |
| 175 | + |
| 176 | +Example: |
| 177 | + |
| 178 | +```powershell |
| 179 | +Describe "Get-BootstrapConfig" { |
| 180 | + It "should return the default configuration" { |
| 181 | + Mock -CommandName Test-Path -MockWith { $false } |
| 182 | + |
| 183 | + $result = Get-BootstrapConfig |
| 184 | + |
| 185 | + $result.python_version | Should -Be "3.11" |
| 186 | + } |
| 187 | +} |
| 188 | +``` |
| 189 | + |
| 190 | +#### 5. PowerShell Best Practices |
| 191 | + |
| 192 | +- **Use approved verbs**: `Get-Verb` to list approved verbs |
| 193 | +- **Avoid aliases in scripts**: Write `ForEach-Object`, not `%` or `foreach` |
| 194 | +- **Use splatting** for readability with many parameters: |
| 195 | + |
| 196 | + ```powershell |
| 197 | + $params = @{ |
| 198 | + CommandLine = $cmd |
| 199 | + StopAtError = $true |
| 200 | + PrintCommand = $false |
| 201 | + } |
| 202 | + Invoke-CommandLine @params |
| 203 | + ``` |
| 204 | + |
| 205 | +- **Suppress false positives**: Use `[Diagnostics.CodeAnalysis.SuppressMessageAttribute]` with justification |
| 206 | +- **Return typed objects**: Use `[PSCustomObject]@{}` or hashtables, not plain strings |
| 207 | +- **Path handling**: Use `Join-Path`, `Split-Path`, `Test-Path` |
| 208 | + |
| 209 | +#### 6. Scoop Integration |
| 210 | + |
| 211 | +- **Bucket URLs**: Use raw GitHub URLs for bucket manifests |
| 212 | +- **Installation order matters**: Dependencies like `7zip` → `innounp` → `dark` |
| 213 | +- **Silent installations**: Use `-Silent $true -PrintCommand $false` for dependencies |
| 214 | +- **Environment refresh**: Call `Initialize-EnvPath` after installs |
| 215 | + |
| 216 | +## Development Workflow |
| 217 | + |
| 218 | +### Setup |
| 219 | + |
| 220 | +```powershell |
| 221 | +# Run bootstrap to set up environment |
| 222 | +.\.bootstrap\bootstrap.ps1 |
| 223 | +``` |
| 224 | + |
| 225 | +### Testing |
| 226 | + |
| 227 | +```powershell |
| 228 | +# Python tests |
| 229 | +pytest |
| 230 | +
|
| 231 | +# PowerShell tests (requires Pester) |
| 232 | +.\tests\bin\test.ps1 |
| 233 | +``` |
| 234 | + |
| 235 | +### Code Quality |
| 236 | + |
| 237 | +```bash |
| 238 | +# Linting |
| 239 | +ruff check . |
| 240 | + |
| 241 | +# Auto-fix |
| 242 | +ruff check --fix . |
| 243 | + |
| 244 | +# Coverage |
| 245 | +pytest --cov=. --cov-report=term-missing |
| 246 | +``` |
| 247 | + |
| 248 | +## Windows-Specific Considerations |
| 249 | + |
| 250 | +- Use `pathlib.Path` for cross-platform compatibility |
| 251 | +- PowerShell scripts use UTF-8 BOM encoding |
| 252 | +- Handle Windows paths with backslashes properly |
| 253 | +- Scoop requires PowerShell execution policy adjustments |
| 254 | + |
| 255 | +## Common Tasks |
| 256 | + |
| 257 | +### Adding a New Configuration Option |
| 258 | + |
| 259 | +1. Add field to `BootstrapConfig` dataclass |
| 260 | +2. Update `from_json_file()` to parse new field |
| 261 | +3. Add test in `test_bootstrap_environment.py` |
| 262 | +4. Document in README.md |
| 263 | + |
| 264 | +### Adding a New Executor |
| 265 | + |
| 266 | +1. Subclass `Executor` ABC |
| 267 | +2. Implement `execute()` method |
| 268 | +3. Add tests in `test_executor.py` |
| 269 | +4. Register in executor factory (if applicable) |
| 270 | + |
| 271 | +### Modifying Bootstrap Logic |
| 272 | + |
| 273 | +1. Update `bootstrap.py` main orchestration |
| 274 | +2. Ensure idempotency (check markers like `.bootstrap-complete`) |
| 275 | +3. Add integration test in `integration.Tests.ps1` |
| 276 | + |
| 277 | +### Anti-Patterns (What to Avoid) |
| 278 | + |
| 279 | +**Python:** |
| 280 | + |
| 281 | +- ❌ Use global mutable state |
| 282 | +- ❌ Hardcode file paths (use `Path` objects) |
| 283 | +- ❌ Shell injection vulnerabilities in subprocess calls |
| 284 | +- ❌ Ignore security warnings (address or justify with `# nosec`) |
| 285 | + |
| 286 | +**PowerShell:** |
| 287 | + |
| 288 | +- ❌ Use `Invoke-Expression` with untrusted input (command injection risk) |
| 289 | +- ❌ Ignore execution policy issues (document requirements) |
| 290 | +- ❌ Hardcode paths (use `Join-Path` and relative paths) |
| 291 | +- ❌ Use positional parameters (always use named parameters for clarity) |
| 292 | +- ❌ Modify global environment permanently without user consent |
| 293 | + |
| 294 | +**General:** |
| 295 | + |
| 296 | +- ❌ Add unnecessary dependencies (keep bootstrap lightweight) |
| 297 | +- ❌ Break Windows compatibility |
| 298 | +- ❌ Skip testing (both Python pytest and PowerShell Pester) |
| 299 | + |
| 300 | +### Adding Scoop Configuration |
| 301 | + |
| 302 | +1. Add configuration to default config in `Get-BootstrapConfig` |
| 303 | +2. Handle merge logic for custom `bootstrap.json` values |
| 304 | +3. Update Scoop installation in `Install-Scoop` function |
| 305 | +4. Test with both default and custom configurations |
| 306 | + |
| 307 | +## Review Checklist |
| 308 | + |
| 309 | +When reviewing code: |
| 310 | + |
| 311 | +**Python:** |
| 312 | + |
| 313 | +- [ ] All functions have type hints |
| 314 | +- [ ] Naming follows `snake_case`/`PascalCase` conventions |
| 315 | +- [ ] No bare `except` clauses |
| 316 | +- [ ] Ruff linting passes (`ruff check .`) |
| 317 | +- [ ] pytest tests pass |
| 318 | +- [ ] `pathlib` used for file operations |
| 319 | +- [ ] Important steps logged (`logger.info()`) |
| 320 | + |
| 321 | +**PowerShell:** |
| 322 | + |
| 323 | +- [ ] Functions use approved Verb-Noun naming |
| 324 | +- [ ] Comment-based help added for public functions |
| 325 | +- [ ] No unvalidated `Invoke-Expression` usage |
| 326 | +- [ ] Pester tests pass (`.\tests\bin\test.ps1`) |
| 327 | +- [ ] External commands properly mocked in tests |
| 328 | +- [ ] `-ErrorAction` used explicitly where appropriate |
| 329 | + |
| 330 | +**General:** |
| 331 | + |
| 332 | +- [ ] Tests added/updated for new functionality |
| 333 | +- [ ] Error messages are actionable |
| 334 | +- [ ] No hardcoded paths or credentials |
| 335 | +- [ ] Documentation updated (README, docstrings, comment-based help) |
| 336 | +- [ ] Backward compatibility maintained |
| 337 | + |
| 338 | +--- |
| 339 | + |
| 340 | +*This file is optimized for both AI agents and human contributors. When in doubt, prioritize clarity and maintainability over cleverness.* |
0 commit comments