Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:43:17 +08:00
commit 8967d326a7
30 changed files with 5154 additions and 0 deletions

View 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

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