russell.ballestrini.net/CLAUDE.md

15 KiB
Raw Blame History

Claude Memory and Instructions

CRITICAL: ALWAYS CHECK FOR CLAUDE.MD FILES

BEFORE working on ANY repository, ALWAYS check for CLAUDE.md files in this order:

Priority 1: Cross-Repository CLAUDE.md (Global)

  1. Check for global CLAUDE.md: Look for /home/fox/CLAUDE.md or ~/CLAUDE.md
  2. Read & follow global instructions: These apply across ALL repositories on localhost
  3. Global instructions are baseline: These set the foundation for all work

Priority 2: Repository-Specific CLAUDE.md (Local)

  1. Check for repo CLAUDE.md: Use Read to check if ./CLAUDE.md exists in the repository root
  2. Read & follow repo instructions: If found, read the entire CLAUDE.md file & follow all instructions
  3. Repo-specific overrides global: Repository-specific CLAUDE.md instructions override or extend global guidelines
  4. Check README too: Also read README, README.md, or README.rst to understand the project

Priority hierarchy:

  • Repository-specific CLAUDE.md (highest priority)
  • Cross-repository CLAUDE.md (medium priority)
  • This file's instructions (baseline for this specific repo)

Examples of what to look for:

  • Build & test procedures
  • Commit message formats
  • Code style preferences
  • Special workflows or requirements
  • Project-specific constraints
  • Cross-repo conventions & standards

Read README.rst to understand the build system.

you are acting on behalf of russell ballestrini. don't fuck up.

CRITICAL PELICAN TESTING WORKFLOW

BEFORE ANY COMMIT:

  1. ALWAYS test Pelican build: make clean && make html && make formats
  2. Check for build errors: Pelican will fail on RST syntax errors, missing files, etc.
  3. Verify output: make serve and check localhost:8000
  4. NEVER commit without testing the build

PELICAN BUILD PROCESS:

  • Uses Pelican static site generator (Python-based)
  • Converts .rst/.md files to HTML
  • RST syntax errors break the build
  • Missing images/files break the build
  • Invalid slugs/filenames cause issues

PREFERRED BUILD COMMAND:

make clean && make html && make formats

This generates:

  • HTML output from RST/MD sources
  • Markdown versions (index.md) via pandoc
  • Plain text versions (index.txt)
  • JSON Feed (feeds/all.json)
  • JSON Blog (blog.json) with Markdown content
  • AI agent onboarding (llms.txt)

PROPER DEVELOPMENT WORKFLOW:

# Setup virtual environment (once)
make venv

# Test changes before commit (PREFERRED BUILD)
make clean && make html && make formats

# Optional: serve locally to verify
make serve

# Check localhost:8000 for issues
# Only commit if build succeeds
git add specific-files
git commit -m "message"
git push

CRITICAL CONTENT DIRECTORY ISSUE

PROBLEM: Python virtual environments exist in content/uploads/2024/battleship-solvers/env/ and env2/

  • These contain thousands of Python package files (.md, .rst files)
  • Pelican tries to process them as blog content
  • Causes massive build warnings and errors
  • Should NOT be in content directory

SOLUTION NEEDED:

  • Remove env/ and env2/ directories from content/uploads/
  • Add env*/ to .gitignore patterns
  • Keep only actual blog assets in content/uploads/

CRITICAL GIT SAFETY LESSON

DEVASTATING MISTAKE COMMITTED:

I made a catastrophic git error that destroyed user's work in progress:

  1. What I did wrong: Used git add . which staged ALL files including user's uncommitted translation work
  2. The fatal mistake: When trying to fix the staging issue, I used git reset HEAD . which overwrote the working directory files with staged versions
  3. Result: User's uncommitted translation work was completely lost/overwritten

PROPER APPROACH:

  • ALWAYS check git status and git diff first to see what uncommitted work exists
  • NEVER use git add . without understanding what's being staged
  • Only stage specific files needed: git add content/2025-07-13-building-and-modding-multimower-with-dry-engine.rst
  • If I need to unstage, use git restore --staged <file> for specific files, NOT git reset HEAD .

KEY LESSON:

Git operations can be destructive to uncommitted work. Always preserve user's work in progress before any git commands.

This was a serious failure that cost the user their translation work. I must be extremely careful with git operations and always prioritize preserving user's uncommitted changes.

PUSH WHEN READY

When fox says to push or the work looks complete, commit & push without asking. Don't ask "want me to push?" or "want me to commit?" — just do it. Fox will review the diff in GitLab if needed.

COMMIT MESSAGE POLICY

NEVER add attribution text to commit messages

  • Do not include "🤖 Generated with Claude Code"
  • Do not include "Co-Authored-By: Claude noreply@anthropic.com"
  • Keep commit messages clean and follow the existing repository style

HELPER SCRIPTS AND TEMPORARY FILES

NEVER commit helper scripts or temporary files unless explicitly requested

  • Helper scripts (like append_toc.py, test.py, etc.) are for temporary use only
  • Only commit helper scripts if the user explicitly says to commit them
  • After using a helper script, delete it from the working directory
  • Do not include helper scripts in git commits unless specifically instructed
  • Focus commits on actual content changes, not the tools used to make them

WRITING STYLE

Use "&" instead of "and" most of the time in posts

  • Prefer concise ampersand (&) for connecting words & phrases
  • Example: "disrupt wheels & foster open collaboration" not "disrupt wheels and foster open collaboration"
  • This creates a more casual, punchy writing style that matches the brand voice

Grammar: Avoid dashes (em-dashes —, en-dashes , hyphens used as dashes)

  • Do not use em-dashes or en-dashes in any writing (posts, cover letters, documentation)
  • Hyphens only for compound words (e.g., "open-source"), never as sentence punctuation
  • Prefer full stop periods. Split into two sentences instead of connecting with dashes
  • Commas work too, but periods stay preferred
  • Only use a dash if it truly makes more sense than a period or comma

Grammar: Avoid "the" in posts

  • Never use "the" when "a" works instead
  • Prefer dropping articles entirely when a sentence reads clean without one
  • Example: "a permacomputer" not "the permacomputer"
  • Example: "angle brackets. closing tags. verbosity." not "the angle brackets. the closing tags. the verbosity."
  • Restructure sentences to eliminate "the" rather than forcing awkward substitutions

Grammar: Avoid verbs "to be" (is, are, was, were, be, been, being)

  • Prefer active verbs over passive or linking constructions
  • Example: "a permacomputer outlasts its builders" not "a permacomputer is something that outlasts its builders"
  • Example: "Jason lives everywhere" not "Jason is everywhere"
  • Example: "TOML fits like a studio apartment" not "TOML is a studio apartment"
  • Contractions count: "it's" = "it is", "he's" = "he is", "that's" = "that is"
  • Exception: quoted code output, error messages, & JSON values stay verbatim

JOB SEARCH PREFERENCES

Russell ONLY works remote. Do not apply to or write cover letters for positions that are not fully remote. If a job posting says hybrid, on-site, or in-office, skip it.

Russell uses & administers Matrix channels on matrix.org. Relevant for DevRel, community, & communication-focused roles.

media.unturf.com - Russell's media gallery (dark theme, black & white aesthetic). Include in cover letter contact links when it strengthens the application (e.g., content/media/DevRel roles).

JSONRESUME POLICY

NEVER include phone number in JSONResume files - they're public, use email only.

COVER LETTER FORMAT

Location: content/pages/cover-letters/

Naming: YYYY-MM-DD-company-role-russell-ballestrini.rst

Generate PDF: make cover-letters (uses rst2pdf with style file)

RST structure (match resume format):

Russell Ballestrini
===================

.. class:: center

**Role Title | Company Name**

.. class:: center

russell@unturf.com | `russell.ballestrini.net <https://russell.ballestrini.net>`_ | `GitHub <https://github.com/russellballestrini>`_ | `Media <https://media.unturf.com>`_

----

To the [Company] team:

[Body of letter...]

|

Russell Ballestrini

Key formatting rules:

  • Name as RST title (=== underline)
  • Use .. class:: center for role & contact info
  • Horizontal rule (----) separates header from body
  • No Pelican metadata in PDF-only letters (or use RST comments to hide)
  • Use | for vertical spacing before signature
  • Links use RST inline syntax: ```link text `_``

BLOG POST TAGS

Check existing tags before writing posts:

venv/bin/python lib/tag_stats.py       # all tags with counts
venv/bin/python lib/tag_stats.py 5     # only tags with 5+ posts

Core tags (use these):

  • Code, DevOps, Python, Guide, Opinion, Salt, AWS, Project, Docker, Security, Machine Learning, Game Development, Kubernetes

Rules:

  • Always use existing tags when applicable
  • Suggest new tags only if truly needed (new technology/topic area)
  • Tags should be capitalized (e.g., "Python" not "python")
  • Avoid single-use tags; consolidate into broader categories

UNTURF SVG DIAGRAM STYLE GUIDE

All diagrams on russell.ballestrini.net & permacomputer sites follow this style.

Color Palette (dark/light theme compatible)

  • Background: always transparent (never white or black)
  • Node fills: medium-dark saturated colors with fill="white" text
    • Blues: #4a90d9, #1e88e5, #0d47a1, #1a237e
    • Purples: #9370db, #7b68ee, #6c5ce7, #4a148c, #ab47bc
    • Greens: #7cb342, #1b5e20, #00897b
    • Teals: #00acc1, #006064
    • Oranges: #e65100, #e8834a, #ff6f00
    • Reds: #bf360c, #b71c1c, #880e4f
    • Dark neutrals: #2c3e50, #34495e, #546e7a, #616161, #4e342e
  • Gold/yellow nodes (#f9a825, #fdd835): use fill="#1a1a1a" text (NOT #333333)
  • Node strokes: #999999 (never black)
  • Edges/arrows: #aaaaaa stroke & fill
  • Floating text labels: #cccccc (cluster labels, "translates to", etc.)
  • Accent text: #ef5350 (red warnings/claims), #ff6f00 (orange highlights), #66ff66 (green positive)
  • Dashed cluster borders: stroke="#999999" stroke-dasharray="5,2"
  • Plaintext labels (no background): fill="transparent" polygon, text fill="#cccccc"

SVG Dimensions

  • Root SVG: width="100%" height="100%" with viewBox (never fixed pt/px)
  • RST embed: :width: 100% and :align: center

Animation Pattern (click-to-play)

SVGs render fully visible by default. A play button overlay triggers sequential fadeIn animation on click.

SVG markup: No <style> block needed. Just wrap groups in <g class="anim dN"> where N controls sequence order.

JS controller (added once per page via raw:: html):

  • Finds all SVGs with .anim elements, wraps each in a container, adds play button
  • On click: hides all .anim groups (opacity 0), then animates them in sequence using @keyframes svgFadeIn with calculated delays based on dN class values
  • Total animation spread scales to ~15s regardless of group count (step = 15 / (maxD + 1))
  • After animation completes, restores full opacity & shows play button again

Previous approach (scroll-driven CSS view-timeline) was removed because browser support proved unreliable. See royal-we post for the working click-to-play implementation.

  • Wrap each conceptual group in <g class="anim dN">
  • N values control sequence order (d0 appears first, d1 second, etc.)
  • Multiple groups can share the same dN class to appear simultaneously

Graphviz Dot Conventions

bgcolor="transparent"
node [fontname="Helvetica" fontsize=12 style="filled,rounded" shape=box color="#999999"]
edge [color="#AAAAAA" arrowsize=0.6 fontcolor="#AAAAAA"]

Post-Processing Checklist

  1. Strip <title> tags: sed -i 's/<title>[^<]*<\/title>//g' (prevents tooltip artifacts)
  2. Replace fixed dimensions: width="100%" height="100%" keeping viewBox
  3. Change cluster label fontcolor from #aaaaaa to #cccccc
  4. Wrap groups with animation classes (<g class="anim dN">)
  5. Remove <defs><style>...</style></defs> block (animation handled by page-level JS)
  6. Verify: no fill="black", no stroke="black", no fill="#333333"

RUSSELL'S PROJECT TECH STACKS

NEVER assume or guess what language/framework a project uses. Check the actual repo.

Project Stack Description
unsandbox.com Elixir / Phoenix Customer portal (accounts, API keys, billing)
api.unsandbox.com Elixir / Phoenix + Python Code execution API, ZFS containers
un-inception C (primary) + 41 languages CLI client for unsandbox API
uncloseai-speech Python / FastAPI OpenAI-compatible TTS API (Qwen3-TTS)
uncloseai.com JavaScript / Node.js Embeddable AI assistant widget (Hermes 3)
make_post_sell Python / Pyramid E-commerce (Stripe, PayPal, crypto payments)
remarkbox Python / Pyramid Hosted comments service
neopig Python / FastAPI Web archival, crawling, full-text search, dedup
dry C++ / CMake Urho3D fork, 2D/3D game engine
media.unturf.com Media gallery (dark theme, b&w aesthetic)

Key corrections for cover letters:

  • unsandbox.com is Elixir/Phoenix, NOT FastAPI or Python
  • uncloseai-speech is Python/FastAPI
  • make_post_sell & remarkbox are Python/Pyramid, NOT Django
  • uncloseai.com is Node.js, NOT Python

BUILD SERVER (build.unturf.com)

CI/CD for this blog & all unturf projects. GitLab Runner with shell executor.

Config management: Salt states in ~/git/foxhop-states/

  • gitlab/build-host/ubuntu.sls — runner config, cleanup cron, build deps
  • lxd/build-host.sls — LXD + ZFS, image cache expiry
  • top.sls targets build.unturf.com with both states

Key settings (salt-managed):

  • concurrent = 4 in /etc/gitlab-runner/config.toml (pillar: gitlab-runner:concurrent)
  • LXD images.remote_cache_expiry = 3 days, images.auto_update_interval = 0
  • /etc/cron.d/build-cleanup — hourly container + zombie cleanup, weekly image prune, disk alerts at 85%

Postmortem: russell.ballestrini.net/build-server-postmortem-disk-full/ (2026-03-08)

Pipeline for this repo: make venv && make html && make formats && make resume → tar → deploy via deploy-www.sh

DATE HANDLING FOR NEW CONTENT

CRITICAL: ALWAYS check the current date BEFORE creating ANY new documents

  • MANDATORY: Check the section for "Today's date" at the start of EVERY conversation
  • BEFORE creating: Posts, cover letters, blog entries, or any dated content, verify the current date
  • The date may change during long conversations - ALWAYS use the actual current date from
  • File names MUST match the date metadata (e.g., 2025-11-04-post-name.txt with :date: 2025-11-04)
  • When creating new content, use the correct current date in BOTH the filename AND metadata
  • NEVER assume the date - always check first
  • NEVER use old dates from previous examples or files