Initial commit
This commit is contained in:
308
references/examples/coding-agent-cli/AGENTS.md
Normal file
308
references/examples/coding-agent-cli/AGENTS.md
Normal file
@@ -0,0 +1,308 @@
|
||||
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-09 -->
|
||||
|
||||
# AI CLI Preparation - Agent Guide (Root)
|
||||
|
||||
**Thin root file**: See scoped AGENTS.md files for specific areas.
|
||||
|
||||
## Precedence & Scoped Files
|
||||
|
||||
This root AGENTS.md provides global defaults. **Nearest AGENTS.md wins** for specific rules.
|
||||
|
||||
**Scoped files:**
|
||||
- [scripts/AGENTS.md](scripts/AGENTS.md) - Shell installation scripts
|
||||
|
||||
## Overview
|
||||
|
||||
AI CLI Preparation is an environment audit tool ensuring AI coding agents (like Claude Code) have access to necessary developer tools. It detects 50+ tools, reports versions, and provides installation guidance.
|
||||
|
||||
**Architecture:**
|
||||
- **Phase 1 (Complete)**: Tool detection, version auditing, offline-first caching
|
||||
- **Phase 2 (Planned)**: Context-aware installation/upgrade management (see [docs/PRD.md](docs/PRD.md))
|
||||
|
||||
**Tech Stack:**
|
||||
- Python 3.10+ (standard library only, no external deps for core)
|
||||
- Make for task automation
|
||||
- Shell scripts (Bash) for installation
|
||||
- JSON for caching (latest_versions.json, tools_snapshot.json)
|
||||
|
||||
**Key Files:**
|
||||
- `cli_audit.py` (2,375 lines): Main audit engine, 50+ tool definitions
|
||||
- `smart_column.py`: ANSI/emoji-aware table formatting
|
||||
- `scripts/`: 13+ installation scripts (install/update/uninstall/reconcile)
|
||||
- `docs/`: Comprehensive technical documentation (12 files, 189KB)
|
||||
|
||||
## Setup
|
||||
|
||||
**Requirements:**
|
||||
- Python 3.10+ (Python 3.14.0rc2 tested)
|
||||
- Standard library only (no pip install needed for core)
|
||||
- Optional: pyflakes for linting
|
||||
|
||||
**First-time setup:**
|
||||
```bash
|
||||
# Allow direnv (if using)
|
||||
direnv allow
|
||||
|
||||
# Show available commands
|
||||
make help
|
||||
|
||||
# Update snapshot (requires network)
|
||||
make update
|
||||
|
||||
# Run audit from snapshot (fast, offline-capable)
|
||||
make audit
|
||||
```
|
||||
|
||||
**Environment variables:**
|
||||
```bash
|
||||
# See .env.default for all options
|
||||
CLI_AUDIT_COLLECT=1 # Collect-only mode (write snapshot)
|
||||
CLI_AUDIT_RENDER=1 # Render-only mode (read snapshot)
|
||||
CLI_AUDIT_OFFLINE=1 # Offline mode (manual cache only)
|
||||
CLI_AUDIT_DEBUG=1 # Debug output
|
||||
CLI_AUDIT_JSON=1 # JSON output
|
||||
```
|
||||
|
||||
## Build & Tests
|
||||
|
||||
**Primary commands:**
|
||||
```bash
|
||||
make audit # Render from snapshot (no network, <100ms)
|
||||
make update # Collect fresh data, write snapshot (~10s)
|
||||
make audit-offline # Offline audit with hints
|
||||
make lint # Run pyflakes (if installed)
|
||||
make upgrade # Interactive upgrade guide
|
||||
```
|
||||
|
||||
**Single tool audit:**
|
||||
```bash
|
||||
make audit-ripgrep # Audit specific tool
|
||||
make audit-offline-python-core # Role-based preset
|
||||
```
|
||||
|
||||
**Installation scripts:**
|
||||
```bash
|
||||
make install-python # Install Python toolchain (uv)
|
||||
make install-node # Install Node.js (nvm)
|
||||
make install-core # Install core tools (fd, fzf, ripgrep, jq, etc.)
|
||||
```
|
||||
|
||||
**Testing:**
|
||||
```bash
|
||||
# Smoke test (verifies table output and JSON format)
|
||||
./scripts/test_smoke.sh
|
||||
|
||||
# Test single tool detection
|
||||
CLI_AUDIT_DEBUG=1 python3 cli_audit.py --only ripgrep
|
||||
|
||||
# Validate snapshot
|
||||
jq '.__meta__' tools_snapshot.json
|
||||
```
|
||||
|
||||
**No formal test suite yet** - README acknowledges: "currently ships without tests"
|
||||
- Smoke tests exist (test_smoke.sh)
|
||||
- Manual validation workflows documented
|
||||
|
||||
## Code Style
|
||||
|
||||
**Python:**
|
||||
- PEP 8 style (4-space indent, snake_case)
|
||||
- Type hints used (`from __future__ import annotations`)
|
||||
- Frozen dataclasses for immutability (`@dataclass(frozen=True)`)
|
||||
- Docstrings minimal (focus on inline comments)
|
||||
|
||||
**Formatting:**
|
||||
- EditorConfig enforced: LF, UTF-8, 4 spaces, trim trailing whitespace
|
||||
- No auto-formatter configured (manual formatting)
|
||||
- Lint via pyflakes: `make lint`
|
||||
|
||||
**Shell scripts:**
|
||||
- Bash with `set -euo pipefail`
|
||||
- Shellcheck-compliant (best effort)
|
||||
- Consistent error handling (see scripts/lib/)
|
||||
|
||||
**Conventions:**
|
||||
- File paths: Absolute paths, no auto-commit
|
||||
- Functions: Snake_case, descriptive names
|
||||
- Constants: UPPER_CASE (e.g., MANUAL_LOCK, HINTS_LOCK)
|
||||
- Lock ordering: MANUAL_LOCK → HINTS_LOCK (enforced for safety)
|
||||
|
||||
## Security
|
||||
|
||||
**Secrets:**
|
||||
- No secrets in VCS
|
||||
- GITHUB_TOKEN optional (for GitHub API rate limit increase)
|
||||
- Set via environment: `export GITHUB_TOKEN=ghp_...`
|
||||
|
||||
**Network:**
|
||||
- HTTPS-only for upstream queries
|
||||
- Retry logic with exponential backoff
|
||||
- Per-origin rate limits (GitHub: 5/min, PyPI: 10/min, crates.io: 5/min)
|
||||
- Timeout enforcement (default: 3s, configurable)
|
||||
|
||||
**Input validation:**
|
||||
- Tool names validated against TOOLS registry
|
||||
- Version strings sanitized (extract_version_number)
|
||||
- Subprocess calls use lists, not shell=True (where possible)
|
||||
|
||||
**Caching:**
|
||||
- Atomic file writes prevent corruption
|
||||
- Offline-first design (committed latest_versions.json)
|
||||
- No arbitrary code execution (package manager commands only)
|
||||
|
||||
## PR/Commit Checklist
|
||||
|
||||
**Before commit:**
|
||||
- [ ] Run `make lint` (pyflakes clean)
|
||||
- [ ] Run `make audit` (verify snapshot renders)
|
||||
- [ ] Test affected tool: `make audit-<tool>`
|
||||
- [ ] Update docs if behavior changed (README.md, docs/, scripts/README.md)
|
||||
- [ ] Add/update smoke test if new output format
|
||||
|
||||
**Commit messages:**
|
||||
- Conventional Commits format: `type(scope): description`
|
||||
- Examples:
|
||||
- `feat(audit): add snapshot-based collect/render modes`
|
||||
- `fix(locks): enforce MANUAL_LOCK→HINTS_LOCK ordering`
|
||||
- `docs(prd): add Phase 2 specifications and ADRs`
|
||||
- `chore(cache): update latest_versions.json`
|
||||
|
||||
**Pull requests:**
|
||||
- Keep PRs small (~≤300 net LOC changed)
|
||||
- Link to issue/ticket if exists
|
||||
- Update CHANGELOG section in PR description
|
||||
- Ensure CI passes (when added)
|
||||
|
||||
## Good vs Bad Examples
|
||||
|
||||
**Good: Atomic dataclass with type safety**
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class Tool:
|
||||
name: str
|
||||
candidates: tuple[str, ...]
|
||||
source_kind: str # gh|pypi|crates|npm|gnu|skip
|
||||
source_args: tuple[str, ...]
|
||||
```
|
||||
|
||||
**Bad: Mutable dict with unclear types**
|
||||
```python
|
||||
# Don't do this
|
||||
tool = {
|
||||
'name': 'ripgrep',
|
||||
'candidates': ['rg'], # List instead of tuple
|
||||
'source': 'github', # Unclear allowed values
|
||||
}
|
||||
```
|
||||
|
||||
**Good: Lock ordering enforcement**
|
||||
```python
|
||||
def update_manual_cache(tool: str, version: str) -> None:
|
||||
with MANUAL_LOCK: # Acquire first
|
||||
with HINTS_LOCK: # Then hints
|
||||
# Safe: consistent ordering prevents deadlock
|
||||
```
|
||||
|
||||
**Bad: Lock ordering violation**
|
||||
```python
|
||||
def update_cache(tool: str) -> None:
|
||||
with HINTS_LOCK: # Wrong order!
|
||||
with MANUAL_LOCK:
|
||||
# Deadlock risk
|
||||
```
|
||||
|
||||
**Good: Parallel execution with isolation**
|
||||
```python
|
||||
with ThreadPoolExecutor(max_workers=16) as executor:
|
||||
futures = [executor.submit(audit_tool, tool) for tool in TOOLS]
|
||||
for future in as_completed(futures):
|
||||
result = future.result() # Failures isolated
|
||||
```
|
||||
|
||||
**Bad: Sequential execution**
|
||||
```python
|
||||
results = []
|
||||
for tool in TOOLS: # Slow: 50 tools * 3s = 150s
|
||||
results.append(audit_tool(tool))
|
||||
```
|
||||
|
||||
## When Stuck
|
||||
|
||||
**Tool detection failing:**
|
||||
1. Check PATH: `echo $PATH | tr ':' '\n'`
|
||||
2. Debug single tool: `CLI_AUDIT_DEBUG=1 python3 cli_audit.py --only <tool>`
|
||||
3. Check version flag: `<tool> --version` or `<tool> -v`
|
||||
4. See [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md#version-detection-failures)
|
||||
|
||||
**Network issues:**
|
||||
1. Increase timeout: `CLI_AUDIT_TIMEOUT_SECONDS=10 make update`
|
||||
2. More retries: `CLI_AUDIT_HTTP_RETRIES=5 make update`
|
||||
3. Use offline mode: `make audit-offline`
|
||||
4. See [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md#network-timeout-issues)
|
||||
|
||||
**Cache corruption:**
|
||||
1. Remove caches: `rm latest_versions.json tools_snapshot.json`
|
||||
2. Regenerate: `make update`
|
||||
|
||||
**Installation script fails:**
|
||||
1. Check permissions: `make scripts-perms`
|
||||
2. Debug script: `bash -x ./scripts/install_<tool>.sh`
|
||||
3. See [scripts/README.md](scripts/README.md) for per-script troubleshooting
|
||||
|
||||
**Documentation:**
|
||||
- Start with [docs/QUICK_REFERENCE.md](docs/QUICK_REFERENCE.md) for one-liners
|
||||
- See [docs/INDEX.md](docs/INDEX.md) for navigation by role/task
|
||||
- Architecture details: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
|
||||
- API reference: [docs/API_REFERENCE.md](docs/API_REFERENCE.md)
|
||||
|
||||
## House Rules
|
||||
|
||||
**Defaults** (override in scoped AGENTS.md if needed):
|
||||
|
||||
**Commits:**
|
||||
- Atomic commits (single logical change)
|
||||
- Conventional Commits: `type(scope): description`
|
||||
- Keep PRs small (~≤300 net LOC changed)
|
||||
- Ticket IDs in commits/PRs if exists
|
||||
|
||||
**Type-safety:**
|
||||
- Use type hints (`from __future__ import annotations`)
|
||||
- Frozen dataclasses for immutability
|
||||
- No `Any` unless truly dynamic
|
||||
|
||||
**Design principles:**
|
||||
- SOLID, KISS, DRY, YAGNI
|
||||
- Composition > Inheritance
|
||||
- Law of Demeter (minimal coupling)
|
||||
|
||||
**Dependencies:**
|
||||
- Standard library preferred (no external Python deps for core)
|
||||
- Latest stable versions when external deps needed
|
||||
- Document why in Decision Log (ADRs for Phase 2)
|
||||
|
||||
**Security:**
|
||||
- No secrets in VCS
|
||||
- HTTPS-only for network calls
|
||||
- No arbitrary code execution
|
||||
- Input validation on external data
|
||||
|
||||
**Documentation currency:**
|
||||
- Update docs in same PR as behavior changes
|
||||
- No drift between code and docs
|
||||
- Document non-obvious decisions in ADRs (see docs/adr/)
|
||||
|
||||
**Testing:**
|
||||
- Aim for ≥80% coverage on changed code (when test suite added)
|
||||
- Bugfixes use TDD: failing test first, then fix
|
||||
- New code paths need tests (future requirement)
|
||||
|
||||
**Current status:**
|
||||
- Phase 1: Production-ready detection/audit
|
||||
- Phase 2: Planned installation/upgrade (see [docs/PRD.md](docs/PRD.md))
|
||||
- No unit tests yet (acknowledged in README)
|
||||
|
||||
---
|
||||
|
||||
**Quick Start:** New to the project? Start with [README.md](README.md) → [docs/QUICK_REFERENCE.md](docs/QUICK_REFERENCE.md) → [docs/INDEX.md](docs/INDEX.md)
|
||||
|
||||
**Contributing:** See [docs/DEVELOPER_GUIDE.md](docs/DEVELOPER_GUIDE.md) for detailed contribution guide
|
||||
389
references/examples/coding-agent-cli/scripts-AGENTS.md
Normal file
389
references/examples/coding-agent-cli/scripts-AGENTS.md
Normal file
@@ -0,0 +1,389 @@
|
||||
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-09 -->
|
||||
|
||||
# Installation Scripts - Agent Guide
|
||||
|
||||
**Scope:** Shell scripts for tool installation, update, uninstall, reconcile
|
||||
|
||||
## Overview
|
||||
|
||||
13+ Bash scripts for installing developer tools with multiple actions:
|
||||
- **install**: Fresh installation (default action)
|
||||
- **update**: Upgrade to latest version
|
||||
- **uninstall**: Remove installation
|
||||
- **reconcile**: Switch to preferred installation method (e.g., system → user)
|
||||
|
||||
**Key scripts:**
|
||||
- `install_core.sh`: Core tools (fd, fzf, ripgrep, jq, yq, bat, delta, just)
|
||||
- `install_python.sh`: Python toolchain via uv
|
||||
- `install_node.sh`: Node.js via nvm
|
||||
- `install_rust.sh`: Rust via rustup
|
||||
- `install_go.sh`, `install_aws.sh`, `install_kubectl.sh`, etc.
|
||||
- `guide.sh`: Interactive upgrade guide
|
||||
- `test_smoke.sh`: Smoke test for audit output
|
||||
|
||||
**Shared utilities:** `lib/` directory (colors, logging, common functions)
|
||||
|
||||
## Setup
|
||||
|
||||
**Requirements:**
|
||||
- Bash 4.0+
|
||||
- `curl` or `wget` for downloads
|
||||
- Internet access for fresh installs
|
||||
- Appropriate permissions (user for `~/.local/bin`, sudo for system)
|
||||
|
||||
**Environment variables:**
|
||||
```bash
|
||||
INSTALL_PREFIX=${INSTALL_PREFIX:-~/.local} # Default: user-level
|
||||
FORCE_INSTALL=1 # Skip confirmation prompts
|
||||
DEBUG=1 # Verbose output
|
||||
```
|
||||
|
||||
**Permissions:**
|
||||
```bash
|
||||
make scripts-perms # Ensure all scripts are executable
|
||||
```
|
||||
|
||||
## Build & Tests
|
||||
|
||||
**Run individual script:**
|
||||
```bash
|
||||
# Install action (default)
|
||||
./scripts/install_python.sh
|
||||
|
||||
# Update action
|
||||
./scripts/install_python.sh update
|
||||
|
||||
# Uninstall action
|
||||
./scripts/install_python.sh uninstall
|
||||
|
||||
# Reconcile action (switch installation method)
|
||||
./scripts/install_node.sh reconcile
|
||||
```
|
||||
|
||||
**Via Make:**
|
||||
```bash
|
||||
make install-python # Install Python toolchain
|
||||
make update-python # Update Python toolchain
|
||||
make uninstall-python # Uninstall Python toolchain
|
||||
make reconcile-node # Switch Node.js to nvm-managed
|
||||
```
|
||||
|
||||
**Smoke test:**
|
||||
```bash
|
||||
./scripts/test_smoke.sh # Verify audit output format
|
||||
```
|
||||
|
||||
**Debug mode:**
|
||||
```bash
|
||||
DEBUG=1 ./scripts/install_python.sh
|
||||
bash -x ./scripts/install_python.sh # Trace execution
|
||||
```
|
||||
|
||||
## Code Style
|
||||
|
||||
**Shell standards:**
|
||||
- Bash 4.0+ features allowed
|
||||
- Shebang: `#!/usr/bin/env bash` or `#!/bin/bash`
|
||||
- Set strict mode: `set -euo pipefail`
|
||||
- `-e`: Exit on error
|
||||
- `-u`: Error on undefined variables
|
||||
- `-o pipefail`: Fail on pipe errors
|
||||
|
||||
**Formatting:**
|
||||
- 4-space indentation (matches EditorConfig)
|
||||
- Function names: lowercase_with_underscores
|
||||
- Constants: UPPER_CASE
|
||||
- Local variables: lowercase
|
||||
|
||||
**Structure:**
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Source shared utilities
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/lib/colors.sh" || true
|
||||
source "${SCRIPT_DIR}/lib/common.sh" || true
|
||||
|
||||
# Main function per action
|
||||
install_tool() {
|
||||
echo_info "Installing <tool>..."
|
||||
# Implementation
|
||||
}
|
||||
|
||||
update_tool() {
|
||||
echo_info "Updating <tool>..."
|
||||
# Implementation
|
||||
}
|
||||
|
||||
uninstall_tool() {
|
||||
echo_info "Uninstalling <tool>..."
|
||||
# Implementation
|
||||
}
|
||||
|
||||
reconcile_tool() {
|
||||
echo_info "Reconciling <tool>..."
|
||||
# Implementation
|
||||
}
|
||||
|
||||
# Action dispatcher
|
||||
ACTION="${1:-install}"
|
||||
case "$ACTION" in
|
||||
install) install_tool ;;
|
||||
update) update_tool ;;
|
||||
uninstall) uninstall_tool ;;
|
||||
reconcile) reconcile_tool ;;
|
||||
*) echo "Usage: $0 {install|update|uninstall|reconcile}"; exit 1 ;;
|
||||
esac
|
||||
```
|
||||
|
||||
**Error handling:**
|
||||
```bash
|
||||
# Good: Check command exists before using
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
echo_error "curl not found. Install it first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Good: Check return codes
|
||||
if ! download_file "$URL" "$DEST"; then
|
||||
echo_error "Download failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Good: Cleanup on error
|
||||
trap 'rm -rf "$TMPDIR"' EXIT ERR
|
||||
```
|
||||
|
||||
**Confirmation prompts:**
|
||||
```bash
|
||||
# Good: Skip prompt if FORCE_INSTALL=1
|
||||
if [[ "${FORCE_INSTALL:-0}" != "1" ]]; then
|
||||
read -p "Install <tool>? [y/N] " -n 1 -r
|
||||
echo
|
||||
[[ ! $REPLY =~ ^[Yy]$ ]] && exit 0
|
||||
fi
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
**Download verification:**
|
||||
```bash
|
||||
# Always use HTTPS
|
||||
URL="https://github.com/owner/repo/releases/download/..."
|
||||
|
||||
# Verify checksums when available
|
||||
EXPECTED_SHA256="abc123..."
|
||||
ACTUAL_SHA256=$(sha256sum "$FILE" | awk '{print $1}')
|
||||
if [[ "$ACTUAL_SHA256" != "$EXPECTED_SHA256" ]]; then
|
||||
echo_error "Checksum mismatch!"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
**Path safety:**
|
||||
```bash
|
||||
# Good: Quote variables, use absolute paths
|
||||
INSTALL_DIR="${HOME}/.local/bin"
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
mv "$TMPFILE" "$INSTALL_DIR/tool"
|
||||
|
||||
# Bad: Unquoted, relative paths
|
||||
mkdir -p $INSTALL_DIR
|
||||
mv tool bin/
|
||||
```
|
||||
|
||||
**Sudo usage:**
|
||||
```bash
|
||||
# Good: Prompt for sudo only when needed
|
||||
if [[ "$INSTALL_PREFIX" == "/usr/local" ]]; then
|
||||
if ! sudo -v; then
|
||||
echo_error "Sudo required for system installation"
|
||||
exit 1
|
||||
fi
|
||||
sudo mv "$FILE" "$INSTALL_PREFIX/bin/"
|
||||
else
|
||||
# User-level, no sudo
|
||||
mv "$FILE" "$INSTALL_PREFIX/bin/"
|
||||
fi
|
||||
```
|
||||
|
||||
**No secrets in scripts:**
|
||||
- No API keys, tokens, passwords in scripts
|
||||
- Use environment variables: `${GITHUB_TOKEN:-}`
|
||||
- Document required env vars in script comments
|
||||
|
||||
## PR/Commit Checklist
|
||||
|
||||
**Before commit:**
|
||||
- [ ] Run `shellcheck <script>` (if available)
|
||||
- [ ] Test install action: `./scripts/install_<tool>.sh`
|
||||
- [ ] Test update action: `./scripts/install_<tool>.sh update`
|
||||
- [ ] Test uninstall action: `./scripts/install_<tool>.sh uninstall`
|
||||
- [ ] Update `scripts/README.md` if new script or behavior change
|
||||
- [ ] Verify script permissions: `make scripts-perms`
|
||||
|
||||
**Script checklist:**
|
||||
- [ ] Shebang: `#!/usr/bin/env bash`
|
||||
- [ ] Strict mode: `set -euo pipefail`
|
||||
- [ ] Source shared lib: `source "${SCRIPT_DIR}/lib/colors.sh"`
|
||||
- [ ] Action dispatcher (install/update/uninstall/reconcile)
|
||||
- [ ] Error handling (check return codes, trap on exit)
|
||||
- [ ] Confirmation prompts (respect FORCE_INSTALL)
|
||||
- [ ] PATH updates (add to ~/.bashrc or ~/.zshrc if needed)
|
||||
|
||||
**Commit messages:**
|
||||
- `feat(scripts): add install_terraform.sh`
|
||||
- `fix(install-python): handle uv bootstrap failure`
|
||||
- `docs(scripts): update README with reconcile action`
|
||||
|
||||
## Good vs Bad Examples
|
||||
|
||||
**Good: Robust download with fallback**
|
||||
```bash
|
||||
download_file() {
|
||||
local url="$1"
|
||||
local dest="$2"
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fsSL "$url" -o "$dest"
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -q "$url" -O "$dest"
|
||||
else
|
||||
echo_error "Neither curl nor wget found"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
```
|
||||
|
||||
**Bad: Assumes curl exists**
|
||||
```bash
|
||||
download_file() {
|
||||
curl -fsSL "$1" -o "$2" # Fails if curl not installed
|
||||
}
|
||||
```
|
||||
|
||||
**Good: Version comparison**
|
||||
```bash
|
||||
version_gt() {
|
||||
test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"
|
||||
}
|
||||
|
||||
CURRENT_VERSION="1.2.3"
|
||||
LATEST_VERSION="1.3.0"
|
||||
|
||||
if version_gt "$LATEST_VERSION" "$CURRENT_VERSION"; then
|
||||
echo "Upgrade available"
|
||||
fi
|
||||
```
|
||||
|
||||
**Bad: String comparison for versions**
|
||||
```bash
|
||||
if [[ "$LATEST_VERSION" > "$CURRENT_VERSION" ]]; then
|
||||
# Wrong: "1.10.0" < "1.9.0" (string comparison)
|
||||
echo "Upgrade available"
|
||||
fi
|
||||
```
|
||||
|
||||
**Good: Cleanup on exit**
|
||||
```bash
|
||||
TMPDIR=$(mktemp -d)
|
||||
trap 'rm -rf "$TMPDIR"' EXIT ERR
|
||||
|
||||
# Download to temp
|
||||
download_file "$URL" "$TMPDIR/file"
|
||||
# ... process ...
|
||||
# Cleanup happens automatically via trap
|
||||
```
|
||||
|
||||
**Bad: Manual cleanup (error-prone)**
|
||||
```bash
|
||||
TMPDIR=$(mktemp -d)
|
||||
download_file "$URL" "$TMPDIR/file"
|
||||
# ... process ...
|
||||
rm -rf "$TMPDIR" # Skipped if earlier command fails
|
||||
```
|
||||
|
||||
**Good: Action-specific logic**
|
||||
```bash
|
||||
install_rust() {
|
||||
if command -v rustup >/dev/null 2>&1; then
|
||||
echo_warn "rustup already installed"
|
||||
return 0
|
||||
fi
|
||||
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
source "$HOME/.cargo/env"
|
||||
}
|
||||
|
||||
update_rust() {
|
||||
if ! command -v rustup >/dev/null 2>&1; then
|
||||
echo_error "rustup not installed. Run install first."
|
||||
return 1
|
||||
fi
|
||||
|
||||
rustup update
|
||||
}
|
||||
```
|
||||
|
||||
## When Stuck
|
||||
|
||||
**Script fails silently:**
|
||||
1. Add debug: `bash -x ./scripts/install_<tool>.sh`
|
||||
2. Check logs: `./scripts/install_<tool>.sh 2>&1 | tee install.log`
|
||||
3. Verify permissions: `ls -la scripts/`
|
||||
|
||||
**Download fails:**
|
||||
1. Check network: `curl -I https://github.com`
|
||||
2. Check URL: `echo "$URL"` (verify it's correct)
|
||||
3. Try manual download: `curl -fsSL "$URL"`
|
||||
|
||||
**Installation fails:**
|
||||
1. Check prerequisites (e.g., Python for uv, curl for rustup)
|
||||
2. Check disk space: `df -h`
|
||||
3. Check permissions: `ls -ld "$INSTALL_PREFIX"`
|
||||
|
||||
**PATH not updated:**
|
||||
1. Source shell config: `source ~/.bashrc` or `source ~/.zshrc`
|
||||
2. Check PATH: `echo $PATH | tr ':' '\n' | grep local`
|
||||
3. Verify binary location: `ls -la ~/.local/bin/<tool>`
|
||||
|
||||
**Reconcile fails:**
|
||||
1. Check current installation: `which <tool>`
|
||||
2. Check installation method: `cli_audit.py --only <tool>`
|
||||
3. Manually remove old version first: `apt remove <tool>` or `cargo uninstall <tool>`
|
||||
|
||||
**Documentation:**
|
||||
- Script-specific docs: [README.md](README.md) (this directory)
|
||||
- Troubleshooting: [../docs/TROUBLESHOOTING.md](../docs/TROUBLESHOOTING.md)
|
||||
- Architecture: [../docs/DEPLOYMENT.md](../docs/DEPLOYMENT.md#installation-scripts)
|
||||
|
||||
## House Rules
|
||||
|
||||
**Installation preferences** (Phase 2 planning):
|
||||
- User-level preferred: `~/.local/bin` (workstations)
|
||||
- System-level for servers: `/usr/local/bin`
|
||||
- Vendor tools first: rustup, nvm, uv over system packages
|
||||
- See [../docs/adr/ADR-002-package-manager-hierarchy.md](../docs/adr/ADR-002-package-manager-hierarchy.md)
|
||||
|
||||
**Reconciliation strategy:**
|
||||
- Parallel approach: Keep both installations, prefer user via PATH
|
||||
- No automatic removal (user chooses)
|
||||
- See [../docs/adr/ADR-003-parallel-installation-approach.md](../docs/adr/ADR-003-parallel-installation-approach.md)
|
||||
|
||||
**Version policy:**
|
||||
- Always latest by default
|
||||
- Warn on major version upgrades
|
||||
- See [../docs/adr/ADR-004-always-latest-version-policy.md](../docs/adr/ADR-004-always-latest-version-policy.md)
|
||||
|
||||
**Script structure:**
|
||||
- Multi-action support: install, update, uninstall, reconcile
|
||||
- Shared utilities in `lib/`
|
||||
- Consistent error handling and logging
|
||||
- Make integration via `make install-<tool>`
|
||||
|
||||
---
|
||||
|
||||
**Quick Start:** Run `make install-core` to install essential tools, then `make audit` to verify.
|
||||
|
||||
**Troubleshooting:** See [README.md](README.md) for per-script troubleshooting and [../docs/TROUBLESHOOTING.md](../docs/TROUBLESHOOTING.md) for general issues.
|
||||
Reference in New Issue
Block a user