Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions .claude/skills/dev-setup/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
---
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

Two user-facing scripts in `scripts/` and two agent-facing scripts in skill directories:

### Environment Check

Run the readiness check before starting work:

```bash
python .claude/skills/dev-setup/scripts/check-env.py
```

### User-Facing Scripts (`scripts/`)

### `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 <name>` then `lsof -ti:<port> | 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)
```

### Agent-Facing Scripts (in skill directories)

#### `setup-worktree.py` — Git worktree management (in `git` skill)

Creates isolated worktrees in `.worktrees/` for parallel development.

```bash
python .claude/skills/git/scripts/setup-worktree.py feat/42-dark-mode # create worktree + branch
python .claude/skills/git/scripts/setup-worktree.py feat/42-dark-mode --install # also pip install -e
python .claude/skills/git/scripts/setup-worktree.py feat/42-dark-mode --with-content # also copy content/
python .claude/skills/git/scripts/setup-worktree.py feat/42-dark-mode --integration # --install + --with-content
python .claude/skills/git/scripts/setup-worktree.py --list # list active worktrees
python .claude/skills/git/scripts/setup-worktree.py --cleanup 42-dark-mode # remove worktree + branch
```

#### `github-issue-updater.py` — Issue metadata updater (in `github` skill)

Sets issue type (task/bug/feature), adds labels, and assigns milestones in a single command.

```bash
python .claude/skills/github/scripts/github-issue-updater.py 42 --type task
python .claude/skills/github/scripts/github-issue-updater.py 42 --add-label "engine,themes"
python .claude/skills/github/scripts/github-issue-updater.py 42 --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.
89 changes: 89 additions & 0 deletions .claude/skills/dev-setup/scripts/check-env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env python3
"""Check that the SquishMark development environment is ready."""

import os
import sys
from pathlib import Path


def check(label: str, condition: bool, fix: str) -> bool:
if condition:
print(f" OK {label}")
else:
print(f" FAIL {label}")
print(f" Fix: {fix}")
return condition


def main() -> None:
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")
checks = []

# venv exists
venv = root / ".venv"
checks.append(
check(
"Virtual environment exists",
venv.is_dir(),
"python -m venv .venv",
)
)

# venv is active
checks.append(
check(
"Virtual environment is active",
sys.prefix != sys.base_prefix,
"source .venv/bin/activate",
)
)

# package is importable
try:
import squishmark # noqa: F401

importable = True
except ImportError:
importable = False
checks.append(
check(
"squishmark package installed",
importable,
'pip install -e ".[dev]"',
)
)

# content directory
checks.append(
check(
"content/ directory exists",
(root / "content").is_dir(),
"Create a content/ directory with posts/, pages/, and config.yml",
)
)

# data directory
data_dir = root / "data"
checks.append(
check(
"data/ directory exists",
data_dir.is_dir(),
"mkdir data",
)
)

print()
passed = sum(checks)
total = len(checks)
if passed == total:
print(f"All {total} checks passed.")
else:
print(f"{passed}/{total} checks passed.")
sys.exit(1)


if __name__ == "__main__":
main()
45 changes: 45 additions & 0 deletions .claude/skills/git/SKILL.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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"


Expand Down
Loading
Loading