that initial commit
This commit is contained in:
814
docs/standards/development-setup.md
Normal file
814
docs/standards/development-setup.md
Normal file
@@ -0,0 +1,814 @@
|
||||
# Development Setup Standards
|
||||
|
||||
## Purpose
|
||||
|
||||
This document defines the standard development setup procedure for StarPunk. It provides step-by-step instructions for developer agents and humans to create a fully functional development environment from scratch.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### System Requirements
|
||||
|
||||
- **Operating System**: Linux, macOS, or Windows with WSL2
|
||||
- **Python**: 3.11 or higher
|
||||
- **Disk Space**: 500MB minimum
|
||||
- **Network**: Internet connection for package downloads
|
||||
|
||||
### Required Tools
|
||||
|
||||
- **Python 3.11+**: System Python installation
|
||||
- **uv**: Python package and environment manager
|
||||
- **git**: Version control (for cloning repository)
|
||||
|
||||
## Installation Steps
|
||||
|
||||
### Step 1: Verify Python Installation
|
||||
|
||||
```bash
|
||||
# Check Python version
|
||||
python3 --version
|
||||
|
||||
# Expected output: Python 3.11.x or higher
|
||||
```
|
||||
|
||||
**If Python 3.11+ is not installed**:
|
||||
|
||||
**Ubuntu/Debian**:
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install python3.11 python3.11-venv
|
||||
```
|
||||
|
||||
**macOS**:
|
||||
```bash
|
||||
brew install python@3.11
|
||||
```
|
||||
|
||||
**Arch Linux**:
|
||||
```bash
|
||||
sudo pacman -S python
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 2: Install uv
|
||||
|
||||
**Linux/macOS (Recommended)**:
|
||||
```bash
|
||||
# Install via official installer
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
|
||||
# Verify installation
|
||||
uv --version
|
||||
```
|
||||
|
||||
**Alternative (via pip)**:
|
||||
```bash
|
||||
# Install uv using pip
|
||||
pip install --user uv
|
||||
|
||||
# Verify installation
|
||||
uv --version
|
||||
```
|
||||
|
||||
**Windows**:
|
||||
```powershell
|
||||
# Using PowerShell
|
||||
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
|
||||
|
||||
# Verify installation
|
||||
uv --version
|
||||
```
|
||||
|
||||
**Expected Output**: `uv 0.x.x` (any recent version)
|
||||
|
||||
See [ADR-006](/home/phil/Projects/starpunk/docs/decisions/ADR-006-python-virtual-environment-uv.md) for full uv standards.
|
||||
|
||||
---
|
||||
|
||||
### Step 3: Clone Repository
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/YOUR_USERNAME/starpunk.git
|
||||
cd starpunk
|
||||
|
||||
# Verify you're in the correct directory
|
||||
pwd
|
||||
# Expected: /path/to/starpunk
|
||||
|
||||
# List files to confirm
|
||||
ls -la
|
||||
# Should see: app.py, requirements.txt, starpunk/, etc.
|
||||
```
|
||||
|
||||
**If starting from scratch without git**:
|
||||
Create the project structure manually following [Project Structure Design](/home/phil/Projects/starpunk/docs/design/project-structure.md).
|
||||
|
||||
---
|
||||
|
||||
### Step 4: Create Virtual Environment
|
||||
|
||||
```bash
|
||||
# Create virtual environment using uv
|
||||
uv venv .venv --python 3.11
|
||||
|
||||
# Verify creation
|
||||
ls -la .venv
|
||||
|
||||
# Expected: bin/, lib/, include/, pyvenv.cfg
|
||||
```
|
||||
|
||||
**Verification**:
|
||||
```bash
|
||||
# Check Python executable exists
|
||||
.venv/bin/python --version
|
||||
|
||||
# Expected: Python 3.11.x
|
||||
```
|
||||
|
||||
**Important**: Virtual environment is created in `.venv/` directory at project root.
|
||||
|
||||
---
|
||||
|
||||
### Step 5: Install Dependencies
|
||||
|
||||
```bash
|
||||
# Install all production dependencies
|
||||
uv pip install -r requirements.txt
|
||||
|
||||
# Verify installation
|
||||
uv pip list
|
||||
|
||||
# Expected output should include:
|
||||
# - Flask (3.0.x)
|
||||
# - markdown (3.5.x)
|
||||
# - feedgen (1.0.x)
|
||||
# - httpx (0.27.x)
|
||||
# - python-dotenv (1.0.x)
|
||||
# - pytest (8.0.x)
|
||||
```
|
||||
|
||||
**For Development** (optional):
|
||||
```bash
|
||||
# Install development dependencies
|
||||
uv pip install -r requirements-dev.txt
|
||||
|
||||
# Includes: pytest-cov, black, flake8, mypy
|
||||
```
|
||||
|
||||
**Troubleshooting**:
|
||||
- If installation fails, check internet connection
|
||||
- Verify uv is installed correctly: `which uv`
|
||||
- Check Python version in venv: `.venv/bin/python --version`
|
||||
|
||||
---
|
||||
|
||||
### Step 6: Configure Environment
|
||||
|
||||
```bash
|
||||
# Copy environment template
|
||||
cp .env.example .env
|
||||
|
||||
# Edit .env file with your settings
|
||||
nano .env # or vim, code, etc.
|
||||
```
|
||||
|
||||
**Required Configuration** (edit `.env`):
|
||||
|
||||
```bash
|
||||
# Site Configuration
|
||||
SITE_URL=http://localhost:5000
|
||||
SITE_NAME=My StarPunk Site
|
||||
SITE_AUTHOR=Your Name
|
||||
|
||||
# Admin Authentication
|
||||
ADMIN_ME=https://your-website.com
|
||||
# ^ Replace with YOUR IndieWeb identity URL
|
||||
|
||||
# Session Security
|
||||
SESSION_SECRET=GENERATE_RANDOM_SECRET_HERE
|
||||
# ^ Generate with: python3 -c "import secrets; print(secrets.token_hex(32))"
|
||||
|
||||
# Data Paths
|
||||
DATA_PATH=./data
|
||||
NOTES_PATH=./data/notes
|
||||
DATABASE_PATH=./data/starpunk.db
|
||||
|
||||
# Flask Configuration
|
||||
FLASK_ENV=development
|
||||
FLASK_DEBUG=1
|
||||
```
|
||||
|
||||
**Generate SESSION_SECRET**:
|
||||
```bash
|
||||
# Generate cryptographically secure secret
|
||||
python3 -c "import secrets; print(secrets.token_hex(32))"
|
||||
|
||||
# Copy output and paste into .env as SESSION_SECRET value
|
||||
```
|
||||
|
||||
**Important**:
|
||||
- **NEVER commit `.env` file** to git
|
||||
- `.env.example` is a template and SHOULD be committed
|
||||
- Each developer/deployment has their own `.env`
|
||||
|
||||
---
|
||||
|
||||
### Step 7: Initialize Database
|
||||
|
||||
```bash
|
||||
# Create data directory structure
|
||||
mkdir -p data/notes
|
||||
|
||||
# Initialize database schema
|
||||
.venv/bin/python -c "from starpunk.database import init_db; init_db()"
|
||||
|
||||
# Verify database created
|
||||
ls -la data/
|
||||
|
||||
# Expected: starpunk.db file should exist
|
||||
```
|
||||
|
||||
**Alternative** (if Flask CLI is set up):
|
||||
```bash
|
||||
# Initialize via Flask CLI
|
||||
.venv/bin/flask db init
|
||||
```
|
||||
|
||||
**Verify Database Schema**:
|
||||
```bash
|
||||
# Check tables exist
|
||||
sqlite3 data/starpunk.db ".tables"
|
||||
|
||||
# Expected output: notes, sessions, tokens, auth_state
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 8: Verify Installation
|
||||
|
||||
```bash
|
||||
# Run all verification checks
|
||||
.venv/bin/python -c "
|
||||
from starpunk.config import load_config
|
||||
from starpunk.database import verify_db
|
||||
import flask
|
||||
print('✓ Flask version:', flask.__version__)
|
||||
print('✓ Config loads successfully')
|
||||
print('✓ Database schema verified')
|
||||
print('✓ Installation complete!')
|
||||
"
|
||||
```
|
||||
|
||||
**Manual Verification Checklist**:
|
||||
```bash
|
||||
# Check virtual environment
|
||||
[ -d ".venv" ] && echo "✓ Virtual environment exists" || echo "✗ Missing .venv"
|
||||
|
||||
# Check Python version
|
||||
.venv/bin/python --version | grep "3.11" && echo "✓ Python 3.11+" || echo "✗ Wrong Python version"
|
||||
|
||||
# Check Flask installed
|
||||
.venv/bin/python -c "import flask" && echo "✓ Flask installed" || echo "✗ Flask missing"
|
||||
|
||||
# Check .env file exists
|
||||
[ -f ".env" ] && echo "✓ .env configured" || echo "✗ Missing .env"
|
||||
|
||||
# Check data directory
|
||||
[ -d "data/notes" ] && echo "✓ Data directory exists" || echo "✗ Missing data/"
|
||||
|
||||
# Check database
|
||||
[ -f "data/starpunk.db" ] && echo "✓ Database initialized" || echo "✗ Missing database"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 9: Run Development Server
|
||||
|
||||
```bash
|
||||
# Run Flask development server
|
||||
.venv/bin/flask --app app.py run --debug
|
||||
|
||||
# Alternative: Run directly
|
||||
.venv/bin/python app.py
|
||||
```
|
||||
|
||||
**Expected Output**:
|
||||
```
|
||||
* Serving Flask app 'app.py'
|
||||
* Debug mode: on
|
||||
WARNING: This is a development server. Do not use it in a production deployment.
|
||||
* Running on http://127.0.0.1:5000
|
||||
Press CTRL+C to quit
|
||||
```
|
||||
|
||||
**Access Application**:
|
||||
- Open browser to: `http://localhost:5000`
|
||||
- Should see StarPunk homepage
|
||||
- No notes will exist on first run
|
||||
|
||||
**Stop Server**:
|
||||
- Press `Ctrl+C` in terminal
|
||||
|
||||
---
|
||||
|
||||
### Step 10: Run Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
.venv/bin/pytest
|
||||
|
||||
# Run with coverage report
|
||||
.venv/bin/pytest --cov=starpunk tests/
|
||||
|
||||
# Run specific test file
|
||||
.venv/bin/pytest tests/test_database.py
|
||||
|
||||
# Run with verbose output
|
||||
.venv/bin/pytest -v
|
||||
```
|
||||
|
||||
**Expected Output** (once tests are written):
|
||||
```
|
||||
======================== test session starts =========================
|
||||
collected 45 items
|
||||
|
||||
tests/test_auth.py ........ [ 17%]
|
||||
tests/test_database.py ....... [ 33%]
|
||||
tests/test_feed.py ..... [ 44%]
|
||||
tests/test_micropub.py .......... [ 66%]
|
||||
tests/test_notes.py .......... [ 88%]
|
||||
tests/test_utils.py ..... [100%]
|
||||
|
||||
========================= 45 passed in 2.34s =========================
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration Management
|
||||
|
||||
### Environment Variables
|
||||
|
||||
All configuration is managed via environment variables loaded from `.env` file.
|
||||
|
||||
**Configuration Categories**:
|
||||
|
||||
1. **Site Identity**
|
||||
- `SITE_URL` - Public URL of site
|
||||
- `SITE_NAME` - Site title
|
||||
- `SITE_AUTHOR` - Author name
|
||||
- `SITE_DESCRIPTION` - Site description (for RSS)
|
||||
|
||||
2. **Authentication**
|
||||
- `ADMIN_ME` - Admin's IndieWeb identity URL
|
||||
- `SESSION_SECRET` - Secret key for session signing
|
||||
- `SESSION_LIFETIME` - Session duration in days (default: 30)
|
||||
|
||||
3. **Data Storage**
|
||||
- `DATA_PATH` - Base data directory (default: `./data`)
|
||||
- `NOTES_PATH` - Notes directory (default: `./data/notes`)
|
||||
- `DATABASE_PATH` - SQLite database path (default: `./data/starpunk.db`)
|
||||
|
||||
4. **Flask Settings**
|
||||
- `FLASK_ENV` - Environment: `development` or `production`
|
||||
- `FLASK_DEBUG` - Debug mode: `1` (on) or `0` (off)
|
||||
- `FLASK_SECRET_KEY` - Falls back to `SESSION_SECRET`
|
||||
|
||||
5. **External Services**
|
||||
- `INDIELOGIN_URL` - IndieLogin service URL (default: `https://indielogin.com`)
|
||||
|
||||
### Configuration Loading
|
||||
|
||||
Configuration is loaded in this priority order:
|
||||
|
||||
1. **Environment variables** (highest priority)
|
||||
2. **`.env` file** (loaded via python-dotenv)
|
||||
3. **Default values** (in `config.py`)
|
||||
|
||||
**Example** (`starpunk/config.py`):
|
||||
```python
|
||||
import os
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load .env file
|
||||
load_dotenv()
|
||||
|
||||
class Config:
|
||||
# Site
|
||||
SITE_URL = os.getenv('SITE_URL', 'http://localhost:5000')
|
||||
SITE_NAME = os.getenv('SITE_NAME', 'StarPunk')
|
||||
|
||||
# Security
|
||||
SECRET_KEY = os.getenv('SESSION_SECRET')
|
||||
if not SECRET_KEY:
|
||||
raise ValueError("SESSION_SECRET must be set in .env")
|
||||
|
||||
# Data paths
|
||||
DATA_PATH = Path(os.getenv('DATA_PATH', './data'))
|
||||
NOTES_PATH = Path(os.getenv('NOTES_PATH', './data/notes'))
|
||||
DATABASE_PATH = Path(os.getenv('DATABASE_PATH', './data/starpunk.db'))
|
||||
|
||||
# Flask
|
||||
DEBUG = os.getenv('FLASK_DEBUG', '1') == '1'
|
||||
ENV = os.getenv('FLASK_ENV', 'development')
|
||||
```
|
||||
|
||||
### Development vs Production Settings
|
||||
|
||||
**Development** (`.env`):
|
||||
```bash
|
||||
FLASK_ENV=development
|
||||
FLASK_DEBUG=1
|
||||
SITE_URL=http://localhost:5000
|
||||
```
|
||||
|
||||
**Production** (`.env`):
|
||||
```bash
|
||||
FLASK_ENV=production
|
||||
FLASK_DEBUG=0
|
||||
SITE_URL=https://your-domain.com
|
||||
```
|
||||
|
||||
**Key Differences**:
|
||||
- **Debug mode**: Development has detailed errors, production hides them
|
||||
- **URL**: Development uses localhost, production uses actual domain
|
||||
- **HTTPS**: Production requires HTTPS, development doesn't
|
||||
- **Session cookies**: Production sets `Secure` flag, development doesn't
|
||||
|
||||
---
|
||||
|
||||
## Common Development Tasks
|
||||
|
||||
### Starting Development
|
||||
|
||||
```bash
|
||||
# 1. Activate virtual environment context (via uv)
|
||||
cd /path/to/starpunk
|
||||
|
||||
# 2. Run development server
|
||||
.venv/bin/flask --app app.py run --debug
|
||||
|
||||
# Server runs on http://localhost:5000
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# All tests
|
||||
.venv/bin/pytest
|
||||
|
||||
# Specific module
|
||||
.venv/bin/pytest tests/test_notes.py
|
||||
|
||||
# Specific test
|
||||
.venv/bin/pytest tests/test_notes.py::test_create_note
|
||||
|
||||
# With coverage
|
||||
.venv/bin/pytest --cov=starpunk --cov-report=html tests/
|
||||
# Open htmlcov/index.html to view coverage report
|
||||
```
|
||||
|
||||
### Code Formatting
|
||||
|
||||
```bash
|
||||
# Format all Python code
|
||||
.venv/bin/black starpunk/ tests/
|
||||
|
||||
# Check formatting without changing
|
||||
.venv/bin/black --check starpunk/ tests/
|
||||
|
||||
# Format specific file
|
||||
.venv/bin/black starpunk/notes.py
|
||||
```
|
||||
|
||||
### Linting
|
||||
|
||||
```bash
|
||||
# Lint all code
|
||||
.venv/bin/flake8 starpunk/ tests/
|
||||
|
||||
# Lint specific file
|
||||
.venv/bin/flake8 starpunk/notes.py
|
||||
|
||||
# Type checking (if mypy installed)
|
||||
.venv/bin/mypy starpunk/
|
||||
```
|
||||
|
||||
### Adding Dependencies
|
||||
|
||||
```bash
|
||||
# 1. Install new package
|
||||
uv pip install package-name
|
||||
|
||||
# 2. Update requirements.txt
|
||||
uv pip freeze | sort > requirements.txt
|
||||
|
||||
# 3. Commit updated requirements.txt
|
||||
git add requirements.txt
|
||||
git commit -m "Add package-name dependency"
|
||||
```
|
||||
|
||||
### Database Operations
|
||||
|
||||
```bash
|
||||
# Reset database (WARNING: deletes all data)
|
||||
rm data/starpunk.db
|
||||
.venv/bin/python -c "from starpunk.database import init_db; init_db()"
|
||||
|
||||
# Backup database
|
||||
cp data/starpunk.db data/starpunk.db.backup
|
||||
|
||||
# Inspect database
|
||||
sqlite3 data/starpunk.db
|
||||
# SQL> .tables
|
||||
# SQL> SELECT * FROM notes;
|
||||
# SQL> .quit
|
||||
```
|
||||
|
||||
### Creating a Note Manually
|
||||
|
||||
```bash
|
||||
# 1. Create directory for current month
|
||||
mkdir -p data/notes/$(date +%Y)/$(date +%m)
|
||||
|
||||
# 2. Create markdown file
|
||||
cat > data/notes/$(date +%Y)/$(date +%m)/test-note.md << 'EOF'
|
||||
This is a test note created manually.
|
||||
|
||||
It has **markdown** formatting and `code`.
|
||||
EOF
|
||||
|
||||
# 3. Add to database
|
||||
.venv/bin/python -c "
|
||||
from starpunk.notes import scan_and_import_notes
|
||||
scan_and_import_notes()
|
||||
"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: Virtual Environment Not Found
|
||||
|
||||
**Symptom**: `bash: .venv/bin/python: No such file or directory`
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Check if .venv exists
|
||||
ls -la .venv
|
||||
|
||||
# If not, create it
|
||||
uv venv .venv --python 3.11
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Issue: Import Errors
|
||||
|
||||
**Symptom**: `ModuleNotFoundError: No module named 'flask'`
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Install dependencies
|
||||
uv pip install -r requirements.txt
|
||||
|
||||
# Verify Flask is installed
|
||||
.venv/bin/python -c "import flask; print(flask.__version__)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Issue: Database Not Found
|
||||
|
||||
**Symptom**: `sqlite3.OperationalError: unable to open database file`
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Create data directory
|
||||
mkdir -p data/notes
|
||||
|
||||
# Initialize database
|
||||
.venv/bin/python -c "from starpunk.database import init_db; init_db()"
|
||||
|
||||
# Verify
|
||||
ls -la data/starpunk.db
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Issue: Port Already in Use
|
||||
|
||||
**Symptom**: `OSError: [Errno 98] Address already in use`
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Find process using port 5000
|
||||
lsof -i :5000
|
||||
|
||||
# Kill the process
|
||||
kill -9 <PID>
|
||||
|
||||
# Or use different port
|
||||
.venv/bin/flask run --port 5001
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Issue: Missing SESSION_SECRET
|
||||
|
||||
**Symptom**: `ValueError: SESSION_SECRET must be set in .env`
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Generate secret
|
||||
python3 -c "import secrets; print(secrets.token_hex(32))"
|
||||
|
||||
# Add to .env file
|
||||
echo "SESSION_SECRET=<generated-secret>" >> .env
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Issue: Permission Denied on data/
|
||||
|
||||
**Symptom**: `PermissionError: [Errno 13] Permission denied: 'data/notes'`
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Fix permissions
|
||||
chmod -R 755 data/
|
||||
chmod 644 data/starpunk.db # If exists
|
||||
|
||||
# Verify
|
||||
ls -la data/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Agent-Specific Standards
|
||||
|
||||
### For AI Developer Agents
|
||||
|
||||
When setting up development environment, agents MUST:
|
||||
|
||||
1. **Use absolute paths** in all bash commands
|
||||
```bash
|
||||
# CORRECT
|
||||
/home/phil/Projects/starpunk/.venv/bin/python
|
||||
|
||||
# WRONG
|
||||
.venv/bin/python
|
||||
```
|
||||
|
||||
2. **Check before creating** virtual environment
|
||||
```bash
|
||||
if [ ! -d "/home/phil/Projects/starpunk/.venv" ]; then
|
||||
uv venv /home/phil/Projects/starpunk/.venv --python 3.11
|
||||
fi
|
||||
```
|
||||
|
||||
3. **Verify each step** before proceeding
|
||||
```bash
|
||||
# Example verification
|
||||
[ -f "/home/phil/Projects/starpunk/.env" ] || echo "ERROR: .env missing"
|
||||
```
|
||||
|
||||
4. **Never modify global Python**
|
||||
```bash
|
||||
# FORBIDDEN
|
||||
pip install flask
|
||||
|
||||
# CORRECT
|
||||
uv pip install flask # Uses active venv
|
||||
```
|
||||
|
||||
See [ADR-006](/home/phil/Projects/starpunk/docs/decisions/ADR-006-python-virtual-environment-uv.md) for complete agent standards.
|
||||
|
||||
---
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Secrets Management
|
||||
|
||||
**DO**:
|
||||
- Store all secrets in `.env` file
|
||||
- Generate strong random secrets
|
||||
- Use different secrets for dev/production
|
||||
- Never log secrets
|
||||
|
||||
**DON'T**:
|
||||
- Commit `.env` to version control
|
||||
- Hardcode secrets in code
|
||||
- Share secrets via email/chat
|
||||
- Reuse secrets across projects
|
||||
|
||||
### File Permissions
|
||||
|
||||
**Recommended Permissions**:
|
||||
```bash
|
||||
# Application code
|
||||
chmod 644 *.py
|
||||
chmod 755 starpunk/
|
||||
|
||||
# Data directory
|
||||
chmod 755 data/
|
||||
chmod 755 data/notes/
|
||||
chmod 644 data/starpunk.db
|
||||
chmod 644 data/notes/**/*.md
|
||||
|
||||
# Configuration
|
||||
chmod 600 .env # Only owner can read
|
||||
```
|
||||
|
||||
### Development-Only Features
|
||||
|
||||
In development mode (FLASK_DEBUG=1):
|
||||
- Detailed error pages with stack traces
|
||||
- Auto-reload on code changes
|
||||
- No HTTPS requirement
|
||||
- More verbose logging
|
||||
|
||||
**NEVER run production with FLASK_DEBUG=1**
|
||||
|
||||
---
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
After completing setup, verify:
|
||||
|
||||
- [ ] Python 3.11+ installed
|
||||
- [ ] uv installed and working
|
||||
- [ ] Virtual environment created in `.venv/`
|
||||
- [ ] All dependencies installed
|
||||
- [ ] `.env` file configured with SESSION_SECRET
|
||||
- [ ] `data/` directory exists and is gitignored
|
||||
- [ ] Database initialized with all tables
|
||||
- [ ] Development server starts successfully
|
||||
- [ ] Can access http://localhost:5000
|
||||
- [ ] Tests run successfully
|
||||
- [ ] Code formatting works (black)
|
||||
- [ ] Linting works (flake8)
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Essential Commands
|
||||
|
||||
```bash
|
||||
# Start development server
|
||||
.venv/bin/flask --app app.py run --debug
|
||||
|
||||
# Run tests
|
||||
.venv/bin/pytest
|
||||
|
||||
# Format code
|
||||
.venv/bin/black starpunk/ tests/
|
||||
|
||||
# Install dependency
|
||||
uv pip install <package>
|
||||
uv pip freeze | sort > requirements.txt
|
||||
|
||||
# Database reset
|
||||
rm data/starpunk.db
|
||||
.venv/bin/python -c "from starpunk.database import init_db; init_db()"
|
||||
|
||||
# Generate secret
|
||||
python3 -c "import secrets; print(secrets.token_hex(32))"
|
||||
```
|
||||
|
||||
### File Locations
|
||||
|
||||
```
|
||||
.venv/ # Virtual environment
|
||||
.env # Configuration (secret, gitignored)
|
||||
.env.example # Configuration template
|
||||
requirements.txt # Production dependencies
|
||||
data/ # User data (gitignored)
|
||||
starpunk/ # Application code
|
||||
tests/ # Test suite
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
After setup is complete:
|
||||
|
||||
1. Read [Python Coding Standards](/home/phil/Projects/starpunk/docs/standards/python-coding-standards.md)
|
||||
2. Review [Project Structure](/home/phil/Projects/starpunk/docs/design/project-structure.md)
|
||||
3. Explore [Architecture Overview](/home/phil/Projects/starpunk/docs/architecture/overview.md)
|
||||
4. Start implementing features following [API Contracts](/home/phil/Projects/starpunk/docs/design/api-contracts.md)
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [ADR-006: Python Virtual Environment with uv](/home/phil/Projects/starpunk/docs/decisions/ADR-006-python-virtual-environment-uv.md)
|
||||
- [Project Structure Design](/home/phil/Projects/starpunk/docs/design/project-structure.md)
|
||||
- [Python Coding Standards](/home/phil/Projects/starpunk/docs/standards/python-coding-standards.md)
|
||||
- [Flask Configuration Handling](https://flask.palletsprojects.com/en/3.0.x/config/)
|
||||
- [python-dotenv Documentation](https://pypi.org/project/python-dotenv/)
|
||||
- [uv Documentation](https://docs.astral.sh/uv/)
|
||||
472
docs/standards/documentation-organization.md
Normal file
472
docs/standards/documentation-organization.md
Normal file
@@ -0,0 +1,472 @@
|
||||
# Documentation Organization Standard
|
||||
|
||||
## Purpose
|
||||
|
||||
This document defines the organization and structure of all StarPunk documentation. It establishes clear guidelines for what types of documents belong where, naming conventions, and when to create each type.
|
||||
|
||||
## Philosophy
|
||||
|
||||
Documentation follows the same principle as code: "Every document must justify its existence." Documents should be:
|
||||
|
||||
- **Actionable**: Provide clear guidance for implementation
|
||||
- **Discoverable**: Easy to find based on purpose
|
||||
- **Maintainable**: Updated as decisions evolve
|
||||
- **Minimal**: No redundant or unnecessary documentation
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
docs/
|
||||
├── architecture/ # High-level system design and overviews
|
||||
├── decisions/ # Architecture Decision Records (ADRs)
|
||||
├── design/ # Detailed technical designs and specifications
|
||||
└── standards/ # Coding standards, conventions, and guidelines
|
||||
```
|
||||
|
||||
## Document Types
|
||||
|
||||
### 1. Architecture Documents (`docs/architecture/`)
|
||||
|
||||
**Purpose**: High-level system architecture, component relationships, and technology overviews.
|
||||
|
||||
**When to Create**:
|
||||
- Describing the overall system architecture
|
||||
- Documenting major subsystems and their interactions
|
||||
- Explaining technology stack choices (overview)
|
||||
- Defining deployment architecture
|
||||
- Documenting security architecture
|
||||
- Creating data flow diagrams
|
||||
|
||||
**Naming Convention**: `{topic}.md`
|
||||
|
||||
**Examples**:
|
||||
- `overview.md` - System architecture overview
|
||||
- `components.md` - Component descriptions and relationships
|
||||
- `data-flow.md` - How data moves through the system
|
||||
- `security.md` - Security architecture and threat model
|
||||
- `deployment.md` - Deployment architecture and strategies
|
||||
- `technology-stack.md` - Complete technology stack with rationale
|
||||
|
||||
**Characteristics**:
|
||||
- Broad scope, high-level view
|
||||
- Focus on "what" and "why" at system level
|
||||
- Includes diagrams and visual representations
|
||||
- Describes component interactions
|
||||
- Documents architectural patterns
|
||||
- References relevant ADRs
|
||||
|
||||
**Template Structure**:
|
||||
```markdown
|
||||
# {Topic} Architecture
|
||||
|
||||
## Overview
|
||||
[High-level description]
|
||||
|
||||
## Components
|
||||
[Major components and their roles]
|
||||
|
||||
## Interactions
|
||||
[How components communicate]
|
||||
|
||||
## Patterns
|
||||
[Architectural patterns employed]
|
||||
|
||||
## Diagrams
|
||||
[Visual representations]
|
||||
|
||||
## References
|
||||
[Links to related ADRs and design docs]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Architecture Decision Records (`docs/decisions/`)
|
||||
|
||||
**Purpose**: Document significant architectural decisions with context, rationale, and alternatives considered.
|
||||
|
||||
**When to Create**:
|
||||
- Selecting technologies or libraries
|
||||
- Choosing between architectural approaches
|
||||
- Making trade-offs that affect multiple components
|
||||
- Establishing patterns or standards
|
||||
- Accepting technical debt
|
||||
- Rejecting common alternatives
|
||||
|
||||
**Naming Convention**: `ADR-{NNN}-{short-title}.md`
|
||||
- NNN = Zero-padded sequential number (001, 002, etc.)
|
||||
- short-title = Kebab-case description
|
||||
|
||||
**Examples**:
|
||||
- `ADR-001-python-web-framework.md`
|
||||
- `ADR-002-flask-extensions.md`
|
||||
- `ADR-003-frontend-technology.md`
|
||||
- `ADR-004-file-based-note-storage.md`
|
||||
- `ADR-005-indielogin-authentication.md`
|
||||
- `ADR-006-python-virtual-environment-uv.md`
|
||||
|
||||
**Characteristics**:
|
||||
- Focused on a single decision
|
||||
- Documents the decision-making process
|
||||
- Includes evaluation criteria
|
||||
- Lists alternatives considered with scores
|
||||
- Explains trade-offs and consequences
|
||||
- Has a status (Proposed, Accepted, Superseded)
|
||||
- Immutable once accepted (new ADR to change)
|
||||
|
||||
**Required Template**:
|
||||
```markdown
|
||||
# ADR-{NNN}: {Title}
|
||||
|
||||
## Status
|
||||
{Proposed|Accepted|Superseded by ADR-XXX}
|
||||
|
||||
## Context
|
||||
What is the issue we're addressing? What are the requirements?
|
||||
What constraints do we face?
|
||||
|
||||
## Decision
|
||||
What have we decided? Be specific and clear.
|
||||
|
||||
## Rationale
|
||||
Why did we make this decision?
|
||||
|
||||
### {Technology/Approach} Score: X/10
|
||||
- Simplicity Score: X/10 - [explanation]
|
||||
- Fitness Score: X/10 - [explanation]
|
||||
- Maintenance Score: X/10 - [explanation]
|
||||
- Standards Compliance: Pass/Fail - [explanation]
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
- [List benefits]
|
||||
|
||||
### Negative
|
||||
- [List drawbacks]
|
||||
|
||||
### Mitigation
|
||||
- [How we address drawbacks]
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
### {Alternative 1} (Rejected/Considered)
|
||||
- Simplicity: X/10 - [explanation]
|
||||
- Fitness: X/10 - [explanation]
|
||||
- Maintenance: X/10 - [explanation]
|
||||
- Verdict: [Why rejected]
|
||||
|
||||
### {Alternative 2} (Rejected/Considered)
|
||||
[Same structure]
|
||||
|
||||
## Implementation Notes
|
||||
[Any specific guidance for implementing this decision]
|
||||
|
||||
## References
|
||||
- [Links to specifications, documentation, related ADRs]
|
||||
```
|
||||
|
||||
**Status Values**:
|
||||
- **Proposed**: Decision is under consideration
|
||||
- **Accepted**: Decision is final and should be implemented
|
||||
- **Superseded**: Decision has been replaced (link to new ADR)
|
||||
|
||||
**Numbering Rules**:
|
||||
- Numbers are sequential starting from 001
|
||||
- Never reuse or skip numbers
|
||||
- Numbers are assigned when ADR is created, not when accepted
|
||||
- Numbers provide chronological history
|
||||
|
||||
---
|
||||
|
||||
### 3. Design Documents (`docs/design/`)
|
||||
|
||||
**Purpose**: Detailed technical specifications for implementation, including schemas, APIs, file formats, and component interfaces.
|
||||
|
||||
**When to Create**:
|
||||
- Defining database schemas
|
||||
- Specifying API contracts and endpoints
|
||||
- Designing file formats and structures
|
||||
- Defining component interfaces
|
||||
- Specifying configuration formats
|
||||
- Documenting project structure
|
||||
- Creating initial setup specifications
|
||||
|
||||
**Naming Convention**: `{component-or-feature}.md`
|
||||
|
||||
**Examples**:
|
||||
- `project-structure.md` - Complete directory and file organization
|
||||
- `database-schema.md` - Database tables, columns, indexes, relationships
|
||||
- `api-contracts.md` - RESTful API endpoint specifications
|
||||
- `micropub-endpoint.md` - Micropub API implementation details
|
||||
- `rss-feed-format.md` - RSS feed structure and generation
|
||||
- `ui-patterns.md` - User interface patterns and components
|
||||
- `component-interfaces.md` - How components communicate
|
||||
- `initial-files.md` - Bootstrap files and configurations
|
||||
- `authentication-flow.md` - Detailed auth implementation
|
||||
|
||||
**Characteristics**:
|
||||
- Detailed and specific
|
||||
- Implementation-ready specifications
|
||||
- Includes code examples, schemas, formats
|
||||
- Defines exact file contents when applicable
|
||||
- Provides acceptance criteria
|
||||
- Focus on "how" at implementation level
|
||||
- Should be sufficient for developer to implement
|
||||
|
||||
**Template Structure**:
|
||||
```markdown
|
||||
# {Component/Feature} Design
|
||||
|
||||
## Purpose
|
||||
[What this component/feature does]
|
||||
|
||||
## Overview
|
||||
[High-level description]
|
||||
|
||||
## Specification
|
||||
|
||||
### {Aspect 1}
|
||||
[Detailed specification with examples]
|
||||
|
||||
### {Aspect 2}
|
||||
[Detailed specification with examples]
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### File Structure
|
||||
[If applicable]
|
||||
|
||||
### API Endpoints
|
||||
[If applicable]
|
||||
|
||||
### Database Schema
|
||||
[If applicable]
|
||||
|
||||
### Configuration
|
||||
[If applicable]
|
||||
|
||||
## Examples
|
||||
[Concrete examples of usage]
|
||||
|
||||
## Acceptance Criteria
|
||||
[How to verify correct implementation]
|
||||
|
||||
## References
|
||||
[Related ADRs, standards, specifications]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Standards Documents (`docs/standards/`)
|
||||
|
||||
**Purpose**: Establish coding conventions, development practices, and guidelines that ensure consistency.
|
||||
|
||||
**When to Create**:
|
||||
- Defining coding style guidelines
|
||||
- Establishing naming conventions
|
||||
- Documenting development setup procedures
|
||||
- Creating testing standards
|
||||
- Defining commit message formats
|
||||
- Establishing documentation standards
|
||||
- Setting API design principles
|
||||
|
||||
**Naming Convention**: `{topic}-standards.md` or `{topic}-setup.md`
|
||||
|
||||
**Examples**:
|
||||
- `documentation-organization.md` - This document
|
||||
- `python-coding-standards.md` - Python style guide
|
||||
- `development-setup.md` - Setup procedures
|
||||
- `testing-strategy.md` - Testing approach and standards
|
||||
- `api-design.md` - API design principles
|
||||
- `indieweb-compliance.md` - How to meet IndieWeb specs
|
||||
- `commit-conventions.md` - Git commit standards
|
||||
|
||||
**Characteristics**:
|
||||
- Prescriptive and normative
|
||||
- Define consistent practices
|
||||
- Include examples of good/bad patterns
|
||||
- Focus on "how we work"
|
||||
- Living documents (updated as practices evolve)
|
||||
- Should include rationale for standards
|
||||
|
||||
**Template Structure**:
|
||||
```markdown
|
||||
# {Topic} Standards
|
||||
|
||||
## Purpose
|
||||
[Why these standards exist]
|
||||
|
||||
## Principles
|
||||
[Core principles underlying the standards]
|
||||
|
||||
## Standards
|
||||
|
||||
### {Standard Category 1}
|
||||
[Detailed standard with examples]
|
||||
|
||||
**Good Example**:
|
||||
```[code/example]
|
||||
```
|
||||
|
||||
**Bad Example**:
|
||||
```[code/example]
|
||||
```
|
||||
|
||||
**Rationale**: [Why this is the standard]
|
||||
|
||||
### {Standard Category 2}
|
||||
[Same structure]
|
||||
|
||||
## Enforcement
|
||||
[How standards are enforced - tools, reviews, etc.]
|
||||
|
||||
## Exceptions
|
||||
[When it's acceptable to deviate]
|
||||
|
||||
## References
|
||||
[Related standards, tools, specifications]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cross-References
|
||||
|
||||
Documents should reference each other appropriately:
|
||||
|
||||
- **ADRs** → Should be referenced by Architecture, Design, and Standards docs
|
||||
- **Architecture** → References ADRs, links to Design docs
|
||||
- **Design** → References ADRs, may reference Standards
|
||||
- **Standards** → May reference ADRs for rationale
|
||||
|
||||
**Reference Format**:
|
||||
```markdown
|
||||
See [ADR-001: Python Web Framework](/home/phil/Projects/starpunk/docs/decisions/ADR-001-python-web-framework.md)
|
||||
See [Database Schema Design](/home/phil/Projects/starpunk/docs/design/database-schema.md)
|
||||
See [Python Coding Standards](/home/phil/Projects/starpunk/docs/standards/python-coding-standards.md)
|
||||
```
|
||||
|
||||
## Document Lifecycle
|
||||
|
||||
### Creating Documents
|
||||
|
||||
1. **Determine Type**: Which category does this belong in?
|
||||
2. **Check for Existing**: Does a similar document already exist?
|
||||
3. **Name Appropriately**: Follow naming conventions
|
||||
4. **Use Template**: Start from the appropriate template
|
||||
5. **Be Specific**: Include actionable details
|
||||
6. **Reference Related Docs**: Link to ADRs and other docs
|
||||
|
||||
### Updating Documents
|
||||
|
||||
**ADRs**: Once accepted, ADRs are immutable. To change a decision:
|
||||
- Create a new ADR
|
||||
- Reference the old ADR
|
||||
- Mark old ADR as "Superseded by ADR-XXX"
|
||||
|
||||
**Other Documents**: Living documents that should be updated:
|
||||
- Add changelog section if document changes significantly
|
||||
- Update references when related docs change
|
||||
- Keep in sync with actual implementation
|
||||
|
||||
### Deprecating Documents
|
||||
|
||||
**Don't Delete**: Mark as deprecated instead
|
||||
- Add "DEPRECATED" to title
|
||||
- Add note explaining why and what replaced it
|
||||
- Keep for historical reference
|
||||
|
||||
## Quality Checklist
|
||||
|
||||
Before finalizing any document:
|
||||
|
||||
- [ ] Is this the right document type for this content?
|
||||
- [ ] Is the file in the correct directory?
|
||||
- [ ] Does it follow the naming convention?
|
||||
- [ ] Does it use the appropriate template?
|
||||
- [ ] Is the content actionable and specific?
|
||||
- [ ] Are all references correct and complete?
|
||||
- [ ] Are there code examples where helpful?
|
||||
- [ ] Is the rationale clear?
|
||||
- [ ] Can a developer implement from this?
|
||||
- [ ] Have I removed unnecessary content?
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
**Avoid**:
|
||||
- Mixing document types (ADR with design specs)
|
||||
- Redundant documentation (saying same thing in multiple places)
|
||||
- Vague specifications ("should be fast", "user-friendly")
|
||||
- Missing rationale (standards without explaining why)
|
||||
- Orphaned documents (not referenced anywhere)
|
||||
- Documentation for documentation's sake
|
||||
- Copy-pasting without adapting
|
||||
|
||||
## Tools and Automation
|
||||
|
||||
**Validation**:
|
||||
- Check links: Ensure all references point to existing files
|
||||
- Naming validation: Verify naming conventions
|
||||
- Template compliance: Ensure required sections exist
|
||||
|
||||
**Future Enhancements** (V2):
|
||||
- Automated ADR numbering
|
||||
- Documentation index generator
|
||||
- Link checker
|
||||
- Template enforcement
|
||||
|
||||
## Examples
|
||||
|
||||
### When to Create Which Document Type
|
||||
|
||||
**Scenario**: Choosing between SQLite and PostgreSQL
|
||||
|
||||
→ Create an ADR (`ADR-007-database-engine.md`)
|
||||
- This is a technology decision
|
||||
- Needs evaluation of alternatives
|
||||
- Has long-term architectural impact
|
||||
|
||||
**Scenario**: Defining the exact database schema
|
||||
|
||||
→ Create a Design doc (`database-schema.md`)
|
||||
- This is detailed specification
|
||||
- Implementation-ready
|
||||
- May evolve as requirements change
|
||||
|
||||
**Scenario**: Establishing how to write database migrations
|
||||
|
||||
→ Create a Standards doc (`database-migration-standards.md`)
|
||||
- This is a practice/process
|
||||
- Defines consistent approach
|
||||
- Applies to all database changes
|
||||
|
||||
**Scenario**: Explaining how authentication, database, and file storage work together
|
||||
|
||||
→ Create an Architecture doc or update existing (`architecture/overview.md`)
|
||||
- This is system-level interaction
|
||||
- Cross-component relationship
|
||||
- High-level understanding
|
||||
|
||||
## Summary
|
||||
|
||||
| Type | Purpose | Location | Mutability | Naming |
|
||||
|------|---------|----------|------------|--------|
|
||||
| **Architecture** | System-level design | `docs/architecture/` | Living | `{topic}.md` |
|
||||
| **ADR** | Decision records | `docs/decisions/` | Immutable | `ADR-{NNN}-{title}.md` |
|
||||
| **Design** | Implementation specs | `docs/design/` | Living | `{component}.md` |
|
||||
| **Standards** | Coding/process guidelines | `docs/standards/` | Living | `{topic}-standards.md` |
|
||||
|
||||
## Principles Summary
|
||||
|
||||
1. **One Document Type Per File**: Don't mix ADR with Design specs
|
||||
2. **Right Place, Right Name**: Follow conventions strictly
|
||||
3. **Actionable Content**: Documentation should enable implementation
|
||||
4. **Minimal but Complete**: Every document must justify its existence
|
||||
5. **Reference Richly**: Link to related documents
|
||||
6. **Update Living Docs**: Keep non-ADR docs current
|
||||
7. **Never Delete**: Deprecate instead for historical reference
|
||||
|
||||
## References
|
||||
|
||||
- Michael Nygard's ADR pattern: https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions
|
||||
- Arc42 documentation: https://arc42.org/
|
||||
- Diátaxis documentation framework: https://diataxis.fr/
|
||||
724
docs/standards/git-branching-strategy.md
Normal file
724
docs/standards/git-branching-strategy.md
Normal file
@@ -0,0 +1,724 @@
|
||||
# Git Branching Strategy
|
||||
|
||||
## Overview
|
||||
|
||||
This document defines the git branching strategy for StarPunk. The strategy balances simplicity with discipline, appropriate for a personal/small-team project while supporting semantic versioning and clean releases.
|
||||
|
||||
**Philosophy**: Keep it simple. Minimize long-lived branches. Integrate frequently.
|
||||
|
||||
## Primary Branch
|
||||
|
||||
**Branch**: `main`
|
||||
|
||||
**Purpose**: The primary development branch and source of truth
|
||||
|
||||
**Characteristics**:
|
||||
- Always contains the latest development code
|
||||
- Should be stable and pass all tests
|
||||
- Protected from direct commits (use pull requests)
|
||||
- Tagged for releases
|
||||
- Never rewritten or force-pushed
|
||||
|
||||
**Version state**: Development version (0.x.y) until first stable release (1.0.0)
|
||||
|
||||
## Branch Types
|
||||
|
||||
### Feature Branches
|
||||
|
||||
**Naming**: `feature/<description>` or `<description>`
|
||||
|
||||
**Purpose**: Develop new features or enhancements
|
||||
|
||||
**Lifecycle**:
|
||||
- Branch from: `main`
|
||||
- Merge into: `main`
|
||||
- Delete after: Merged or abandoned
|
||||
|
||||
**Examples**:
|
||||
- `feature/micropub-endpoint`
|
||||
- `feature/rss-feed`
|
||||
- `indieauth-integration`
|
||||
- `note-markdown-support`
|
||||
|
||||
**Workflow**:
|
||||
```bash
|
||||
# Create feature branch
|
||||
git checkout -b feature/micropub-endpoint main
|
||||
|
||||
# Work on feature
|
||||
git add .
|
||||
git commit -m "Add micropub endpoint skeleton"
|
||||
|
||||
# Keep updated with main
|
||||
git fetch origin
|
||||
git rebase origin/main
|
||||
|
||||
# When ready, create pull request or merge
|
||||
git checkout main
|
||||
git merge feature/micropub-endpoint
|
||||
|
||||
# Delete feature branch
|
||||
git branch -d feature/micropub-endpoint
|
||||
```
|
||||
|
||||
### Fix Branches
|
||||
|
||||
**Naming**: `fix/<description>` or `bugfix/<description>`
|
||||
|
||||
**Purpose**: Fix bugs in development code
|
||||
|
||||
**Lifecycle**:
|
||||
- Branch from: `main`
|
||||
- Merge into: `main`
|
||||
- Delete after: Merged
|
||||
|
||||
**Examples**:
|
||||
- `fix/slug-generation-unicode`
|
||||
- `bugfix/rss-invalid-xml`
|
||||
- `fix/auth-redirect-loop`
|
||||
|
||||
**Workflow**: Same as feature branches
|
||||
|
||||
### Hotfix Branches
|
||||
|
||||
**Naming**: `hotfix/<version>-<description>`
|
||||
|
||||
**Purpose**: Critical fixes to production releases (post-1.0.0)
|
||||
|
||||
**Lifecycle**:
|
||||
- Branch from: Release tag (e.g., `v1.0.0`)
|
||||
- Merge into: `main`
|
||||
- Tag as new release: `v1.0.1`
|
||||
- Delete after: Released
|
||||
|
||||
**Examples**:
|
||||
- `hotfix/1.0.1-security-path-traversal`
|
||||
- `hotfix/1.1.1-rss-encoding`
|
||||
|
||||
**Workflow**:
|
||||
```bash
|
||||
# Create hotfix from release tag
|
||||
git checkout -b hotfix/1.0.1-security-fix v1.0.0
|
||||
|
||||
# Fix the issue
|
||||
git commit -m "Fix security vulnerability"
|
||||
|
||||
# Update version
|
||||
# Edit starpunk/__init__.py: __version__ = "1.0.1"
|
||||
# Update CHANGELOG.md
|
||||
|
||||
git commit -m "Bump version to 1.0.1"
|
||||
|
||||
# Tag the hotfix
|
||||
git tag -a v1.0.1 -m "Hotfix 1.0.1: Security vulnerability fix"
|
||||
|
||||
# Merge into main
|
||||
git checkout main
|
||||
git merge hotfix/1.0.1-security-fix
|
||||
|
||||
# Push
|
||||
git push origin main v1.0.1
|
||||
|
||||
# Delete hotfix branch
|
||||
git branch -d hotfix/1.0.1-security-fix
|
||||
```
|
||||
|
||||
### Release Branches (Optional)
|
||||
|
||||
**Naming**: `release/<version>`
|
||||
|
||||
**Purpose**: Prepare for release (testing, docs, version bumps)
|
||||
|
||||
**Used when**: Release preparation requires multiple commits and testing
|
||||
|
||||
**Note**: For V1 (simple project), we likely don't need release branches. We can prepare releases directly on `main` or feature branches.
|
||||
|
||||
**If used**:
|
||||
```bash
|
||||
# Create release branch
|
||||
git checkout -b release/1.0.0 main
|
||||
|
||||
# Prepare release
|
||||
# - Update version numbers
|
||||
# - Update CHANGELOG.md
|
||||
# - Update documentation
|
||||
# - Final testing
|
||||
|
||||
git commit -m "Prepare release 1.0.0"
|
||||
|
||||
# Tag
|
||||
git tag -a v1.0.0 -m "Release 1.0.0: First stable release"
|
||||
|
||||
# Merge to main
|
||||
git checkout main
|
||||
git merge release/1.0.0
|
||||
|
||||
# Push
|
||||
git push origin main v1.0.0
|
||||
|
||||
# Delete release branch
|
||||
git branch -d release/1.0.0
|
||||
```
|
||||
|
||||
## Branch Naming Conventions
|
||||
|
||||
### Format
|
||||
|
||||
**Preferred**: `<type>/<description>`
|
||||
|
||||
**Types**:
|
||||
- `feature/` - New features
|
||||
- `fix/` - Bug fixes
|
||||
- `bugfix/` - Bug fixes (alternative)
|
||||
- `hotfix/` - Production hotfixes
|
||||
- `docs/` - Documentation only
|
||||
- `refactor/` - Code refactoring
|
||||
- `test/` - Test additions/changes
|
||||
- `chore/` - Maintenance tasks
|
||||
|
||||
**Description**:
|
||||
- Use lowercase
|
||||
- Use hyphens to separate words
|
||||
- Be descriptive but concise
|
||||
- Reference issue number if applicable
|
||||
|
||||
### Examples
|
||||
|
||||
**Good**:
|
||||
- `feature/micropub-create-action`
|
||||
- `fix/rss-pubdate-timezone`
|
||||
- `docs/api-documentation`
|
||||
- `refactor/note-storage-layer`
|
||||
- `test/slug-generation-edge-cases`
|
||||
- `chore/update-dependencies`
|
||||
|
||||
**Acceptable** (simple description):
|
||||
- `micropub-endpoint`
|
||||
- `rss-feed`
|
||||
- `auth-integration`
|
||||
|
||||
**Avoid**:
|
||||
- `my-feature` (too vague)
|
||||
- `feature/Feature1` (not descriptive)
|
||||
- `fix_bug` (use hyphens)
|
||||
- `FEATURE-MICROPUB` (use lowercase)
|
||||
|
||||
## Workflows
|
||||
|
||||
### Development Workflow (Pre-1.0)
|
||||
|
||||
**For single developer**:
|
||||
|
||||
1. Work directly on `main` for small changes
|
||||
2. Use feature branches for larger features
|
||||
3. Commit frequently with clear messages
|
||||
4. Tag development milestones (e.g., `v0.1.0`)
|
||||
|
||||
**For multiple developers**:
|
||||
|
||||
1. Always use feature branches
|
||||
2. Create pull requests
|
||||
3. Review before merging
|
||||
4. Delete feature branches after merge
|
||||
|
||||
### Feature Development
|
||||
|
||||
```bash
|
||||
# Start feature
|
||||
git checkout -b feature/new-feature main
|
||||
|
||||
# Develop
|
||||
git add file.py
|
||||
git commit -m "Implement core functionality"
|
||||
|
||||
# Keep updated
|
||||
git fetch origin
|
||||
git rebase origin/main
|
||||
|
||||
# Finish
|
||||
git checkout main
|
||||
git merge feature/new-feature
|
||||
git push origin main
|
||||
git branch -d feature/new-feature
|
||||
```
|
||||
|
||||
### Release Workflow
|
||||
|
||||
**For development releases (0.x.y)**:
|
||||
|
||||
```bash
|
||||
# Ensure on main
|
||||
git checkout main
|
||||
|
||||
# Update version
|
||||
# Edit starpunk/__init__.py
|
||||
# Update CHANGELOG.md
|
||||
|
||||
# Commit
|
||||
git add starpunk/__init__.py CHANGELOG.md
|
||||
git commit -m "Bump version to 0.2.0"
|
||||
|
||||
# Tag
|
||||
git tag -a v0.2.0 -m "Development release 0.2.0: Phase 1.2 complete"
|
||||
|
||||
# Push
|
||||
git push origin main v0.2.0
|
||||
```
|
||||
|
||||
**For stable releases (1.0.0+)**:
|
||||
|
||||
```bash
|
||||
# Option 1: Direct on main (simple)
|
||||
git checkout main
|
||||
# Update version, changelog, documentation
|
||||
git commit -m "Prepare release 1.0.0"
|
||||
git tag -a v1.0.0 -m "Release 1.0.0: First stable release"
|
||||
git push origin main v1.0.0
|
||||
|
||||
# Option 2: Using release branch (if needed)
|
||||
git checkout -b release/1.0.0 main
|
||||
# Prepare release
|
||||
git commit -m "Prepare release 1.0.0"
|
||||
git tag -a v1.0.0 -m "Release 1.0.0: First stable release"
|
||||
git checkout main
|
||||
git merge release/1.0.0
|
||||
git push origin main v1.0.0
|
||||
git branch -d release/1.0.0
|
||||
```
|
||||
|
||||
### Hotfix Workflow
|
||||
|
||||
```bash
|
||||
# Create from release tag
|
||||
git checkout -b hotfix/1.0.1-critical-fix v1.0.0
|
||||
|
||||
# Fix
|
||||
git commit -m "Fix critical bug"
|
||||
|
||||
# Version bump
|
||||
git commit -m "Bump version to 1.0.1"
|
||||
|
||||
# Tag
|
||||
git tag -a v1.0.1 -m "Hotfix 1.0.1: Critical bug fix"
|
||||
|
||||
# Merge to main
|
||||
git checkout main
|
||||
git merge hotfix/1.0.1-critical-fix
|
||||
|
||||
# Push
|
||||
git push origin main v1.0.1
|
||||
|
||||
# Clean up
|
||||
git branch -d hotfix/1.0.1-critical-fix
|
||||
```
|
||||
|
||||
## Branch Protection Rules
|
||||
|
||||
### Main Branch Protection
|
||||
|
||||
**For solo development**:
|
||||
- Recommended but not enforced via GitHub
|
||||
- Self-discipline: treat `main` as protected
|
||||
- Don't force push
|
||||
- Don't rewrite history
|
||||
|
||||
**For team development**:
|
||||
- Require pull request reviews
|
||||
- Require status checks to pass
|
||||
- Prevent force push
|
||||
- Prevent deletion
|
||||
|
||||
**GitHub settings** (when ready):
|
||||
```
|
||||
Settings → Branches → Add branch protection rule
|
||||
|
||||
Branch name pattern: main
|
||||
|
||||
Protect matching branches:
|
||||
☑ Require a pull request before merging
|
||||
☑ Require approvals (1)
|
||||
☑ Require status checks to pass before merging
|
||||
☑ Require branches to be up to date before merging
|
||||
☑ Require conversation resolution before merging
|
||||
☑ Do not allow bypassing the above settings
|
||||
☐ Allow force pushes (NEVER enable)
|
||||
☐ Allow deletions (NEVER enable)
|
||||
```
|
||||
|
||||
## Tagging Strategy
|
||||
|
||||
### Tag Format
|
||||
|
||||
**Version tags**: `vMAJOR.MINOR.PATCH[-PRERELEASE]`
|
||||
|
||||
**Examples**:
|
||||
- `v0.1.0` - Development release
|
||||
- `v1.0.0` - First stable release
|
||||
- `v1.0.1` - Patch release
|
||||
- `v1.1.0` - Minor release
|
||||
- `v2.0.0` - Major release
|
||||
- `v1.0.0-alpha.1` - Pre-release
|
||||
|
||||
### Tag Types
|
||||
|
||||
**Annotated tags** (ALWAYS use for releases):
|
||||
```bash
|
||||
git tag -a v1.0.0 -m "Release 1.0.0: First stable release"
|
||||
```
|
||||
|
||||
**Why annotated**:
|
||||
- Contains tagger, date, message
|
||||
- Can include release notes
|
||||
- Can be GPG signed
|
||||
- Treated as full Git objects
|
||||
- Better for releases
|
||||
|
||||
**Lightweight tags** (NEVER use for releases):
|
||||
```bash
|
||||
git tag v1.0.0 # Don't do this for releases
|
||||
```
|
||||
|
||||
### Tag Messages
|
||||
|
||||
**Format**:
|
||||
```
|
||||
Release MAJOR.MINOR.PATCH: <Brief description>
|
||||
|
||||
[Optional longer description]
|
||||
[Release highlights]
|
||||
```
|
||||
|
||||
**Examples**:
|
||||
```bash
|
||||
git tag -a v1.0.0 -m "Release 1.0.0: First stable release
|
||||
|
||||
Complete IndieWeb CMS implementation with:
|
||||
- IndieAuth authentication
|
||||
- Micropub publishing endpoint
|
||||
- RSS feed generation
|
||||
- File-based note storage
|
||||
- Markdown support"
|
||||
|
||||
git tag -a v1.0.1 -m "Hotfix 1.0.1: Security vulnerability fix"
|
||||
|
||||
git tag -a v0.2.0 -m "Development release 0.2.0: Phase 1.2 complete"
|
||||
```
|
||||
|
||||
### Viewing Tags
|
||||
|
||||
```bash
|
||||
# List all tags
|
||||
git tag
|
||||
|
||||
# List tags with messages
|
||||
git tag -n
|
||||
|
||||
# Show tag details
|
||||
git show v1.0.0
|
||||
|
||||
# List tags matching pattern
|
||||
git tag -l "v1.0.*"
|
||||
```
|
||||
|
||||
### Pushing Tags
|
||||
|
||||
```bash
|
||||
# Push specific tag
|
||||
git push origin v1.0.0
|
||||
|
||||
# Push all tags
|
||||
git push origin --tags
|
||||
|
||||
# Delete remote tag (if needed)
|
||||
git push origin :refs/tags/v1.0.0
|
||||
```
|
||||
|
||||
## Integration with Semantic Versioning
|
||||
|
||||
### Version-to-Branch Mapping
|
||||
|
||||
**Development phase (0.x.y)**:
|
||||
- Work on `main`
|
||||
- Tag development milestones: `v0.1.0`, `v0.2.0`, etc.
|
||||
- Breaking changes allowed
|
||||
|
||||
**Stable releases (1.x.y)**:
|
||||
- Work on `main` or feature branches
|
||||
- Tag stable releases: `v1.0.0`, `v1.1.0`, etc.
|
||||
- Breaking changes require major version bump
|
||||
|
||||
**Major releases (2.0.0+)**:
|
||||
- Work on `main` or feature branches
|
||||
- Tag major releases: `v2.0.0`, `v3.0.0`, etc.
|
||||
- Document breaking changes thoroughly
|
||||
|
||||
### Branch-to-Release Flow
|
||||
|
||||
```
|
||||
feature/micropub → main → v0.1.0 (development)
|
||||
feature/rss → main → v0.2.0 (development)
|
||||
feature/auth → main → v0.3.0 (development)
|
||||
main → v1.0.0 (stable)
|
||||
fix/bug → main → v1.0.1 (patch)
|
||||
feature/new → main → v1.1.0 (minor)
|
||||
feature/breaking → main → v2.0.0 (major)
|
||||
```
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Scenario 1: Developing a New Feature
|
||||
|
||||
```bash
|
||||
# Create feature branch
|
||||
git checkout -b feature/micropub-endpoint main
|
||||
|
||||
# Develop
|
||||
git commit -am "Add micropub create action"
|
||||
git commit -am "Add micropub update action"
|
||||
|
||||
# Keep updated with main
|
||||
git fetch origin
|
||||
git rebase origin/main
|
||||
|
||||
# Merge when ready
|
||||
git checkout main
|
||||
git merge feature/micropub-endpoint
|
||||
|
||||
# Push and clean up
|
||||
git push origin main
|
||||
git branch -d feature/micropub-endpoint
|
||||
```
|
||||
|
||||
### Scenario 2: Releasing a Development Version
|
||||
|
||||
```bash
|
||||
# Update version
|
||||
echo '__version__ = "0.2.0"' > starpunk/__init__.py
|
||||
|
||||
# Update changelog
|
||||
# Edit CHANGELOG.md
|
||||
|
||||
# Commit
|
||||
git commit -am "Bump version to 0.2.0"
|
||||
|
||||
# Tag
|
||||
git tag -a v0.2.0 -m "Development release 0.2.0"
|
||||
|
||||
# Push
|
||||
git push origin main v0.2.0
|
||||
```
|
||||
|
||||
### Scenario 3: First Stable Release
|
||||
|
||||
```bash
|
||||
# Final preparations on main
|
||||
git commit -am "Update documentation for 1.0.0"
|
||||
|
||||
# Version bump
|
||||
echo '__version__ = "1.0.0"' > starpunk/__init__.py
|
||||
# Edit CHANGELOG.md
|
||||
|
||||
git commit -am "Bump version to 1.0.0"
|
||||
|
||||
# Tag
|
||||
git tag -a v1.0.0 -m "Release 1.0.0: First stable release"
|
||||
|
||||
# Push
|
||||
git push origin main v1.0.0
|
||||
```
|
||||
|
||||
### Scenario 4: Critical Production Bug
|
||||
|
||||
```bash
|
||||
# Create hotfix from last release
|
||||
git checkout -b hotfix/1.0.1-security-fix v1.0.0
|
||||
|
||||
# Fix the bug
|
||||
git commit -am "Fix security vulnerability"
|
||||
|
||||
# Version bump
|
||||
echo '__version__ = "1.0.1"' > starpunk/__init__.py
|
||||
# Edit CHANGELOG.md
|
||||
git commit -am "Bump version to 1.0.1"
|
||||
|
||||
# Tag
|
||||
git tag -a v1.0.1 -m "Hotfix 1.0.1: Security vulnerability fix"
|
||||
|
||||
# Merge to main
|
||||
git checkout main
|
||||
git merge hotfix/1.0.1-security-fix
|
||||
|
||||
# Push
|
||||
git push origin main v1.0.1
|
||||
|
||||
# Clean up
|
||||
git branch -d hotfix/1.0.1-security-fix
|
||||
```
|
||||
|
||||
### Scenario 5: Multiple Features in Progress
|
||||
|
||||
```bash
|
||||
# Developer 1: Feature A
|
||||
git checkout -b feature/feature-a main
|
||||
# Work on feature A
|
||||
|
||||
# Developer 2: Feature B
|
||||
git checkout -b feature/feature-b main
|
||||
# Work on feature B
|
||||
|
||||
# Feature A finishes first
|
||||
git checkout main
|
||||
git merge feature/feature-a
|
||||
git push origin main
|
||||
|
||||
# Feature B rebases onto updated main
|
||||
git checkout feature/feature-b
|
||||
git rebase origin/main
|
||||
# Continue work
|
||||
|
||||
# Feature B finishes
|
||||
git checkout main
|
||||
git merge feature/feature-b
|
||||
git push origin main
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Do
|
||||
|
||||
1. **Commit often** with clear messages
|
||||
2. **Pull before push** to avoid conflicts
|
||||
3. **Rebase feature branches** to keep history clean
|
||||
4. **Delete merged branches** to reduce clutter
|
||||
5. **Tag releases** with annotated tags
|
||||
6. **Write descriptive commit messages** (50 char summary, then details)
|
||||
7. **Test before merging** to main
|
||||
8. **Use pull requests** for team development
|
||||
9. **Keep main stable** - always passing tests
|
||||
10. **Document breaking changes** in commits and changelog
|
||||
|
||||
### Don't
|
||||
|
||||
1. **Never force push** to `main`
|
||||
2. **Never rewrite history** on `main`
|
||||
3. **Don't commit directly** to `main` (team development)
|
||||
4. **Don't merge broken code** - tests must pass
|
||||
5. **Don't create long-lived branches** - integrate frequently
|
||||
6. **Don't use lightweight tags** for releases
|
||||
7. **Don't forget to push tags** after creating them
|
||||
8. **Don't merge without updating** from origin first
|
||||
9. **Don't commit secrets** or sensitive data
|
||||
10. **Don't skip version bumps** before tagging
|
||||
|
||||
### Commit Message Format
|
||||
|
||||
**Format**:
|
||||
```
|
||||
<type>: <summary> (50 chars or less)
|
||||
|
||||
<optional detailed description>
|
||||
|
||||
<optional footer: references, breaking changes>
|
||||
```
|
||||
|
||||
**Types**:
|
||||
- `feat:` New feature
|
||||
- `fix:` Bug fix
|
||||
- `docs:` Documentation
|
||||
- `style:` Formatting, whitespace
|
||||
- `refactor:` Code refactoring
|
||||
- `test:` Adding tests
|
||||
- `chore:` Maintenance tasks
|
||||
|
||||
**Examples**:
|
||||
```
|
||||
feat: Add Micropub create endpoint
|
||||
|
||||
Implements the create action for Micropub specification.
|
||||
Supports h-entry posts with content, name, and published properties.
|
||||
|
||||
Refs: #15
|
||||
|
||||
fix: Correct RSS pubDate timezone handling
|
||||
|
||||
Previously used local timezone, now uses UTC as per RSS spec.
|
||||
|
||||
Fixes: #23
|
||||
|
||||
docs: Update installation instructions
|
||||
|
||||
chore: Bump version to 1.0.0
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Problem: Merge Conflict
|
||||
|
||||
```bash
|
||||
# During merge
|
||||
git merge feature/my-feature
|
||||
# CONFLICT (content): Merge conflict in file.py
|
||||
|
||||
# Resolve conflicts
|
||||
# Edit file.py, resolve conflicts
|
||||
git add file.py
|
||||
git commit -m "Merge feature/my-feature"
|
||||
```
|
||||
|
||||
### Problem: Accidentally Committed to Main
|
||||
|
||||
```bash
|
||||
# If not pushed yet
|
||||
git reset HEAD~1 # Undo last commit, keep changes
|
||||
git stash # Save changes
|
||||
git checkout -b feature/my-feature # Create feature branch
|
||||
git stash pop # Apply changes
|
||||
git commit -am "Feature implementation"
|
||||
```
|
||||
|
||||
### Problem: Need to Update Feature Branch
|
||||
|
||||
```bash
|
||||
# Option 1: Rebase (clean history)
|
||||
git checkout feature/my-feature
|
||||
git rebase origin/main
|
||||
|
||||
# Option 2: Merge (preserves history)
|
||||
git checkout feature/my-feature
|
||||
git merge origin/main
|
||||
```
|
||||
|
||||
### Problem: Wrong Tag Name
|
||||
|
||||
```bash
|
||||
# Delete local tag
|
||||
git tag -d v1.0.0
|
||||
|
||||
# Delete remote tag
|
||||
git push origin :refs/tags/v1.0.0
|
||||
|
||||
# Create correct tag
|
||||
git tag -a v1.0.1 -m "Release 1.0.1"
|
||||
git push origin v1.0.1
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
### Internal Documentation
|
||||
- [Versioning Strategy](/home/phil/Projects/starpunk/docs/standards/versioning-strategy.md) - Version numbering scheme
|
||||
- [ADR-008: Versioning Strategy](/home/phil/Projects/starpunk/docs/decisions/ADR-008-versioning-strategy.md) - Versioning decision rationale
|
||||
- [ADR-009: Git Branching Strategy](/home/phil/Projects/starpunk/docs/decisions/ADR-009-git-branching-strategy.md) - This strategy's decision record
|
||||
- [Development Setup](/home/phil/Projects/starpunk/docs/standards/development-setup.md) - Development environment
|
||||
|
||||
### External Resources
|
||||
- [Git Branching Model](https://nvie.com/posts/a-successful-git-branching-model/) - Git Flow (inspiration, not followed exactly)
|
||||
- [GitHub Flow](https://guides.github.com/introduction/flow/) - Simpler flow (closer to our approach)
|
||||
- [Semantic Versioning](https://semver.org/) - Version numbering
|
||||
- [Git Documentation](https://git-scm.com/doc) - Official Git documentation
|
||||
|
||||
---
|
||||
|
||||
**Document**: Git Branching Strategy
|
||||
**Version**: 1.0
|
||||
**Last Updated**: 2025-11-18
|
||||
**Status**: Active
|
||||
1008
docs/standards/python-coding-standards.md
Normal file
1008
docs/standards/python-coding-standards.md
Normal file
File diff suppressed because it is too large
Load Diff
734
docs/standards/utility-function-patterns.md
Normal file
734
docs/standards/utility-function-patterns.md
Normal file
@@ -0,0 +1,734 @@
|
||||
# Utility Function Patterns
|
||||
|
||||
## Purpose
|
||||
|
||||
This document establishes coding patterns and conventions specifically for utility functions in StarPunk. These patterns ensure utilities are consistent, testable, and maintainable.
|
||||
|
||||
## Philosophy
|
||||
|
||||
Utility functions should be:
|
||||
- **Pure**: No side effects where possible
|
||||
- **Focused**: One responsibility per function
|
||||
- **Predictable**: Same input always produces same output
|
||||
- **Testable**: Easy to test in isolation
|
||||
- **Documented**: Clear purpose and usage
|
||||
|
||||
## Function Design Patterns
|
||||
|
||||
### 1. Pure Functions (Preferred)
|
||||
|
||||
**Pattern**: Functions that don't modify state or have side effects.
|
||||
|
||||
**Good Example**:
|
||||
```python
|
||||
def calculate_content_hash(content: str) -> str:
|
||||
"""Calculate SHA-256 hash of content."""
|
||||
return hashlib.sha256(content.encode('utf-8')).hexdigest()
|
||||
```
|
||||
|
||||
**Why**: Easy to test, no hidden dependencies, predictable behavior.
|
||||
|
||||
**When to Use**: Calculations, transformations, validations.
|
||||
|
||||
### 2. Functions with I/O Side Effects
|
||||
|
||||
**Pattern**: Functions that read/write files or interact with external systems.
|
||||
|
||||
**Good Example**:
|
||||
```python
|
||||
def write_note_file(file_path: Path, content: str) -> None:
|
||||
"""Write note content to file atomically."""
|
||||
temp_path = file_path.with_suffix(file_path.suffix + '.tmp')
|
||||
try:
|
||||
temp_path.write_text(content, encoding='utf-8')
|
||||
temp_path.replace(file_path)
|
||||
except Exception:
|
||||
temp_path.unlink(missing_ok=True)
|
||||
raise
|
||||
```
|
||||
|
||||
**Why**: Side effects are isolated, error handling is explicit, cleanup is guaranteed.
|
||||
|
||||
**When to Use**: File operations, database operations, network calls.
|
||||
|
||||
### 3. Validation Functions
|
||||
|
||||
**Pattern**: Functions that check validity and return boolean or raise exception.
|
||||
|
||||
**Good Example** (Boolean Return):
|
||||
```python
|
||||
def validate_slug(slug: str) -> bool:
|
||||
"""Validate that slug meets requirements."""
|
||||
if not slug:
|
||||
return False
|
||||
if len(slug) > MAX_SLUG_LENGTH:
|
||||
return False
|
||||
return bool(SLUG_PATTERN.match(slug))
|
||||
```
|
||||
|
||||
**Good Example** (Exception Raising):
|
||||
```python
|
||||
def require_valid_slug(slug: str) -> None:
|
||||
"""Require slug to be valid, raise ValueError if not."""
|
||||
if not validate_slug(slug):
|
||||
raise ValueError(
|
||||
f"Invalid slug '{slug}': must be 1-100 characters, "
|
||||
f"lowercase alphanumeric with hyphens only"
|
||||
)
|
||||
```
|
||||
|
||||
**When to Use**: Input validation, precondition checking, security checks.
|
||||
|
||||
### 4. Generator Functions
|
||||
|
||||
**Pattern**: Functions that yield values instead of returning lists.
|
||||
|
||||
**Good Example**:
|
||||
```python
|
||||
def iter_note_files(notes_dir: Path) -> Iterator[Path]:
|
||||
"""Iterate over all note files in directory."""
|
||||
for year_dir in sorted(notes_dir.iterdir()):
|
||||
if not year_dir.is_dir():
|
||||
continue
|
||||
for month_dir in sorted(year_dir.iterdir()):
|
||||
if not month_dir.is_dir():
|
||||
continue
|
||||
for note_file in sorted(month_dir.glob("*.md")):
|
||||
yield note_file
|
||||
```
|
||||
|
||||
**Why**: Memory efficient, lazy evaluation, can be interrupted.
|
||||
|
||||
**When to Use**: Processing many items, large datasets, streaming operations.
|
||||
|
||||
## Error Handling Patterns
|
||||
|
||||
### 1. Specific Exceptions
|
||||
|
||||
**Pattern**: Raise specific exception types with descriptive messages.
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
def generate_slug(content: str) -> str:
|
||||
"""Generate slug from content."""
|
||||
if not content or not content.strip():
|
||||
raise ValueError("Content cannot be empty or whitespace-only")
|
||||
|
||||
# ... rest of function
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
```python
|
||||
def generate_slug(content: str) -> str:
|
||||
if not content:
|
||||
raise Exception("Bad input") # Too generic
|
||||
```
|
||||
|
||||
### 2. Error Message Format
|
||||
|
||||
**Pattern**: Include context, expected behavior, and actual problem.
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
raise ValueError(
|
||||
f"Invalid slug '{slug}': must contain only lowercase letters, "
|
||||
f"numbers, and hyphens (pattern: {SLUG_PATTERN.pattern})"
|
||||
)
|
||||
|
||||
raise FileNotFoundError(
|
||||
f"Note file not found: {file_path}. "
|
||||
f"Database may be out of sync with filesystem."
|
||||
)
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
```python
|
||||
raise ValueError("Invalid slug")
|
||||
raise FileNotFoundError("File missing")
|
||||
```
|
||||
|
||||
### 3. Exception Chaining
|
||||
|
||||
**Pattern**: Preserve original exception context when re-raising.
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
def read_note_file(file_path: Path) -> str:
|
||||
"""Read note content from file."""
|
||||
try:
|
||||
return file_path.read_text(encoding='utf-8')
|
||||
except UnicodeDecodeError as e:
|
||||
raise ValueError(
|
||||
f"Failed to read {file_path}: invalid UTF-8 encoding"
|
||||
) from e
|
||||
```
|
||||
|
||||
**Why**: Preserves stack trace, shows root cause.
|
||||
|
||||
### 4. Cleanup on Error
|
||||
|
||||
**Pattern**: Use try/finally or context managers to ensure cleanup.
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
def write_note_file(file_path: Path, content: str) -> None:
|
||||
"""Write note content to file atomically."""
|
||||
temp_path = file_path.with_suffix('.tmp')
|
||||
try:
|
||||
temp_path.write_text(content, encoding='utf-8')
|
||||
temp_path.replace(file_path)
|
||||
except Exception:
|
||||
temp_path.unlink(missing_ok=True) # Cleanup on error
|
||||
raise
|
||||
```
|
||||
|
||||
## Type Hint Patterns
|
||||
|
||||
### 1. Basic Types
|
||||
|
||||
**Pattern**: Use built-in types where possible.
|
||||
|
||||
```python
|
||||
def generate_slug(content: str, created_at: Optional[datetime] = None) -> str:
|
||||
"""Generate URL-safe slug from content."""
|
||||
pass
|
||||
|
||||
def validate_note_path(file_path: Path, data_dir: Path) -> bool:
|
||||
"""Validate file path is within data directory."""
|
||||
pass
|
||||
```
|
||||
|
||||
### 2. Collection Types
|
||||
|
||||
**Pattern**: Specify element types for collections.
|
||||
|
||||
```python
|
||||
from typing import List, Dict, Set, Optional
|
||||
|
||||
def make_slug_unique(base_slug: str, existing_slugs: Set[str]) -> str:
|
||||
"""Make slug unique by adding suffix if needed."""
|
||||
pass
|
||||
|
||||
def get_note_paths(notes_dir: Path) -> List[Path]:
|
||||
"""Get all note file paths."""
|
||||
pass
|
||||
```
|
||||
|
||||
### 3. Optional Types
|
||||
|
||||
**Pattern**: Use Optional[T] for nullable parameters and returns.
|
||||
|
||||
```python
|
||||
from typing import Optional
|
||||
|
||||
def find_note(slug: str) -> Optional[Path]:
|
||||
"""Find note file by slug, returns None if not found."""
|
||||
pass
|
||||
```
|
||||
|
||||
### 4. Union Types (Use Sparingly)
|
||||
|
||||
**Pattern**: Use Union when a parameter truly accepts multiple types.
|
||||
|
||||
```python
|
||||
from typing import Union
|
||||
from pathlib import Path
|
||||
|
||||
def ensure_path(path: Union[str, Path]) -> Path:
|
||||
"""Convert string or Path to Path object."""
|
||||
return Path(path) if isinstance(path, str) else path
|
||||
```
|
||||
|
||||
**Note**: Prefer single types. Only use Union when necessary.
|
||||
|
||||
## Documentation Patterns
|
||||
|
||||
### 1. Function Docstrings
|
||||
|
||||
**Pattern**: Google-style docstrings with sections.
|
||||
|
||||
```python
|
||||
def generate_slug(content: str, created_at: Optional[datetime] = None) -> str:
|
||||
"""
|
||||
Generate URL-safe slug from note content
|
||||
|
||||
Creates a slug by extracting the first few words from the content and
|
||||
normalizing them to lowercase with hyphens. If content is insufficient,
|
||||
falls back to timestamp-based slug.
|
||||
|
||||
Args:
|
||||
content: The note content (markdown text)
|
||||
created_at: Optional timestamp for fallback slug (defaults to now)
|
||||
|
||||
Returns:
|
||||
URL-safe slug string (lowercase, alphanumeric + hyphens only)
|
||||
|
||||
Raises:
|
||||
ValueError: If content is empty or contains only whitespace
|
||||
|
||||
Examples:
|
||||
>>> generate_slug("Hello World! This is my first note.")
|
||||
'hello-world-this-is-my'
|
||||
|
||||
>>> generate_slug("Testing... with special chars!@#")
|
||||
'testing-with-special-chars'
|
||||
|
||||
Notes:
|
||||
- This function does NOT check for uniqueness
|
||||
- Caller must verify slug doesn't exist in database
|
||||
"""
|
||||
```
|
||||
|
||||
**Required Sections**:
|
||||
- Summary (first line)
|
||||
- Description (paragraph after summary)
|
||||
- Args (if any parameters)
|
||||
- Returns (if returns value)
|
||||
- Raises (if raises exceptions)
|
||||
|
||||
**Optional Sections**:
|
||||
- Examples (highly recommended)
|
||||
- Notes (for important caveats)
|
||||
- References (for external specs)
|
||||
|
||||
### 2. Inline Comments
|
||||
|
||||
**Pattern**: Explain why, not what.
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
# Use atomic rename to prevent file corruption if interrupted
|
||||
temp_path.replace(file_path)
|
||||
|
||||
# Random suffix prevents enumeration attacks
|
||||
suffix = secrets.token_urlsafe(4)[:4]
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
```python
|
||||
# Rename temp file to final path
|
||||
temp_path.replace(file_path)
|
||||
|
||||
# Generate suffix
|
||||
suffix = secrets.token_urlsafe(4)[:4]
|
||||
```
|
||||
|
||||
### 3. Module Docstrings
|
||||
|
||||
**Pattern**: Describe module purpose and contents.
|
||||
|
||||
```python
|
||||
"""
|
||||
Core utility functions for StarPunk
|
||||
|
||||
This module provides essential utilities for slug generation, file operations,
|
||||
hashing, and date/time handling. These utilities are used throughout the
|
||||
application and have no external dependencies beyond standard library and
|
||||
Flask configuration.
|
||||
|
||||
Functions:
|
||||
generate_slug: Create URL-safe slug from content
|
||||
calculate_content_hash: Calculate SHA-256 hash
|
||||
write_note_file: Atomically write note to file
|
||||
format_rfc822: Format datetime for RSS feeds
|
||||
|
||||
Constants:
|
||||
MAX_SLUG_LENGTH: Maximum slug length (100)
|
||||
SLUG_PATTERN: Regex for valid slugs
|
||||
"""
|
||||
```
|
||||
|
||||
## Testing Patterns
|
||||
|
||||
### 1. Test Function Naming
|
||||
|
||||
**Pattern**: `test_{function_name}_{scenario}`
|
||||
|
||||
```python
|
||||
def test_generate_slug_from_content():
|
||||
"""Test basic slug generation from content."""
|
||||
slug = generate_slug("Hello World This Is My Note")
|
||||
assert slug == "hello-world-this-is-my"
|
||||
|
||||
def test_generate_slug_empty_content():
|
||||
"""Test slug generation raises error on empty content."""
|
||||
with pytest.raises(ValueError):
|
||||
generate_slug("")
|
||||
|
||||
def test_generate_slug_special_characters():
|
||||
"""Test slug generation removes special characters."""
|
||||
slug = generate_slug("Testing... with!@# special chars")
|
||||
assert slug == "testing-with-special-chars"
|
||||
```
|
||||
|
||||
### 2. Test Organization
|
||||
|
||||
**Pattern**: Group related tests in classes.
|
||||
|
||||
```python
|
||||
class TestSlugGeneration:
|
||||
"""Test slug generation functions"""
|
||||
|
||||
def test_generate_slug_from_content(self):
|
||||
"""Test basic slug generation."""
|
||||
pass
|
||||
|
||||
def test_generate_slug_empty_content(self):
|
||||
"""Test error on empty content."""
|
||||
pass
|
||||
|
||||
|
||||
class TestContentHashing:
|
||||
"""Test content hashing functions"""
|
||||
|
||||
def test_calculate_hash_consistency(self):
|
||||
"""Test hash is consistent."""
|
||||
pass
|
||||
```
|
||||
|
||||
### 3. Fixtures for Common Setup
|
||||
|
||||
**Pattern**: Use pytest fixtures for reusable test data.
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
@pytest.fixture
|
||||
def temp_note_file(tmp_path):
|
||||
"""Create temporary note file for testing."""
|
||||
file_path = tmp_path / "test.md"
|
||||
file_path.write_text("# Test Note")
|
||||
return file_path
|
||||
|
||||
def test_read_note_file(temp_note_file):
|
||||
"""Test reading note file."""
|
||||
content = read_note_file(temp_note_file)
|
||||
assert content == "# Test Note"
|
||||
```
|
||||
|
||||
### 4. Parameterized Tests
|
||||
|
||||
**Pattern**: Test multiple cases with one test function.
|
||||
|
||||
```python
|
||||
@pytest.mark.parametrize("content,expected", [
|
||||
("Hello World", "hello-world"),
|
||||
("Testing 123", "testing-123"),
|
||||
("Special!@# Chars", "special-chars"),
|
||||
])
|
||||
def test_generate_slug_variations(content, expected):
|
||||
"""Test slug generation with various inputs."""
|
||||
slug = generate_slug(content)
|
||||
assert slug == expected
|
||||
```
|
||||
|
||||
## Security Patterns
|
||||
|
||||
### 1. Path Validation
|
||||
|
||||
**Pattern**: Always validate paths before file operations.
|
||||
|
||||
```python
|
||||
def safe_file_operation(file_path: Path, data_dir: Path) -> None:
|
||||
"""Perform file operation with path validation."""
|
||||
# ALWAYS validate first
|
||||
if not validate_note_path(file_path, data_dir):
|
||||
raise ValueError(f"Invalid file path: {file_path}")
|
||||
|
||||
# Now safe to operate
|
||||
file_path.write_text("content")
|
||||
```
|
||||
|
||||
### 2. Use secrets for Random
|
||||
|
||||
**Pattern**: Use `secrets` module, not `random` for security-sensitive operations.
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
import secrets
|
||||
|
||||
def generate_random_suffix(length: int = 4) -> str:
|
||||
"""Generate cryptographically secure random suffix."""
|
||||
chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
|
||||
return ''.join(secrets.choice(chars) for _ in range(length))
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
```python
|
||||
import random
|
||||
|
||||
def generate_random_suffix(length: int = 4) -> str:
|
||||
"""Generate random suffix."""
|
||||
chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
|
||||
return ''.join(random.choice(chars) for _ in range(length)) # NOT secure
|
||||
```
|
||||
|
||||
### 3. Input Sanitization
|
||||
|
||||
**Pattern**: Validate and sanitize all external input.
|
||||
|
||||
```python
|
||||
def generate_slug(content: str) -> str:
|
||||
"""Generate slug from content."""
|
||||
# Validate input
|
||||
if not content or not content.strip():
|
||||
raise ValueError("Content cannot be empty")
|
||||
|
||||
# Sanitize by removing dangerous characters
|
||||
normalized = normalize_slug_text(content)
|
||||
|
||||
# Additional validation
|
||||
if not validate_slug(normalized):
|
||||
# Fallback to safe default
|
||||
normalized = datetime.utcnow().strftime("%Y%m%d-%H%M%S")
|
||||
|
||||
return normalized
|
||||
```
|
||||
|
||||
## Performance Patterns
|
||||
|
||||
### 1. Lazy Evaluation
|
||||
|
||||
**Pattern**: Don't compute what you don't need.
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
def generate_slug(content: str, created_at: Optional[datetime] = None) -> str:
|
||||
"""Generate slug from content."""
|
||||
slug = normalize_slug_text(content)
|
||||
|
||||
# Only generate timestamp if needed
|
||||
if not slug:
|
||||
created_at = created_at or datetime.utcnow()
|
||||
slug = created_at.strftime("%Y%m%d-%H%M%S")
|
||||
|
||||
return slug
|
||||
```
|
||||
|
||||
### 2. Compile Regex Once
|
||||
|
||||
**Pattern**: Define regex patterns as module constants.
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
# Module level - compiled once
|
||||
SLUG_PATTERN = re.compile(r'^[a-z0-9]+(?:-[a-z0-9]+)*$')
|
||||
SAFE_SLUG_PATTERN = re.compile(r'[^a-z0-9-]')
|
||||
|
||||
def validate_slug(slug: str) -> bool:
|
||||
"""Validate slug."""
|
||||
return bool(SLUG_PATTERN.match(slug))
|
||||
|
||||
def normalize_slug_text(text: str) -> str:
|
||||
"""Normalize text for slug."""
|
||||
return SAFE_SLUG_PATTERN.sub('', text)
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
```python
|
||||
def validate_slug(slug: str) -> bool:
|
||||
"""Validate slug."""
|
||||
# Compiles regex every call - inefficient
|
||||
return bool(re.match(r'^[a-z0-9]+(?:-[a-z0-9]+)*$', slug))
|
||||
```
|
||||
|
||||
### 3. Avoid Premature Optimization
|
||||
|
||||
**Pattern**: Write clear code first, optimize if needed.
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
def extract_first_words(text: str, max_words: int = 5) -> str:
|
||||
"""Extract first N words from text."""
|
||||
words = text.split()
|
||||
return ' '.join(words[:max_words])
|
||||
```
|
||||
|
||||
**Don't Do This Unless Profiling Shows It's Necessary**:
|
||||
```python
|
||||
def extract_first_words(text: str, max_words: int = 5) -> str:
|
||||
"""Extract first N words from text."""
|
||||
# Premature optimization - more complex, minimal gain
|
||||
words = []
|
||||
count = 0
|
||||
for word in text.split():
|
||||
words.append(word)
|
||||
count += 1
|
||||
if count >= max_words:
|
||||
break
|
||||
return ' '.join(words)
|
||||
```
|
||||
|
||||
## Constants Pattern
|
||||
|
||||
### 1. Module-Level Constants
|
||||
|
||||
**Pattern**: Define configuration as constants at module level.
|
||||
|
||||
```python
|
||||
# Slug configuration
|
||||
MAX_SLUG_LENGTH = 100
|
||||
MIN_SLUG_LENGTH = 1
|
||||
SLUG_WORDS_COUNT = 5
|
||||
RANDOM_SUFFIX_LENGTH = 4
|
||||
|
||||
# File operations
|
||||
TEMP_FILE_SUFFIX = '.tmp'
|
||||
TRASH_DIR_NAME = '.trash'
|
||||
|
||||
# Hashing
|
||||
CONTENT_HASH_ALGORITHM = 'sha256'
|
||||
```
|
||||
|
||||
**Why**: Easy to modify, clear intent, compile-time resolution.
|
||||
|
||||
### 2. Naming Convention
|
||||
|
||||
**Pattern**: ALL_CAPS_WITH_UNDERSCORES
|
||||
|
||||
```python
|
||||
MAX_NOTE_LENGTH = 10000 # Good
|
||||
DEFAULT_ENCODING = 'utf-8' # Good
|
||||
maxNoteLength = 10000 # Bad
|
||||
max_note_length = 10000 # Bad (looks like variable)
|
||||
```
|
||||
|
||||
### 3. Magic Numbers
|
||||
|
||||
**Pattern**: Replace magic numbers with named constants.
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
SLUG_WORDS_COUNT = 5
|
||||
|
||||
def generate_slug(content: str) -> str:
|
||||
"""Generate slug from content."""
|
||||
words = content.split()[:SLUG_WORDS_COUNT]
|
||||
return normalize_slug_text(' '.join(words))
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
```python
|
||||
def generate_slug(content: str) -> str:
|
||||
"""Generate slug from content."""
|
||||
words = content.split()[:5] # What is 5? Why 5?
|
||||
return normalize_slug_text(' '.join(words))
|
||||
```
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
||||
### 1. God Functions
|
||||
|
||||
**Bad**:
|
||||
```python
|
||||
def process_note(content, do_hash=True, do_slug=True, do_path=True, ...):
|
||||
"""Do everything with a note."""
|
||||
# 500 lines of code doing too many things
|
||||
pass
|
||||
```
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
def generate_slug(content: str) -> str:
|
||||
"""Generate slug."""
|
||||
pass
|
||||
|
||||
def calculate_hash(content: str) -> str:
|
||||
"""Calculate hash."""
|
||||
pass
|
||||
|
||||
def generate_path(slug: str, timestamp: datetime) -> Path:
|
||||
"""Generate path."""
|
||||
pass
|
||||
```
|
||||
|
||||
### 2. Mutable Default Arguments
|
||||
|
||||
**Bad**:
|
||||
```python
|
||||
def create_note(content: str, tags: list = []) -> Note:
|
||||
tags.append('note') # Modifies shared default list!
|
||||
return Note(content, tags)
|
||||
```
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
def create_note(content: str, tags: Optional[List[str]] = None) -> Note:
|
||||
if tags is None:
|
||||
tags = []
|
||||
tags.append('note')
|
||||
return Note(content, tags)
|
||||
```
|
||||
|
||||
### 3. Returning Multiple Types
|
||||
|
||||
**Bad**:
|
||||
```python
|
||||
def find_note(slug: str) -> Union[Note, bool, None]:
|
||||
"""Find note by slug."""
|
||||
if slug_invalid:
|
||||
return False
|
||||
note = db.query(slug)
|
||||
return note if note else None
|
||||
```
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
def find_note(slug: str) -> Optional[Note]:
|
||||
"""Find note by slug, returns None if not found."""
|
||||
if not validate_slug(slug):
|
||||
raise ValueError(f"Invalid slug: {slug}")
|
||||
return db.query(slug) # Returns Note or None
|
||||
```
|
||||
|
||||
### 4. Silent Failures
|
||||
|
||||
**Bad**:
|
||||
```python
|
||||
def generate_slug(content: str) -> str:
|
||||
"""Generate slug."""
|
||||
try:
|
||||
slug = normalize_slug_text(content)
|
||||
return slug if slug else "untitled" # Silent fallback
|
||||
except Exception:
|
||||
return "untitled" # Swallowing errors
|
||||
```
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
def generate_slug(content: str) -> str:
|
||||
"""Generate slug."""
|
||||
if not content or not content.strip():
|
||||
raise ValueError("Content cannot be empty")
|
||||
|
||||
slug = normalize_slug_text(content)
|
||||
if not slug:
|
||||
# Explicit fallback with timestamp
|
||||
slug = datetime.utcnow().strftime("%Y%m%d-%H%M%S")
|
||||
|
||||
return slug
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
**Key Principles**:
|
||||
1. Pure functions are preferred
|
||||
2. Specific exceptions with clear messages
|
||||
3. Type hints on all functions
|
||||
4. Comprehensive docstrings
|
||||
5. Security-first validation
|
||||
6. Test everything thoroughly
|
||||
7. Constants for configuration
|
||||
8. Clear over clever
|
||||
|
||||
**Remember**: Utility functions are the foundation. Make them rock-solid.
|
||||
|
||||
## References
|
||||
|
||||
- [Python Coding Standards](/home/phil/Projects/starpunk/docs/standards/python-coding-standards.md)
|
||||
- [PEP 8 - Style Guide](https://peps.python.org/pep-0008/)
|
||||
- [PEP 257 - Docstring Conventions](https://peps.python.org/pep-0257/)
|
||||
- [PEP 484 - Type Hints](https://peps.python.org/pep-0484/)
|
||||
- [Python secrets Documentation](https://docs.python.org/3/library/secrets.html)
|
||||
- [Pytest Best Practices](https://docs.pytest.org/en/stable/goodpractices.html)
|
||||
603
docs/standards/version-implementation-guide.md
Normal file
603
docs/standards/version-implementation-guide.md
Normal file
@@ -0,0 +1,603 @@
|
||||
# Version Implementation Guide
|
||||
|
||||
## Quick Reference
|
||||
|
||||
This guide shows exactly where and how version information is stored in StarPunk.
|
||||
|
||||
**See Also**: [versioning-strategy.md](versioning-strategy.md) for complete strategy documentation.
|
||||
|
||||
## Version Storage Locations
|
||||
|
||||
### 1. Primary Source: `starpunk/__init__.py`
|
||||
|
||||
**Status**: ✅ Already implemented
|
||||
|
||||
**Location**: `/home/phil/Projects/starpunk/starpunk/__init__.py`
|
||||
|
||||
**Content**:
|
||||
```python
|
||||
# Package version (Semantic Versioning 2.0.0)
|
||||
# See docs/standards/versioning-strategy.md for details
|
||||
__version__ = "0.1.0"
|
||||
__version_info__ = (0, 1, 0)
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```python
|
||||
from starpunk import __version__, __version_info__
|
||||
|
||||
print(f"StarPunk version {__version__}")
|
||||
print(f"Version info: {__version_info__}") # (0, 1, 0)
|
||||
```
|
||||
|
||||
**Format**:
|
||||
- `__version__`: String in PEP 440 format (e.g., `"1.0.0"`, `"1.0.0a1"`, `"1.0.0rc1"`)
|
||||
- `__version_info__`: Tuple of integers (major, minor, patch) for programmatic comparison
|
||||
|
||||
---
|
||||
|
||||
### 2. README.md
|
||||
|
||||
**Status**: ✅ Already implemented
|
||||
|
||||
**Location**: `/home/phil/Projects/starpunk/README.md`
|
||||
|
||||
**Content**: Shows current version at the top of the file:
|
||||
```markdown
|
||||
# StarPunk
|
||||
|
||||
A minimal, self-hosted IndieWeb CMS for publishing notes with RSS syndication.
|
||||
|
||||
**Current Version**: 0.1.0 (development)
|
||||
|
||||
## Versioning
|
||||
|
||||
StarPunk follows [Semantic Versioning 2.0.0](https://semver.org/):
|
||||
- Version format: `MAJOR.MINOR.PATCH`
|
||||
- Current: `0.1.0` (pre-release development)
|
||||
- First stable release will be `1.0.0`
|
||||
```
|
||||
|
||||
**Update when**: Version changes (manually)
|
||||
|
||||
---
|
||||
|
||||
### 3. CHANGELOG.md
|
||||
|
||||
**Status**: ✅ Created
|
||||
|
||||
**Location**: `/home/phil/Projects/starpunk/CHANGELOG.md`
|
||||
|
||||
**Format**: Based on [Keep a Changelog](https://keepachangelog.com/)
|
||||
|
||||
**Structure**:
|
||||
```markdown
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- Features being developed
|
||||
|
||||
## [1.0.0] - 2024-11-18
|
||||
|
||||
### Added
|
||||
- New features
|
||||
|
||||
### Changed
|
||||
- Changes to existing features
|
||||
|
||||
### Fixed
|
||||
- Bug fixes
|
||||
|
||||
### Security
|
||||
- Security patches
|
||||
```
|
||||
|
||||
**Update when**: Every change (add to `[Unreleased]`), then move to versioned section on release
|
||||
|
||||
---
|
||||
|
||||
### 4. Git Tags
|
||||
|
||||
**Status**: ⏳ To be created when releasing
|
||||
|
||||
**Format**: `vMAJOR.MINOR.PATCH[-PRERELEASE]`
|
||||
|
||||
**Examples**:
|
||||
```bash
|
||||
v0.1.0 # Development version
|
||||
v0.2.0 # Next development version
|
||||
v1.0.0-alpha.1 # Alpha pre-release
|
||||
v1.0.0-beta.1 # Beta pre-release
|
||||
v1.0.0-rc.1 # Release candidate
|
||||
v1.0.0 # Stable release
|
||||
```
|
||||
|
||||
**How to Create**:
|
||||
```bash
|
||||
# Annotated tag (recommended)
|
||||
git tag -a v0.1.0 -m "Development version 0.1.0: Phase 1.1 complete
|
||||
|
||||
- Core utilities implemented
|
||||
- Slug generation
|
||||
- File operations
|
||||
- Content hashing
|
||||
|
||||
See CHANGELOG.md for full details."
|
||||
|
||||
# Push tag
|
||||
git push origin v0.1.0
|
||||
```
|
||||
|
||||
**Current Tags**: None yet (will create when Phase 1.1 is complete)
|
||||
|
||||
---
|
||||
|
||||
### 5. pyproject.toml (Optional, Future)
|
||||
|
||||
**Status**: ⚠️ Not currently used
|
||||
|
||||
**Location**: `/home/phil/Projects/starpunk/pyproject.toml` (if created)
|
||||
|
||||
**Content** (if we create this file):
|
||||
```toml
|
||||
[project]
|
||||
name = "starpunk"
|
||||
version = "0.1.0"
|
||||
description = "Minimal IndieWeb CMS"
|
||||
authors = [
|
||||
{name = "Your Name", email = "your@email.com"}
|
||||
]
|
||||
dependencies = [
|
||||
"flask>=3.0.0",
|
||||
"python-markdown>=3.5.0",
|
||||
"feedgen>=1.0.0",
|
||||
"httpx>=0.25.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/YOUR_USERNAME/starpunk"
|
||||
Repository = "https://github.com/YOUR_USERNAME/starpunk"
|
||||
Changelog = "https://github.com/YOUR_USERNAME/starpunk/blob/main/CHANGELOG.md"
|
||||
```
|
||||
|
||||
**Decision**: Not needed for V1 (keeping it simple)
|
||||
|
||||
---
|
||||
|
||||
## How to Update Version
|
||||
|
||||
### Step-by-Step Process
|
||||
|
||||
When you're ready to release a new version:
|
||||
|
||||
#### 1. Decide on Version Number
|
||||
|
||||
Use the decision tree from `versioning-strategy.md`:
|
||||
|
||||
- **Breaking changes?** → Increment MAJOR (e.g., 1.0.0 → 2.0.0)
|
||||
- **New features?** → Increment MINOR (e.g., 1.0.0 → 1.1.0)
|
||||
- **Bug fixes only?** → Increment PATCH (e.g., 1.0.0 → 1.0.1)
|
||||
|
||||
**During 0.x development**:
|
||||
- **Phase complete?** → Increment MINOR (e.g., 0.1.0 → 0.2.0)
|
||||
- **Bug fix?** → Increment PATCH (e.g., 0.1.0 → 0.1.1)
|
||||
|
||||
#### 2. Update `starpunk/__init__.py`
|
||||
|
||||
```python
|
||||
# Change version strings
|
||||
__version__ = "0.2.0" # New version
|
||||
__version_info__ = (0, 2, 0) # New version tuple
|
||||
```
|
||||
|
||||
#### 3. Update `CHANGELOG.md`
|
||||
|
||||
Move `[Unreleased]` items to new version section:
|
||||
|
||||
```markdown
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
|
||||
## [0.2.0] - 2024-11-19
|
||||
|
||||
### Added
|
||||
- Data models implementation
|
||||
- Database schema
|
||||
- Note class with validation
|
||||
|
||||
### Changed
|
||||
- Improved error handling in utilities
|
||||
|
||||
[Unreleased]: https://github.com/YOUR_USERNAME/starpunk/compare/v0.2.0...HEAD
|
||||
[0.2.0]: https://github.com/YOUR_USERNAME/starpunk/compare/v0.1.0...v0.2.0
|
||||
[0.1.0]: https://github.com/YOUR_USERNAME/starpunk/releases/tag/v0.1.0
|
||||
```
|
||||
|
||||
#### 4. Update `README.md`
|
||||
|
||||
Change current version:
|
||||
```markdown
|
||||
**Current Version**: 0.2.0 (development)
|
||||
```
|
||||
|
||||
#### 5. Commit Changes
|
||||
|
||||
```bash
|
||||
git add starpunk/__init__.py CHANGELOG.md README.md
|
||||
git commit -m "Bump version to 0.2.0"
|
||||
```
|
||||
|
||||
#### 6. Create Git Tag
|
||||
|
||||
```bash
|
||||
git tag -a v0.2.0 -m "Development version 0.2.0: Phase 1.2 complete
|
||||
|
||||
- Data models implementation
|
||||
- Database schema defined
|
||||
- Note class with full validation
|
||||
- Session and token models
|
||||
|
||||
See CHANGELOG.md for full details."
|
||||
```
|
||||
|
||||
#### 7. Push to Repository
|
||||
|
||||
```bash
|
||||
git push origin main
|
||||
git push origin v0.2.0
|
||||
```
|
||||
|
||||
#### 8. Create GitHub Release (Optional)
|
||||
|
||||
If using GitHub:
|
||||
1. Go to repository → Releases
|
||||
2. Click "Draft a new release"
|
||||
3. Choose tag: `v0.2.0`
|
||||
4. Release title: `Version 0.2.0`
|
||||
5. Description: Copy from CHANGELOG.md
|
||||
6. Publish release
|
||||
|
||||
---
|
||||
|
||||
## How to Check Version
|
||||
|
||||
### From Python Code
|
||||
|
||||
```python
|
||||
# Method 1: Import from package
|
||||
from starpunk import __version__
|
||||
print(f"Version: {__version__}")
|
||||
|
||||
# Method 2: Use version_info for comparisons
|
||||
from starpunk import __version_info__
|
||||
if __version_info__ >= (1, 0, 0):
|
||||
print("Stable release")
|
||||
else:
|
||||
print("Development version")
|
||||
```
|
||||
|
||||
### From Command Line
|
||||
|
||||
```bash
|
||||
# Method 1: Python one-liner
|
||||
python -c "from starpunk import __version__; print(__version__)"
|
||||
|
||||
# Method 2: Check Git tags
|
||||
git tag -l
|
||||
|
||||
# Method 3: Check current HEAD tag
|
||||
git describe --tags --abbrev=0
|
||||
|
||||
# Method 4: Show all version info
|
||||
git describe --tags
|
||||
```
|
||||
|
||||
### From Web Interface (Future)
|
||||
|
||||
When implemented, will show in:
|
||||
- Footer of all pages
|
||||
- `/api/info` endpoint
|
||||
- Admin dashboard
|
||||
|
||||
---
|
||||
|
||||
## Version Comparison Examples
|
||||
|
||||
### Using `__version_info__`
|
||||
|
||||
```python
|
||||
from starpunk import __version_info__
|
||||
|
||||
# Check minimum version
|
||||
def require_version(major, minor, patch):
|
||||
return __version_info__ >= (major, minor, patch)
|
||||
|
||||
# Examples
|
||||
if require_version(1, 0, 0):
|
||||
print("Running stable version")
|
||||
|
||||
if require_version(0, 2, 0):
|
||||
print("Has data models")
|
||||
```
|
||||
|
||||
### Parsing `__version__` String
|
||||
|
||||
```python
|
||||
from starpunk import __version__
|
||||
from packaging.version import Version
|
||||
|
||||
current = Version(__version__)
|
||||
required = Version("1.0.0")
|
||||
|
||||
if current >= required:
|
||||
print("Version requirement met")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Release Checklist
|
||||
|
||||
Use this checklist when releasing a new version:
|
||||
|
||||
### Pre-Release
|
||||
|
||||
- [ ] All tests pass: `pytest`
|
||||
- [ ] Code formatted: `black starpunk/ tests/`
|
||||
- [ ] Code linted: `flake8 starpunk/ tests/`
|
||||
- [ ] Documentation updated
|
||||
- [ ] All features working
|
||||
- [ ] Manual testing completed
|
||||
|
||||
### Version Bump
|
||||
|
||||
- [ ] Decide version number (MAJOR.MINOR.PATCH)
|
||||
- [ ] Update `starpunk/__init__.py` → `__version__`
|
||||
- [ ] Update `starpunk/__init__.py` → `__version_info__`
|
||||
- [ ] Update `CHANGELOG.md` with changes and date
|
||||
- [ ] Update `README.md` with current version
|
||||
- [ ] Review changes: `git diff`
|
||||
|
||||
### Commit and Tag
|
||||
|
||||
- [ ] Stage files: `git add starpunk/__init__.py CHANGELOG.md README.md`
|
||||
- [ ] Commit: `git commit -m "Bump version to X.Y.Z"`
|
||||
- [ ] Create annotated tag: `git tag -a vX.Y.Z -m "Release X.Y.Z: [description]"`
|
||||
- [ ] Push commits: `git push origin main`
|
||||
- [ ] Push tag: `git push origin vX.Y.Z`
|
||||
|
||||
### Post-Release
|
||||
|
||||
- [ ] Verify tag exists: `git tag -l`
|
||||
- [ ] Create GitHub release (if applicable)
|
||||
- [ ] Test installation from tag
|
||||
- [ ] Announce release (if applicable)
|
||||
- [ ] Create `[Unreleased]` section in CHANGELOG.md for next development
|
||||
|
||||
---
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Scenario 1: Completing a Development Phase
|
||||
|
||||
**Situation**: Phase 1.2 is complete (data models implemented)
|
||||
|
||||
**Action**:
|
||||
1. Version: 0.1.0 → 0.2.0 (increment MINOR during 0.x)
|
||||
2. Update `__init__.py`: `__version__ = "0.2.0"`, `__version_info__ = (0, 2, 0)`
|
||||
3. Update CHANGELOG: Add `[0.2.0]` section with date
|
||||
4. Commit: `"Bump version to 0.2.0"`
|
||||
5. Tag: `v0.2.0`
|
||||
6. Push
|
||||
|
||||
### Scenario 2: Fixing a Bug
|
||||
|
||||
**Situation**: Found bug in slug generation, fixed it
|
||||
|
||||
**Action**:
|
||||
1. Version: 0.2.0 → 0.2.1 (increment PATCH)
|
||||
2. Update `__init__.py`: `__version__ = "0.2.1"`, `__version_info__ = (0, 2, 1)`
|
||||
3. Update CHANGELOG: Add `[0.2.1]` section with bug fix
|
||||
4. Commit: `"Bump version to 0.2.1"`
|
||||
5. Tag: `v0.2.1`
|
||||
6. Push
|
||||
|
||||
### Scenario 3: First Stable Release
|
||||
|
||||
**Situation**: All V1 features complete, ready for production
|
||||
|
||||
**Action**:
|
||||
1. Version: 0.9.0 → 1.0.0 (first stable release!)
|
||||
2. Update `__init__.py`: `__version__ = "1.0.0"`, `__version_info__ = (1, 0, 0)`
|
||||
3. Update CHANGELOG: Comprehensive `[1.0.0]` section
|
||||
4. Update README: Change "0.x.0 (development)" to "1.0.0 (stable)"
|
||||
5. Commit: `"Bump version to 1.0.0 - First stable release"`
|
||||
6. Tag: `v1.0.0` with detailed release notes
|
||||
7. Push
|
||||
8. Create GitHub release with announcement
|
||||
|
||||
### Scenario 4: Adding New Feature (Post-1.0)
|
||||
|
||||
**Situation**: Added tags feature in version 1.0.0, want to release it
|
||||
|
||||
**Action**:
|
||||
1. Version: 1.0.0 → 1.1.0 (increment MINOR for new feature)
|
||||
2. Update `__init__.py`: `__version__ = "1.1.0"`, `__version_info__ = (1, 1, 0)`
|
||||
3. Update CHANGELOG: Add `[1.1.0]` section with "Added: Tags support"
|
||||
4. Commit and tag
|
||||
5. Push
|
||||
|
||||
### Scenario 5: Breaking Change (Post-1.0)
|
||||
|
||||
**Situation**: Changed Micropub API response format (breaking change)
|
||||
|
||||
**Action**:
|
||||
1. Version: 1.5.0 → 2.0.0 (increment MAJOR for breaking change)
|
||||
2. Update `__init__.py`: `__version__ = "2.0.0"`, `__version_info__ = (2, 0, 0)`
|
||||
3. Update CHANGELOG: Clear "Breaking Changes" section
|
||||
4. Create upgrade guide: `docs/upgrade/1.x-to-2.0.md`
|
||||
5. Commit and tag
|
||||
6. Push
|
||||
7. Prominently announce breaking changes
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Version Mismatch
|
||||
|
||||
**Problem**: `__version__` and `__version_info__` don't match
|
||||
|
||||
**Solution**: They must always match:
|
||||
```python
|
||||
# Correct
|
||||
__version__ = "1.2.3"
|
||||
__version_info__ = (1, 2, 3)
|
||||
|
||||
# Wrong
|
||||
__version__ = "1.2.3"
|
||||
__version_info__ = (1, 2, 0) # Mismatch!
|
||||
```
|
||||
|
||||
### Forgot to Update CHANGELOG
|
||||
|
||||
**Problem**: Released version without updating CHANGELOG
|
||||
|
||||
**Solution**:
|
||||
1. Update CHANGELOG now
|
||||
2. Commit: `"Update CHANGELOG for version X.Y.Z"`
|
||||
3. Don't change version number or tag
|
||||
4. Consider creating patch release with corrected changelog
|
||||
|
||||
### Wrong Version Number
|
||||
|
||||
**Problem**: Tagged v1.1.0 but should have been v2.0.0
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Delete local tag
|
||||
git tag -d v1.1.0
|
||||
|
||||
# Delete remote tag
|
||||
git push origin :refs/tags/v1.1.0
|
||||
|
||||
# Create correct tag
|
||||
git tag -a v2.0.0 -m "Release 2.0.0"
|
||||
git push origin v2.0.0
|
||||
```
|
||||
|
||||
**Warning**: Only do this immediately after release, before anyone uses the tag!
|
||||
|
||||
### Git Tag vs Python Version Mismatch
|
||||
|
||||
**Problem**: Git tag says `v1.0.0` but `__version__` says `"0.9.0"`
|
||||
|
||||
**Solution**: These must always match:
|
||||
1. Check Git tag: `git describe --tags`
|
||||
2. Check Python version: `python -c "from starpunk import __version__; print(__version__)"`
|
||||
3. They should match (ignoring `v` prefix)
|
||||
4. If mismatch, fix `__init__.py` and create new tag
|
||||
|
||||
---
|
||||
|
||||
## Pre-Release Versions
|
||||
|
||||
### Alpha Releases
|
||||
|
||||
**When**: Early development, unstable, missing features
|
||||
|
||||
**Format**:
|
||||
- Git tag: `v1.0.0-alpha.1`
|
||||
- Python `__version__`: `"1.0.0a1"` (PEP 440 format)
|
||||
- `__version_info__`: `(1, 0, 0, "alpha", 1)` (extended tuple)
|
||||
|
||||
**Example**:
|
||||
```python
|
||||
__version__ = "1.0.0a1"
|
||||
__version_info__ = (1, 0, 0) # Simplified, or extended: (1, 0, 0, "alpha", 1)
|
||||
```
|
||||
|
||||
### Beta Releases
|
||||
|
||||
**When**: Feature complete, testing phase
|
||||
|
||||
**Format**:
|
||||
- Git tag: `v1.0.0-beta.1`
|
||||
- Python `__version__`: `"1.0.0b1"`
|
||||
- `__version_info__`: `(1, 0, 0)`
|
||||
|
||||
### Release Candidates
|
||||
|
||||
**When**: Final testing before stable release
|
||||
|
||||
**Format**:
|
||||
- Git tag: `v1.0.0-rc.1`
|
||||
- Python `__version__`: `"1.0.0rc1"`
|
||||
- `__version_info__`: `(1, 0, 0)`
|
||||
|
||||
**Note**: For V1, we may skip pre-releases entirely and go straight to 1.0.0 if 0.x testing is sufficient.
|
||||
|
||||
---
|
||||
|
||||
## Integration with Other Systems
|
||||
|
||||
### Flask Application
|
||||
|
||||
```python
|
||||
# In Flask app
|
||||
from starpunk import __version__
|
||||
|
||||
@app.route('/api/info')
|
||||
def api_info():
|
||||
return {
|
||||
"name": "StarPunk",
|
||||
"version": __version__,
|
||||
"micropub_endpoint": "/api/micropub"
|
||||
}
|
||||
```
|
||||
|
||||
### CLI Tool (Future)
|
||||
|
||||
```python
|
||||
import click
|
||||
from starpunk import __version__
|
||||
|
||||
@click.group()
|
||||
@click.version_option(version=__version__, prog_name="StarPunk")
|
||||
def cli():
|
||||
"""StarPunk CMS"""
|
||||
pass
|
||||
```
|
||||
|
||||
### User-Agent String
|
||||
|
||||
```python
|
||||
from starpunk import __version__
|
||||
|
||||
USER_AGENT = f"StarPunk/{__version__} (https://github.com/YOUR_USERNAME/starpunk)"
|
||||
|
||||
# Use in HTTP requests
|
||||
headers = {"User-Agent": USER_AGENT}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [Versioning Strategy (complete spec)](versioning-strategy.md)
|
||||
- [ADR-008: Versioning Strategy](../decisions/ADR-008-versioning-strategy.md)
|
||||
- [Semantic Versioning 2.0.0](https://semver.org/)
|
||||
- [PEP 440 - Version Identification](https://peps.python.org/pep-0440/)
|
||||
- [Keep a Changelog](https://keepachangelog.com/)
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0
|
||||
**Last Updated**: 2024-11-18
|
||||
**Status**: Active
|
||||
1319
docs/standards/versioning-strategy.md
Normal file
1319
docs/standards/versioning-strategy.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user