Stack

Click a document in the left sidebar to begin.

\n```\n\nRules:\n- CDN for Bootstrap CSS and JS bundle (includes Popper)\n- Single `static/css/style.css` for all custom styles\n- No build step required \u2014 no Sass compilation\n- Pin a specific Bootstrap version via CDN URL\n\n**Why**: CDN avoids bundling complexity. Single custom stylesheet keeps overrides organized.\n\n---\n\n## 2. Dark Theme\n\n**Rule**: Use `data-bs-theme=\"dark\"` on `` for Bootstrap's native dark mode. Override with CSS custom properties.\n\n```html\n\n```\n\n```css\n/* static/css/style.css */\n:root {\n --cc-bg: #1a1a2e;\n --cc-surface: #16213e;\n --cc-text: #e0e0e0;\n --cc-border: #2a2a4a;\n --cc-accent: #4fc3f7;\n}\n\nbody {\n background-color: var(--cc-bg);\n color: var(--cc-text);\n}\n```\n\n**Why**: Bootstrap 5.3+ has native dark mode. Custom properties let you extend it with project-specific colors.\n\n---\n\n## 3. Layout\n\n**Rule**: Use Bootstrap's container and grid system. Stick to `container` or `container-fluid`.\n\n```html\n\n \n\n
\n {% block content %}{% endblock %}\n
\n\n```\n\n### Grid\n\n```html\n
\n
Main content
\n
Sidebar
\n
\n```\n\nRules:\n- Use responsive breakpoints (`col-md-*`, `col-lg-*`)\n- `mt-4`, `mb-3`, `p-3` for spacing (Bootstrap utility classes)\n- Don't fight the grid \u2014 use it or skip it, don't half-use it\n\n---\n\n## 4. Components\n\n**Rule**: Use Bootstrap's built-in components. Style with utility classes first, custom CSS second.\n\n### Cards\n\n```html\n
\n
Section Title
\n
\n

Content here

\n
\n
\n```\n\n### Tables\n\n```html\n\n \n \n \n \n \n \n \n {% for item in items %}\n \n \n \n \n {% endfor %}\n \n
NameStatus
{{ item.name }}Active
\n```\n\n### Badges\n\n```html\nPrimary\nActive\nError\nWarning\n```\n\n### Buttons\n\n```html\n\n\n```\n\n---\n\n## 5. Modals\n\n**Rule**: Use Bootstrap modals for dialogs. For HTMX-loaded content, use custom overlays.\n\n```html\n\n
\n
\n
\n
\n
Confirm
\n \n
\n
Are you sure?
\n \n
\n
\n
\n```\n\n```html\n\n
\n
\n
\n        \n    
\n
\n```\n\n**Why**: Bootstrap modals work for static dialogs. Custom overlays are simpler for HTMX-fetched content.\n\n---\n\n## 6. Custom Component Patterns\n\n**Rule**: Define reusable component classes in your stylesheet using CSS custom properties.\n\n```css\n/* Operation buttons \u2014 consistent sizing */\n.op-btn {\n font-size: 0.75rem;\n font-weight: 600;\n padding: 0.25rem 0.65rem;\n border-radius: 4px;\n white-space: nowrap;\n}\n\n.op-btn--local { background: var(--btn-local, #4fc3f7); color: #000; }\n.op-btn--remote { background: var(--btn-remote, #81c784); color: #000; }\n.op-btn--danger { background: var(--btn-danger, #ef5350); color: #fff; }\n\n/* Card headers */\n.cc-card-header {\n font-size: 0.85rem;\n text-transform: uppercase;\n color: var(--cc-text-muted, #888);\n letter-spacing: 0.05em;\n}\n```\n\nRules:\n- Use BEM-like naming: `.component--modifier`\n- Define colors as CSS custom properties in `:root`\n- Keep custom CSS minimal \u2014 use Bootstrap utilities first\n- Document component classes in a BRANDING.md or style guide\n\n**Why**: Custom properties make theming consistent. BEM naming prevents class collision.\n\n---\n\n## 7. HTMX + Bootstrap Integration\n\n**Rule**: HTMX and Bootstrap coexist without conflict. Use Bootstrap for layout/style, HTMX for behavior.\n\n```html\n\n\n\n\n
\n
\n
\n \n
\n
\n
\n```\n\n**Why**: Bootstrap handles visual structure. HTMX handles dynamic behavior. No JavaScript framework needed.\n\n---\n\n## Summary Checklist\n\n- [ ] Bootstrap 5.3+ loaded from CDN (CSS + JS bundle)\n- [ ] Single `static/css/style.css` for custom styles\n- [ ] Dark theme via `data-bs-theme=\"dark\"` + CSS custom properties\n- [ ] Container-based layout with responsive grid\n- [ ] Standard Bootstrap components (cards, tables, badges, buttons)\n- [ ] Custom component classes with BEM naming and CSS variables\n- [ ] HTMX attributes on Bootstrap-styled elements\n", "common": "# Common Best Practices\n\nAlways included regardless of technology stack. Covers project structure conventions, shell scripts, metadata files, git hygiene, and development workflow. This file does not change between projects.\n\n---\n\n## 1. Project Directory Layout\n\n**Rule**: Every project follows a predictable directory structure.\n\n```\nproject-name/\n\u251c\u2500\u2500 bin/ # Operation scripts (see Shell Scripts section)\n\u251c\u2500\u2500 data/ # Runtime data (DB, logs, backups) \u2014 gitignored\n\u2502 \u251c\u2500\u2500 logs/ # Script and process output logs\n\u2502 \u2514\u2500\u2500 backups/ # Database and file backups\n\u251c\u2500\u2500 docs/ # Project documentation\n\u251c\u2500\u2500 tests/ # Test suite\n\u251c\u2500\u2500 PROJECT/ # Build specification (optional, for spec-driven projects)\n\u251c\u2500\u2500 .env # Environment config \u2014 gitignored\n\u251c\u2500\u2500 .env.example # Template with placeholder values \u2014 committed\n\u251c\u2500\u2500 .gitignore\n\u251c\u2500\u2500 CLAUDE.md # AI agent instructions\n\u2514\u2500\u2500 Links.md # External links\n```\n\nAdditional directories depend on the stack (e.g., `templates/`, `static/`, `migrations/`).\n\n**Why**: Consistent layout lets any developer or AI agent locate files instantly.\n\n---\n\n## 2. Shell Scripts (bin/ Directory)\n\n**Rule**: All user-facing operations live in `bin/` as bash scripts with standardized headers, logging, and error handling.\n\n### Script Template\n\n```bash\n#!/bin/bash\n# CommandCenter Operation\n# Name: Human Readable Name\n# Type: daemon|batch\n# Port: 8000\n\n# --- Standard Preamble ---\nset -euo pipefail\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nPROJECT_DIR=\"$(cd \"$SCRIPT_DIR/..\" && pwd)\"\nLOG_DIR=\"$PROJECT_DIR/data/logs\"\nmkdir -p \"$LOG_DIR\"\n\nTIMESTAMP=$(date '+%Y-%m-%d_%H%M%S')\nSCRIPT_NAME=$(basename \"$0\" .sh)\nLOG_FILE=\"$LOG_DIR/${SCRIPT_NAME}_${TIMESTAMP}.log\"\n\necho \"=== $SCRIPT_NAME started at $(date '+%Y-%m-%d %H:%M:%S') ===\" | tee \"$LOG_FILE\"\necho \"Arguments: $*\" | tee -a \"$LOG_FILE\"\necho \"Working dir: $PROJECT_DIR\" | tee -a \"$LOG_FILE\"\necho \"---\" | tee -a \"$LOG_FILE\"\n\ncd \"$PROJECT_DIR\"\n\n# --- Your Commands Here ---\n# All output goes to both console and log file via tee\nyour_command 2>&1 | tee -a \"$LOG_FILE\"\n\necho \"=== $SCRIPT_NAME finished at $(date '+%Y-%m-%d %H:%M:%S') ===\" | tee -a \"$LOG_FILE\"\n```\n\n### Header Fields\n\nThe first comment block is parsed by Command Center's scanner for auto-discovery:\n\n| Field | Required | Values | Description |\n|-------|----------|--------|-------------|\n| `# CommandCenter Operation` | Yes | literal | Marks script as discoverable |\n| `# Name:` | Yes | free text | Display name in UI |\n| `# Type:` | No | `daemon` or `batch` | Default: `batch`. Daemons stay running. |\n| `# Port:` | No | integer | Port number for daemon services |\n\nScripts without the `# CommandCenter Operation` header are still valid scripts but won't appear in Command Center's UI.\n\n### Standard Scripts\n\nEvery project should have these scripts (where applicable):\n\n| Script | Type | Purpose |\n|--------|------|---------|\n| `bin/start.sh` | daemon | Start the dev server |\n| `bin/stop.sh` | batch | Stop the dev server |\n| `bin/test.sh` | batch | Run test suite |\n| `bin/build.sh` | batch | Build/compile the project |\n| `bin/deploy.sh` | batch | Deploy to production |\n| `bin/backup.sh` | batch | Backup data/database |\n\n### Logging Pattern\n\n- All stdout and stderr captured via `tee` to `data/logs/`\n- Log filename includes script name and timestamp: `start_2026-03-07_143022.log`\n- First lines of log always record: timestamp, arguments, working directory\n- These logs are viewable from Command Center's Process Monitor\n\n**Why**: Standardized scripts make every project operable the same way. Log capture enables monitoring, alerting, and post-mortem analysis.\n\n---\n\n## 3. External Links (Links.md)\n\n**Rule**: Every project maintains a `Links.md` file at its root with a markdown table of relevant URLs.\n\n```markdown\n| Label | URL |\n|-------|-----|\n| Local Dev | http://localhost:5001 |\n| Production | https://example.com |\n| Docs | https://docs.example.com |\n| GitHub | https://github.com/user/repo |\n```\n\nRules:\n- One table, two columns: Label and URL\n- Labels are short and descriptive\n- Command Center's scanner reads this on startup and stores links in the project's `extra` JSON\n- Links appear in the project's configuration page\n\n**Why**: Centralizes all project URLs in one discoverable, parseable location. Both AI agents and Command Center consume it.\n\n---\n\n## 4. CLAUDE.md Convention\n\n**Rule**: Every project has a `CLAUDE.md` at its root following this section structure:\n\n1. `## Project Overview` \u2014 What the project does, key features\n2. `## Architecture` \u2014 Tech stack, key files, patterns\n3. `## Dev Commands` \u2014 Bash commands to run the project (in a code block)\n4. `## Service Endpoints` \u2014 URLs: `- Label: https://url`\n5. `## Bookmarks` \u2014 Grouped links: `### Group` then `- [Title](URL)`\n\nSection rename rules \u2014 always use the standard name:\n- `## Commands` / `## Development Commands` / `## Build Commands` \u2192 `## Dev Commands`\n- `## Overview` / `## Project Purpose` \u2192 `## Project Overview`\n- `## Stack` \u2192 `## Architecture`\n\n**Why**: Consistent structure lets AI agents parse project context reliably.\n\n---\n\n## 5. Git Hygiene\n\n**Rule**: Maintain a comprehensive `.gitignore`. Never commit secrets, generated files, or runtime data.\n\n```gitignore\n# Runtime\ndata/\n*.db\n*.log\n\n# Environment\n.env\nvenv/\nnode_modules/\n\n# Python\n__pycache__/\n*.pyc\n*.egg-info/\ndist/\nbuild/\n\n# OS\n.DS_Store\nThumbs.db\n```\n\nRules:\n- `data/` \u2014 runtime databases, logs, backups, uploads\n- `.env` \u2014 secrets and local config\n- Commit `.env.example` with placeholder values\n- Write imperative commit messages: \"Add health endpoint\" not \"Added health endpoint\"\n\n**Why**: Clean repos are cloneable and runnable. No secrets in history.\n\n---\n\n## 6. Development Workflow\n\n**Rule**: Follow these rules when working in any git-managed project.\n\n1. **Always commit changes immediately** after completing a task if the task has no errors\n2. **Commit messages** should have descriptive text (no AI/tool mentions)\n3. **DO NOT push** \u2014 only commit to local git\n4. **NO co-authored-by lines** in commits\n5. **Always end code change responses with a restart notice** for any project that runs a web server:\n - If only templates/CSS/static files changed: \"No restart needed \u2014 browser refresh is enough.\"\n - If any Python/JS server files changed: \"Restart required \u2014 run the start script or equivalent.\"\n\n**Why**: Consistent workflow prevents accidental pushes, keeps commit history clean, and ensures developers know when to restart.\n\n---\n\n## Summary Checklist\n\n- [ ] Standard directory layout with `bin/`, `data/`, `docs/`, `tests/`\n- [ ] Shell scripts in `bin/` with CommandCenter headers and tee logging\n- [ ] `Links.md` for external URLs\n- [ ] `CLAUDE.md` following section convention\n- [ ] `.env.example` committed, `.env` gitignored\n- [ ] Comprehensive `.gitignore`\n- [ ] Commit immediately, don't push, no AI mentions\n", "django": "# Django Best Practices\n\nTechnology reference for Django web applications. This file does not change between projects.\n\nPrerequisites: `stack/common.md`, `stack/python.md`\n\n---\n\n## 1. Project Structure\n\n**Rule**: Use Django's standard layout but keep it flat for small projects. One app for simple services.\n\n```\nproject-name/\n\u251c\u2500\u2500 manage.py # Django management CLI\n\u251c\u2500\u2500 config/ # Project settings package\n\u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u251c\u2500\u2500 settings.py # Base settings\n\u2502 \u251c\u2500\u2500 settings_dev.py # Development overrides\n\u2502 \u251c\u2500\u2500 settings_prod.py # Production overrides\n\u2502 \u251c\u2500\u2500 urls.py # Root URL configuration\n\u2502 \u2514\u2500\u2500 wsgi.py # WSGI entry point\n\u251c\u2500\u2500 core/ # Main app\n\u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u251c\u2500\u2500 models.py\n\u2502 \u251c\u2500\u2500 views.py\n\u2502 \u251c\u2500\u2500 urls.py\n\u2502 \u251c\u2500\u2500 admin.py\n\u2502 \u251c\u2500\u2500 forms.py\n\u2502 \u2514\u2500\u2500 templatetags/\n\u251c\u2500\u2500 templates/\n\u2502 \u251c\u2500\u2500 base.html\n\u2502 \u2514\u2500\u2500 core/\n\u251c\u2500\u2500 static/\n\u2502 \u251c\u2500\u2500 css/\n\u2502 \u2514\u2500\u2500 js/\n\u251c\u2500\u2500 tests/\n\u2502 \u251c\u2500\u2500 conftest.py\n\u2502 \u2514\u2500\u2500 test_views.py\n\u251c\u2500\u2500 bin/ # (from common.md)\n\u251c\u2500\u2500 data/ # (from common.md)\n\u251c\u2500\u2500 requirements.txt\n\u251c\u2500\u2500 .env\n\u2514\u2500\u2500 CLAUDE.md\n```\n\n**Why**: One app avoids unnecessary complexity. The `config/` package cleanly separates settings from app code.\n\n---\n\n## 2. Settings Management\n\n**Rule**: Use a settings package with base + environment overrides. Load secrets from environment variables.\n\n```python\n# config/settings.py (base)\nimport os\nfrom pathlib import Path\nfrom dotenv import load_dotenv\n\nload_dotenv()\n\nBASE_DIR = Path(__file__).resolve().parent.parent\nSECRET_KEY = os.environ.get('SECRET_KEY', 'dev-key-change-me')\nDEBUG = False\nALLOWED_HOSTS = []\n\nINSTALLED_APPS = [\n 'django.contrib.admin',\n 'django.contrib.auth',\n 'django.contrib.contenttypes',\n 'django.contrib.sessions',\n 'django.contrib.messages',\n 'django.contrib.staticfiles',\n 'core',\n]\n\nDATABASES = {\n 'default': {\n 'ENGINE': 'django.db.backends.sqlite3',\n 'NAME': BASE_DIR / 'data' / 'db.sqlite3',\n }\n}\n\nSTATIC_URL = '/static/'\nSTATICFILES_DIRS = [BASE_DIR / 'static']\nTEMPLATES = [{\n 'BACKEND': 'django.template.backends.django.DjangoTemplates',\n 'DIRS': [BASE_DIR / 'templates'],\n 'APP_DIRS': True,\n 'OPTIONS': {\n 'context_processors': [\n 'django.template.context_processors.debug',\n 'django.template.context_processors.request',\n 'django.contrib.auth.context_processors.auth',\n 'django.contrib.messages.context_processors.messages',\n 'core.context_processors.global_context',\n ],\n },\n}]\n```\n\n```python\n# config/settings_dev.py\nfrom .settings import *\nDEBUG = True\nALLOWED_HOSTS = ['*']\n```\n\n```python\n# config/settings_prod.py\nfrom .settings import *\nDEBUG = False\nSECRET_KEY = os.environ['SECRET_KEY']\nALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')\n```\n\n```bash\n# .env\nDJANGO_SETTINGS_MODULE=config.settings_dev\nSECRET_KEY=your-secret-here\n```\n\n**Why**: `DJANGO_SETTINGS_MODULE` is Django's standard for environment switching.\n\n---\n\n## 3. Models\n\n**Rule**: Use Django's ORM. Add `created_at`/`updated_at` to all models. Use JSONField for extensible data.\n\n```python\n# core/models.py\nfrom django.db import models\n\nclass TimestampMixin(models.Model):\n created_at = models.DateTimeField(auto_now_add=True)\n updated_at = models.DateTimeField(auto_now=True)\n\n class Meta:\n abstract = True\n\nclass Project(TimestampMixin):\n name = models.CharField(max_length=200, unique=True)\n title = models.CharField(max_length=200)\n description = models.TextField(blank=True, default='')\n project_type = models.CharField(max_length=50, default='software')\n status = models.CharField(max_length=50, default='active')\n extra = models.JSONField(default=dict, blank=True)\n\n class Meta:\n ordering = ['project_type', 'title']\n\n def __str__(self):\n return self.title\n```\n\n**Why**: ORM handles migrations and validation. JSONField stores extensible data without schema changes. Abstract mixin avoids repeating timestamps.\n\n---\n\n## 4. Views and URL Organization\n\n**Rule**: Keep views thin. Function-based views for simple endpoints, class-based for CRUD. Business logic in separate modules.\n\n```python\n# core/views.py\nfrom django.shortcuts import render, get_object_or_404\nfrom django.http import JsonResponse\nfrom .models import Project\nfrom . import ops\n\ndef project_list(request):\n projects = Project.objects.all()\n return render(request, 'core/project_list.html', {'projects': projects})\n\ndef api_start_project(request, pk):\n if request.method != 'POST':\n return JsonResponse({'error': 'Method not allowed'}, status=405)\n project = get_object_or_404(Project, pk=pk)\n result = ops.start_service(project)\n return JsonResponse(result)\n```\n\n```python\n# core/urls.py\nfrom django.urls import path\nfrom . import views\n\napp_name = 'core'\nurlpatterns = [\n path('projects/', views.project_list, name='project-list'),\n path('api/project//start/', views.api_start_project, name='api-start'),\n]\n```\n\n```python\n# config/urls.py\nfrom django.contrib import admin\nfrom django.urls import path, include\n\nurlpatterns = [\n path('admin/', admin.site.urls),\n path('', include('core.urls')),\n]\n```\n\n**Why**: URL namespacing (`app_name`) prevents collisions. Thin views are testable.\n\n---\n\n## 5. Migrations\n\n**Rule**: Use Django's migration system. Always review generated migrations before committing.\n\n```bash\npython manage.py makemigrations\npython manage.py migrate\n```\n\nRules:\n- Run `makemigrations` after any model change\n- Review generated migration files before committing\n- Commit migration files to git (they are the schema)\n- Never manually edit applied migrations\n- Use `--name` for clarity: `makemigrations --name add_priority_field`\n\n**Why**: Django's migration system is the source of truth for schema.\n\n---\n\n## 6. Templates (Django Template Language)\n\n**Rule**: Use template inheritance with `base.html`. Prefix partials with `_`. Keep logic out.\n\n```html\n\n\n\n\n {% block title %}App{% endblock %}\n {% load static %}\n \n {% block head %}{% endblock %}\n\n\n {% include '_nav.html' %}\n
\n {% block content %}{% endblock %}\n
\n {% block scripts %}{% endblock %}\n\n\n```\n\nRules:\n- All pages extend `base.html`\n- App-specific templates in `templates//`\n- Partials prefixed with `_`\n- Use `{% load static %}` and `{% static %}` for static files\n- Auto-escaping enabled by default \u2014 don't disable\n\n---\n\n## 7. HTMX Integration\n\n**Rule**: Use HTMX with `django-htmx` middleware for request detection.\n\n```python\n# pip install django-htmx \u2014 add to INSTALLED_APPS and MIDDLEWARE\n\ndef toggle_status(request, pk):\n project = get_object_or_404(Project, pk=pk)\n project.status = 'inactive' if project.status == 'active' else 'active'\n project.save()\n\n if request.htmx:\n return render(request, 'core/_project_row.html', {'project': project})\n return redirect('core:project-list')\n```\n\n```html\n\n```\n\n**Why**: `django-htmx` adds clean request detection via `request.htmx`.\n\n---\n\n## 8. Context Processors\n\n**Rule**: Use context processors for global template variables.\n\n```python\n# core/context_processors.py\nfrom .models import Project\n\ndef global_context(request):\n return {\n 'running_count': Project.objects.filter(status='running').count(),\n 'app_name': 'My App',\n }\n```\n\nRegister in `settings.py` under `TEMPLATES[0]['OPTIONS']['context_processors']`.\n\n---\n\n## 9. Testing\n\n**Rule**: Use pytest-django with fixtures. Test through the Django test client.\n\n```python\n# tests/conftest.py\nimport pytest\n\n@pytest.fixture\ndef client(db):\n from django.test import Client\n return Client()\n```\n\n```python\n# tests/test_views.py\nimport pytest\nfrom core.models import Project\n\n@pytest.mark.django_db\ndef test_project_list(client):\n Project.objects.create(name='test', title='Test')\n response = client.get('/projects/')\n assert response.status_code == 200\n assert b'Test' in response.content\n```\n\n```ini\n# pytest.ini\n[tool:pytest]\nDJANGO_SETTINGS_MODULE = config.settings_dev\n```\n\n**Why**: pytest-django handles database setup/teardown per test.\n\n---\n\n## 10. Security\n\n**Rule**: Use Django's built-in protections. Don't disable them.\n\nProduction checklist:\n```python\n# config/settings_prod.py\nDEBUG = False\nSECURE_BROWSER_XSS_FILTER = True\nSECURE_CONTENT_TYPE_NOSNIFF = True\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\n```\n\n```bash\npython manage.py check --deploy\n```\n\n**Why**: `check --deploy` catches common misconfigurations.\n\n---\n\n## 11. Admin\n\n**Rule**: Register models in admin for quick data inspection.\n\n```python\n# core/admin.py\nfrom django.contrib import admin\nfrom .models import Project\n\n@admin.register(Project)\nclass ProjectAdmin(admin.ModelAdmin):\n list_display = ['title', 'project_type', 'status', 'created_at']\n list_filter = ['project_type', 'status']\n search_fields = ['title', 'name']\n readonly_fields = ['created_at', 'updated_at']\n```\n\n---\n\n## Standard bin/ Scripts for Django\n\n```bash\n# bin/start.sh\n#!/bin/bash\n# CommandCenter Operation\n# Name: Service Start\n# Type: daemon\n# Port: 8000\n\nset -euo pipefail\ncd \"$(dirname \"${BASH_SOURCE[0]}\")/..\"\nsource venv/bin/activate\npython manage.py runserver 0.0.0.0:8000 2>&1\n```\n\n```bash\n# bin/stop.sh\n#!/bin/bash\n# CommandCenter Operation\n# Name: Service Stop\n# Type: batch\n\npkill -f \"manage.py runserver\" || echo \"No Django process found\"\n```\n\n```bash\n# bin/migrate.sh\n#!/bin/bash\n# CommandCenter Operation\n# Name: Run Migrations\n# Type: batch\n\nset -euo pipefail\ncd \"$(dirname \"${BASH_SOURCE[0]}\")/..\"\nsource venv/bin/activate\necho \"=== makemigrations ===\"\npython manage.py makemigrations 2>&1\necho \"=== migrate ===\"\npython manage.py migrate 2>&1\n```\n\n```bash\n# bin/test.sh\n#!/bin/bash\n# CommandCenter Operation\n# Name: Run Tests\n# Type: batch\n\nset -euo pipefail\ncd \"$(dirname \"${BASH_SOURCE[0]}\")/..\"\nsource venv/bin/activate\npython -m pytest tests/ -v 2>&1\n```\n\n```bash\n# bin/shell.sh\n#!/bin/bash\n# CommandCenter Operation\n# Name: Django Shell\n# Type: daemon\n\ncd \"$(dirname \"${BASH_SOURCE[0]}\")/..\"\nsource venv/bin/activate\npython manage.py shell\n```\n\n```bash\n# bin/collectstatic.sh\n#!/bin/bash\n# CommandCenter Operation\n# Name: Collect Static\n# Type: batch\n\nset -euo pipefail\ncd \"$(dirname \"${BASH_SOURCE[0]}\")/..\"\nsource venv/bin/activate\npython manage.py collectstatic --noinput 2>&1\n```\n\n---\n\n## Summary Checklist\n\n- [ ] Settings package with base + dev/prod overrides\n- [ ] Models with TimestampMixin, JSONField for extensible data\n- [ ] Thin views, business logic in separate modules\n- [ ] URL namespacing with `app_name`\n- [ ] Django migration system for all schema changes\n- [ ] Template inheritance from `base.html`, partials prefixed with `_`\n- [ ] HTMX with `django-htmx` middleware\n- [ ] Context processors for global template data\n- [ ] pytest-django with test client fixtures\n- [ ] Security verified with `manage.py check --deploy`\n- [ ] Admin registered for all models\n- [ ] Standard `bin/` scripts: start, stop, migrate, test, shell, collectstatic\n", "flask": "# Flask Best Practices\n\nTechnology reference for Flask web applications. This file does not change between projects.\n\nPrerequisites: `stack/common.md`, `stack/python.md`\n\n---\n\n## 1. Application Factory\n\n**Rule**: Use a `create_app()` factory function. Initialize extensions and register blueprints inside it.\n\n```python\n# app.py\nfrom flask import Flask\n\ndef create_app(config=None):\n app = Flask(__name__)\n app.config.from_object(config or 'config.DevConfig')\n\n # Initialize database\n from db import init_db\n with app.app_context():\n init_db()\n\n # Register routes\n from routes import bp\n app.register_blueprint(bp)\n\n # Register error handlers\n from errors import register_error_handlers\n register_error_handlers(app)\n\n # Startup validation\n from startup import validate_startup\n validate_startup(app)\n\n return app\n\nif __name__ == '__main__':\n import os\n app = create_app()\n app.run(port=int(os.environ.get('APP_PORT', 5001)))\n```\n\n**Why**: Factory pattern enables creating multiple app instances with different configs \u2014 essential for testing. Deferred imports prevent circular dependencies.\n\n---\n\n## 2. Blueprints and Route Organization\n\n**Rule**: Keep route handlers thin. Extract business logic into separate modules. Group related routes with Blueprints.\n\n```python\n# routes.py\nfrom flask import Blueprint, render_template, request, jsonify\nimport ops\n\nbp = Blueprint('main', __name__)\n\n@bp.route('/projects')\ndef projects_list():\n projects = ops.get_all_projects()\n return render_template('projects.html', projects=projects)\n\n@bp.route('/api/project//start', methods=['POST'])\ndef start_project(project_id):\n result = ops.start_service(project_id) # Logic in ops.py, not here\n return jsonify(result)\n```\n\n```python\n# ops.py \u2014 business logic, no Flask imports needed\nfrom db import get_db\n\ndef get_all_projects():\n db = get_db()\n return db.execute('SELECT * FROM projects ORDER BY title').fetchall()\n\ndef start_service(project_id):\n ...\n return {'status': 'started', 'pid': pid}\n```\n\nRules:\n- Route handlers do: parse request, call business logic, return response\n- Route handlers don't: contain SQL, file I/O, subprocess calls, or complex logic\n- One Blueprint per feature area for larger apps\n- API routes return JSON; page routes return rendered templates\n\n**Why**: Thin routes are testable and readable. Business logic in separate modules can be reused without Flask context.\n\n---\n\n## 3. Error Handling\n\n**Rule**: Register Flask error handlers for 404/500. Return JSON for API routes, HTML for page routes. Never expose stack traces.\n\n```python\n# errors.py\nfrom flask import render_template, jsonify, request\n\ndef register_error_handlers(app):\n @app.errorhandler(404)\n def not_found(e):\n if request.path.startswith('/api/'):\n return jsonify({'error': 'Not found'}), 404\n return render_template('404.html'), 404\n\n @app.errorhandler(500)\n def server_error(e):\n app.logger.error('Internal error: %s', e, exc_info=True)\n if request.path.startswith('/api/'):\n return jsonify({'error': 'Internal server error'}), 500\n return render_template('500.html'), 500\n\n @app.errorhandler(Exception)\n def unhandled_exception(e):\n app.logger.error('Unhandled exception: %s', e, exc_info=True)\n return render_template('500.html'), 500\n```\n\n**Why**: Dual response format (JSON/HTML) keeps API clients and browsers happy.\n\n---\n\n## 4. Templates (Jinja2)\n\n**Rule**: Use template inheritance with `base.html`. Prefix partials with `_`. Keep logic out of templates.\n\n```html\n\n\n\n\n {% block title %}App{% endblock %}\n \n {% block head %}{% endblock %}\n\n\n {% include '_nav.html' %}\n
\n {% block content %}{% endblock %}\n
\n {% block scripts %}{% endblock %}\n\n\n```\n\n```html\n\n{% extends 'base.html' %}\n{% block title %}Projects{% endblock %}\n{% block content %}\n

Projects

\n {% for p in projects %}\n {% include 'types/_project_row.html' %}\n {% endfor %}\n{% endblock %}\n```\n\nRules:\n- All pages extend `base.html`\n- Partials prefixed with `_` (e.g., `_nav.html`, `_project_row.html`)\n- Type-specific partials in `templates/types/`\n- No Python logic in templates \u2014 pass ready-to-render data from routes\n- Use `url_for('static', filename=...)` for all static file references\n- Auto-escaping is enabled by default \u2014 don't disable it\n\n**Why**: Template inheritance eliminates duplication. Partial naming conventions make includes discoverable.\n\n---\n\n## 5. Context Processors\n\n**Rule**: Use context processors to inject global data into all templates.\n\n```python\n@app.context_processor\ndef inject_globals():\n return {\n 'app_name': app.config.get('APP_NAME', 'My App'),\n 'running_count': ops.get_running_count(),\n }\n```\n\n**Why**: Avoids passing the same variables to every `render_template()` call.\n\n---\n\n## 6. HTMX Integration\n\n**Rule**: Use HTMX for dynamic UI updates. Return HTML fragments from API endpoints. Use `HX-Trigger` headers for cross-component updates.\n\n```python\n@bp.route('/api/project//toggle', methods=['POST'])\ndef toggle_status(project_id):\n new_status = ops.toggle_status(project_id)\n project = ops.get_project(project_id)\n return render_template('types/_project_row.html', project=project)\n```\n\n```html\n\n```\n\n### OOB (Out-of-Band) Swaps\n\n```python\nhtml = render_template('types/_project_row.html', project=project)\nhtml += render_template('_nav_badge.html', count=running_count)\nresponse = make_response(html)\nresponse.headers['HX-Trigger'] = 'projectUpdated'\nreturn response\n```\n\n```html\n{{ count }}\n```\n\n**Why**: HTMX eliminates JavaScript for common interactions. HTML fragments are simpler than JSON APIs for server-rendered apps.\n\n---\n\n## 7. Testing with Flask Test Client\n\n**Rule**: Test through the Flask test client. Use fixtures for app, client, and database.\n\n```python\n# tests/conftest.py\nimport pytest\nfrom app import create_app\nfrom config import TestConfig\n\n@pytest.fixture\ndef app():\n app = create_app(TestConfig)\n yield app\n\n@pytest.fixture\ndef client(app):\n return app.test_client()\n\n@pytest.fixture\ndef db(app):\n from db import get_db, init_db\n with app.app_context():\n init_db()\n yield get_db()\n```\n\n```python\n# tests/test_routes.py\ndef test_projects_page(client):\n response = client.get('/projects')\n assert response.status_code == 200\n assert b'Projects' in response.data\n\ndef test_api_returns_json(client):\n response = client.get('/api/projects')\n assert response.content_type == 'application/json'\n\ndef test_htmx_endpoint(client):\n response = client.post('/api/project/1/toggle',\n headers={'HX-Request': 'true'})\n assert response.status_code == 200\n```\n\n**Why**: Test client exercises the full stack without a running server.\n\n---\n\n## 8. Security\n\n**Rule**: Set secure headers. Validate all input. Debug mode off in production.\n\n```python\n@app.after_request\ndef set_headers(response):\n response.headers['X-Content-Type-Options'] = 'nosniff'\n response.headers['X-Frame-Options'] = 'DENY'\n return response\n```\n\nFlask security checklist:\n- Jinja2 auto-escaping enabled (default \u2014 don't disable)\n- `SECRET_KEY` set from environment variable in production\n- `secure_filename()` from werkzeug for file uploads\n- Input validation on all form data (length, type, allowed values)\n- Debug mode disabled in production (`FLASK_DEBUG=0`)\n\n---\n\n## 9. Health Check\n\n**Rule**: Expose a `/health` endpoint that verifies database connectivity.\n\n```python\n@bp.route('/health')\ndef health():\n try:\n db = get_db()\n db.execute('SELECT 1')\n return jsonify({'status': 'ok'}), 200\n except Exception as e:\n return jsonify({'status': 'error', 'detail': str(e)}), 500\n```\n\n---\n\n## 10. Debug Mode and Reloading\n\n**Rule**: Use Flask's debug mode in development only. Understand the reloader behavior.\n\n```bash\n# Development\nFLASK_DEBUG=1 flask run --port 5001\n\n# Production\ngunicorn app:create_app()\n```\n\nKey behaviors in debug mode:\n- **Auto-reloader**: Restarts server on Python file changes\n- **Double startup**: `WERKZEUG_RUN_MAIN` check needed to avoid running startup code twice\n- **Interactive debugger**: Shows in browser on errors (never expose in prod)\n\n```python\nif os.environ.get('WERKZEUG_RUN_MAIN') == 'true' or not app.debug:\n run_scanner()\n```\n\n---\n\n## Standard bin/ Scripts for Flask\n\n```bash\n# bin/start.sh\n#!/bin/bash\n# CommandCenter Operation\n# Name: Service Start\n# Type: daemon\n# Port: 5001\n\nset -euo pipefail\ncd \"$(dirname \"${BASH_SOURCE[0]}\")/..\"\nsource venv/bin/activate\nFLASK_DEBUG=1 flask run --port 5001 2>&1\n```\n\n```bash\n# bin/stop.sh\n#!/bin/bash\n# CommandCenter Operation\n# Name: Service Stop\n# Type: batch\n\npkill -f \"flask run --port 5001\" || echo \"No Flask process found\"\n```\n\n```bash\n# bin/test.sh\n#!/bin/bash\n# CommandCenter Operation\n# Name: Run Tests\n# Type: batch\n\nset -euo pipefail\ncd \"$(dirname \"${BASH_SOURCE[0]}\")/..\"\nsource venv/bin/activate\npython -m pytest tests/ -v 2>&1\n```\n\n---\n\n## Summary Checklist\n\n- [ ] Application factory with `create_app()`\n- [ ] Blueprints with thin route handlers\n- [ ] Error handlers for 404/500 (JSON + HTML)\n- [ ] Template inheritance from `base.html`, partials prefixed with `_`\n- [ ] Context processors for global template data\n- [ ] HTMX for dynamic UI (HTML fragments, OOB swaps)\n- [ ] Test client fixtures in conftest.py\n- [ ] Secure headers and input validation\n- [ ] `/health` endpoint\n- [ ] Debug mode only in dev, `WERKZEUG_RUN_MAIN` guard\n- [ ] Standard `bin/` scripts: start.sh, stop.sh, test.sh\n", "postgres": "# PostgreSQL Best Practices\n\nTechnology reference for PostgreSQL in Python applications. This file does not change between projects.\n\nPrerequisites: `stack/python.md`\n\n---\n\n## Connection Setup\n\n**Rule**: Use `psycopg2` with connection pooling. Store connection string in environment variable.\n\n```python\nimport os\nimport psycopg2\nfrom psycopg2.extras import RealDictCursor\nfrom psycopg2.pool import SimpleConnectionPool\n\nDATABASE_URL = os.environ['DATABASE_URL']\n\npool = SimpleConnectionPool(minconn=1, maxconn=10, dsn=DATABASE_URL)\n\ndef get_db():\n conn = pool.getconn()\n conn.autocommit = False\n return conn\n\ndef release_db(conn):\n pool.putconn(conn)\n```\n\n### With Context Manager\n\n```python\nfrom contextlib import contextmanager\n\n@contextmanager\ndef db_connection():\n conn = pool.getconn()\n try:\n yield conn\n conn.commit()\n except Exception:\n conn.rollback()\n raise\n finally:\n pool.putconn(conn)\n```\n\n**Why**: Connection pooling avoids overhead. Context manager ensures proper commit/rollback.\n\n---\n\n## Schema Design\n\n**Rule**: Use native types (TIMESTAMP, BOOLEAN, JSONB). Use JSONB for extensible fields.\n\n```sql\nCREATE TABLE IF NOT EXISTS items (\n id SERIAL PRIMARY KEY,\n name TEXT UNIQUE NOT NULL,\n status TEXT DEFAULT 'active',\n extra JSONB DEFAULT '{}',\n created_at TIMESTAMP DEFAULT NOW(),\n updated_at TIMESTAMP DEFAULT NOW()\n);\n\nCREATE OR REPLACE FUNCTION update_timestamp()\nRETURNS TRIGGER AS $$\nBEGIN\n NEW.updated_at = NOW();\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nCREATE TRIGGER items_updated\n BEFORE UPDATE ON items\n FOR EACH ROW\n EXECUTE FUNCTION update_timestamp();\n```\n\n### JSONB Queries\n\n```sql\nSELECT * FROM items WHERE extra->>'tech_stack' = 'flask';\nUPDATE items SET extra = jsonb_set(extra, '{port}', '8080') WHERE id = 1;\nSELECT * FROM items WHERE extra ? 'workflow';\n```\n\n**Why**: JSONB is indexable and queryable unlike plain TEXT JSON.\n\n---\n\n## Queries\n\n**Rule**: Always use parameterized queries with `%s` placeholders. Never interpolate.\n\n```python\n# CORRECT\ncursor = db.execute('SELECT * FROM items WHERE id = %s', (item_id,))\n\n# NEVER\ndb.execute(f\"SELECT * FROM items WHERE id = {item_id}\")\n```\n\n### Helper Patterns\n\n```python\ndef query(sql, params=(), one=False):\n with db_connection() as conn:\n with conn.cursor(cursor_factory=RealDictCursor) as cur:\n cur.execute(sql, params)\n if one:\n return cur.fetchone()\n return cur.fetchall()\n\ndef execute(sql, params=()):\n with db_connection() as conn:\n with conn.cursor() as cur:\n cur.execute(sql, params)\n if cur.description:\n return cur.fetchone()\n return cur.rowcount\n```\n\n---\n\n## Migrations\n\n**Rule**: Use numbered migration files. Track applied migrations in a table.\n\n```\nmigrations/\n\u251c\u2500\u2500 001_initial_schema.sql\n\u251c\u2500\u2500 002_add_priority_column.sql\n\u2514\u2500\u2500 003_create_audit_table.sql\n```\n\n```python\ndef run_migrations(db):\n db.execute('''\n CREATE TABLE IF NOT EXISTS schema_migrations (\n version INTEGER PRIMARY KEY,\n applied_at TIMESTAMP DEFAULT NOW()\n )\n ''')\n db.commit()\n\n applied = {r[0] for r in db.execute('SELECT version FROM schema_migrations').fetchall()}\n\n migration_files = sorted(glob.glob('migrations/*.sql'))\n for f in migration_files:\n version = int(os.path.basename(f).split('_')[0])\n if version not in applied:\n with open(f) as sql_file:\n db.execute(sql_file.read())\n db.execute('INSERT INTO schema_migrations (version) VALUES (%s)', (version,))\n db.commit()\n logger.info('Applied migration %s', f)\n```\n\n**Why**: Numbered files are auditable, version-controlled, reproducible.\n\n---\n\n## Indexing\n\n**Rule**: Index columns in WHERE, JOIN, ORDER BY. Use partial indexes for filtered queries.\n\n```sql\nCREATE INDEX idx_items_status ON items(status);\nCREATE INDEX idx_items_active ON items(status) WHERE status = 'active';\nCREATE INDEX idx_items_tech ON items USING GIN (extra);\n```\n\n---\n\n## Backup\n\n**Rule**: Use `pg_dump` for logical backups. Schedule via bin/ scripts.\n\n```bash\n#!/bin/bash\n# CommandCenter Operation\n# Name: Database Backup\n# Type: batch\n\nset -euo pipefail\nTIMESTAMP=$(date '+%Y%m%d_%H%M%S')\nBACKUP_DIR=\"data/backups\"\nmkdir -p \"$BACKUP_DIR\"\n\npg_dump \"$DATABASE_URL\" > \"$BACKUP_DIR/backup_${TIMESTAMP}.sql\" 2>&1\n\n# Keep last 7 backups\nls -t \"$BACKUP_DIR\"/backup_*.sql | tail -n +8 | xargs -r rm\n```\n\n---\n\n## When to Use PostgreSQL Over SQLite\n\n| Need | Use PostgreSQL |\n|------|---------------|\n| Multiple concurrent writers | Yes |\n| Multi-server deployment | Yes |\n| Full-text search | Yes |\n| JSONB with indexing | Yes |\n| Dataset > 1GB | Yes |\n| Complex queries (CTEs, window functions) | Yes |\n\nFor single-server tools, prefer SQLite (see `stack/sqlite.md`).\n", "python": "# Python Best Practices\n\nTechnology reference for Python development. Framework-agnostic \u2014 applies to any Python project. This file does not change between projects.\n\nPrerequisite: `stack/common.md`\n\n---\n\n## 1. Configuration Management\n\n**Rule**: Use environment variables loaded via `python-dotenv`. Never hardcode secrets, ports, or paths.\n\n```python\n# config.py\nimport os\nfrom dotenv import load_dotenv\n\nload_dotenv()\n\nclass Config:\n SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-key-change-me')\n DATABASE_PATH = os.environ.get('DATABASE_PATH', 'data/app.db')\n DEBUG = False\n\nclass DevConfig(Config):\n DEBUG = True\n\nclass ProdConfig(Config):\n SECRET_KEY = os.environ['SECRET_KEY'] # Crash if missing in prod\n\nclass TestConfig(Config):\n DATABASE_PATH = ':memory:'\n TESTING = True\n```\n\n```bash\n# .env\nSECRET_KEY=your-secret-here\nDATABASE_PATH=data/app.db\nAPP_PORT=5001\n```\n\n**Why**: Config classes make environment switching explicit. Crashing on missing secrets in prod prevents silent misconfiguration.\n\n---\n\n## 2. Logging\n\n**Rule**: Use Python's `logging` module with named loggers, never `print()`. Configure formatters and handlers at startup.\n\n```python\nimport logging\nimport os\n\ndef setup_logging(level=None):\n level = level or ('DEBUG' if os.getenv('APP_DEBUG') else 'INFO')\n\n formatter = logging.Formatter(\n '%(asctime)s %(name)s %(levelname)s %(message)s',\n datefmt='%Y-%m-%d %H:%M:%S'\n )\n\n console = logging.StreamHandler()\n console.setFormatter(formatter)\n\n root = logging.getLogger()\n root.setLevel(level)\n root.addHandler(console)\n\n # File handler\n os.makedirs('data/logs', exist_ok=True)\n file_handler = logging.FileHandler('data/logs/app.log')\n file_handler.setFormatter(formatter)\n root.addHandler(file_handler)\n```\n\n```python\n# In any module\nimport logging\nlogger = logging.getLogger(__name__)\n\nlogger.info('Server starting on port %s', port)\nlogger.error('Failed to connect: %s', err)\n```\n\n**Why**: Named loggers trace messages to source modules. Structured format enables log parsing.\n\n---\n\n## 3. Environment Separation\n\n**Rule**: Maintain distinct configs for dev/test/prod. Never run debug mode in production.\n\n| Setting | Dev | Test | Prod |\n|---------|-----|------|------|\n| DEBUG | True | False | False |\n| DATABASE | data/app.db | :memory: | data/app.db |\n| SECRET_KEY | hardcoded default | hardcoded default | env var (required) |\n| LOGGING | DEBUG | WARNING | INFO |\n\n**Why**: Environment separation prevents dev shortcuts from reaching production.\n\n---\n\n## 4. Testing\n\n**Rule**: Use `pytest` with fixtures. Isolate each test with a fresh database. Test at the boundary, not internals.\n\n```python\n# tests/conftest.py\nimport pytest\n\n@pytest.fixture\ndef db():\n \"\"\"Provide a clean in-memory database for each test.\"\"\"\n from db import get_db, init_db\n init_db(':memory:')\n yield get_db()\n```\n\n```python\n# tests/test_ops.py\ndef test_create_project(db):\n from ops import create_project\n project = create_project(db, title='Test')\n assert project is not None\n row = db.execute('SELECT * FROM projects WHERE title = ?', ('Test',)).fetchone()\n assert row is not None\n```\n\n**Why**: Fixtures ensure clean state per test. In-memory DB makes tests fast.\n\n---\n\n## 5. Security Basics\n\n**Rule**: Validate all user input. Use parameterized queries exclusively. Never trust client data.\n\nChecklist:\n- Parameterized queries for all DB operations (`?` placeholders, never f-strings)\n- `secure_filename()` for any file path from user input\n- Length and type validation on inputs\n- Secret key loaded from environment, not hardcoded in prod\n- Never expose stack traces to end users\n\n**Why**: These basics prevent the most common attack vectors with minimal effort.\n\n---\n\n## 6. Dependency Management\n\n**Rule**: Pin exact versions in `requirements.txt`. Always use virtual environments. Keep dependencies minimal.\n\n```bash\npython -m venv venv\nsource venv/bin/activate\npip install flask python-dotenv\npip freeze > requirements.txt\n```\n\n```\n# requirements.txt \u2014 pin exact versions\nFlask==3.1.0\npython-dotenv==1.0.1\n```\n\nRules:\n- One `requirements.txt` at project root\n- Pin exact versions (`==`), not ranges\n- `pip freeze > requirements.txt` after any install\n- Keep it small \u2014 prefer stdlib over third-party\n- `venv/` directory is always gitignored\n\n**Why**: Pinned versions ensure reproducible builds. Fewer dependencies mean fewer security vulnerabilities.\n\n---\n\n## 7. Health Check and Startup Validation\n\n**Rule**: Validate required config and DB connectivity at startup. Crash early on misconfiguration.\n\n```python\ndef validate_startup():\n \"\"\"Crash early if critical config is missing.\"\"\"\n required_vars = ['PROJECTS_DIR']\n missing = [v for v in required_vars if not os.environ.get(v)]\n if missing:\n raise RuntimeError(f'Missing required env vars: {\", \".join(missing)}')\n\n from db import get_db\n try:\n get_db().execute('SELECT 1')\n except Exception as e:\n raise RuntimeError(f'Database not accessible: {e}')\n\n logger.info('Startup validation passed')\n```\n\n**Why**: Startup validation catches misconfigurations immediately rather than at first user request.\n\n---\n\n## 8. Project Directory Layout (Python-specific)\n\nPython web projects extend the common layout:\n\n```\nproject-name/\n\u251c\u2500\u2500 app.py # Entry point / app factory\n\u251c\u2500\u2500 routes.py # Route handlers\n\u251c\u2500\u2500 models.py # Data models and type registries\n\u251c\u2500\u2500 db.py # Database connection, schema, migrations\n\u251c\u2500\u2500 ops.py # Business logic and operations\n\u251c\u2500\u2500 config.py # Config classes (Dev/Prod/Test)\n\u251c\u2500\u2500 templates/ # Jinja2 or Django templates\n\u2502 \u251c\u2500\u2500 base.html\n\u2502 \u2514\u2500\u2500 types/ # Type-specific partials\n\u251c\u2500\u2500 static/\n\u2502 \u251c\u2500\u2500 css/\n\u2502 \u2514\u2500\u2500 js/\n\u251c\u2500\u2500 tests/\n\u2502 \u251c\u2500\u2500 conftest.py\n\u2502 \u2514\u2500\u2500 test_*.py\n\u251c\u2500\u2500 bin/ # (from common.md)\n\u251c\u2500\u2500 data/ # (from common.md)\n\u251c\u2500\u2500 requirements.txt\n\u251c\u2500\u2500 .env\n\u251c\u2500\u2500 .gitignore\n\u251c\u2500\u2500 CLAUDE.md\n\u2514\u2500\u2500 Links.md\n```\n\n---\n\n## Summary Checklist\n\n- [ ] Config via env vars with `python-dotenv`, no hardcoded secrets\n- [ ] Logging with named loggers, not `print()`\n- [ ] Distinct dev/test/prod configs\n- [ ] pytest with fixtures and isolated test DB\n- [ ] Input validation, parameterized queries\n- [ ] Pinned dependencies in requirements.txt, venv gitignored\n- [ ] Startup validation for required config\n", "sqlite": "# SQLite Best Practices\n\nTechnology reference for SQLite in Python applications. This file does not change between projects.\n\nPrerequisites: `stack/python.md`\n\n---\n\n## Connection Setup\n\n**Rule**: Always enable WAL mode, foreign keys, and use Row factory.\n\n```python\nimport sqlite3\nimport os\n\ndef get_db(db_path):\n os.makedirs(os.path.dirname(db_path), exist_ok=True)\n conn = sqlite3.connect(db_path)\n conn.row_factory = sqlite3.Row\n conn.execute('PRAGMA journal_mode=WAL')\n conn.execute('PRAGMA foreign_keys=ON')\n return conn\n```\n\n### PRAGMAs\n\n| PRAGMA | Value | Why |\n|--------|-------|-----|\n| `journal_mode=WAL` | Write-Ahead Logging | Allows concurrent reads during writes |\n| `foreign_keys=ON` | Enforce FK constraints | SQLite disables FK enforcement by default |\n\n---\n\n## Schema Design\n\n**Rule**: Use `TEXT` for dates (ISO 8601), `INTEGER` for booleans, and a JSON `TEXT` column for extensible fields.\n\n```sql\nCREATE TABLE IF NOT EXISTS items (\n id INTEGER PRIMARY KEY,\n name TEXT UNIQUE NOT NULL,\n status TEXT DEFAULT 'active',\n extra TEXT DEFAULT '{}', -- JSON for extensible fields\n created_at TEXT DEFAULT (datetime('now')),\n updated_at TEXT DEFAULT (datetime('now'))\n);\n```\n\n### JSON Column Pattern\n\n```python\nimport json\n\ndef get_extra(row):\n return json.loads(row['extra'] or '{}')\n\ndef set_extra(db, item_id, key, value):\n row = db.execute('SELECT extra FROM items WHERE id = ?', (item_id,)).fetchone()\n extra = json.loads(row['extra'] or '{}')\n extra[key] = value\n db.execute('UPDATE items SET extra = ? WHERE id = ?', (json.dumps(extra), item_id))\n db.commit()\n```\n\n**Why**: JSON columns let you add fields without migrations.\n\n---\n\n## Queries\n\n**Rule**: Always use parameterized queries. Never interpolate user input into SQL.\n\n```python\n# CORRECT \u2014 parameterized\ndb.execute('SELECT * FROM items WHERE id = ?', (item_id,))\ndb.execute('INSERT INTO items (name, status) VALUES (?, ?)', (name, status))\n\n# NEVER \u2014 string interpolation\ndb.execute(f\"SELECT * FROM items WHERE id = {item_id}\") # SQL INJECTION\n```\n\n### Helper Patterns\n\n```python\ndef row_to_dict(row):\n \"\"\"Convert sqlite3.Row to dict, parsing JSON columns.\"\"\"\n if row is None:\n return None\n d = dict(row)\n if 'extra' in d:\n d['extra'] = json.loads(d['extra'] or '{}')\n return d\n\ndef query(db, sql, params=(), one=False):\n rows = db.execute(sql, params).fetchall()\n if one:\n return row_to_dict(rows[0]) if rows else None\n return [row_to_dict(r) for r in rows]\n\ndef execute(db, sql, params=()):\n cursor = db.execute(sql, params)\n db.commit()\n return cursor.lastrowid\n```\n\n---\n\n## Migrations\n\n**Rule**: Run migrations at startup using `PRAGMA table_info()` to detect missing columns.\n\n```python\ndef _run_migrations(db):\n \"\"\"Add columns that don't exist yet. Runs on every startup.\"\"\"\n columns = {r['name'] for r in db.execute('PRAGMA table_info(items)').fetchall()}\n\n migrations = [\n ('new_field', 'ALTER TABLE items ADD COLUMN new_field TEXT DEFAULT \"\"'),\n ('priority', 'ALTER TABLE items ADD COLUMN priority INTEGER DEFAULT 0'),\n ]\n\n for col_name, sql in migrations:\n if col_name not in columns:\n db.execute(sql)\n\n db.commit()\n```\n\n### New Table Detection\n\n```python\ndef _table_exists(db, table_name):\n row = db.execute(\n \"SELECT name FROM sqlite_master WHERE type='table' AND name=?\",\n (table_name,)\n ).fetchone()\n return row is not None\n```\n\n**Why**: Startup migrations avoid manual schema management. `PRAGMA table_info` is idempotent.\n\n---\n\n## Limitations\n\n| Scenario | SQLite OK? | Alternative |\n|----------|-----------|-------------|\n| Single-server web app | Yes | \u2014 |\n| Local tool / CLI | Yes | \u2014 |\n| Multiple concurrent writers | No | PostgreSQL |\n| Multi-server deployment | No | PostgreSQL |\n| Dataset > 1GB | Maybe | PostgreSQL |\n| Full-text search (heavy) | Maybe | PostgreSQL + pg_trgm |\n\n---\n\n## Backup\n\n**Rule**: Use file copy with WAL checkpoint first.\n\n```python\nimport shutil\n\ndef backup_database(db_path, backup_dir='data/backups'):\n os.makedirs(backup_dir, exist_ok=True)\n timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')\n backup_path = os.path.join(backup_dir, f'backup_{timestamp}.db')\n\n conn = sqlite3.connect(db_path)\n conn.execute('PRAGMA wal_checkpoint(TRUNCATE)')\n conn.close()\n\n shutil.copy2(db_path, backup_path)\n return backup_path\n```\n\n**Why**: WAL checkpoint flushes pending writes before copying.\n", }; // Build the document list in the sidebar function buildDocList() { const list = document.getElementById('doc-list'); Object.keys(DOCS).sort().forEach(key => { const li = document.createElement('li'); const a = document.createElement('a'); a.textContent = key.replace(/-/g, ' '); a.onclick = () => showDoc(key); li.appendChild(a); list.appendChild(li); }); } function showDoc(key) { if (!DOCS[key]) { console.warn('Document not found:', key); return; } document.getElementById('home-panel').style.display = 'none'; document.getElementById('doc-panel').style.display = 'block'; const content = DOCS[key]; const html = markdownToHtml(content); document.getElementById('doc-content').innerHTML = html; // Update active nav item document.querySelectorAll('.file-list a').forEach(el => el.classList.remove('active')); const activeLink = Array.from(document.querySelectorAll('.file-list a')).find(a => a.textContent.replace(/ /g, '-') === key); if (activeLink) activeLink.classList.add('active'); } function markdownToHtml(md) { // Simple markdown to HTML converter let html = md .replace(/^### (.*?)$/gm, '

$1

') .replace(/^## (.*?)$/gm, '

$1

') .replace(/^# (.*?)$/gm, '

$1

') .replace(/^- (.*?)$/gm, '
  • $1
  • ') .replace(/(
  • .*?<\/li>)/s, '
      $1
    ') .replace(/\*\*(.*?)\*\*/g, '$1') .replace(/\*(.*?)\*/g, '$1') .replace(/`([^`]+)`/g, '$1') .replace(/\n\n+/g, '

    ') .replace(/^(?!<[^>]+>)/gm, '

    '); return '

    ' + html + '

    '; } buildDocList();