that initial commit

This commit is contained in:
2025-11-18 19:21:31 -07:00
commit a68fd570c7
69 changed files with 31070 additions and 0 deletions

View 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/)

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

View 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

File diff suppressed because it is too large Load Diff

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

View 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

File diff suppressed because it is too large Load Diff