From 5c65eb5921ad2110f46bca7dac8de9f0066b240a Mon Sep 17 00:00:00 2001 From: Xeek <6032840+x3ek@users.noreply.github.com> Date: Sat, 21 Mar 2026 09:41:30 -0500 Subject: [PATCH 1/5] chore(dx): slim CLAUDE.md and reorganize into skills Reduce CLAUDE.md from 462 to 81 lines by moving domain-specific content into skills. Create dev-setup skill (local setup, scripts, config, deployment) and git skill (conventional commits, branch naming). Move git conventions out of github skill. Add content repo structure and frontmatter to theme-creator skill. Drop discoverable content (architecture, tech stack, repo structure, design decisions). Part of #73 Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/skills/dev-setup/SKILL.md | 154 ++++++++++ .claude/skills/git/SKILL.md | 45 +++ .claude/skills/github/SKILL.md | 18 +- .claude/skills/theme-creator/SKILL.md | 37 ++- CLAUDE.md | 411 ++------------------------ 5 files changed, 255 insertions(+), 410 deletions(-) create mode 100644 .claude/skills/dev-setup/SKILL.md create mode 100644 .claude/skills/git/SKILL.md diff --git a/.claude/skills/dev-setup/SKILL.md b/.claude/skills/dev-setup/SKILL.md new file mode 100644 index 0000000..9092bf5 --- /dev/null +++ b/.claude/skills/dev-setup/SKILL.md @@ -0,0 +1,154 @@ +--- +name: dev-setup +description: Development environment setup, scripts, configuration, and deployment for SquishMark +--- + +# Development Environment + +## Local Setup + +```bash +# Clone the repo +git clone https://github.com/xeek-dev/squishmark.git +cd squishmark + +# Create virtual environment +python -m venv .venv +source .venv/bin/activate # or .venv\Scripts\activate on Windows + +# Install dependencies +pip install -e ".[dev]" + +# Run locally +uvicorn squishmark.main:app --reload +``` + +## Development Scripts + +Three scripts in `scripts/` streamline local development: + +### `start-dev.py` — Dev server with multi-server management + +Starts a uvicorn dev server pointing at the local `content/` directory. Supports running multiple named server instances simultaneously (tracked in `.dev-servers.json`). The default instance name is the current git branch. + +```bash +# Basic usage +python scripts/start-dev.py # foreground on :8000 +python scripts/start-dev.py -b # background on :8000 +python scripts/start-dev.py -b --port 8001 # background on :8001 +python scripts/start-dev.py --name api -b # named background instance + +# Server management +python scripts/start-dev.py --list # show all tracked servers +python scripts/start-dev.py --stop # stop current branch's server +python scripts/start-dev.py --stop api # stop named server +python scripts/start-dev.py --stop 12345 # stop server by PID +python scripts/start-dev.py --stop-all # stop all servers +python scripts/start-dev.py --restart -b # restart in background + +# Other options +python scripts/start-dev.py --host 0.0.0.0 # bind to all interfaces +python scripts/start-dev.py --no-reload # disable auto-reload +``` + +> **Stale port gotcha:** Background start (`-b`) can silently fail if a stale process holds the port. The old process keeps serving while the new one exits. Always verify after restart: `--stop ` then `lsof -ti: | xargs kill` before starting fresh. + +### `run-checks.py` — Local CI checks + +Runs the same checks as CI: **ruff format**, **ruff check**, **pytest**, and **pyright**. By default runs all checks and reports a summary. + +```bash +python scripts/run-checks.py # run all checks +python scripts/run-checks.py --fail-fast # stop on first failure +python scripts/run-checks.py --docker # also run docker build (slow) +``` + +### `setup-worktree.py` — Git worktree management + +Creates isolated worktrees in `.worktrees/` for parallel development. Branch names follow the project's `type/issue-description` convention; the type prefix is stripped to form the directory name (e.g., `feat/42-dark-mode` becomes `.worktrees/42-dark-mode`). + +```bash +python scripts/setup-worktree.py feat/42-dark-mode # create worktree + branch +python scripts/setup-worktree.py feat/42-dark-mode --install # also pip install -e +python scripts/setup-worktree.py feat/42-dark-mode --with-content # also copy content/ +python scripts/setup-worktree.py feat/42-dark-mode --integration # --install + --with-content +python scripts/setup-worktree.py --list # list active worktrees +python scripts/setup-worktree.py --cleanup 42-dark-mode # remove worktree + branch +python scripts/setup-worktree.py --cleanup 42-dark-mode --force # skip confirmation +``` + +### `github-issue-updater.py` — Issue metadata updater + +Sets issue type (task/bug/feature), adds labels, and assigns milestones in a single command. Issue types require GraphQL mutations which this script handles automatically. + +```bash +python scripts/github-issue-updater.py 42 --type task +python scripts/github-issue-updater.py 42 --add-label "engine,themes" +python scripts/github-issue-updater.py 42 --milestone "SquishMark 1.0" +python scripts/github-issue-updater.py 42 --type task --add-label engine --milestone "SquishMark 1.0" +``` + +## Configuration + +### Environment Variables + +```bash +# Required +GITHUB_CONTENT_REPO=xeek-dev/xeek-dev-content +GITHUB_TOKEN=ghp_... # Only for private repos + +# Optional +CACHE_TTL_SECONDS=300 +DATABASE_URL=sqlite:///data/squishmark.db + +# GitHub OAuth (for admin features) +GITHUB_CLIENT_ID=... +GITHUB_CLIENT_SECRET=... +``` + +### config.yml (in content repo) + +```yaml +site: + title: "My Blog" + description: "A blog about things" + author: "Your Name" + url: "https://example.com" + favicon: "/static/user/custom-icon.png" # Optional: override auto-detected favicon + featured_max: 5 # Optional: max featured posts (default: 5) + +theme: + name: default + pygments_style: monokai + +posts: + per_page: 10 +``` + +## Deployment (Fly.io) + +```bash +# First time +fly launch + +# Create volume for SQLite +fly volumes create squishmark_data --size 1 + +# Set secrets +fly secrets set GITHUB_TOKEN=ghp_... +fly secrets set GITHUB_CLIENT_ID=... +fly secrets set GITHUB_CLIENT_SECRET=... + +# Deploy +fly deploy +``` + +## Common Tasks + +### Adding a new route +1. Create router in `src/squishmark/routers/` +2. Register in `main.py` +3. Add corresponding Jinja2 template if needed + +### Testing with a local content repo +Set `GITHUB_CONTENT_REPO` to a local path (prefixed with `file://`) for development without GitHub API calls. diff --git a/.claude/skills/git/SKILL.md b/.claude/skills/git/SKILL.md new file mode 100644 index 0000000..47babe4 --- /dev/null +++ b/.claude/skills/git/SKILL.md @@ -0,0 +1,45 @@ +--- +name: git +description: Git conventions — conventional commits, branch naming, commit message format +--- + +# Git Conventions + +## Conventional Commits + +Commit messages and PR titles **must** use [Conventional Commits](https://www.conventionalcommits.org/) format: + +``` +type(scope): description +``` + +Types: `feat`, `fix`, `refactor`, `chore`, `docs`, `test`, `ci` + +Scope is optional but recommended. Examples: +- `feat(terminal): add pixel art title renderer` +- `fix(docs): correct cache-busting instructions` +- `refactor(theme): extract loader into subpackage` +- `chore(dx): clean up permission settings` + +## Branch Naming + +``` +type/issue-description +``` + +Use the same type prefix as the anticipated merge commit. Examples: +- `feat/42-dark-mode` +- `fix/15-header-overflow` +- `refactor/16-theme-subpackage` +- `chore/23-update-dependencies` +- `docs/8-api-documentation` + +## Commit Messages + +- Use multiple `-m` flags instead of `$()` heredoc substitution, which triggers a command-substitution security prompt +- Example: `git commit -m "fix(engine): wire notes into rendering" -m "Detailed body here." -m "Co-Authored-By: ..."` + +## Issue Titles + +- Use plain English descriptions, **not** conventional commit format +- Conventional commit format is only for commit messages and PR titles diff --git a/.claude/skills/github/SKILL.md b/.claude/skills/github/SKILL.md index 36a444a..a93a10d 100644 --- a/.claude/skills/github/SKILL.md +++ b/.claude/skills/github/SKILL.md @@ -245,23 +245,7 @@ Your comment text here. *— Claude* ``` -### Conventional Commits - -Commit messages and PR titles must use [Conventional Commits](https://www.conventionalcommits.org/) format: - -``` -type(scope): description -``` - -Types: `feat`, `fix`, `refactor`, `chore`, `docs`, `test`, `ci` - -### Branch Naming - -``` -type/issue-description -``` - -Examples: `feat/42-dark-mode`, `fix/15-header-overflow`, `docs/20-github-skill` +For git conventions (conventional commits, branch naming), see the `git` skill. ## Auth Troubleshooting diff --git a/.claude/skills/theme-creator/SKILL.md b/.claude/skills/theme-creator/SKILL.md index aee69b1..b4bbe92 100644 --- a/.claude/skills/theme-creator/SKILL.md +++ b/.claude/skills/theme-creator/SKILL.md @@ -182,15 +182,50 @@ template: custom.html # Use custom.html instead of post.html --- ``` +## Content Repository Structure + +Users create a content repo with this structure. Theme authors need to understand this to know what data is available: + +``` +my-content-repo/ +├── posts/ +│ ├── 2026-01-01-hello-world.md +│ └── 2026-01-15-another-post.md +├── pages/ +│ └── about.md +├── static/ # User static files (favicon, images) +│ └── favicon.ico # Auto-detected and served at /favicon.ico +├── theme/ # Optional custom theme +│ └── ... +└── config.yml +``` + +### Frontmatter Format + +```yaml +--- +title: My Post Title +date: 2026-01-15 +tags: [python, blogging] +draft: false +featured: true # Optional: include in featured_posts template context +featured_order: 1 # Optional: sort order (lower = first, nulls last) +--- + +Post content in markdown... +``` + ## Pygments CSS +Pygments renders code blocks server-side — HTML comes pre-highlighted with no client-side JavaScript. Supports 500+ languages. + Generate syntax highlighting CSS for your theme: ```bash pygmentize -S -{% if theme.show_sidebar %} - -{% endif %} -``` - ## Live Reload When running `python scripts/start-dev.py`, debug mode is enabled automatically. This activates live reload — a small script is injected before `` that opens a WebSocket to `/dev/livereload`. Any change to files in `themes/` or the content repo's `theme/` directory triggers a full page reload in the browser. diff --git a/.claude/skills/theme-creator/references/filters.md b/.claude/skills/theme-creator/references/filters.md new file mode 100644 index 0000000..e982059 --- /dev/null +++ b/.claude/skills/theme-creator/references/filters.md @@ -0,0 +1,9 @@ +# Jinja2 Filters + +| Filter | Signature | Description | +|--------|-----------|-------------| +| `format_date` | `format_date(value, fmt="%B %d, %Y")` | Formats a date — e.g. `{{ post.date \| format_date }}` | +| `accent_first_word` | `accent_first_word(value)` | Wraps first word in `` | +| `accent_last_word` | `accent_last_word(value)` | Wraps last word in `` | + +All accent filters return `Markup` (safe HTML). diff --git a/.claude/skills/theme-creator/references/model-fields.md b/.claude/skills/theme-creator/references/model-fields.md new file mode 100644 index 0000000..8db0a69 --- /dev/null +++ b/.claude/skills/theme-creator/references/model-fields.md @@ -0,0 +1,56 @@ +# Model Fields + +## SiteConfig + +| Field | Type | Description | +|-------|------|-------------| +| `title` | `str` | Site title | +| `description` | `str` | Site description | +| `author` | `str` | Site author name | +| `url` | `str` | Site URL | +| `favicon` | `str \| None` | Custom favicon path override | +| `featured_max` | `int` | Max number of featured posts returned (default: 5) | + +## Post + +| Field | Type | Description | +|-------|------|-------------| +| `slug` | `str` | URL slug | +| `title` | `str` | Post title | +| `date` | `datetime.date \| None` | Publication date | +| `tags` | `list[str]` | Tag list | +| `description` | `str` | Short description / excerpt | +| `content` | `str` | Raw markdown content | +| `html` | `str` | Rendered HTML content | +| `draft` | `bool` | Draft flag | +| `featured` | `bool` | Whether the post is featured | +| `featured_order` | `int \| None` | Sort order within featured posts (lower = first, nulls last) | +| `template` | `str \| None` | Custom template override | +| `theme` | `str \| None` | Per-post theme override | +| `author` | `str \| None` | Post author (overrides site author) | +| `url` | `str` | Computed URL (property) — e.g. `/posts/my-post` | + +## Page + +| Field | Type | Description | +|-------|------|-------------| +| `slug` | `str` | URL slug | +| `title` | `str` | Page title | +| `content` | `str` | Raw markdown content | +| `html` | `str` | Rendered HTML content | +| `template` | `str \| None` | Custom template override | +| `theme` | `str \| None` | Per-page theme override | +| `url` | `str` | Computed URL (property) — e.g. `/about` | + +## Pagination + +| Field | Type | Description | +|-------|------|-------------| +| `page` | `int` | Current page number | +| `per_page` | `int` | Items per page | +| `total_items` | `int` | Total number of posts | +| `total_pages` | `int` | Total number of pages | +| `has_prev` | `bool` | Whether a previous page exists (property) | +| `has_next` | `bool` | Whether a next page exists (property) | +| `prev_page` | `int \| None` | Previous page number (property) | +| `next_page` | `int \| None` | Next page number (property) | diff --git a/.claude/skills/theme-creator/references/pygments.md b/.claude/skills/theme-creator/references/pygments.md new file mode 100644 index 0000000..735395f --- /dev/null +++ b/.claude/skills/theme-creator/references/pygments.md @@ -0,0 +1,15 @@ +# Pygments CSS + +Pygments renders code blocks server-side — HTML comes pre-highlighted with no client-side JavaScript. Supports 500+ languages. + +## Generate CSS + +```bash +pygmentize -S +{% if theme.show_sidebar %} + +{% endif %} +``` From 64bc6e9e11a215c203ef4f34382a4d23582c63ac Mon Sep 17 00:00:00 2001 From: Xeek <6032840+x3ek@users.noreply.github.com> Date: Sat, 21 Mar 2026 09:47:57 -0500 Subject: [PATCH 4/5] fix(dx): correct parent path index in check-env.py Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/skills/dev-setup/scripts/check-env.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/skills/dev-setup/scripts/check-env.py b/.claude/skills/dev-setup/scripts/check-env.py index 80caa40..4b150e3 100644 --- a/.claude/skills/dev-setup/scripts/check-env.py +++ b/.claude/skills/dev-setup/scripts/check-env.py @@ -16,7 +16,7 @@ def check(label: str, condition: bool, fix: str) -> bool: def main() -> None: - root = Path(__file__).resolve().parents[3] # .claude/skills/dev-setup/scripts -> repo root + root = Path(__file__).resolve().parents[4] # .claude/skills/dev-setup/scripts/check-env.py -> repo root os.chdir(root) print("Checking SquishMark dev environment...\n") From e54d914811ce77ee15f3363246a8044ea40cdd59 Mon Sep 17 00:00:00 2001 From: Xeek <6032840+x3ek@users.noreply.github.com> Date: Sat, 21 Mar 2026 10:08:39 -0500 Subject: [PATCH 5/5] fix(dx): correct PROJECT_ROOT path in moved setup-worktree.py Script moved from scripts/ to .claude/skills/git/scripts/ but PROJECT_ROOT still used parent.parent (2 levels). Now uses parents[4] to reach repo root from new location. Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/skills/git/scripts/setup-worktree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/skills/git/scripts/setup-worktree.py b/.claude/skills/git/scripts/setup-worktree.py index abcae62..9c9f06e 100644 --- a/.claude/skills/git/scripts/setup-worktree.py +++ b/.claude/skills/git/scripts/setup-worktree.py @@ -20,7 +20,7 @@ import sys from pathlib import Path -PROJECT_ROOT = Path(__file__).parent.parent.resolve() +PROJECT_ROOT = Path(__file__).resolve().parents[4] # .claude/skills/git/scripts/file.py -> repo root WORKTREES_DIR = PROJECT_ROOT / ".worktrees"