Add .claude skills and update .gitignore

Introduce six new .claude skill definitions for developer workflows: audit-subprocess, code-review, create-pr, db-inspect, deploy, and new-feature (each added under .claude/skills/). Update .gitignore to stop globally excluding the .claude directory (so skills can be tracked) and add ignores for generated artifacts and local settings: /.pr, /.reviews, and .claude/settings.local.json.
This commit is contained in:
Juan Denis committed 2026-03-03 00:31:21 -05:00
1 parent 683653d402
commit 023d3c40c5
7 files changed
+621 -4

No files matched your search

+158
View File
@@ -0,0 +1,158 @@
---
name: audit-subprocess
description: Scan backend services for subprocess bugs — missing sudo, missing exception handling on distro-specific commands, raw subprocess calls bypassing system utilities, and container/environment assumptions that break in LXC or restricted environments. Reports issues with file, line, and fix.
argument-hint: "[file-or-directory]"
---
Audit subprocess calls in ServerKit backend services for recurring bug patterns.
Scope: **${ARGUMENTS:-backend/app/services/}**
## What to Scan For
### Pattern 1: Missing `sudo` on privileged commands
Search for `subprocess.run()` calls that invoke commands requiring root but lack `sudo`:
- **`systemctl`** (start, stop, restart, enable, disable, daemon-reload) — ALL require sudo
- **`firewall-cmd`** (--state, --get-default-zone, --list-services, --list-ports, --list-rich-rules, --get-zones, --zone=*, --add-*, --remove-*, --reload) — ALL require sudo
- **`ufw`** (status, enable, disable, allow, deny, delete) — ALL require sudo
- **`iptables`** / **`ip6tables`** — ALL require sudo
- **`nginx`** (-s reload, -t) — ALL require sudo
- **`certbot`** — ALL require sudo
- **`freshclam`** — requires sudo
For each match, check if the command list starts with `'sudo'`. If not, flag it.
**Ignore**: Commands that legitimately don't need sudo (git, python, pip, npm, node, which, cat, ls, grep, df, free, uptime, whoami, hostname, lsb_release).
### Pattern 2: Missing exception handling on distro-specific commands
Search for `subprocess.run()` calls that use distro-specific package managers without `try/except FileNotFoundError`:
- **`dpkg`** — Debian/Ubuntu only, doesn't exist on RHEL/Fedora/CentOS
- **`apt`** / **`apt-get`** — Debian/Ubuntu only
- **`rpm`** — RHEL family only, doesn't exist on Debian
- **`dnf`** / **`yum`** — RHEL family only
For each match, check if it's wrapped in a `try/except` that catches `FileNotFoundError` (or a broad `Exception` on the immediate call). If not, flag it.
**Acceptable**: If the call is guarded by `os.path.exists('/usr/bin/<cmd>')` before the subprocess call, that's fine — don't flag it.
### Pattern 3: Raw subprocess calls that should use system utilities
The project provides centralized utilities in `app/utils/system.py`. Flag direct subprocess calls that bypass them:
- **`subprocess.run(['sudo', 'systemctl', ...])`** → should use `ServiceControl.start/stop/restart/reload/enable/disable/daemon_reload()`
- **`subprocess.run(['systemctl', 'is-active', ...])`** → should use `ServiceControl.is_active()`
- **`subprocess.run(['systemctl', 'is-enabled', ...])`** → should use `ServiceControl.is_enabled()`
- **`subprocess.run(['dpkg', ...])`** or **`subprocess.run(['rpm', ...])`** for package checks → should use `PackageManager.is_installed()`
- **`os.path.exists('/usr/bin/apt')`** or similar distro detection → should use `PackageManager.detect()`
- **`for path in ['/usr/bin/X', '/usr/sbin/X']: if os.path.exists(path)`** binary search → should use `is_command_available()`
- **`subprocess.run(['sudo', 'apt-get', 'install', ...])`** or **`['sudo', 'dnf', 'install', ...]`** → should use `PackageManager.install()`
- **`subprocess.run(['sudo', <privileged-cmd>, ...])`** for other privileged commands → should use `run_privileged()`
**Acceptable exceptions** (do NOT flag these):
- Docker CLI calls in `docker_service.py` (uses Docker socket)
- Git CLI calls (no privilege needed)
- Python/pip in venvs (per-app, no root)
- MySQL/PostgreSQL CLI (auth via socket/password)
- WP-CLI (`sudo -u www-data` pattern)
- Read-only commands (`clamscan`, `fail2ban-client status`, `uname`, `which`, `ssh-keygen`, etc.)
### Pattern 4: Container/environment assumptions
Code that assumes bare-metal or KVM and breaks inside LXC containers, Docker, or restricted environments. Search for these patterns:
#### 4a. Unguarded `/proc` and `/sys` reads
- **`open('/proc/cpuinfo')`** or similar `/proc/` reads without `try/except (FileNotFoundError, PermissionError)`
- **`open('/sys/class/...')`** or `/sys/` reads — often restricted in unprivileged containers
- **`/proc/meminfo`**, **`/proc/net/`**, **`/proc/diskstats`** — may be empty or permission-denied
Flag if the read is NOT wrapped in a try/except that handles `FileNotFoundError` or `PermissionError`.
#### 4b. `psutil` calls that fail in containers
- **`psutil.disk_io_counters()`** — returns `None` when `/proc/diskstats` is unavailable
- **`psutil.disk_partitions()`** — returns container-internal mounts, not host disks
- **`psutil.sensors_temperatures()`** — fails without `/sys/class/thermal/`
- **`psutil.sensors_fans()`** — same issue
- **`psutil.net_io_counters(pernic=True)`** — may show veth interfaces only
Flag if the return value is used without a `None` / empty check. For example, `psutil.disk_io_counters().read_bytes` will crash if the call returns `None`.
#### 4c. Docker socket assumptions
- **`subprocess.run(['docker', ...])`** without checking if Docker is installed/running first
- Direct access to **`/var/run/docker.sock`** without verifying the socket exists
- Any Docker operation that doesn't handle `FileNotFoundError` or `ConnectionError`
Flag if there's no pre-check (e.g., `is_docker_installed()`) or no try/except around the call.
**Acceptable**: Calls inside `docker_service.py` that are already gated behind `is_docker_installed()` at the API layer.
#### 4d. Firewall/network commands without capability checks
- **`firewall-cmd`**, **`ufw`**, **`iptables`** — require `CAP_NET_ADMIN` which is dropped in unprivileged LXC
- These commands fail silently or with cryptic errors when capabilities are missing
Flag if the subprocess call doesn't handle the failure case (no try/except, or no check of `returncode`).
#### 4e. Hardcoded device paths
- **`/dev/`** references (e.g., `/dev/sda`, `/dev/null` is fine) — block devices don't exist in containers
- **`/dev/fuse`** — FUSE device, unavailable in unprivileged LXC
Flag hardcoded `/dev/` paths (except `/dev/null`, `/dev/stdin`, `/dev/stdout`, `/dev/stderr`, `/dev/urandom`, `/dev/random`).
**Acceptable**: Paths discovered dynamically from `psutil` or `lsblk` output.
## How to Audit
1. Use **Grep** to find all `subprocess.run(` calls in the target scope
2. For each match, read the surrounding context (5-10 lines) to check:
- Is `sudo` present for privileged commands?
- Is there a `try/except FileNotFoundError` for distro-specific commands?
- Is there an `os.path.exists()` guard?
- Is there a system utility (`ServiceControl`, `PackageManager`, `run_privileged`, `is_command_available`) that should be used instead?
3. Also search for `os.path.exists('/usr/bin/apt')` and similar distro-detection patterns
4. Search for `open('/proc/` and `open('/sys/` — check for missing error handling
5. Search for `psutil.disk_io_counters`, `psutil.disk_partitions`, `psutil.sensors_` — check return values are guarded against `None`
6. Search for `/dev/` string literals — flag hardcoded device paths (except standard ones)
7. Search for `docker` subprocess calls outside `docker_service.py` — check for availability guards
8. For firewall commands, check that `returncode` is inspected or the call is wrapped in try/except
9. Collect all violations
## Report Format
For each issue found, report:
```
[PATTERN] file_path:line_number
Command: ['systemctl', 'restart', ...]
Fix: Add 'sudo' as first element
```
or for Pattern 3:
```
[PATTERN 3] file_path:line_number
Command: subprocess.run(['sudo', 'systemctl', 'restart', service], ...)
Fix: Use ServiceControl.restart(service)
```
Then provide a summary:
```
## Summary
- Files scanned: X
- Total subprocess calls: X
- Missing sudo: X instances across Y files
- Missing exception handling: X instances across Y files
- Should use system utilities: X instances across Y files
- Container/environment assumptions: X instances across Y files
- Clean: X calls
```
If issues are found, ask the user if they want you to fix them automatically.
If no issues are found, confirm the codebase is clean for these patterns.
+165
View File
@@ -0,0 +1,165 @@
---
name: code-review
description: Run a multi-perspective code review on recent changes using specialized reviewer personas. Each reviewer focuses on their domain — security, ops, cross-platform, API design, frontend, and performance. Produces a dated review report.
argument-hint: "[file, directory, or git range]"
---
# Code Review — Multi-Perspective Audit
Review code from the perspective of 6 specialized reviewers, each with their own focus area. Produces a structured report in `.reviews/`.
## Scope
Determine what to review based on the argument:
- **No argument**: Review all uncommitted changes (`git diff` + `git diff --cached`)
- **A file or directory path**: Review that specific path
- **A git range** (e.g., `main..HEAD`, `HEAD~3`): Review that commit range
- **`last`**: Review the last commit (`HEAD~1..HEAD`)
## The Reviewers
### Mara — Security
Focuses on authentication, authorization, and injection vectors.
- JWT handling: token validation, expiry, refresh flows
- SQL injection: raw queries, unparameterized input
- XSS: unsanitized user input rendered in responses or frontend
- CORS misconfigurations
- Path traversal in file operations (`../` in user-supplied paths)
- Secrets in code (API keys, passwords, hardcoded tokens)
- Missing `@jwt_required()` on API routes that need auth
- CSRF protection gaps
### Raj — Ops & Infrastructure
Focuses on subprocess calls, privilege escalation, and system operations.
- Missing `sudo` on privileged commands (systemctl, firewall-cmd, ufw, certbot, nginx)
- Raw `subprocess.run()` that should use `run_privileged()`, `ServiceControl`, or `PackageManager`
- Missing error handling on subprocess calls (unchecked `returncode`, no try/except)
- Hardcoded paths that assume specific filesystem layout
- Logging gaps: operations that modify system state without logging what they did
- Service restart ordering issues
### Sol — Cross-Platform & Containers
Focuses on portability across distros and container environments.
- Distro-specific commands (`dpkg`, `apt`, `rpm`, `dnf`) without `FileNotFoundError` handling
- Unguarded `/proc/` and `/sys/` reads that fail in LXC/containers
- `psutil` calls that return `None` in containers (`disk_io_counters`, `sensors_temperatures`) used without null checks
- Docker socket assumptions without availability checks
- Firewall commands that need `CAP_NET_ADMIN` (dropped in unprivileged LXC)
- Hardcoded `/dev/` device paths
- Assumptions about init system (systemd vs others)
- Package manager detection that only checks one family
### Kai — API Design
Focuses on REST conventions, error handling, and request/response patterns.
- Inconsistent error response format (should always be `{'error': 'message'}, status_code`)
- Missing input validation on request body fields
- Wrong HTTP status codes (e.g., 200 on error, 404 when it should be 403)
- Missing pagination on list endpoints
- Endpoints that return too much data (no field filtering)
- Inconsistent URL naming (`/api/v1/` prefix, plural nouns)
- Missing rate limiting on sensitive endpoints (login, password reset)
- N+1 query patterns in endpoints that return lists
### Lena — Frontend
Focuses on React patterns, UX quality, and style consistency.
- Class components or non-hook patterns (should be functional + hooks only)
- Inline styles (should use LESS with design system variables)
- Missing loading states or error handling on data fetches
- Accessibility gaps: missing alt text, aria labels, keyboard navigation
- Hardcoded strings that should come from the API or config
- Memory leaks: missing cleanup in `useEffect`
- Props drilling beyond 3 levels (should use Context)
- Missing key props in lists
- Console.log left in production code
### Omar — Performance
Focuses on efficiency, caching, and resource usage.
- Synchronous blocking calls that should be async (especially subprocess calls in request handlers)
- Missing database indexes on frequently queried columns
- Unbounded queries: `SELECT *` without `LIMIT` or pagination
- Repeated identical subprocess calls that could be cached
- Large file reads loaded entirely into memory
- Frontend: unnecessary re-renders, missing `useMemo`/`useCallback` where expensive
- Missing `timeout` on subprocess calls or external HTTP requests
- Socket.IO events that broadcast too frequently
## How to Review
1. Determine the scope from `$ARGUMENTS` and read all relevant code
2. For each reviewer, analyze the code **only through their lens** — don't overlap
3. Rate each finding:
- **Fix** — Bug, vulnerability, or will break in production. Must address.
- **Improve** — Works but suboptimal. Should address.
- **Note** — Observation or suggestion. Nice to know, no action needed.
4. Collect all findings
## Report
Create the `.reviews/` directory if it doesn't exist. Write to `.reviews/YYYY-MM-DD-review.md` (use today's date). If a file for today exists, append a counter: `YYYY-MM-DD-2-review.md`.
```markdown
# Code Review — YYYY-MM-DD
**Scope:** <what was reviewed>
**Files reviewed:** N
## Summary
| Reviewer | Focus | Fix | Improve | Note |
|----------|-------|-----|---------|------|
| Mara | Security | N | N | N |
| Raj | Ops | N | N | N |
| Sol | Cross-Platform | N | N | N |
| Kai | API Design | N | N | N |
| Lena | Frontend | N | N | N |
| Omar | Performance | N | N | N |
| **Total** | | **N** | **N** | **N** |
## Findings
### Mara — Security
#### [Fix] Short description
`file_path:line_number`
Explanation of the issue and why it matters.
**Suggested fix:** What to change.
#### [Improve] Short description
...
### Raj — Ops & Infrastructure
...
### Sol — Cross-Platform & Containers
...
### Kai — API Design
...
### Lena — Frontend
...
### Omar — Performance
...
## Verdict
**PASS** — No Fix-severity findings. Ship it.
or
**NEEDS WORK** — N Fix-severity findings must be addressed before merging.
```
After writing the report, print the summary table and verdict to the user. If there are Fix-severity findings, ask if they want you to address them now.
+110
View File
@@ -0,0 +1,110 @@
---
name: create-pr
description: Generate a pull request title and description from the current branch's commits. Produces a concise summary, optional feature highlights, and collapsible technical details.
---
# Create PR Description
Generate a pull request title and description that's scannable, informative, and has just enough personality to feel human.
## Instructions
### 1. Gather context (do ALL of these)
Run these commands to build a complete picture before writing anything:
```bash
# Commit overview
git log main..HEAD --oneline --stat
# Full diff stat for file-level scope
git diff main..HEAD --stat
# Actual code changes — read the diff, don't just skim filenames
git diff main..HEAD
```
If the full diff is too large, diff individual areas (backend routes, frontend, storage, etc.) in batches. You must understand **what the code actually does**, not just which files were touched.
### 2. Write the PR file
Write the file to `.pr/YYYY-MM-DD.md` (using today's date). Create the `.pr/` directory if it doesn't exist. If a file for today's date already exists, append a counter: `YYYY-MM-DD-2.md`, `YYYY-MM-DD-3.md`, etc.
The structure depends on whether the PR introduces user-facing features or is purely internal (refactors, bug fixes, infra).
#### When the PR has user-facing features:
~~~markdown
# <Title>
<2-3 sentence summary>
### Highlights
- Highlight 1
- Highlight 2
- ...
<details>
<summary>Technical changes</summary>
- Detail 1
- Detail 2
- ...
</details>
~~~
#### When the PR is purely internal (no user-facing features):
~~~markdown
# <Title>
<2-3 sentence summary>
<details>
<summary>Technical changes</summary>
- Detail 1
- Detail 2
- ...
</details>
~~~
Omit the Highlights section entirely for internal-only PRs — don't force it.
### Style Rules
#### Title
- Imperative mood, start with a verb (Add, Fix, Refactor, etc.)
- Summarize the entire PR scope — not just one commit
#### Summary
- **2-3 sentences max.** This is the elevator pitch, not the full story.
- **Open with a touch of personality.** One line that makes the reader smile — a wry observation, a lighthearted remark, a playful metaphor. Not forced, just human. Examples of the energy (don't copy these literally, invent your own each time):
- "This one's mostly about cleaning house."
- "Turns out the type checker was right to complain."
- A playful metaphor about what the code was doing wrong
- A wry observation about the state of things before this PR
- **Then say what the PR does at a high level.** Name the main change areas (new feature, refactor target, bug fixed) but don't enumerate every file. The personality is in *how* you describe the changes, not in being vague.
- **Do not repeat what Highlights or Technical changes already cover.** The summary is the "why" and the big picture; details live below.
#### Highlights (only when applicable)
- One bullet per user-facing feature, behavior change, or notable improvement.
- Write from the user's perspective — what they'll notice, not internal implementation.
- Plain language, no code references. "Schedules now respect your configured timezone" not "`SchedulerService` gains a `timezone` attribute".
- 3-7 bullets is the sweet spot. If you can only think of 1-2, fold them into the summary and skip this section.
#### Technical changes (inside the accordion)
- One bullet per discrete change. Be specific — name files, classes, functions, patterns.
- Format: `backtick code references` for identifiers, plain text for descriptions.
- Every meaningful change in the diff must have a bullet. If a change touches security (CORS, auth, SQL injection), error handling, accessibility, or concurrency, it gets its own bullet — do not bury these.
- Bullets should describe the mechanism, not just the intent. "Race condition in `get_or_create_chat` fixed by moving creation inside the lookup session" is good. "Fix database issues" is not.
- Group related changes together (all typing fixes, all security hardening, all API changes, etc.)
#### General
- **No test plan section.** Do not include "Test plan" or "Testing".
- **No mention of tests.** Do not reference test files, test results, or testing.
- **No emoji.**
- **No "Generated by" footer.**
+52
View File
@@ -0,0 +1,52 @@
---
name: db-inspect
description: Inspect the ServerKit SQLite database — show tables, schemas, row counts, and run queries. Runs via WSL for local dev or SSH MCP for production. Use when debugging data issues or understanding the current database state.
disable-model-invocation: true
argument-hint: "[query or table-name]"
allowed-tools: Bash, mcp__ssh-mcp__exec
---
Inspect the ServerKit database. Argument: **$ARGUMENTS**
## Database Location
- **Local dev**: `backend/instance/serverkit.db` (relative to project root)
- **Production**: `/opt/serverkit/backend/instance/serverkit.db` (access via SSH MCP)
## If no argument or argument is "schema"
Show the full database schema:
```bash
sqlite3 backend/instance/serverkit.db '.schema'
```
And row counts for all tables:
```bash
sqlite3 backend/instance/serverkit.db "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;"
```
Then for each table, get the row count and present as a formatted table: Table Name | Row Count
## If argument is a table name
Show the schema and last 10 rows:
```bash
sqlite3 -header -column backend/instance/serverkit.db 'PRAGMA table_info(<table>);'
sqlite3 -header -column backend/instance/serverkit.db 'SELECT * FROM <table> ORDER BY id DESC LIMIT 10;'
```
## If argument looks like a SQL query
Run it directly:
```bash
sqlite3 -header -column backend/instance/serverkit.db '<query>'
```
## If argument is "production" or "prod"
Run the schema/overview commands on the production server via SSH MCP instead, using `/opt/serverkit/backend/instance/serverkit.db`.
## Safety
- NEVER run DELETE, DROP, UPDATE, or INSERT queries unless explicitly asked
- Always show the query before running it
- For production, double-check with the user before running any query
+61
View File
@@ -0,0 +1,61 @@
---
name: deploy
description: Deploy ServerKit to the production server via SSH. Runs update, rebuilds frontend, restarts services, and performs health checks. Use when pushing changes to production.
disable-model-invocation: true
argument-hint: "[branch]"
allowed-tools: mcp__ssh-mcp__exec, mcp__ssh-mcp__sudo-exec, Bash
---
Deploy ServerKit to the production server. Target branch: **${ARGUMENTS:-main}**
## Pre-deploy Checks
1. Check the current git status locally — warn if there are uncommitted changes
2. Check if the local branch is ahead of remote — warn if unpushed commits exist
## Deploy Steps
Run these on the remote server via SSH MCP:
### 1. Check current state
```
serverkit version
serverkit status
```
### 2. Run the update
```
serverkit update --branch ${ARGUMENTS:-main}
```
This will:
- Stop services
- Pull latest code from the target branch
- Update Python dependencies
- Sync app templates
- Rebuild frontend Docker container
- Restart all services
### 3. Health check (wait 10 seconds after update)
```
curl -s http://127.0.0.1:5000/api/v1/system/health
curl -s http://localhost
serverkit status
```
### 4. Report results
Report clearly:
- Previous version vs new version
- Which branch is deployed
- Backend status (systemd)
- Frontend status (Docker)
- API health check result
- Any errors from logs: `journalctl -u serverkit -n 20 --no-pager`
## On Failure
If any health check fails:
1. Show the last 50 lines of backend logs: `journalctl -u serverkit -n 50 --no-pager`
2. Show frontend Docker logs: `cd /opt/serverkit && docker compose logs --tail=50`
3. Suggest rollback: `serverkit update --branch main --force`
+72
View File
@@ -0,0 +1,72 @@
---
name: new-feature
description: Scaffold a new full-stack feature for ServerKit with Flask backend (model, service, API blueprint) and React frontend (page, styles, route). Use when creating new modules, pages, or API endpoints.
disable-model-invocation: true
argument-hint: "[feature-name]"
---
Scaffold a new full-stack feature named **$ARGUMENTS** for ServerKit.
Follow these steps in order:
## 1. Backend Model
Create `backend/app/models/$ARGUMENTS.py` with:
- SQLAlchemy model class (PascalCase name)
- Standard columns: `id`, `created_at`, `updated_at`
- A `to_dict()` method returning JSON-serializable data
- Follow existing model patterns in `backend/app/models/`
## 2. Backend Service
Create `backend/app/services/${ARGUMENTS}_service.py` with:
- Stateless module functions (no classes unless stateful)
- CRUD operations: `create_*`, `get_*`, `get_all_*`, `update_*`, `delete_*`
- Consistent error handling returning `{'error': 'message'}, status_code`
- Import and use the model from step 1
## 3. Backend API Blueprint
Create `backend/app/api/$ARGUMENTS.py` with:
- Flask Blueprint named `${ARGUMENTS}_bp`
- RESTful routes prefixed with the feature name
- All routes decorated with `@jwt_required()`
- JSON request parsing and response formatting
- Call service functions from step 2
Then register the blueprint in `backend/app/__init__.py`:
```python
from app.api.$ARGUMENTS import ${ARGUMENTS}_bp
app.register_blueprint(${ARGUMENTS}_bp, url_prefix='/api/v1/$ARGUMENTS')
```
## 4. Frontend API Methods
Add methods to `frontend/src/services/api.js` in the `ApiService` class:
- `get$ARGUMENTS()`, `create$ARGUMENTS()`, `update$ARGUMENTS()`, `delete$ARGUMENTS()`
- Follow the existing pattern of other API methods in that file
## 5. Frontend Page
Create `frontend/src/pages/$ARGUMENTS.jsx` with:
- Functional component using React hooks
- Fetch data on mount using the API methods from step 4
- Basic CRUD UI with table/list and form
- Loading states and error handling
- Import the LESS stylesheet from step 6
Then add the route in `frontend/src/App.jsx` inside the authenticated routes.
## 6. Frontend Styles
Create `frontend/src/styles/pages/$ARGUMENTS.less` with:
- BEM-like naming: `.${ARGUMENTS}__element--modifier`
- Use existing design system variables (`@card-bg`, `@primary-color`, `@spacing-md`, etc.)
- Import the new file in `frontend/src/styles/main.less`
## Rules
- Python: PEP 8, type hints where helpful
- React: Functional components with hooks only, PascalCase filenames
- LESS: No inline styles, use design system variables
- One logical change per commit
+3 -4
View File
@@ -36,10 +36,6 @@
Thumbs.db
nul
# Claude Code
/.claude/
/.claude_/
# Temporary files
*.tmp
*.temp
@@ -62,3 +58,6 @@ new_templates/
backend/dev-data/
/.vscode
/.vscode1
/.pr
/.reviews
/.claude/settings.local.json