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,97 @@
# ADR-001: Python Web Framework Selection
## Status
Accepted
## Context
StarPunk requires a Python web framework to implement the API-first architecture with RESTful endpoints, Micropub support, IndieAuth integration, and web interface. The framework must support both API and server-side rendered HTML with minimal complexity.
## Decision
Use **Flask** as the primary web framework.
## Rationale
### Simplicity Score: 9/10
- Minimal boilerplate code required
- Explicit routing and request handling
- Easy to understand for newcomers
- Core framework is ~1000 lines of code
- Follows "micro-framework" philosophy aligned with StarPunk principles
### Fitness Score: 10/10
- Perfect for single-user applications
- Built-in development server
- Excellent template engine (Jinja2) for HTML generation
- Simple decorator-based routing
- Easy integration with SQLite
- Native support for both JSON APIs and HTML rendering
- Werkzeug provides robust HTTP utilities
- Blueprint support for code organization
### Maintenance Score: 9/10
- Extremely mature (13+ years)
- Large community and extensive documentation
- Stable API with minimal breaking changes
- Extensive ecosystem of well-tested extensions
- Active development and security updates
### Standards Compliance: Pass
- Standard WSGI interface
- Full HTTP status code support
- Proper content-type handling
- Easy CORS implementation
- Session management built-in
## Consequences
### Positive
- Minimal learning curve
- Small dependency footprint
- Easy to test (built-in test client)
- Flexible enough for API-first architecture
- Can render HTML templates for public interface
- Easy deployment (WSGI compatible)
### Negative
- No built-in ORM (but we're using raw SQLite, so this is actually positive)
- Requires manual selection of extensions
- Less opinionated than larger frameworks
### Mitigation
- Extension selection will be minimal (see ADR-002 for extensions)
- Lack of opinion allows us to stay minimal
- Manual configuration gives us full control
## Alternatives Considered
### FastAPI (Rejected)
- **Simplicity**: 6/10 - Requires async/await understanding, Pydantic models
- **Fitness**: 7/10 - Overkill for single-user CMS, async not needed
- **Maintenance**: 8/10 - Newer framework, but growing
- **Verdict**: Too complex for project needs, async unnecessary
### Django (Rejected)
- **Simplicity**: 3/10 - Large framework with heavy abstractions
- **Fitness**: 4/10 - Designed for multi-user applications, includes admin panel, ORM, and many features we don't need
- **Maintenance**: 10/10 - Excellent maintenance and security
- **Verdict**: Violates "minimal code" principle, too much unnecessary functionality
### Bottle (Considered)
- **Simplicity**: 10/10 - Single file framework
- **Fitness**: 7/10 - Very minimal, but perhaps too minimal
- **Maintenance**: 6/10 - Smaller community, slower updates
- **Verdict**: Close second, but Flask has better ecosystem for IndieAuth/Micropub
## Implementation Notes
Flask will be used with:
- Jinja2 templates for HTML rendering (included with Flask)
- Werkzeug for HTTP utilities (included with Flask)
- Minimal extensions only (see ADR-002)
- Standard WSGI deployment
- Blueprint organization for clear separation of concerns
## References
- Flask Documentation: https://flask.palletsprojects.com/
- WSGI Specification: https://peps.python.org/pep-3333/
- Flask Design Decisions: https://flask.palletsprojects.com/en/3.0.x/design/

View File

@@ -0,0 +1,134 @@
# ADR-002: Flask Extensions and Dependencies
## Status
Accepted
## Context
Flask is intentionally minimal. We need to select only essential extensions that align with the "minimal code" philosophy while supporting required functionality.
## Decision
Use the following minimal set of dependencies:
- **Flask** - Core framework
- **markdown** - Markdown to HTML conversion
- **feedgen** - RSS feed generation
- **httpx** - HTTP client for IndieAuth verification
- **python-dotenv** - Environment configuration
- **pytest** - Testing framework
**NO additional Flask extensions** will be used in V1.
## Rationale
### Core Dependencies
#### markdown
- **Purpose**: Convert markdown notes to HTML
- **Simplicity**: Pure Python, simple API
- **Justification**: Core requirement for note rendering
- **Alternative**: mistune (faster but less standard)
- **Verdict**: markdown is more standard and sufficient for single-user
#### feedgen
- **Purpose**: Generate valid RSS 2.0 feeds
- **Simplicity**: High-level API, handles all RSS requirements
- **Justification**: Ensures RSS 2.0 compliance without manual XML generation
- **Alternative**: Manual XML generation (error-prone)
- **Verdict**: feedgen guarantees valid RSS output
#### httpx
- **Purpose**: HTTP client for IndieAuth endpoint verification
- **Simplicity**: Modern, clean API
- **Justification**: Need to verify IndieAuth endpoints and fetch client metadata
- **Alternative**: requests (synchronous only), urllib (too low-level)
- **Verdict**: httpx provides clean API and can be sync or async if needed later
#### python-dotenv
- **Purpose**: Load environment variables from .env file
- **Simplicity**: Single-purpose, simple API
- **Justification**: Standard pattern for configuration management
- **Alternative**: Manual environment variable handling
- **Verdict**: Industry standard, minimal overhead
#### pytest
- **Purpose**: Testing framework
- **Simplicity**: Minimal boilerplate, clear assertions
- **Justification**: Required for test coverage
- **Alternative**: unittest (more verbose), nose2 (unmaintained)
- **Verdict**: pytest is current Python testing standard
### Extensions REJECTED for V1
#### Flask-SQLAlchemy (Rejected)
- **Reason**: Adds ORM abstraction we don't need
- **Decision**: Use sqlite3 standard library directly
- **Benefit**: Simpler code, explicit queries, no magic
#### Flask-Login (Rejected)
- **Reason**: Session-based authentication, we need token-based
- **Decision**: Implement simple token validation ourselves
- **Benefit**: Full control over IndieAuth flow
#### Flask-CORS (Rejected)
- **Reason**: Single function decorator, don't need extension
- **Decision**: Use @after_request decorator for CORS headers
- **Benefit**: 5 lines of code vs. another dependency
#### Flask-Limiter (Rejected for V1)
- **Reason**: Rate limiting is nice-to-have, not critical for single-user
- **Decision**: Defer to V2 or rely on reverse proxy
- **Benefit**: Reduced complexity
#### Flask-WTF (Rejected)
- **Reason**: Form handling for single form (note creation) is overkill
- **Decision**: Simple HTML forms with manual validation
- **Benefit**: No CSRF complexity in V1, manual validation is clear
## Consequences
### Positive
- Minimal dependency tree
- Full control over implementation
- Easy to understand codebase
- Fast installation and startup
- Reduced attack surface
### Negative
- Must implement some features manually (token validation, CORS)
- No form CSRF protection in V1 (acceptable for single-user)
- Manual SQL queries required
### Mitigation
- Document manual implementations clearly
- Ensure manual code is well-tested
- Keep manual implementations simple and obvious
- Plan to add CSRF in V2 if needed
## Complete Dependency List
```
Flask==3.0.*
markdown==3.5.*
feedgen==1.0.*
httpx==0.27.*
python-dotenv==1.0.*
pytest==8.0.*
```
## Development Dependencies
```
pytest-cov # Test coverage reporting
black # Code formatting
flake8 # Linting
```
## Standards Compliance
- All dependencies are pure Python or have minimal C extensions
- All are actively maintained with security updates
- All support Python 3.11+
- Total dependency count: 6 direct dependencies (excluding dev tools)
## References
- Flask Extensions: https://flask.palletsprojects.com/en/3.0.x/extensions/
- Markdown Spec: https://daringfireball.net/projects/markdown/
- RSS 2.0: https://www.rssboard.org/rss-specification
- Python Packaging: https://packaging.python.org/

View File

@@ -0,0 +1,289 @@
# ADR-003: Front-end Technology Stack
## Status
Accepted
## Context
StarPunk requires a front-end for:
1. Public interface (homepage, note permalinks) - Server-side rendered
2. Admin interface (note creation/editing) - Requires some interactivity
3. Progressive enhancement principle - Core functionality must work without JavaScript
The front-end must be minimal, elegant, and align with the "no client-side complexity" principle stated in CLAUDE.MD.
## Decision
### Public Interface: Server-Side Rendered HTML
- **Template Engine**: Jinja2 (included with Flask)
- **CSS**: Custom CSS (no framework)
- **JavaScript**: None required for V1
- **Build Tools**: None required
### Admin Interface: Enhanced Server-Side Rendering
- **Template Engine**: Jinja2 (included with Flask)
- **CSS**: Custom CSS (shared with public interface)
- **JavaScript**: Minimal vanilla JavaScript for markdown preview only
- **Build Tools**: None required
### Asset Management
- **CSS**: Single stylesheet served statically
- **JavaScript**: Single optional file for markdown preview
- **No bundler**: Direct file serving
- **No transpilation**: Modern browsers only (ES6+)
## Rationale
### Server-Side Rendering (SSR)
**Simplicity Score: 10/10**
- Zero build process
- No JavaScript framework complexity
- Direct Flask template rendering
- Familiar Jinja2 syntax
**Fitness Score: 10/10**
- Perfect for content-first site
- Faster initial page load
- Better SEO (though not critical for single-user)
- Works without JavaScript
- Easier to implement microformats
**Maintenance Score: 10/10**
- Jinja2 is stable and mature
- No framework version updates
- No npm dependency hell
- Templates are simple HTML
### No CSS Framework
**Simplicity Score: 10/10**
- Custom CSS is ~200 lines for entire site
- No unused classes or styles
- Full control over appearance
- No framework learning curve
**Fitness Score: 9/10**
- StarPunk needs minimal, elegant design
- Single theme, no customization needed
- Mobile-responsive can be achieved with simple media queries
- No complex components needed
### Minimal JavaScript Approach
**Simplicity Score: 9/10**
- Vanilla JavaScript only (no React/Vue/Svelte)
- Single purpose: markdown preview in admin
- Optional progressive enhancement
- No build step required
**Fitness Score: 10/10**
- Markdown preview improves UX but isn't required
- All functionality works without JavaScript
- Can use fetch API for preview without library
- Modern browser features are sufficient
## Consequences
### Positive
- Zero build time
- No node_modules directory
- Instant development setup
- Fast page loads
- Works with JavaScript disabled
- Easy to understand and modify
- Microformats implementation is straightforward
- Complete control over HTML output
### Negative
- No TypeScript type checking
- No hot module replacement (but Flask auto-reload works)
- Manual CSS organization required
- Must write responsive CSS manually
### Mitigation
- Keep JavaScript minimal and well-commented
- Organize CSS with clear sections
- Use CSS custom properties for theming
- Test manually in multiple browsers
- Validate HTML with W3C validator
## Frontend File Structure
```
static/
├── css/
│ └── style.css # Single stylesheet for entire site
└── js/
└── preview.js # Optional markdown preview (admin only)
templates/
├── base.html # Base template with HTML structure
├── index.html # Homepage (note list)
├── note.html # Single note permalink
└── admin/
├── base.html # Admin base template
├── dashboard.html # Admin dashboard
├── new.html # Create new note
└── edit.html # Edit existing note
```
## CSS Architecture
### Custom CSS Properties (Variables)
```css
:root {
--color-text: #333;
--color-bg: #fff;
--color-link: #0066cc;
--color-border: #ddd;
--font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-mono: 'SF Mono', Monaco, monospace;
--spacing-unit: 1rem;
--max-width: 42rem;
}
```
### Mobile-First Responsive Design
```css
/* Base: Mobile styles */
body { padding: 1rem; }
/* Tablet and up */
@media (min-width: 768px) {
body { padding: 2rem; }
}
```
## JavaScript Architecture
### Markdown Preview Implementation
```javascript
// static/js/preview.js
// Simple markdown preview using marked.js CDN (no build step)
// Progressive enhancement - form works without this
```
**Decision**: Use marked.js from CDN for client-side preview
- **Justification**: Same library as server-side (consistency)
- **Simplicity**: No bundling required
- **Reliability**: CDN delivers cached version
- **Alternative**: No preview (acceptable fallback)
## Template Organization
### Jinja2 Template Strategy
- **Inheritance**: Use base templates for common structure
- **Blocks**: Define clear content blocks for overriding
- **Macros**: Create reusable microformat snippets
- **Filters**: Use Jinja2 filters for date formatting
### Example Base Template Structure
```jinja2
{# templates/base.html #}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{{ site.title }}{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<link rel="alternate" type="application/rss+xml" href="{{ url_for('feed') }}">
{% block head %}{% endblock %}
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
```
## Microformats Integration
Server-side rendering makes microformats implementation straightforward:
```jinja2
{# Macro for h-entry note rendering #}
{% macro render_note(note) %}
<article class="h-entry">
<div class="e-content">
{{ note.content_html | safe }}
</div>
<footer>
<a class="u-url" href="{{ url_for('note', slug=note.slug) }}">
<time class="dt-published" datetime="{{ note.created_at.isoformat() }}">
{{ note.created_at.strftime('%B %d, %Y') }}
</time>
</a>
</footer>
</article>
{% endmacro %}
```
## Build and Development Workflow
### Development
1. Run Flask development server: `flask run`
2. Edit templates/CSS/JS directly
3. Browser auto-refresh on template changes
4. No build step required
### Production
1. Copy static files to production
2. Templates are rendered on-demand
3. Optionally enable Flask caching for rendered HTML
4. Serve static assets with nginx/Apache (optional)
## Browser Support
- Modern browsers (Chrome 90+, Firefox 88+, Safari 14+, Edge 90+)
- Mobile browsers (iOS Safari 14+, Chrome Android 90+)
- Progressive enhancement ensures basic functionality on older browsers
## Alternatives Considered
### React/Vue/Svelte (Rejected)
- **Simplicity**: 2/10 - Requires build tools, npm, bundlers
- **Fitness**: 3/10 - Massive overkill for content site
- **Maintenance**: 5/10 - Constant framework updates
- **Verdict**: Violates "no client-side complexity" principle
### htmx (Considered)
- **Simplicity**: 8/10 - Single JavaScript file, declarative
- **Fitness**: 6/10 - Useful for dynamic updates, but not needed in V1
- **Maintenance**: 8/10 - Stable, minimal dependencies
- **Verdict**: Interesting for V2, but V1 doesn't need dynamic updates
### Alpine.js (Considered)
- **Simplicity**: 8/10 - Lightweight, declarative
- **Fitness**: 5/10 - Good for small interactions, but we barely need any
- **Maintenance**: 8/10 - Well maintained
- **Verdict**: Too much for the minimal JS we need
### Tailwind CSS (Rejected)
- **Simplicity**: 4/10 - Requires build process, large configuration
- **Fitness**: 3/10 - Utility-first doesn't fit minimal design needs
- **Maintenance**: 7/10 - Well maintained but heavy
- **Verdict**: Build process violates simplicity; custom CSS is sufficient
### Bootstrap/Bulma (Rejected)
- **Simplicity**: 5/10 - Large framework with many unused features
- **Fitness**: 3/10 - Component-heavy, we need minimal custom design
- **Maintenance**: 9/10 - Very stable
- **Verdict**: Too much CSS for what we need
### PicoCSS/Water.css (Considered)
- **Simplicity**: 9/10 - Classless CSS, just include and go
- **Fitness**: 7/10 - Good starting point but may not match design vision
- **Maintenance**: 8/10 - Maintained, simple
- **Verdict**: Close consideration, but custom CSS gives full control
## Standards Compliance
- Semantic HTML5 elements
- Valid HTML (W3C validator)
- Accessible forms and navigation
- Proper heading hierarchy
- ARIA labels where needed
- Mobile-responsive (viewport meta tag)
- Progressive enhancement (works without JS)
## References
- Jinja2 Documentation: https://jinja.palletsprojects.com/
- MDN Web Docs: https://developer.mozilla.org/
- Microformats2: http://microformats.org/wiki/h-entry
- Progressive Enhancement: https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement
- Semantic HTML: https://developer.mozilla.org/en-US/docs/Glossary/Semantics

View File

@@ -0,0 +1,384 @@
# ADR-004: File-Based Note Storage Architecture
## Status
Accepted
## Context
The user explicitly requires notes to be stored as files on disk rather than as database records. This is critical for:
1. Data portability - notes can be backed up, moved, and read without the application
2. User ownership - direct access to content in human-readable format
3. Simplicity - text files are the simplest storage mechanism
4. Future-proofing - markdown files will be readable forever
However, we also need SQLite for:
- Metadata (timestamps, slugs, published status)
- Authentication tokens
- Fast querying and indexing
- Relational data
The challenge is designing how file-based storage and database metadata work together efficiently.
## Decision
### Hybrid Architecture: Files + Database Metadata
**Notes Content**: Stored as markdown files on disk
**Notes Metadata**: Stored in SQLite database
**Source of Truth**: Files are authoritative for content; database is authoritative for metadata
### File Storage Strategy
#### Directory Structure
```
data/
├── notes/
│ ├── 2024/
│ │ ├── 11/
│ │ │ ├── my-first-note.md
│ │ │ └── another-note.md
│ │ └── 12/
│ │ └── december-note.md
│ └── 2025/
│ └── 01/
│ └── new-year-note.md
├── starpunk.db # SQLite database
└── .backups/ # Optional backup directory
```
#### File Naming Convention
- **Format**: `{slug}.md`
- **Slug rules**: lowercase, alphanumeric, hyphens only, no spaces
- **Example**: `my-first-note.md`
- **Uniqueness**: Enforced by filesystem (can't have two files with same name in same directory)
#### File Organization
- **Pattern**: Year/Month subdirectories (`YYYY/MM/`)
- **Rationale**:
- Keeps directories manageable (max ~30 files per month)
- Easy chronological browsing
- Matches natural mental model
- Scalable to thousands of notes
- **Example path**: `data/notes/2024/11/my-first-note.md`
### Database Schema
```sql
CREATE TABLE notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
slug TEXT UNIQUE NOT NULL, -- URL identifier
file_path TEXT UNIQUE NOT NULL, -- Relative path from data/notes/
published BOOLEAN DEFAULT 0, -- Publication status
created_at TIMESTAMP NOT NULL, -- Creation timestamp
updated_at TIMESTAMP NOT NULL, -- Last modification timestamp
content_hash TEXT -- SHA-256 of file content for change detection
);
CREATE INDEX idx_notes_created_at ON notes(created_at DESC);
CREATE INDEX idx_notes_published ON notes(published);
CREATE INDEX idx_notes_slug ON notes(slug);
```
### File Format
#### Markdown File Structure
```markdown
[Content of the note in markdown format]
```
**That's it.** No frontmatter, no metadata in file. Keep it pure.
**Rationale**:
- Maximum portability
- Readable by any markdown editor
- No custom parsing required
- Metadata belongs in database (timestamps, slugs, etc.)
- User sees just their content when opening file
#### Optional Future Enhancement (V2+)
If frontmatter becomes necessary, use standard YAML:
```markdown
---
title: Optional Title
tags: tag1, tag2
---
[Content here]
```
But for V1: **NO frontmatter**.
## Rationale
### File Storage Benefits
**Simplicity Score: 10/10**
- Text files are the simplest storage
- No binary formats
- Human-readable
- Easy to backup (rsync, git, Dropbox, etc.)
**Portability Score: 10/10**
- Standard markdown format
- Readable without application
- Can be edited in any text editor
- Easy to migrate to other systems
**Ownership Score: 10/10**
- User has direct access to their content
- No vendor lock-in
- Can grep their own notes
- Backup is simple file copy
### Hybrid Approach Benefits
**Performance**: Database indexes enable fast queries
**Flexibility**: Rich metadata without cluttering files
**Integrity**: Database enforces uniqueness and relationships
**Simplicity**: Each system does what it's best at
## Consequences
### Positive
- Notes are portable markdown files
- User can edit notes directly in filesystem if desired
- Easy backup (just copy data/ directory)
- Database provides fast metadata queries
- Can rebuild database from files if needed
- Git-friendly (can version control notes)
- Maximum data ownership
### Negative
- Must keep file and database in sync
- Potential for orphaned database records
- Potential for orphaned files
- File operations are slower than database queries
- Must handle file system errors
### Mitigation Strategies
#### Sync Strategy
1. **On note creation**: Write file FIRST, then database record
2. **On note update**: Update file FIRST, then database record (update timestamp, content_hash)
3. **On note delete**: Mark as deleted in database, optionally move file to .trash/
4. **On startup**: Optional integrity check to detect orphans
#### Orphan Detection
```python
# Pseudo-code for integrity check
def check_integrity():
# Find database records without files
for note in database.all_notes():
if not file_exists(note.file_path):
log_error(f"Orphaned database record: {note.slug}")
# Find files without database records
for file in filesystem.all_markdown_files():
if not database.has_note(file_path=file):
log_error(f"Orphaned file: {file}")
```
#### Content Hash Strategy
- Calculate SHA-256 hash of file content on write
- Store hash in database
- On read, can verify content hasn't been externally modified
- Enables change detection and cache invalidation
## Data Flow Patterns
### Creating a Note
1. Generate slug from content or timestamp
2. Determine file path: `data/notes/{YYYY}/{MM}/{slug}.md`
3. Create directories if needed
4. Write markdown content to file
5. Calculate content hash
6. Insert record into database
7. Return success
**Transaction Safety**: If database insert fails, delete file and raise error
### Reading a Note
**By Slug**:
1. Query database for file_path by slug
2. Read file content from disk
3. Return content + metadata
**For List**:
1. Query database for metadata (sorted, filtered)
2. Optionally read file content for each note
3. Return list with metadata and content
### Updating a Note
1. Query database for existing file_path
2. Write new content to file (atomic write to temp, then rename)
3. Calculate new content hash
4. Update database record (timestamp, content_hash)
5. Return success
**Transaction Safety**: Keep backup of original file until database update succeeds
### Deleting a Note
**Soft Delete (Recommended)**:
1. Update database: set `deleted_at` timestamp
2. Optionally move file to `.trash/` subdirectory
3. Return success
**Hard Delete**:
1. Delete database record
2. Delete file from filesystem
3. Return success
## File System Operations
### Atomic Writes
```python
# Pseudo-code for atomic file write
def write_note_safely(path, content):
temp_path = f"{path}.tmp"
write(temp_path, content)
atomic_rename(temp_path, path) # Atomic on POSIX systems
```
### Directory Creation
```python
# Ensure directory exists before writing
def ensure_note_directory(year, month):
path = f"data/notes/{year}/{month}"
makedirs(path, exist_ok=True)
return path
```
### Slug Generation
```python
# Generate URL-safe slug
def generate_slug(content=None, timestamp=None):
if content:
# Extract first few words, normalize
words = extract_first_words(content, max=5)
slug = normalize(words) # lowercase, hyphens, no special chars
else:
# Fallback: timestamp-based
slug = timestamp.strftime("%Y%m%d-%H%M%S")
# Ensure uniqueness
if database.slug_exists(slug):
slug = f"{slug}-{random_suffix()}"
return slug
```
## Backup Strategy
### Simple Backup
```bash
# User can backup with simple copy
cp -r data/ backup/
# Or with rsync
rsync -av data/ backup/
# Or with git
cd data/ && git add . && git commit -m "Backup"
```
### Restore Strategy
1. Copy data/ directory to new location
2. Application reads database
3. If database missing or corrupt, rebuild from files:
```python
def rebuild_database_from_files():
for file_path in glob("data/notes/**/*.md"):
content = read_file(file_path)
metadata = extract_metadata_from_path(file_path)
database.insert_note(
slug=metadata.slug,
file_path=file_path,
created_at=file_stat.created,
updated_at=file_stat.modified,
content_hash=hash(content)
)
```
## Standards Compliance
### Markdown Standard
- CommonMark specification
- No custom extensions in V1
- Standard markdown processors can read files
### File System Compatibility
- ASCII-safe filenames
- No special characters in paths
- Maximum path length under 255 characters
- POSIX-compatible directory structure
## Alternatives Considered
### All-Database Storage (Rejected)
- **Simplicity**: 8/10 - Simpler code, single source of truth
- **Portability**: 2/10 - Requires database export
- **Ownership**: 3/10 - User doesn't have direct access
- **Verdict**: Violates user requirement for file-based storage
### Flat File Directory (Rejected)
```
data/notes/
├── note-1.md
├── note-2.md
├── note-3.md
...
├── note-9999.md
```
- **Simplicity**: 10/10 - Simplest possible structure
- **Scalability**: 3/10 - Thousands of files in one directory is slow
- **Verdict**: Not scalable, poor performance with many notes
### Git-Based Storage (Rejected for V1)
- **Simplicity**: 6/10 - Requires git integration
- **Portability**: 9/10 - Excellent versioning
- **Performance**: 7/10 - Git operations have overhead
- **Verdict**: Interesting for V2, but adds complexity to V1
### Frontmatter in Files (Rejected for V1)
```markdown
---
slug: my-note
created: 2024-11-18
published: true
---
Note content here
```
- **Simplicity**: 7/10 - Requires YAML parsing
- **Portability**: 8/10 - Common pattern, but not pure markdown
- **Single Source**: 10/10 - All data in one place
- **Verdict**: Deferred to V2; V1 keeps files pure
### JSON Metadata Sidecar (Rejected)
```
notes/
├── my-note.md
├── my-note.json # Metadata
```
- **Simplicity**: 6/10 - Doubles number of files
- **Portability**: 7/10 - Markdown still clean, but extra files
- **Sync Issues**: 5/10 - Must keep two files in sync
- **Verdict**: Database metadata is cleaner
## Implementation Checklist
- [ ] Create data/notes directory structure on initialization
- [ ] Implement slug generation algorithm
- [ ] Implement atomic file write operations
- [ ] Implement content hash calculation
- [ ] Create database schema with indexes
- [ ] Implement sync between files and database
- [ ] Implement orphan detection (optional for V1)
- [ ] Add file system error handling
- [ ] Create backup documentation for users
- [ ] Test with thousands of notes for performance
## References
- CommonMark Spec: https://spec.commonmark.org/
- POSIX File Operations: https://pubs.opengroup.org/onlinepubs/9699919799/
- File System Best Practices: https://www.pathname.com/fhs/
- Atomic File Operations: https://lwn.net/Articles/457667/

View File

@@ -0,0 +1,421 @@
# ADR-005: IndieLogin Authentication Integration
## Status
Accepted
## Context
The user has explicitly required external IndieLogin authentication via indielogin.com for V1. This is different from implementing a full IndieAuth server (which CLAUDE.MD mentions). The distinction is important:
- **IndieAuth Server**: Host your own authentication endpoint (complex)
- **IndieLogin Service**: Use indielogin.com as an external authentication provider (simple)
The user wants the simpler approach: delegate authentication to indielogin.com using their API (https://indielogin.com/api).
IndieLogin.com is a service that:
1. Handles the OAuth 2.0 / IndieAuth flow
2. Verifies user identity via their website
3. Returns authenticated identity to our application
4. Supports multiple authentication methods (RelMeAuth, email, etc.)
## Decision
### Use IndieLogin.com as External Authentication Provider
**Authentication Flow**: OAuth 2.0 Authorization Code flow via indielogin.com
**API Endpoint**: https://indielogin.com/auth
**Token Validation**: Server-side session tokens (not IndieAuth tokens)
**User Identity**: URL (me parameter) verified by indielogin.com
### Architecture
```
User Browser → StarPunk → indielogin.com → User's Website
↑ ↓
└──────────────────────────────┘
(Authenticated session)
```
## Authentication Flow
### 1. Login Initiation
```
User clicks "Login"
StarPunk generates state token (CSRF protection)
Redirect to: https://indielogin.com/auth?
- me={user_website}
- client_id={starpunk_url}
- redirect_uri={starpunk_url}/auth/callback
- state={random_token}
```
### 2. IndieLogin Processing
```
indielogin.com verifies user identity:
- Checks for rel="me" links on user's website
- Or sends email verification
- Or uses other IndieAuth methods
User authenticates via their chosen method
indielogin.com redirects back to StarPunk
```
### 3. Callback Verification
```
indielogin.com → StarPunk callback with:
- code={authorization_code}
- state={original_state}
StarPunk verifies state matches
StarPunk exchanges code for verified identity:
POST https://indielogin.com/auth
- code={authorization_code}
- client_id={starpunk_url}
- redirect_uri={starpunk_url}/auth/callback
indielogin.com responds with:
{ "me": "https://user-website.com" }
StarPunk creates authenticated session
```
### 4. Session Management
```
StarPunk stores session token in cookie
Session token maps to authenticated user URL
Admin routes check for valid session
```
## Implementation Requirements
### Configuration Variables
```
SITE_URL=https://starpunk.example.com
ADMIN_ME=https://your-website.com
SESSION_SECRET=random_secret_key
```
### Database Schema Addition
```sql
-- Add to existing schema
CREATE TABLE sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_token TEXT UNIQUE NOT NULL,
me TEXT NOT NULL, -- Authenticated user URL
created_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP NOT NULL,
last_used_at TIMESTAMP
);
CREATE INDEX idx_sessions_token ON sessions(session_token);
CREATE INDEX idx_sessions_expires ON sessions(expires_at);
CREATE TABLE auth_state (
state TEXT PRIMARY KEY,
created_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP NOT NULL -- Short-lived (5 minutes)
);
```
### HTTP Client for API Calls
Use **httpx** (already selected in ADR-002) for:
- POST to https://indielogin.com/auth to exchange code
- Verify response contains valid "me" URL
- Handle network errors gracefully
### Routes Required
```
GET /admin/login - Display login form
POST /admin/login - Initiate IndieLogin flow
GET /auth/callback - Handle IndieLogin redirect
POST /admin/logout - Destroy session
```
### Login Flow Implementation
#### Step 1: Login Form
```python
# /admin/login (GET)
# Display simple form asking for user's website URL
# Form submits to POST /admin/login with "me" parameter
```
#### Step 2: Initiate Authentication
```python
# /admin/login (POST)
def initiate_login(me_url):
# Validate me_url format
if not is_valid_url(me_url):
return error("Invalid URL")
# Generate and store state token
state = generate_random_token()
store_state(state, expires_in_minutes=5)
# Build IndieLogin authorization URL
params = {
'me': me_url,
'client_id': SITE_URL,
'redirect_uri': f"{SITE_URL}/auth/callback",
'state': state
}
auth_url = f"https://indielogin.com/auth?{urlencode(params)}"
# Redirect user to IndieLogin
return redirect(auth_url)
```
#### Step 3: Handle Callback
```python
# /auth/callback (GET)
def handle_callback(code, state):
# Verify state token (CSRF protection)
if not verify_state(state):
return error("Invalid state")
# Exchange code for verified identity
response = httpx.post('https://indielogin.com/auth', data={
'code': code,
'client_id': SITE_URL,
'redirect_uri': f"{SITE_URL}/auth/callback"
})
if response.status_code != 200:
return error("Authentication failed")
data = response.json()
me = data.get('me')
# Verify this is the authorized admin
if me != ADMIN_ME:
return error("Unauthorized user")
# Create session
session_token = generate_random_token()
create_session(session_token, me, expires_in_days=30)
# Set session cookie
set_cookie('session', session_token, httponly=True, secure=True)
# Redirect to admin dashboard
return redirect('/admin')
```
#### Step 4: Session Validation
```python
# Decorator for protected routes
def require_auth(f):
def wrapper(*args, **kwargs):
session_token = request.cookies.get('session')
if not session_token:
return redirect('/admin/login')
session = get_session(session_token)
if not session or session.expired:
return redirect('/admin/login')
# Update last_used_at
update_session_activity(session_token)
# Store user info in request context
g.user_me = session.me
return f(*args, **kwargs)
return wrapper
# Usage
@app.route('/admin')
@require_auth
def admin_dashboard():
return render_template('admin/dashboard.html')
```
## Rationale
### Why IndieLogin.com Instead of Self-Hosted IndieAuth?
**Simplicity Score: 10/10 (IndieLogin) vs 4/10 (Self-hosted)**
- IndieLogin.com handles all complexity of:
- Discovering user's auth endpoints
- Verifying user identity
- Supporting multiple auth methods (RelMeAuth, email, etc.)
- PKCE implementation
- Self-hosted would require implementing full IndieAuth spec (complex)
**Fitness Score: 10/10**
- Perfect for single-user system
- User controls their identity via their own website
- No password management needed
- Aligns with IndieWeb principles
**Maintenance Score: 10/10**
- indielogin.com is maintained by IndieWeb community
- No auth code to maintain ourselves
- Security updates handled externally
- Well-tested service
**Standards Compliance: Pass**
- Uses OAuth 2.0 / IndieAuth standards
- Compatible with IndieWeb ecosystem
- User identity is their URL (IndieWeb principle)
### Why Session Cookies Instead of Access Tokens?
For admin interface (not Micropub):
- **Simpler**: Standard web session pattern
- **Secure**: HttpOnly cookies prevent XSS
- **Appropriate**: Admin is human using browser, not API client
- **Note**: Micropub will still use access tokens (separate ADR needed)
## Consequences
### Positive
- Extremely simple implementation (< 100 lines of code)
- No authentication code to maintain
- Secure by default (delegated to trusted service)
- True IndieWeb authentication (user owns identity)
- No passwords to manage
- Works immediately without setup
- Community-maintained service
### Negative
- Dependency on external service (indielogin.com)
- Requires internet connection to authenticate
- Single point of failure for login (mitigated: session stays valid)
- User must have their own website/URL
### Mitigation
- Sessions last 30 days, so brief indielogin.com outages don't lock out user
- Document fallback: edit database to create session manually if needed
- IndieLogin.com is stable, community-run service with good uptime
- For V2: Consider optional email fallback or self-hosted IndieAuth
## Security Considerations
### State Token (CSRF Protection)
- Generate cryptographically random state token
- Store in database with short expiry (5 minutes)
- Verify state matches on callback
- Delete state after use (single-use tokens)
### Session Token Security
- Generate with secrets.token_urlsafe(32) or similar
- Store hash in database (not plaintext)
- Mark cookies as HttpOnly and Secure
- Set SameSite=Lax for CSRF protection
- Implement session expiry (30 days)
- Support manual logout (session deletion)
### Identity Verification
- Only allow ADMIN_ME URL to authenticate
- Verify "me" URL from indielogin.com exactly matches config
- Reject any other authenticated users
- Log authentication attempts
### Network Security
- Use HTTPS for all communication
- Verify SSL certificates on httpx requests
- Handle network timeouts gracefully
- Log authentication failures
## Testing Strategy
### Unit Tests
- State token generation and validation
- Session creation and expiry
- URL validation
- Cookie handling
### Integration Tests
- Mock indielogin.com API responses
- Test full authentication flow
- Test session expiry
- Test unauthorized user rejection
- Test CSRF protection (invalid state)
### Manual Testing
- Authenticate with real indielogin.com
- Verify session persistence
- Test logout functionality
- Test session expiry
- Test with wrong "me" URL
## Alternatives Considered
### Self-Hosted IndieAuth Server (Rejected)
- **Complexity**: Must implement full IndieAuth spec
- **Maintenance**: Security updates, endpoint discovery, token generation
- **Verdict**: Too complex for V1, violates simplicity principle
### Password Authentication (Rejected)
- **Security**: Must hash passwords, handle resets, prevent brute force
- **IndieWeb**: Violates IndieWeb principle of URL-based identity
- **Verdict**: Not aligned with project goals
### OAuth via GitHub/Google (Rejected)
- **Simplicity**: Easy to implement
- **IndieWeb**: Not IndieWeb-compatible, user doesn't own identity
- **Verdict**: Violates IndieWeb requirements
### Email Magic Links (Rejected)
- **Simplicity**: Requires email sending infrastructure
- **IndieWeb**: Not standard IndieWeb authentication
- **Verdict**: Deferred to V2 as fallback option
### Multi-User IndieAuth (Rejected for V1)
- **Scope**: V1 is explicitly single-user
- **Complexity**: Would require user management
- **Verdict**: Out of scope, defer to V2
## Implementation Checklist
- [ ] Add SESSION_SECRET and ADMIN_ME to configuration
- [ ] Create sessions and auth_state database tables
- [ ] Implement state token generation and storage
- [ ] Create login form template
- [ ] Implement /admin/login routes (GET and POST)
- [ ] Implement /auth/callback route
- [ ] Implement session creation and validation
- [ ] Create require_auth decorator
- [ ] Implement logout functionality
- [ ] Set secure cookie parameters
- [ ] Add authentication error handling
- [ ] Write unit tests for auth flow
- [ ] Write integration tests with mocked indielogin.com
- [ ] Test with real indielogin.com
- [ ] Document setup process for users
## Configuration Example
```bash
# .env file
SITE_URL=https://starpunk.example.com
ADMIN_ME=https://your-website.com
SESSION_SECRET=your-random-secret-key-here
```
## User Setup Documentation
1. Deploy StarPunk to your server at `https://starpunk.example.com`
2. Configure `ADMIN_ME` to your personal website URL
3. Visit `/admin/login`
4. Enter your website URL (must match ADMIN_ME)
5. indielogin.com will verify your identity
6. Authenticate via your chosen method
7. Redirected back to StarPunk admin interface
## References
- IndieLogin.com: https://indielogin.com/
- IndieLogin API Documentation: https://indielogin.com/api
- IndieAuth Specification: https://indieauth.spec.indieweb.org/
- OAuth 2.0 Spec: https://oauth.net/2/
- Web Authentication Best Practices: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html

View File

@@ -0,0 +1,552 @@
# ADR-006: Python Virtual Environment Management with uv
## Status
Accepted
## Context
StarPunk is a Python-based web application that requires dependency management and virtual environment isolation. Developer agents (AI assistants like Claude Code) need clear, unambiguous standards for:
- Creating and managing Python virtual environments
- Installing and tracking dependencies
- Ensuring reproducible development environments
- Avoiding common pitfalls (polluting global Python, dependency conflicts)
- Maintaining consistency across development and deployment
Traditional tools (pip, venv, virtualenv, poetry, pipenv) have various limitations:
- **pip + venv**: Slow dependency resolution, manual requirements.txt management
- **poetry**: Complex configuration, slow, dependency lock issues
- **pipenv**: Abandoned maintenance, slow performance
- **conda**: Heavyweight, non-standard for web development
We need a tool that is fast, simple, and provides excellent developer experience while maintaining compatibility with standard Python packaging.
## Decision
Use **uv** for all Python virtual environment and dependency management in StarPunk.
uv will be the standard tool for:
- Creating virtual environments
- Installing dependencies
- Managing requirements
- Running Python commands in the virtual environment
- Synchronizing dependencies
## Rationale
### Simplicity Score: 10/10
- Single tool for all environment management
- Simple command syntax (uv venv, uv pip install, uv run)
- Drop-in replacement for pip and virtualenv
- No complex configuration files
- Works with standard requirements.txt
- Written in Rust, installed as single binary
### Performance Score: 10/10
- 10-100x faster than pip for dependency resolution
- Parallel downloads and installations
- Efficient caching mechanism
- Near-instant virtual environment creation
- Minimal overhead for running commands
### Fitness Score: 9/10
- Perfect for small to medium Python projects
- Excellent for single-developer projects
- Works with standard Python packaging (PEP 517/518)
- Compatible with requirements.txt workflow
- Supports editable installs for development
- Works seamlessly with Flask and all our dependencies
### Maintenance Score: 9/10
- Actively developed by Astral (creators of ruff)
- Strong community adoption
- Excellent documentation
- Regular updates and improvements
- Modern codebase (Rust)
- Backed by funding and commercial support
### Standards Compliance: Pass
- Full compatibility with pip
- Works with PyPI and all standard package indices
- Supports PEP 440 version specifiers
- Compatible with requirements.txt format
- Works with standard Python virtual environments
- No proprietary lock files (uses standard formats)
## Implementation Details
### 1. Installation Standards
#### System-Level uv Installation
Developer agents MUST ensure uv is installed before creating environments:
```bash
# Check if uv is installed
which uv
# If not installed, install via pip (fallback)
pip install uv
# Or install via official installer (preferred on Linux/macOS)
curl -LsSf https://astral.sh/uv/install.sh | sh
```
#### Verification
```bash
# Verify uv installation
uv --version
# Expected output: uv 0.x.x (or newer)
```
### 2. Virtual Environment Creation Standards
#### Location and Naming
- **Standard location**: `/home/phil/Projects/starpunk/.venv`
- **Name**: Always use `.venv` (hidden directory)
- **DO NOT** use: `venv`, `env`, `virtualenv`, or custom names
#### Creation Command
```bash
# Create virtual environment with uv
cd /home/phil/Projects/starpunk
uv venv .venv
# Specify Python version (recommended)
uv venv .venv --python 3.11
```
#### Post-Creation Verification
```bash
# Verify .venv directory exists
ls -la /home/phil/Projects/starpunk/.venv
# Verify Python executable
/home/phil/Projects/starpunk/.venv/bin/python --version
```
### 3. Dependency Installation Standards
#### Using requirements.txt (Primary Method)
```bash
# Install all dependencies from requirements.txt
uv pip install -r /home/phil/Projects/starpunk/requirements.txt
# Verify installation
uv pip list
```
#### Installing Individual Packages
```bash
# Install a single package
uv pip install flask==3.0.*
# Install multiple packages
uv pip install flask markdown feedgen
```
#### Development Dependencies
```bash
# Install dev dependencies (if requirements-dev.txt exists)
uv pip install -r /home/phil/Projects/starpunk/requirements-dev.txt
```
### 4. Running Commands in Virtual Environment
#### Using uv run (Recommended)
```bash
# Run Python script
uv run /home/phil/Projects/starpunk/.venv/bin/python script.py
# Run Flask development server
uv run /home/phil/Projects/starpunk/.venv/bin/flask run
# Run pytest
uv run /home/phil/Projects/starpunk/.venv/bin/pytest
# Run Python REPL
uv run /home/phil/Projects/starpunk/.venv/bin/python
```
#### Direct Execution (Alternative)
```bash
# Execute using absolute path to venv Python
/home/phil/Projects/starpunk/.venv/bin/python script.py
/home/phil/Projects/starpunk/.venv/bin/flask run
/home/phil/Projects/starpunk/.venv/bin/pytest
```
### 5. Dependency Tracking Standards
#### Generating requirements.txt
```bash
# Freeze current environment to requirements.txt
uv pip freeze > /home/phil/Projects/starpunk/requirements.txt
# Freeze with sorted output for consistency
uv pip freeze | sort > /home/phil/Projects/starpunk/requirements.txt
```
#### Adding New Dependencies
When adding a new dependency:
1. Install the package: `uv pip install package-name`
2. Update requirements.txt: `uv pip freeze | sort > requirements.txt`
3. Verify installation: `uv pip list | grep package-name`
### 6. Environment Updates and Maintenance
#### Updating Dependencies
```bash
# Update a specific package
uv pip install --upgrade flask
# Update all packages (use with caution)
uv pip install --upgrade -r requirements.txt
# Regenerate requirements.txt after updates
uv pip freeze | sort > requirements.txt
```
#### Cleaning and Rebuilding
```bash
# Remove virtual environment
rm -rf /home/phil/Projects/starpunk/.venv
# Recreate from scratch
uv venv .venv --python 3.11
uv pip install -r requirements.txt
```
## Developer Agent Standards
### Critical Rules for AI Assistants
#### Rule 1: ALWAYS Check for Existing Virtual Environment
Before creating a new virtual environment, ALWAYS check:
```bash
# Check if .venv exists
if [ -d "/home/phil/Projects/starpunk/.venv" ]; then
echo "Virtual environment exists"
/home/phil/Projects/starpunk/.venv/bin/python --version
else
echo "Virtual environment does not exist"
fi
```
**NEVER** create a new virtual environment if one already exists without explicit user permission.
#### Rule 2: ALWAYS Use Absolute Paths
Agent threads reset cwd between bash calls. ALWAYS use absolute paths:
**CORRECT:**
```bash
uv venv /home/phil/Projects/starpunk/.venv
/home/phil/Projects/starpunk/.venv/bin/python script.py
uv pip install -r /home/phil/Projects/starpunk/requirements.txt
```
**INCORRECT:**
```bash
uv venv .venv # Relative path - WRONG
./venv/bin/python script.py # Relative path - WRONG
uv pip install -r requirements.txt # Relative path - WRONG
```
#### Rule 3: Verify Before Executing
Before running Python commands, verify the virtual environment:
```bash
# Verification checklist
[ -d "/home/phil/Projects/starpunk/.venv" ] && echo "✓ venv exists" || echo "✗ venv missing"
[ -f "/home/phil/Projects/starpunk/.venv/bin/python" ] && echo "✓ Python exists" || echo "✗ Python missing"
/home/phil/Projects/starpunk/.venv/bin/python --version
```
#### Rule 4: Handle Errors Gracefully
If virtual environment operations fail:
1. **Check uv installation**: `which uv`
2. **Check Python version**: `python3 --version`
3. **Check disk space**: `df -h /home/phil/Projects/starpunk`
4. **Report specific error** to user with context
5. **DO NOT** silently continue with global Python
#### Rule 5: Never Modify Global Python
**NEVER** run these commands:
```bash
# FORBIDDEN - modifies global Python
pip install package
python3 -m pip install package
sudo pip install package
```
**ALWAYS** use virtual environment:
```bash
# CORRECT - uses virtual environment
uv pip install package
/home/phil/Projects/starpunk/.venv/bin/pip install package
```
#### Rule 6: Track Dependency Changes
After installing or removing packages:
1. Update requirements.txt: `uv pip freeze | sort > requirements.txt`
2. Verify changes: `git diff requirements.txt` (if applicable)
3. Inform user of changes made
### Standard Agent Workflow
#### Scenario 1: First-Time Setup
```bash
# 1. Check if venv exists
if [ ! -d "/home/phil/Projects/starpunk/.venv" ]; then
echo "Creating virtual environment..."
uv venv /home/phil/Projects/starpunk/.venv --python 3.11
fi
# 2. Verify creation
/home/phil/Projects/starpunk/.venv/bin/python --version
# 3. Install dependencies (if requirements.txt exists)
if [ -f "/home/phil/Projects/starpunk/requirements.txt" ]; then
uv pip install -r /home/phil/Projects/starpunk/requirements.txt
fi
# 4. Verify installation
uv pip list
```
#### Scenario 2: Running Development Server
```bash
# 1. Verify venv exists
[ -d "/home/phil/Projects/starpunk/.venv" ] || echo "ERROR: Virtual environment missing"
# 2. Verify Flask is installed
/home/phil/Projects/starpunk/.venv/bin/python -c "import flask; print(flask.__version__)"
# 3. Run Flask development server
/home/phil/Projects/starpunk/.venv/bin/flask --app /home/phil/Projects/starpunk/app.py run
```
#### Scenario 3: Adding New Dependency
```bash
# 1. Install package
uv pip install httpx
# 2. Verify installation
uv pip show httpx
# 3. Update requirements.txt
uv pip freeze | sort > /home/phil/Projects/starpunk/requirements.txt
# 4. Confirm to user
echo "Added httpx to project dependencies"
```
#### Scenario 4: Running Tests
```bash
# 1. Verify pytest is installed
/home/phil/Projects/starpunk/.venv/bin/python -c "import pytest; print(pytest.__version__)"
# 2. Run tests
/home/phil/Projects/starpunk/.venv/bin/pytest /home/phil/Projects/starpunk/tests/
# 3. Run tests with coverage (if pytest-cov installed)
/home/phil/Projects/starpunk/.venv/bin/pytest --cov=/home/phil/Projects/starpunk/src /home/phil/Projects/starpunk/tests/
```
## Project-Specific Standards
### Python Version Requirements
- **Minimum**: Python 3.11
- **Recommended**: Python 3.11 or 3.12
- **Rationale**: Modern Python features, improved performance, security updates
### Directory Structure
```
/home/phil/Projects/starpunk/
├── .venv/ # Virtual environment (NEVER commit)
├── requirements.txt # Production dependencies
├── requirements-dev.txt # Development dependencies (optional)
├── src/ # Application source code
├── tests/ # Test files
└── docs/ # Documentation
```
### .gitignore Requirements
The following MUST be in .gitignore:
```
# Virtual Environment
.venv/
venv/
env/
ENV/
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
```
### Environment Variables
Use python-dotenv for configuration:
```bash
# .env file (NEVER commit to git)
FLASK_APP=app.py
FLASK_ENV=development
SECRET_KEY=your-secret-key
DATABASE_PATH=/home/phil/Projects/starpunk/data/starpunk.db
```
Load in application:
```python
from dotenv import load_dotenv
load_dotenv()
```
### Requirements.txt Format
Follow these conventions:
```
# Requirements.txt - StarPunk Dependencies
# Generated: 2025-11-18
# Web Framework
flask==3.0.*
# Content Processing
markdown==3.5.*
# Feed Generation
feedgen==1.0.*
# HTTP Client
httpx==0.27.*
# Configuration
python-dotenv==1.0.*
```
## Consequences
### Positive
- **10-100x faster** dependency resolution and installation
- **Consistent environments** across development and deployment
- **Simple workflow** - one tool for all Python environment tasks
- **No activation required** - uv run handles environment automatically
- **Excellent caching** - faster subsequent installations
- **Standard compatibility** - works with all existing Python tools
- **Clear agent guidelines** - reduces errors in automated workflows
- **Isolated dependencies** - no conflicts with system Python
### Negative
- **Additional tool dependency** - requires uv installation
- **Less familiar** - newer tool, smaller community than pip
- **Rust dependency** - uv is written in Rust (but distributed as binary)
### Mitigation
- uv is easy to install (single binary, no compilation needed)
- uv is pip-compatible (drop-in replacement)
- Fallback to pip + venv is always possible
- Documentation and agent standards make adoption easy
- Active development and growing adoption reduce risk
### Trade-offs Accepted
- **uv vs poetry**: We chose simplicity over advanced features
- **uv vs pipenv**: We chose active maintenance and speed
- **uv vs pip**: We chose performance over ubiquity
- **Single tool complexity**: Better than managing multiple tools
## Verification Checklist
Before considering the environment correctly set up, verify:
- [ ] uv is installed and accessible: `which uv`
- [ ] Virtual environment exists: `ls -la /home/phil/Projects/starpunk/.venv`
- [ ] Python version is 3.11+: `/home/phil/Projects/starpunk/.venv/bin/python --version`
- [ ] Dependencies installed: `uv pip list` shows Flask, markdown, feedgen, httpx
- [ ] requirements.txt exists and is up to date
- [ ] .venv is in .gitignore
- [ ] Flask runs: `/home/phil/Projects/starpunk/.venv/bin/flask --version`
## Integration with Development Workflow
### Running Flask Application
```bash
# Development server
/home/phil/Projects/starpunk/.venv/bin/flask --app app.py run --debug
# Production server (using gunicorn)
/home/phil/Projects/starpunk/.venv/bin/gunicorn app:app
```
### Running Tests
```bash
# All tests
/home/phil/Projects/starpunk/.venv/bin/pytest
# Specific test file
/home/phil/Projects/starpunk/.venv/bin/pytest tests/test_api.py
# With coverage
/home/phil/Projects/starpunk/.venv/bin/pytest --cov=src tests/
```
### Code Quality Tools
```bash
# Format code with black
/home/phil/Projects/starpunk/.venv/bin/black src/
# Lint with flake8
/home/phil/Projects/starpunk/.venv/bin/flake8 src/
# Type checking with mypy (if added)
/home/phil/Projects/starpunk/.venv/bin/mypy src/
```
## Alternatives Considered
### pip + venv (Rejected)
- **Simplicity**: 8/10 - Standard Python tools, well-known
- **Performance**: 4/10 - Very slow dependency resolution
- **Fitness**: 7/10 - Works but painful for larger dependency trees
- **Maintenance**: 10/10 - Built into Python, always maintained
- **Verdict**: Too slow, poor developer experience, but acceptable fallback
### poetry (Rejected)
- **Simplicity**: 5/10 - Complex pyproject.toml, lock file management
- **Performance**: 5/10 - Slow dependency resolution
- **Fitness**: 6/10 - Overkill for simple project, lock files add complexity
- **Maintenance**: 7/10 - Maintained but has had reliability issues
- **Verdict**: Too complex for "minimal code" philosophy
### pipenv (Rejected)
- **Simplicity**: 6/10 - Simpler than poetry, but still adds abstraction
- **Performance**: 4/10 - Known performance issues
- **Fitness**: 5/10 - Previously recommended, now effectively abandoned
- **Maintenance**: 2/10 - Minimal maintenance, community has moved on
- **Verdict**: Dead project, poor performance
### conda (Rejected)
- **Simplicity**: 3/10 - Heavy, complex environment management
- **Performance**: 5/10 - Slower than uv, larger downloads
- **Fitness**: 2/10 - Designed for data science, not web development
- **Maintenance**: 9/10 - Well maintained, large ecosystem
- **Verdict**: Wrong tool for web application development
### PDM (Considered)
- **Simplicity**: 7/10 - Modern, PEP 582 support
- **Performance**: 8/10 - Fast, but not as fast as uv
- **Fitness**: 7/10 - Good for modern Python projects
- **Maintenance**: 8/10 - Actively maintained, growing community
- **Verdict**: Good alternative, but uv is faster and simpler
## References
- uv Documentation: https://docs.astral.sh/uv/
- uv GitHub: https://github.com/astral-sh/uv
- Python Virtual Environments: https://docs.python.org/3/library/venv.html
- PEP 405 (Python Virtual Environments): https://peps.python.org/pep-0405/
- requirements.txt format: https://pip.pypa.io/en/stable/reference/requirements-file-format/
- Astral (uv creators): https://astral.sh/
## Change Log
- 2025-11-18: Initial version - Established uv as standard tool for StarPunk Python environment management

View File

@@ -0,0 +1,487 @@
# ADR-007: Slug Generation Algorithm
## Status
Accepted
## Context
Notes in StarPunk require URL-safe identifiers (slugs) for permalinks and file naming. The slug generation algorithm is critical because:
1. **User experience**: Slugs appear in URLs and should be readable/meaningful
2. **SEO**: Descriptive slugs improve search engine optimization
3. **File system**: Slugs become filenames, must be filesystem-safe
4. **Uniqueness**: Slugs must be unique across all notes
5. **Portability**: Slugs should work across different systems and browsers
The challenge is designing an algorithm that creates readable, unique, safe slugs automatically from note content.
## Decision
### Content-Based Slug Generation with Timestamp Fallback
**Primary Algorithm**: Extract first N words from content and normalize
**Fallback**: Timestamp-based slug when content is insufficient
**Uniqueness**: Random suffix when collision detected
### Algorithm Specification
#### Step 1: Extract Words
```python
# Extract first 5 words from content
words = content.split()[:5]
text = " ".join(words)
```
#### Step 2: Normalize
```python
# Convert to lowercase
text = text.lower()
# Replace spaces with hyphens
text = text.replace(" ", "-")
# Remove all characters except a-z, 0-9, and hyphens
text = re.sub(r'[^a-z0-9-]', '', text)
# Collapse multiple hyphens
text = re.sub(r'-+', '-', text)
# Strip leading/trailing hyphens
text = text.strip('-')
```
#### Step 3: Validate Length
```python
# If slug too short or empty, use timestamp fallback
if len(text) < 1:
text = created_at.strftime("%Y%m%d-%H%M%S")
```
#### Step 4: Truncate
```python
# Limit to 100 characters
text = text[:100]
```
#### Step 5: Check Uniqueness
```python
# If slug exists, add random 4-character suffix
if slug_exists(text):
text = f"{text}-{random_alphanumeric(4)}"
```
### Character Set
**Allowed characters**: `a-z`, `0-9`, `-` (hyphen)
**Rationale**:
- URL-safe without encoding
- Filesystem-safe on all platforms (Windows, Linux, macOS)
- Human-readable
- No escaping required in HTML
- Compatible with DNS hostnames (if ever used)
### Examples
| Input Content | Generated Slug |
|--------------|----------------|
| "Hello World! This is my first note." | `hello-world-this-is-my` |
| "Testing... with special chars!@#" | `testing-with-special-chars` |
| "2024-11-18 Daily Journal Entry" | `2024-11-18-daily-journal-entry` |
| "A" (too short) | `20241118-143022` (timestamp) |
| " " (whitespace only) | Error: ValueError |
| "Hello World" (duplicate) | `hello-world-a7c9` (random suffix) |
### Slug Uniqueness Strategy
**Collision Detection**: Check database for existing slug before use
**Resolution**: Append random 4-character suffix
- Character set: `a-z0-9` (36 characters)
- Combinations: 36^4 = 1,679,616 possible suffixes
- Collision probability: Negligible for reasonable note counts
**Example**:
```
Original: hello-world
Collision: hello-world-a7c9
Collision: hello-world-x3k2
```
### Timestamp Fallback Format
**Pattern**: `YYYYMMDD-HHMMSS`
**Example**: `20241118-143022`
**When Used**:
- Content is empty or whitespace-only (raises error instead)
- Normalized slug is empty (after removing special characters)
- Normalized slug is too short (< 1 character)
**Rationale**:
- Guaranteed unique (unless two notes created in same second)
- Sortable chronologically
- Still readable and meaningful
- No special characters required
## Rationale
### Content-Based Generation (Score: 9/10)
**Pros**:
- **Readability**: Users can understand URL meaning
- **SEO**: Search engines prefer descriptive URLs
- **Memorability**: Easier to remember and share
- **Meaningful**: Reflects note content
**Cons**:
- **Collisions**: Multiple notes might have similar titles
- **Changes**: Editing note doesn't update slug (by design)
### First 5 Words (Score: 8/10)
**Pros**:
- **Sufficient**: 5 words usually capture note topic
- **Concise**: Keeps URLs short and readable
- **Consistent**: Predictable slug length
**Cons**:
- **Arbitrary**: 5 is somewhat arbitrary (could be 3-7)
- **Language**: Assumes space-separated words (English-centric)
**Alternatives Considered**:
- First 3 words: Too short, often not descriptive
- First 10 words: Too long, URLs become unwieldy
- First line: Could be very long, harder to normalize
- First sentence: Variable length, complex to parse
**Decision**: 5 words is a good balance (configurable constant)
### Lowercase with Hyphens (Score: 10/10)
**Pros**:
- **URL Standard**: Common pattern (github.com, stackoverflow.com)
- **Readability**: Easier to read than underscores or camelCase
- **Compatibility**: Works everywhere
- **Simplicity**: One separator type only
**Cons**:
- None significant
### Alphanumeric Only (Score: 10/10)
**Pros**:
- **Safety**: No escaping required in URLs or filenames
- **Portability**: Works on all filesystems (FAT32, NTFS, ext4, APFS)
- **Predictability**: No ambiguity about character handling
**Cons**:
- **Unicode Loss**: Non-ASCII characters stripped (acceptable trade-off)
### Random Suffix for Uniqueness (Score: 9/10)
**Pros**:
- **Simplicity**: No complex conflict resolution
- **Security**: Cryptographically secure random (secrets module)
- **Scalability**: 1.6M possible suffixes per base slug
**Cons**:
- **Ugliness**: Suffix looks less clean (but rare occurrence)
- **Unpredictability**: User can't control suffix
**Alternatives Considered**:
- Incrementing numbers (`hello-world-2`, `hello-world-3`): More predictable but reveals note count
- Longer random suffix: More secure but uglier URLs
- User-specified slug: More complex, deferred to V2
**Decision**: 4-character random suffix is good balance
## Consequences
### Positive
1. **Automatic**: No user input required for slug
2. **Readable**: Slugs are human-readable and meaningful
3. **Safe**: Works on all platforms and browsers
4. **Unique**: Collision resolution ensures uniqueness
5. **SEO-friendly**: Descriptive URLs help search ranking
6. **Predictable**: User can anticipate what slug will be
7. **Simple**: Single, consistent algorithm
### Negative
1. **Not editable**: User can't customize slug in V1
2. **English-biased**: Assumes space-separated words
3. **Unicode stripped**: Non-ASCII content loses characters
4. **Content-dependent**: Similar content = similar slugs
5. **Timestamp fallback**: Short notes get ugly timestamp slugs
### Mitigations
**Non-editable slugs**:
- V1 trade-off for simplicity
- V2 can add custom slug support
- Users can still reference notes by slug once created
**English-bias**:
- Acceptable for V1 (English-first IndieWeb)
- V2 can add Unicode slug support (requires more complex normalization)
**Unicode stripping**:
- Markdown content can still contain Unicode (only slug is ASCII)
- Timestamp fallback ensures note is still creatable
- V2 can use Unicode normalization (transliteration)
**Timestamp fallback**:
- Rare occurrence (most notes have >5 words)
- Still functional and unique
- V2 can improve (use first word if exists + timestamp)
## Standards Compliance
### URL Standards (RFC 3986)
Slugs comply with URL path segment requirements:
- No percent-encoding required
- No reserved characters (`/`, `?`, `#`, etc.)
- Case-insensitive safe (always lowercase)
### Filesystem Standards
Slugs work on all major filesystems:
- **FAT32**: Yes (no special chars, length OK)
- **NTFS**: Yes
- **ext4**: Yes
- **APFS**: Yes
- **HFS+**: Yes
**Reserved names**: None of our slugs conflict with OS reserved names (CON, PRN, etc.)
### IndieWeb Recommendations
Aligns with IndieWeb permalink best practices:
- Descriptive URLs
- No query parameters
- Short and memorable
- Permanent (don't change after creation)
## Implementation Requirements
### Validation Rules
```python
# Valid slug pattern
SLUG_PATTERN = r'^[a-z0-9]+(?:-[a-z0-9]+)*$'
# Constraints
MIN_SLUG_LENGTH = 1
MAX_SLUG_LENGTH = 100
```
### Reserved Slugs
Certain slugs should be reserved for system routes:
**Reserved List** (reject these slugs):
- `admin`
- `api`
- `static`
- `auth`
- `feed`
- `login`
- `logout`
Implementation:
```python
RESERVED_SLUGS = {'admin', 'api', 'static', 'auth', 'feed', 'login', 'logout'}
def is_slug_reserved(slug: str) -> bool:
return slug in RESERVED_SLUGS
```
### Error Cases
```python
# Empty content
generate_slug("") # Raises ValueError
# Whitespace only
generate_slug(" ") # Raises ValueError
# Valid but short
generate_slug("Hi") # Returns timestamp: "20241118-143022"
# Special characters only
generate_slug("!@#$%") # Returns timestamp: "20241118-143022"
```
## Alternatives Considered
### UUID-based Slugs (Rejected)
```python
slug = str(uuid.uuid4()) # "550e8400-e29b-41d4-a716-446655440000"
```
**Pros**: Guaranteed unique, no collision checking
**Cons**: Not human-readable, poor SEO, not memorable
**Verdict**: Violates principle of readable URLs
### Hash-based Slugs (Rejected)
```python
slug = hashlib.sha256(content.encode()).hexdigest()[:12] # "a591a6d40bf4"
```
**Pros**: Deterministic, unique
**Cons**: Not human-readable, changes if content edited
**Verdict**: Not meaningful to users
### Title Extraction (Rejected for V1)
```python
# Extract from # heading or first line
title = extract_title_from_markdown(content)
slug = normalize(title)
```
**Pros**: More semantic, uses actual title
**Cons**: Requires markdown parsing, more complex, title might not exist
**Verdict**: Deferred to V2 (V1 uses first N words which is simpler)
### User-Specified Slugs (Rejected for V1)
```python
def create_note(content, custom_slug=None):
if custom_slug:
slug = validate_and_use(custom_slug)
else:
slug = generate_slug(content)
```
**Pros**: Maximum user control, no surprises
**Cons**: Requires UI input, validation complexity, user burden
**Verdict**: Deferred to V2 (V1 auto-generates for simplicity)
### Incrementing Numbers (Rejected)
```python
# If collision, increment
slug = "hello-world"
slug = "hello-world-2" # Collision
slug = "hello-world-3" # Collision
```
**Pros**: Predictable, simple
**Cons**: Reveals note count, enumeration attack vector, less random
**Verdict**: Random suffix is more secure and scales better
## Performance Considerations
### Generation Speed
- Extract words: O(n) where n = content length (negligible, content is small)
- Normalize: O(m) where m = extracted text length (< 100 chars)
- Uniqueness check: O(1) database lookup with index
- Random suffix: O(1) generation
**Target**: < 1ms per slug generation (easily achieved)
### Database Impact
- Index on `slug` column: O(log n) lookup
- Collision rate: < 1% (most notes have unique first 5 words)
- Random suffix retries: Nearly never (1.6M combinations)
## Testing Requirements
### Test Cases
**Normal Cases**:
- Standard English content → descriptive slug
- Content with punctuation → punctuation removed
- Content with numbers → numbers preserved
- Content with hyphens → hyphens preserved
**Edge Cases**:
- Very short content → timestamp fallback
- Empty content → ValueError
- Special characters only → timestamp fallback
- Very long words → truncated to max length
- Unicode content → stripped to ASCII
**Collision Cases**:
- Duplicate slug → random suffix added
- Multiple collisions → different random suffixes
- Reserved slug → rejected
**Security Cases**:
- Path traversal attempt (`../../../etc/passwd`)
- Special characters (`<script>`, `%00`, etc.)
- Very long input (>10,000 characters)
## Migration Path (V2)
Future enhancements that build on this foundation:
### Custom Slugs
```python
def create_note(content, custom_slug=None):
slug = custom_slug or generate_slug(content)
```
### Unicode Support
```python
def generate_unicode_slug(content):
# Use Unicode normalization (NFKD)
# Transliterate to ASCII (unidecode library)
# Support CJK languages
```
### Title Extraction
```python
def extract_title_from_content(content):
# Check for # heading
# Use first line if no heading
# Fall back to first N words
```
### Slug Editing
```python
def update_note_slug(note_id, new_slug):
# Validate new slug
# Update database
# Rename file
# Create redirect from old slug
```
## References
- [RFC 3986 - URI Generic Syntax](https://www.rfc-editor.org/rfc/rfc3986)
- [IndieWeb Permalink Design](https://indieweb.org/permalink)
- [URL Slug Best Practices](https://moz.com/learn/seo/url)
- [Python secrets Module](https://docs.python.org/3/library/secrets.html)
- [ADR-004: File-Based Note Storage](/home/phil/Projects/starpunk/docs/decisions/ADR-004-file-based-note-storage.md)
## Acceptance Criteria
- [ ] Slug generation creates valid, URL-safe slugs
- [ ] Slugs are descriptive (use first 5 words)
- [ ] Slugs are unique (collision detection + random suffix)
- [ ] Slugs meet length constraints (1-100 characters)
- [ ] Timestamp fallback works for short content
- [ ] Reserved slugs are rejected
- [ ] Unicode content is handled gracefully
- [ ] All edge cases tested
- [ ] Performance meets target (<1ms)
- [ ] Code follows Python coding standards
---
**Approved**: 2024-11-18
**Architect**: StarPunk Architect Agent

View File

@@ -0,0 +1,457 @@
# ADR-008: Versioning Strategy
## Status
Accepted
## Context
StarPunk is an IndieWeb CMS currently in active development, working toward its first production release. We need a comprehensive versioning strategy that:
1. **Communicates clearly** what type of changes each release contains
2. **Works with Python ecosystem** tools (pip, uv, PyPI compatibility)
3. **Aligns with IndieWeb values** of simplicity and sustainability
4. **Supports the project lifecycle** from development through maintenance
5. **Enables dependency management** for users who may build on StarPunk
6. **Provides predictability** for users about what to expect in updates
### Current State
The project currently uses informal version terminology:
- "V1" for the overall first release goal
- "Phase 1.1", "Phase 1.2" for development milestones
- "v1.1", "v2.0" for future iteration references
This works for internal planning but lacks the precision needed for:
- Public releases
- Dependency management
- Communicating breaking changes
- Git tagging and release management
### Requirements
1. **Version number format** that indicates change severity
2. **Python ecosystem compliance** (PEP 440)
3. **Git workflow integration** (tagging, branching)
4. **Changelog format** for human-readable history
5. **Pre-release versioning** for alphas/betas if needed
6. **Upgrade communication** strategy
7. **Simplicity** appropriate for an indie project
## Decision
We will adopt **Semantic Versioning 2.0.0 (SemVer)** with **Python PEP 440 compliance** for all StarPunk releases.
### Version Number Format
**Structure**: `MAJOR.MINOR.PATCH[-PRERELEASE]`
**Examples**:
- `0.1.0` - Development version (Phase 1.1 complete)
- `0.2.0` - Development version (Phase 1.2 complete)
- `1.0.0` - First stable release (all V1 features complete)
- `1.0.1` - Bug fix release
- `1.1.0` - Feature release (backward compatible)
- `2.0.0` - Major release (breaking changes)
- `1.0.0a1` - Alpha pre-release (PEP 440 format)
- `1.0.0b2` - Beta pre-release (PEP 440 format)
- `1.0.0rc1` - Release candidate (PEP 440 format)
### Version Component Rules
**MAJOR version** - Increment when making incompatible changes:
- Breaking API changes
- Database schema changes requiring migration
- Configuration file format changes requiring user intervention
- Removal of deprecated features
- Major architectural changes
**MINOR version** - Increment when adding functionality in backward-compatible manner:
- New features
- New API endpoints
- Non-breaking enhancements
- Optional new configuration parameters
- Significant performance improvements
**PATCH version** - Increment for backward-compatible bug fixes:
- Bug fixes
- Security patches
- Documentation corrections
- Minor performance improvements
- Dependency updates (without feature changes)
### Development Phase (0.x.y)
During development (pre-1.0), we use `0.MINOR.PATCH`:
- MINOR increments for phase completions (Phase 1.1 → 0.1.0, Phase 1.2 → 0.2.0)
- PATCH increments for bug fixes during development
- Breaking changes are allowed without major version increment
- Public API is not considered stable
### Git Tagging Convention
**Format**: `vMAJOR.MINOR.PATCH[-PRERELEASE]`
**Examples**:
- `v0.1.0` - Development version tag
- `v1.0.0` - Stable release tag
- `v1.0.1` - Bug fix release tag
- `v1.0.0-alpha.1` - Alpha pre-release tag (Git format)
**Tag type**: Annotated tags (not lightweight)
- Contains tagger, date, message
- Can include release notes
- Can be GPG signed
### Version Storage
**Primary source of truth**: `starpunk/__init__.py`
```python
__version__ = "1.0.0"
__version_info__ = (1, 0, 0)
```
**Secondary locations**:
- `pyproject.toml` - Package metadata (if used)
- Git tags - Release markers
- `CHANGELOG.md` - Human-readable history
**Synchronization**: Manual for V1 (simple, no automation dependencies)
### Changelog Format
**File**: `CHANGELOG.md`
**Format**: Based on [Keep a Changelog](https://keepachangelog.com/)
**Categories**:
- Added - New features
- Changed - Changes to existing functionality
- Deprecated - Features that will be removed
- Removed - Features that have been removed
- Fixed - Bug fixes
- Security - Security vulnerability fixes
**Example**:
```markdown
## [1.0.0] - 2024-11-18
### Added
- IndieAuth authentication via IndieLogin
- Micropub endpoint for publishing
- RSS feed generation
- File-based note storage
### Security
- Implemented path traversal protection
- Added CSRF protection for authentication flows
```
### Pre-Release Versioning
**Format**: PEP 440 compliant
- Alpha: `1.0.0a1`, `1.0.0a2`
- Beta: `1.0.0b1`, `1.0.0b2`
- Release Candidate: `1.0.0rc1`, `1.0.0rc2`
**Git tags use hyphen**: `v1.0.0-alpha.1` (for readability)
**Python `__version__` uses PEP 440**: `1.0.0a1` (for pip compatibility)
### Phase-to-Version Mapping
**Implementation phases** (internal planning) map to **version numbers** (public releases):
```
Phase 1.1 complete → Version 0.1.0
Phase 1.2 complete → Version 0.2.0
Phase 1.3 complete → Version 0.3.0
Phase 2.1 complete → Version 0.4.0
All V1 complete → Version 1.0.0
V1.1 features → Version 1.1.0
V2 features → Version 2.0.0
```
**Clarification**:
- "V1" refers to feature scope, not version number
- Version 1.0.0 implements the "V1 feature set"
- Phases are development milestones, versions are public releases
## Rationale
### Why Semantic Versioning?
1. **Industry Standard**: Used by Flask, Django, Requests, and most Python packages
2. **Clear Communication**: Version number immediately conveys impact of changes
3. **Predictable**: Users know what to expect from each version increment
4. **Dependency Management**: Works seamlessly with pip version specifiers
5. **Simple**: Easy to understand and apply without complex rules
### Why Not Calendar Versioning (CalVer)?
CalVer (e.g., `2024.11.18`) was considered but rejected:
**Pros**:
- Shows when software was released
- No ambiguity about version order
**Cons**:
- Doesn't communicate impact of changes (patch vs breaking change)
- Less common in Python ecosystem
- Doesn't help users assess upgrade risk
- Overkill for indie project release cadence
**Conclusion**: SemVer's semantic meaning is more valuable than date information
### Why Not ZeroVer (0.x forever)?
Some projects stay at 0.x.y indefinitely to signal "still evolving". Rejected because:
- Creates uncertainty about production readiness
- Version 1.0.0 signals "ready for production use"
- We have a clear 1.0 feature scope (V1)
- Users deserve clarity about stability
### Why PEP 440 Compliance?
**PEP 440** is Python's version identification standard:
- Required for PyPI publication (if we ever publish)
- Compatible with pip, uv, and all Python package managers
- Slightly different pre-release format than SemVer (e.g., `1.0.0a1` vs `1.0.0-alpha.1`)
- Used by all major Python frameworks
**Decision**: Use PEP 440 format in Python code, SemVer-style in Git tags (Git tags are more flexible)
### Why Manual Version Management (V1)?
Considered automation tools:
- `bump2version` - Automates version bumping
- `python-semantic-release` - Determines version from commit messages
- `setuptools_scm` - Derives version from Git tags
**Decision**: Manual for V1 because:
1. Simple - no extra dependencies
2. Full control - explicit about versions
3. Aligns with indie philosophy - minimal tooling
4. Can add automation later if needed
### Why Annotated Tags?
**Annotated tags** vs lightweight tags:
Annotated tags:
- Contain metadata (tagger, date, message)
- Can include release notes
- Can be GPG signed
- Treated as full objects in Git
**Decision**: Always use annotated tags for releases
### Why CHANGELOG.md?
**Changelog** provides human-readable release history:
- Users can quickly see what changed
- Easier to read than Git commits
- Standard location (`CHANGELOG.md`)
- Standard format (Keep a Changelog)
- Can be generated from commits or written manually
**Decision**: Maintain manually for V1 (precise control over messaging)
## Consequences
### Positive
1. **Clear Communication**: Users know exactly what each version means
2. **Ecosystem Compatibility**: Works with all Python tools
3. **Predictable Upgrades**: Users can assess risk before upgrading
4. **Professional Image**: Signals mature, well-maintained software
5. **Dependency Management**: Other projects can depend on StarPunk versions
6. **Git Integration**: Clean tagging and release workflow
7. **Flexible**: Can add automation later without changing format
8. **Standards-Based**: Uses established, documented standards
### Negative
1. **Manual Effort**: Requires discipline to update version in multiple places
2. **Coordination**: Must remember to update version, changelog, and tag
3. **Breaking Change Discipline**: Must carefully evaluate what constitutes breaking change
4. **Learning Curve**: Contributors need to understand SemVer rules
### Neutral
1. **0.x Signals Development**: May discourage early adopters who want "1.0" stability
2. **Commitment to Backward Compatibility**: Once at 1.0, breaking changes require major version
3. **Changelog Maintenance**: Ongoing effort to document changes
### Mitigations
**For manual effort**:
- Document clear release process in `versioning-strategy.md`
- Create release checklist
- Consider automation in V2+ if needed
**For breaking change discipline**:
- Deprecate features one version before removal when possible
- Document breaking changes prominently
- Provide upgrade guides for major versions
**For 0.x concerns**:
- Clearly communicate that 0.x is pre-production
- Move to 1.0.0 once V1 features are complete and tested
- Don't stay at 0.x longer than necessary
## Implementation
### Immediate Actions
1. **Set current version**: `0.1.0` (Phase 1.1 development)
2. **Create `starpunk/__init__.py`**:
```python
__version__ = "0.1.0"
__version_info__ = (0, 1, 0)
```
3. **Create `CHANGELOG.md`** with `[Unreleased]` section
4. **Update README.md** with version information
5. **Tag current state**: `git tag -a v0.1.0 -m "Development version 0.1.0"`
### Release Workflow
When ready to release:
1. Update `starpunk/__init__.py` with new version
2. Update `CHANGELOG.md` with release date
3. Commit: `git commit -m "Bump version to X.Y.Z"`
4. Tag: `git tag -a vX.Y.Z -m "Release X.Y.Z: [description]"`
5. Push: `git push origin main vX.Y.Z`
### Documentation
- Create comprehensive `docs/standards/versioning-strategy.md`
- Document examples, decision tree, and FAQ
- Include upgrade guide template
- Reference in README.md
### Future Enhancements
**V2+ Considerations**:
- Add `bump2version` for automation if manual process becomes tedious
- Consider API versioning if need to support multiple incompatible API versions
- Add database schema versioning if migrations become complex
- Automated changelog generation from commit messages
## Alternatives Considered
### Alternative 1: Calendar Versioning (CalVer)
**Format**: `YYYY.MM.PATCH` (e.g., `2024.11.0`)
**Pros**:
- Shows release date
- Clear chronological order
- Used by Ubuntu, PyCharm
**Cons**:
- Doesn't communicate change impact
- Less common in Python web frameworks
- Doesn't indicate breaking changes
- Overkill for indie project cadence
**Rejected**: Semantic meaning more important than date
### Alternative 2: Simple Integer Versioning
**Format**: `1`, `2`, `3` (e.g., like TeX: 3.14159...)
**Pros**:
- Extremely simple
- No ambiguity
**Cons**:
- No information about change severity
- Doesn't work with Python dependency tools
- Not standard in Python ecosystem
- Too minimalist for practical use
**Rejected**: Too simplistic, poor ecosystem fit
### Alternative 3: Modified SemVer (Django-style)
**Format**: `MAJOR.FEATURE.PATCH` (e.g., Django's `4.2.7`)
**Pros**:
- Used by Django
- Separates features from bug fixes
**Cons**:
- Non-standard SemVer interpretation
- Confusing: when to increment feature vs major?
- Doesn't clearly indicate breaking changes
- Less predictable
**Rejected**: Standard SemVer is clearer
### Alternative 4: ZeroVer (0.x forever)
**Format**: Stay at `0.x.y` indefinitely
**Pros**:
- Signals "always evolving"
- No commitment to stability
**Cons**:
- Users can't tell when production-ready
- Doesn't signal stability improvements
- Version 1.0.0 has meaning: "ready to use"
**Rejected**: We have clear 1.0 goals, should signal when achieved
### Alternative 5: Hybrid SemVer + CalVer
**Format**: `YYYY.MINOR.PATCH` (e.g., `2024.1.0`)
**Pros**:
- Combines date and semantic information
**Cons**:
- Unusual, confusing
- Not standard
- Year increments don't mean anything semantically
**Rejected**: Combines worst of both approaches
## References
### Standards
- [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html) - Official SemVer specification
- [PEP 440 - Version Identification](https://peps.python.org/pep-0440/) - Python version numbering standard
- [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Changelog format standard
- [Calendar Versioning](https://calver.org/) - CalVer specification (considered but not adopted)
### Examples from Python Ecosystem
- [Flask Versioning](https://github.com/pallets/flask/releases) - Uses SemVer
- [Django Release Process](https://docs.djangoproject.com/en/stable/internals/release-process/) - Modified SemVer
- [Requests Versioning](https://github.com/psf/requests/releases) - Uses SemVer
- [FastAPI Versioning](https://github.com/tiangolo/fastapi/releases) - Uses SemVer
### Tools
- [bump2version](https://github.com/c4urself/bump2version) - Version bump automation
- [python-semantic-release](https://python-semantic-release.readthedocs.io/) - Automated semantic releases
- [setuptools_scm](https://github.com/pypa/setuptools_scm) - SCM-based versioning
### Internal Documentation
- [Versioning Strategy](/home/phil/Projects/starpunk/docs/standards/versioning-strategy.md) - Complete versioning specification
- [Architecture Overview](/home/phil/Projects/starpunk/docs/architecture/overview.md) - System architecture
- [Development Setup](/home/phil/Projects/starpunk/docs/standards/development-setup.md) - Development workflow
---
**ADR**: 008
**Date**: 2024-11-18
**Status**: Accepted
**Decision**: Adopt Semantic Versioning 2.0.0 with PEP 440 compliance
**Supersedes**: None

View File

@@ -0,0 +1,484 @@
# ADR-009: Git Branching Strategy
## Status
Accepted
## Context
StarPunk needs a git branching strategy that supports:
1. **Semantic versioning workflow** - Moving from 0.x.y development through 1.0.0 stable release
2. **Single developer initially** - Optimized for solo work but scalable to small teams
3. **Clean release history** - Clear tags and versioning aligned with SemVer
4. **Hotfix capability** - Ability to patch production releases
5. **Minimal complexity** - Appropriate for indie project scale
6. **Development discipline** - Structure without bureaucracy
### Current State
The project just renamed its primary branch from `master` to `main` to align with modern Git conventions and industry best practices. This is a brand new repository with no commits yet, currently at version 0.1.0 (development phase).
### Requirements
The branching strategy must:
1. **Support semantic versioning** releases (0.1.0 → 1.0.0 → 1.0.1 → 1.1.0 → 2.0.0)
2. **Work for solo and team** development
3. **Enable clean releases** with proper tagging
4. **Allow hotfixes** to production versions
5. **Keep main branch stable** - always in working state
6. **Minimize long-lived branches** - integrate frequently
7. **Align with IndieWeb values** - simplicity, no lock-in
8. **Be well-documented** - clear workflows for common scenarios
## Decision
We will adopt a **simplified trunk-based development** strategy with feature branches and semantic versioning integration.
### Core Principles
1. **Single primary branch**: `main`
2. **Branch name**: `main` (not `master`)
3. **Feature branches**: Short-lived branches for features/fixes
4. **Direct tagging**: Tag `main` for releases
5. **Hotfix branches**: For production patches (post-1.0.0)
6. **No long-lived branches**: Integrate to main frequently
7. **Annotated tags**: Always use annotated tags for releases
### Branch Structure
**Primary branch**: `main`
- Single source of truth
- Always stable (tests pass)
- Tagged for releases
- Protected from force push
- Never rewritten
**Feature branches**: `feature/<description>` or `<description>`
- Branch from `main`
- Merge into `main`
- Short-lived (hours to days, not weeks)
- Deleted after merge
**Fix branches**: `fix/<description>` or `bugfix/<description>`
- Branch from `main`
- Merge into `main`
- Deleted after merge
**Hotfix branches**: `hotfix/<version>-<description>` (post-1.0.0 only)
- Branch from release tag (e.g., `v1.0.0`)
- Fix critical production bugs
- Tagged as new patch release (e.g., `v1.0.1`)
- Merged into `main`
- Deleted after release
**Release branches**: `release/<version>` (optional, rarely used)
- Only if release preparation requires multiple commits
- For V1, likely unnecessary (prepare on `main` or feature branch)
- Branch from `main`, merge back to `main`, tag, delete
### Branch Naming Conventions
**Preferred format**: `<type>/<description>`
**Types**:
- `feature/` - New features
- `fix/` or `bugfix/` - Bug fixes
- `hotfix/` - Production hotfixes
- `docs/` - Documentation only
- `refactor/` - Code refactoring
- `test/` - Test additions
- `chore/` - Maintenance tasks
**Description**:
- Lowercase with hyphens
- Descriptive but concise
- Example: `feature/micropub-endpoint`, `fix/rss-timezone`
**Alternative**: Simple description without prefix (e.g., `micropub-endpoint`)
### Tagging Strategy
**Format**: `vMAJOR.MINOR.PATCH[-PRERELEASE]`
**Type**: Annotated tags (always)
**Examples**:
- `v0.1.0` - Development release
- `v1.0.0` - First stable release
- `v1.0.1` - Patch release
- `v1.0.0-alpha.1` - Pre-release
**Tag message format**:
```
Release MAJOR.MINOR.PATCH: <Brief description>
[Optional details]
```
### Workflows
**Development (0.x.y)**:
1. Work on `main` or feature branches
2. Commit frequently
3. Tag development milestones (v0.1.0, v0.2.0)
4. Breaking changes allowed
**Stable releases (1.0.0+)**:
1. Prepare release on `main`
2. Update version and changelog
3. Commit version bump
4. Create annotated tag
5. Push main and tag
**Hotfixes (post-1.0.0)**:
1. Branch from release tag
2. Fix bug
3. Update version and changelog
4. Tag new patch version
5. Merge to `main`
6. Push main and tag
7. Delete hotfix branch
## Rationale
### Why Simplified Trunk-Based Development?
**Trunk-based development** means developers integrate to a single branch (`main`) frequently, using short-lived feature branches.
**Pros**:
1. **Simple** - One primary branch, minimal overhead
2. **Scalable** - Works for solo and small teams
3. **Fast integration** - Reduces merge conflicts
4. **Always releasable** - Main stays stable
5. **Aligns with CI/CD** - Easy to automate testing
6. **Reduces complexity** - No long-lived branches to manage
**Fits StarPunk because**:
- Personal project optimized for simplicity
- Small codebase, infrequent releases
- Solo developer initially
- No need for complex branching
### Why Main Instead of Master?
**Decision**: Use `main` as primary branch name
**Rationale**:
1. **Industry standard** - GitHub, GitLab default since 2020
2. **Inclusive language** - Removes potentially offensive terminology
3. **Aligns with modern practices** - Most new projects use `main`
4. **Clear semantics** - "Main" clearly indicates primary branch
5. **No functional difference** - Just a name, but better name
**Migration**:
- Project just renamed `master``main`
- All documentation uses `main` consistently
### Why Not Git Flow?
**Git Flow** is a popular branching model with `main`, `develop`, `release`, `hotfix`, and `feature` branches.
**Considered but rejected**:
**Pros**:
- Well-defined process
- Clear separation of development and production
- Structured release process
**Cons**:
- **Too complex** for indie project
- **Overhead** - Multiple long-lived branches
- **Slow integration** - Features merge to develop, not main
- **Designed for** scheduled releases, not continuous delivery
- **Overkill** for single developer
**Conclusion**: Git Flow's complexity doesn't justify benefits for StarPunk's scale
### Why Not GitHub Flow?
**GitHub Flow** is a simpler model: just `main` and feature branches, deploy from `main`.
**Very close to our choice**:
**Pros**:
- Simple - only `main` + feature branches
- Fast - deploy anytime
- Works well with pull requests
**Differences from our approach**:
- GitHub Flow deploys directly from `main`
- We tag releases on `main` instead
- We add hotfix branches for production patches
**Conclusion**: We essentially use GitHub Flow + semantic versioning + hotfix branches
### Why Annotated Tags?
**Annotated vs lightweight tags**:
**Annotated tags** (chosen):
- Contain metadata (tagger, date, message)
- Can include release notes
- Can be GPG signed
- Treated as full Git objects
- Required: `git tag -a v1.0.0 -m "Release 1.0.0"`
**Lightweight tags** (rejected):
- Just pointers to commits
- No metadata
- Created: `git tag v1.0.0`
**Decision**: Always use annotated tags for releases
- Provides complete release history
- Can include release notes in tag message
- Better for professional releases
### Why Feature Branches?
**Alternatives**:
1. **Direct commits to main** - Fast but risky
2. **Feature branches** - Slight overhead but safer
3. **Pull request workflow** - Most structured
**Decision**: Use feature branches with flexible merge approach
**For solo development**:
- Feature branches optional for small changes
- Required for larger features
- Merge directly without pull request
**For team development**:
- Feature branches required
- Pull request review before merge
- Delete branch after merge
**Benefits**:
- Isolates work in progress
- Enables experimentation
- Keeps main stable
- Scalable to team workflow
## Consequences
### Positive
1. **Simple to understand** - One primary branch, clear workflows
2. **Scalable** - Works solo, scales to small teams
3. **Fast integration** - Short-lived branches reduce conflicts
4. **Clean history** - Clear tags for every release
5. **Semantic versioning alignment** - Tag strategy matches SemVer
6. **Hotfix capability** - Can patch production releases
7. **Low overhead** - No complex branch management
8. **Standard practices** - Uses modern Git conventions
9. **Well-documented** - Clear workflows for common scenarios
10. **Flexible** - Can use pull requests or direct merges
### Negative
1. **Discipline required** - Main must stay stable (tests must pass)
2. **Manual version management** - Must update version, changelog, tag (for V1)
3. **Solo optimization** - Strategy favors individual over large team
4. **No develop buffer** - Changes go directly to main (requires good testing)
### Neutral
1. **Feature branch discipline** - Must keep branches short-lived
2. **Test coverage important** - Main stability depends on testing
3. **Rebase vs merge** - Team must choose and be consistent
### Mitigations
**For main stability**:
- Run tests before merging
- Use pull requests for team development
- Establish branch protection rules when team grows
**For version management**:
- Document clear release process
- Create release checklist
- Consider automation in V2+ if needed
**For long-lived branches**:
- Review open branches weekly
- Delete stale branches
- Encourage frequent integration
## Implementation
### Immediate Actions
1. **Primary branch**: Already renamed to `main`
2. **Create documentation**: `docs/standards/git-branching-strategy.md`
3. **Update all references**: Ensure docs use `main` consistently
4. **Initial tag**: Tag current state as `v0.1.0`
### Branch Protection (Future)
When team grows or project matures, add GitHub branch protection:
```
Settings → Branches → Add rule for main:
- Require pull request reviews (1 approval)
- Require status checks to pass
- Prevent force push
- Prevent deletion
```
For solo development: Self-discipline instead of enforced rules
### Release Process
**Development releases (0.x.y)**:
1. Update `starpunk/__init__.py` version
2. Update `CHANGELOG.md`
3. Commit: `git commit -m "Bump version to 0.x.y"`
4. Tag: `git tag -a v0.x.y -m "Development release 0.x.y"`
5. Push: `git push origin main v0.x.y`
**Stable releases (1.0.0+)**:
1. Update version and changelog
2. Commit version bump
3. Tag: `git tag -a v1.0.0 -m "Release 1.0.0: First stable release"`
4. Push: `git push origin main v1.0.0`
**Hotfixes**:
1. Branch: `git checkout -b hotfix/1.0.1-fix v1.0.0`
2. Fix bug and update version
3. Tag: `git tag -a v1.0.1 -m "Hotfix 1.0.1: Bug fix"`
4. Merge: `git checkout main && git merge hotfix/1.0.1-fix`
5. Push: `git push origin main v1.0.1`
6. Delete: `git branch -d hotfix/1.0.1-fix`
### Documentation
Created comprehensive documentation:
- **Strategy document**: `docs/standards/git-branching-strategy.md`
- Branch types and naming
- Workflows and examples
- Best practices
- Troubleshooting
- **This ADR**: `docs/decisions/ADR-009-git-branching-strategy.md`
- Decision rationale
- Alternatives considered
## Alternatives Considered
### Alternative 1: Git Flow
**Description**: Use full Git Flow with `main`, `develop`, `release`, `hotfix`, `feature` branches
**Pros**:
- Well-established pattern
- Clear separation of development and production
- Structured release process
- Good for scheduled releases
**Cons**:
- Too complex for indie project
- Multiple long-lived branches
- Slower integration
- More overhead
- Designed for different release model
**Rejected**: Complexity doesn't match project scale
### Alternative 2: Trunk-Based Development (Pure)
**Description**: All commits directly to `main`, no feature branches
**Pros**:
- Maximum simplicity
- Fastest integration
- No branch management
**Cons**:
- Risky - broken commits go to main
- No isolation for work in progress
- Difficult for team collaboration
- No experimentation space
**Rejected**: Too risky, doesn't scale to team
### Alternative 3: GitHub Flow (Pure)
**Description**: `main` + feature branches, deploy from `main` continuously
**Pros**:
- Simple and well-documented
- Works well with pull requests
- Fast deployment
**Cons**:
- Designed for continuous deployment
- Doesn't emphasize versioned releases
- No hotfix branch pattern
**Partially adopted**: We use this + semantic versioning + hotfix branches
### Alternative 4: Release Branches Primary
**Description**: Always use release branches, never tag `main` directly
**Pros**:
- Clear release preparation phase
- Can stabilize release while main continues
**Cons**:
- Adds complexity
- Creates long-lived branches
- Overkill for small project
**Rejected**: Unnecessary complexity for V1
### Alternative 5: Keep Master Branch Name
**Description**: Continue using `master` instead of `main`
**Pros**:
- Traditional name
- No migration needed
- No functional difference
**Cons**:
- Outdated convention
- Out of step with industry
- Potentially offensive terminology
- New projects use `main`
**Rejected**: Modern standard is `main`, no reason not to adopt
## References
### Git Branching Models
- [Trunk-Based Development](https://trunkbaseddevelopment.com/) - Pattern we primarily follow
- [GitHub Flow](https://guides.github.com/introduction/flow/) - Simplified flow (close to ours)
- [Git Flow](https://nvie.com/posts/a-successful-git-branching-model/) - More complex model (not adopted)
- [GitLab Flow](https://docs.gitlab.com/ee/topics/gitlab_flow.html) - Hybrid approach
### Branch Naming
- [GitHub Renaming](https://github.com/github/renaming) - Main branch renaming initiative
- [Git Branch Naming](https://deepsource.io/blog/git-branch-naming-conventions/) - Naming conventions
### Versioning Integration
- [Semantic Versioning](https://semver.org/) - Version numbering
- [Git Tagging](https://git-scm.com/book/en/v2/Git-Basics-Tagging) - Tag documentation
### Internal Documentation
- [ADR-008: Versioning Strategy](/home/phil/Projects/starpunk/docs/decisions/ADR-008-versioning-strategy.md) - Semantic versioning decision
- [Versioning Strategy](/home/phil/Projects/starpunk/docs/standards/versioning-strategy.md) - Complete versioning spec
- [Git Branching Strategy](/home/phil/Projects/starpunk/docs/standards/git-branching-strategy.md) - Complete branching spec
- [Development Setup](/home/phil/Projects/starpunk/docs/standards/development-setup.md) - Development workflow
---
**ADR**: 009
**Date**: 2025-11-18
**Status**: Accepted
**Decision**: Adopt simplified trunk-based development with `main` branch, feature branches, semantic versioning tags, and hotfix capability
**Supersedes**: None