diff --git a/.agents/skills/cavecrew/README.md b/.agents/skills/cavecrew/README.md deleted file mode 100644 index e72295256..000000000 --- a/.agents/skills/cavecrew/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# cavecrew - -Decision guide. When to delegate to caveman subagents instead of doing the work inline. - -## What it does - -Tells the main thread when to spawn a caveman-style subagent versus the vanilla equivalent. The win: subagent tool-results inject back into main context verbatim, and caveman output is roughly 1/3 the size of vanilla prose. Across 20 delegations in one session, that is the difference between context exhaustion and finishing the task. - -Three subagents: - -| Subagent | Job | Use when | -|----------|-----|----------| -| `cavecrew-investigator` | Locate code (read-only) | "Where is X defined / what calls Y / list uses of Z" | -| `cavecrew-builder` | Surgical edit, 1-2 files | Scope is obvious, ≤2 files. Refuses 3+ file scope. | -| `cavecrew-reviewer` | Diff/file review | One-line findings with severity emoji | - -Use vanilla `Explore` or `Code Reviewer` when you want prose, architecture commentary, or rationale. Use main thread directly for one-line answers and 3+ file refactors. - -This skill is a decision guide, not a slash command. It activates when the conversation mentions delegation. - -## How to invoke - -Triggers on phrases like "delegate to subagent", "use cavecrew", "spawn investigator", "save context", "compressed agent output". - -## Example chaining - -Locate → fix → verify (most common): - -1. `cavecrew-investigator` returns site list (`path:line — symbol — note`) -2. Main thread picks 1-2 sites, hands paths to `cavecrew-builder` -3. `cavecrew-reviewer` audits the resulting diff - -Parallel scout: spawn 2-3 `cavecrew-investigator` calls in one message with different angles (defs, callers, tests). Aggregate in main. - -## See also - -- [`SKILL.md`](./SKILL.md) — full decision matrix and output contracts -- [`agents/cavecrew-investigator.md`](../../agents/cavecrew-investigator.md) -- [`agents/cavecrew-builder.md`](../../agents/cavecrew-builder.md) -- [`agents/cavecrew-reviewer.md`](../../agents/cavecrew-reviewer.md) -- [Caveman README](../../README.md) — repo overview diff --git a/.agents/skills/cavecrew/SKILL.md b/.agents/skills/cavecrew/SKILL.md deleted file mode 100644 index efa413fd7..000000000 --- a/.agents/skills/cavecrew/SKILL.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -name: cavecrew -description: > - Decision guide for delegating to caveman-style subagents. Tells the main - thread WHEN to spawn `cavecrew-investigator` (locate code), `cavecrew-builder` - (1-2 file edit), or `cavecrew-reviewer` (diff review) instead of doing the - work inline or using vanilla `Explore`. Subagent output is caveman-compressed - so the tool-result injected back into main context is ~60% smaller — main - context lasts longer across long sessions. - Trigger: "delegate to subagent", "use cavecrew", "spawn investigator/builder/reviewer", - "save context", "compressed agent output". ---- - -Cavecrew = three subagent presets that emit caveman output. Same job as Anthropic defaults (`Explore`, edit-style agents, reviewer); difference is the tool-result they return is compressed, so main context shrinks per delegation. - -## When to use cavecrew vs alternatives - -| Task | Use | -|---|---| -| "Where is X defined / what calls Y / list uses of Z" | `cavecrew-investigator` | -| Same but you also want suggestions/architecture commentary | `Explore` (vanilla) | -| Surgical edit, ≤2 files, scope obvious | `cavecrew-builder` | -| New feature / 3+ files / cross-cutting refactor | Main thread or `feature-dev:code-architect` | -| Review diff, branch, or file for bugs | `cavecrew-reviewer` | -| Deep code review with rationale + alternatives | `Code Reviewer` (vanilla) | -| One-line answer you already know | Main thread, no subagent | - -Rule of thumb: **if you'd want the subagent's output in 1/3 the tokens, pick cavecrew. If you'd want prose, pick vanilla.** - -## Why this exists (the real win) - -Subagent tool results get injected into main context verbatim. A vanilla `Explore` that returns 2k tokens of prose costs 2k tokens of main-context budget every time. The same finding from `cavecrew-investigator` returns ~700 tokens. Across 20 delegations in one session that's the difference between context exhaustion and finishing the task. - -## Output contracts - -What main thread can rely on per agent: - -**`cavecrew-investigator`** -``` -
: -- path:line — `symbol` — short note -totals: . -``` -Or `No match.` Always file-path-first, line-number-attached, backticked symbols. Safe to grep with `path:\d+`. - -**`cavecrew-builder`** -``` - — . -verified: . -``` -Or one of: `too-big.` / `needs-confirm.` / `ambiguous.` / `regressed.` (terminal first token). - -**`cavecrew-reviewer`** -``` -path:line: : . . -totals: N🔴 N🟡 N🔵 N❓ -``` -Or `No issues.` Findings sorted file → line ascending. - -## Chaining patterns - -**Locate → fix → verify** (most common): -1. `cavecrew-investigator` returns site list. -2. Main thread picks 1-2 sites, hands paths to `cavecrew-builder`. -3. `cavecrew-reviewer` audits the diff. - -**Parallel scout** (when investigation is broad): -Spawn 2-3 `cavecrew-investigator` calls in one message (different angles: defs vs callers vs tests). Aggregate in main thread. - -**Single-shot edit** (when site is already known): -Skip investigator. Hand exact path:line to `cavecrew-builder` directly. - -## What NOT to do - -- Don't use `cavecrew-builder` when you don't already know the file. Spawn investigator first or main thread will eat tokens passing context. -- Don't chain `cavecrew-investigator → cavecrew-builder` for a 5-file refactor. Builder will return `too-big.` and you'll have wasted a turn. -- Don't ask `cavecrew-reviewer` for "general feedback" — it returns findings only, no architecture opinions. Use `Code Reviewer` for that. -- Don't expect prose. Cavecrew output is structured, sometimes terse to the point of cryptic. If a human will read it directly, paraphrase. - -## Auto-clarity (inherited) - -Subagents drop caveman → normal English for security warnings, irreversible-action confirmations, and any output where fragment ambiguity could be misread. Resume caveman after. diff --git a/.agents/skills/caveman-commit/README.md b/.agents/skills/caveman-commit/README.md deleted file mode 100644 index d5aee0139..000000000 --- a/.agents/skills/caveman-commit/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# caveman-commit - -Terse Conventional Commits. Why over what. - -## What it does - -Generates commit messages in Conventional Commits format. Subject ≤50 chars, hard cap 72. Imperative mood. Body only when the *why* is non-obvious or there are breaking changes. No AI attribution, no "this commit does X", no emoji unless the project uses them. Body always required for breaking changes, security fixes, data migrations, and reverts — future debuggers need the context. - -Outputs only the message. Does not stage, commit, or amend. - -## How to invoke - -``` -/caveman-commit -``` - -Also triggers on phrases like "write a commit", "commit message", "generate commit". - -## Example output - -Diff: new endpoint for user profile. - -``` -feat(api): add GET /users/:id/profile - -Mobile client needs profile data without the full user payload -to reduce LTE bandwidth on cold-launch screens. - -Closes #128 -``` - -Diff: breaking API rename. - -``` -feat(api)!: rename /v1/orders to /v1/checkout - -BREAKING CHANGE: clients on /v1/orders must migrate to /v1/checkout -before 2026-06-01. Old route returns 410 after that date. -``` - -## See also - -- [`SKILL.md`](./SKILL.md) — full LLM-facing instructions -- [Caveman README](../../README.md) — repo overview diff --git a/.agents/skills/caveman-commit/SKILL.md b/.agents/skills/caveman-commit/SKILL.md deleted file mode 100644 index 729318c3f..000000000 --- a/.agents/skills/caveman-commit/SKILL.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -name: caveman-commit -description: > - Ultra-compressed commit message generator. Cuts noise from commit messages while preserving - intent and reasoning. Conventional Commits format. Subject ≤50 chars, body only when "why" - isn't obvious. Use when user says "write a commit", "commit message", "generate commit", - "/commit", or invokes /caveman-commit. Auto-triggers when staging changes. ---- - -Write commit messages terse and exact. Conventional Commits format. No fluff. Why over what. - -## Rules - -**Subject line:** -- `(): ` — `` optional -- Types: `feat`, `fix`, `refactor`, `perf`, `docs`, `test`, `chore`, `build`, `ci`, `style`, `revert` -- Imperative mood: "add", "fix", "remove" — not "added", "adds", "adding" -- ≤50 chars when possible, hard cap 72 -- No trailing period -- Match project convention for capitalization after the colon - -**Body (only if needed):** -- Skip entirely when subject is self-explanatory -- Add body only for: non-obvious *why*, breaking changes, migration notes, linked issues -- Wrap at 72 chars -- Bullets `-` not `*` -- Reference issues/PRs at end: `Closes #42`, `Refs #17` - -**What NEVER goes in:** -- "This commit does X", "I", "we", "now", "currently" — the diff says what -- "As requested by..." — use Co-authored-by trailer -- "Generated with Claude Code" or any AI attribution -- Emoji (unless project convention requires) -- Restating the file name when scope already says it - -## Examples - -Diff: new endpoint for user profile with body explaining the why -- ❌ "feat: add a new endpoint to get user profile information from the database" -- ✅ - ``` - feat(api): add GET /users/:id/profile - - Mobile client needs profile data without the full user payload - to reduce LTE bandwidth on cold-launch screens. - - Closes #128 - ``` - -Diff: breaking API change -- ✅ - ``` - feat(api)!: rename /v1/orders to /v1/checkout - - BREAKING CHANGE: clients on /v1/orders must migrate to /v1/checkout - before 2026-06-01. Old route returns 410 after that date. - ``` - -## Auto-Clarity - -Always include body for: breaking changes, security fixes, data migrations, anything reverting a prior commit. Never compress these into subject-only — future debuggers need the context. - -## Boundaries - -Only generates the commit message. Does not run `git commit`, does not stage files, does not amend. Output the message as a code block ready to paste. "stop caveman-commit" or "normal mode": revert to verbose commit style. \ No newline at end of file diff --git a/.agents/skills/caveman-compress/README.md b/.agents/skills/caveman-compress/README.md deleted file mode 100644 index 3ef6922b7..000000000 --- a/.agents/skills/caveman-compress/README.md +++ /dev/null @@ -1,163 +0,0 @@ -

- -

- -

caveman-compress

- -

- shrink memory file. save token every session. -

- ---- - -A Claude Code skill that compresses your project memory files (`CLAUDE.md`, todos, preferences) into caveman format — so every session loads fewer tokens automatically. - -Claude read `CLAUDE.md` on every session start. If file big, cost big. Caveman make file small. Cost go down forever. - -## What It Do - -``` -/caveman-compress CLAUDE.md -``` - -``` -CLAUDE.md ← compressed (Claude reads this — fewer tokens every session) -CLAUDE.original.md ← human-readable backup (you edit this) -``` - -Original never lost. You can read and edit `.original.md`. Run skill again to re-compress after edits. - -## Benchmarks - -Real results on real project files: - -| File | Original | Compressed | Saved | -|------|----------:|----------:|------:| -| `claude-md-preferences.md` | 706 | 285 | **59.6%** | -| `project-notes.md` | 1145 | 535 | **53.3%** | -| `claude-md-project.md` | 1122 | 636 | **43.3%** | -| `todo-list.md` | 627 | 388 | **38.1%** | -| `mixed-with-code.md` | 888 | 560 | **36.9%** | -| **Average** | **898** | **481** | **46%** | - -All validations passed ✅ — headings, code blocks, URLs, file paths preserved exactly. - -## Before / After - - - - - - -
- -### 📄 Original (706 tokens) - -> "I strongly prefer TypeScript with strict mode enabled for all new code. Please don't use `any` type unless there's genuinely no way around it, and if you do, leave a comment explaining the reasoning. I find that taking the time to properly type things catches a lot of bugs before they ever make it to runtime." - - - -### rock Caveman (285 tokens) - -> "Prefer TypeScript strict mode always. No `any` unless unavoidable — comment why if used. Proper types catch bugs early." - -
- -**Same instructions. 60% fewer tokens. Every. Single. Session.** - -## Security - -`caveman-compress` is flagged as Snyk High Risk due to subprocess and file I/O patterns detected by static analysis. This is a false positive — see [SECURITY.md](./SECURITY.md) for a full explanation of what the skill does and does not do. - -## Install - -Compress is built in with the `caveman` plugin. Install `caveman` once, then use `/caveman-compress`. - -If you need local files, the compress skill lives at: - -```bash -caveman-compress/ -``` - -**Requires:** Python 3.10+ - -## Usage - -``` -/caveman-compress -``` - -Examples: -``` -/caveman-compress CLAUDE.md -/caveman-compress docs/preferences.md -/caveman-compress todos.md -``` - -### What files work - -| Type | Compress? | -|------|-----------| -| `.md`, `.txt`, `.rst`, `.typ`, `.typst`, `.tex` | ✅ Yes | -| Extensionless natural language | ✅ Yes | -| `.py`, `.js`, `.ts`, `.json`, `.yaml` | ❌ Skip (code/config) | -| `*.original.md` | ❌ Skip (backup files) | - -## How It Work - -``` -/caveman-compress CLAUDE.md - ↓ -detect file type (no tokens) - ↓ -Claude compresses (tokens — one call) - ↓ -validate output (no tokens) - checks: headings, code blocks, URLs, file paths, bullets - ↓ -if errors: Claude fixes cherry-picked issues only (tokens — targeted fix) - does NOT recompress — only patches broken parts - ↓ -retry up to 2 times - ↓ -write compressed → CLAUDE.md -write original → CLAUDE.original.md -``` - -Only two things use tokens: initial compression + targeted fix if validation fails. Everything else is local Python. - -## What Is Preserved - -Caveman compress natural language. It never touch: - -- Code blocks (` ``` ` fenced or indented) -- Inline code (`` `backtick content` ``) -- URLs and links -- File paths (`/src/components/...`) -- Commands (`npm install`, `git commit`) -- Technical terms, library names, API names -- Headings (exact text preserved) -- Tables (structure preserved, cell text compressed) -- Dates, version numbers, numeric values - -## Why This Matter - -`CLAUDE.md` loads on **every session start**. A 1000-token project memory file costs tokens every single time you open a project. Over 100 sessions that's 100,000 tokens of overhead — just for context you already wrote. - -Caveman cut that by ~46% on average. Same instructions. Same accuracy. Less waste. - -``` -┌────────────────────────────────────────────┐ -│ TOKEN SAVINGS PER FILE █████ 46% │ -│ SESSIONS THAT BENEFIT ██████████ 100% │ -│ INFORMATION PRESERVED ██████████ 100% │ -│ SETUP TIME █ 1x │ -└────────────────────────────────────────────┘ -``` - -## Part of Caveman - -This skill is part of the [caveman](https://github.com/JuliusBrussee/caveman) toolkit — making Claude use fewer tokens without losing accuracy. - -- **caveman** — make Claude *speak* like caveman (cuts response tokens ~65%) -- **caveman-compress** — make Claude *read* less (cuts context tokens ~46%) diff --git a/.agents/skills/caveman-compress/SECURITY.md b/.agents/skills/caveman-compress/SECURITY.md deleted file mode 100644 index 693108c58..000000000 --- a/.agents/skills/caveman-compress/SECURITY.md +++ /dev/null @@ -1,31 +0,0 @@ -# Security - -## Snyk High Risk Rating - -`caveman-compress` receives a Snyk High Risk rating due to static analysis heuristics. This document explains what the skill does and does not do. - -### What triggers the rating - -1. **subprocess usage**: The skill calls the `claude` CLI via `subprocess.run()` as a fallback when `ANTHROPIC_API_KEY` is not set. The subprocess call uses a fixed argument list — no shell interpolation occurs. User file content is passed via stdin, not as a shell argument. - -2. **File read/write**: The skill reads the file the user explicitly points it at, compresses it, and writes the result back to the same path. A `.original.md` backup is saved alongside it. No files outside the user-specified path are read or written. - -### What the skill does NOT do - -- Does not execute user file content as code -- Does not make network requests except to Anthropic's API (via SDK or CLI) -- Does not access files outside the path the user provides -- Does not use shell=True or string interpolation in subprocess calls -- Does not collect or transmit any data beyond the file being compressed - -### Auth behavior - -If `ANTHROPIC_API_KEY` is set, the skill uses the Anthropic Python SDK directly (no subprocess). If not set, it falls back to the `claude` CLI, which uses the user's existing Claude desktop authentication. - -### File size limit - -Files larger than 500KB are rejected before any API call is made. - -### Reporting a vulnerability - -If you believe you've found a genuine security issue, please open a GitHub issue with the label `security`. diff --git a/.agents/skills/caveman-compress/SKILL.md b/.agents/skills/caveman-compress/SKILL.md deleted file mode 100644 index 00ce4543b..000000000 --- a/.agents/skills/caveman-compress/SKILL.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -name: caveman-compress -description: > - Compress natural language memory files (CLAUDE.md, todos, preferences) into caveman format - to save input tokens. Preserves all technical substance, code, URLs, and structure. - Compressed version overwrites the original file. Human-readable backup saved as FILE.original.md. - Trigger: /caveman-compress FILEPATH or "compress memory file" ---- - -# Caveman Compress - -## Purpose - -Compress natural language files (CLAUDE.md, todos, preferences) into caveman-speak to reduce input tokens. Compressed version overwrites original. Human-readable backup saved as `.original.md`. - -## Trigger - -`/caveman-compress ` or when user asks to compress a memory file. - -## Process - -1. The compression scripts live in `scripts/` (adjacent to this SKILL.md). If the path is not immediately available, search for `scripts/__main__.py` next to this SKILL.md. - -2. From the directory containing this SKILL.md, run: - -python3 -m scripts - -3. The CLI will: -- detect file type (no tokens) -- call Claude to compress -- validate output (no tokens) -- if errors: cherry-pick fix with Claude (targeted fixes only, no recompression) -- retry up to 2 times -- if still failing after 2 retries: report error to user, leave original file untouched - -4. Return result to user - -## Compression Rules - -### Remove -- Articles: a, an, the -- Filler: just, really, basically, actually, simply, essentially, generally -- Pleasantries: "sure", "certainly", "of course", "happy to", "I'd recommend" -- Hedging: "it might be worth", "you could consider", "it would be good to" -- Redundant phrasing: "in order to" → "to", "make sure to" → "ensure", "the reason is because" → "because" -- Connective fluff: "however", "furthermore", "additionally", "in addition" - -### Preserve EXACTLY (never modify) -- Code blocks (fenced ``` and indented) -- Inline code (`backtick content`) -- URLs and links (full URLs, markdown links) -- File paths (`/src/components/...`, `./config.yaml`) -- Commands (`npm install`, `git commit`, `docker build`) -- Technical terms (library names, API names, protocols, algorithms) -- Proper nouns (project names, people, companies) -- Dates, version numbers, numeric values -- Environment variables (`$HOME`, `NODE_ENV`) - -### Preserve Structure -- All markdown headings (keep exact heading text, compress body below) -- Bullet point hierarchy (keep nesting level) -- Numbered lists (keep numbering) -- Tables (compress cell text, keep structure) -- Frontmatter/YAML headers in markdown files - -### Compress -- Use short synonyms: "big" not "extensive", "fix" not "implement a solution for", "use" not "utilize" -- Fragments OK: "Run tests before commit" not "You should always run tests before committing" -- Drop "you should", "make sure to", "remember to" — just state the action -- Merge redundant bullets that say the same thing differently -- Keep one example where multiple examples show the same pattern - -CRITICAL RULE: -Anything inside ``` ... ``` must be copied EXACTLY. -Do not: -- remove comments -- remove spacing -- reorder lines -- shorten commands -- simplify anything - -Inline code (`...`) must be preserved EXACTLY. -Do not modify anything inside backticks. - -If file contains code blocks: -- Treat code blocks as read-only regions -- Only compress text outside them -- Do not merge sections around code - -## Pattern - -Original: -> You should always make sure to run the test suite before pushing any changes to the main branch. This is important because it helps catch bugs early and prevents broken builds from being deployed to production. - -Compressed: -> Run tests before push to main. Catch bugs early, prevent broken prod deploys. - -Original: -> The application uses a microservices architecture with the following components. The API gateway handles all incoming requests and routes them to the appropriate service. The authentication service is responsible for managing user sessions and JWT tokens. - -Compressed: -> Microservices architecture. API gateway route all requests to services. Auth service manage user sessions + JWT tokens. - -## Boundaries - -- ONLY compress natural language files (.md, .txt, .typ, .typst, .tex, extensionless) -- NEVER modify: .py, .js, .ts, .json, .yaml, .yml, .toml, .env, .lock, .css, .html, .xml, .sql, .sh -- If file has mixed content (prose + code), compress ONLY the prose sections -- If unsure whether something is code or prose, leave it unchanged -- Original file is backed up as FILE.original.md before overwriting -- Never compress FILE.original.md (skip it) diff --git a/.agents/skills/caveman-compress/scripts/__init__.py b/.agents/skills/caveman-compress/scripts/__init__.py deleted file mode 100644 index 16b8c53c2..000000000 --- a/.agents/skills/caveman-compress/scripts/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Caveman compress scripts. - -This package provides tools to compress natural language markdown files -into caveman format to save input tokens. -""" - -__all__ = ["cli", "compress", "detect", "validate"] - -__version__ = "1.0.0" diff --git a/.agents/skills/caveman-compress/scripts/__main__.py b/.agents/skills/caveman-compress/scripts/__main__.py deleted file mode 100644 index 4e28416e1..000000000 --- a/.agents/skills/caveman-compress/scripts/__main__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .cli import main - -main() diff --git a/.agents/skills/caveman-compress/scripts/benchmark.py b/.agents/skills/caveman-compress/scripts/benchmark.py deleted file mode 100644 index f9e2ee011..000000000 --- a/.agents/skills/caveman-compress/scripts/benchmark.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python3 -from pathlib import Path -import sys - -# Support both direct execution and module import -try: - from .validate import validate -except ImportError: - sys.path.insert(0, str(Path(__file__).parent)) - from validate import validate - -try: - import tiktoken - _enc = tiktoken.get_encoding("o200k_base") -except ImportError: - _enc = None - - -def count_tokens(text): - if _enc is None: - return len(text.split()) # fallback: word count - return len(_enc.encode(text)) - - -def benchmark_pair(orig_path: Path, comp_path: Path): - orig_text = orig_path.read_text() - comp_text = comp_path.read_text() - - orig_tokens = count_tokens(orig_text) - comp_tokens = count_tokens(comp_text) - saved = 100 * (orig_tokens - comp_tokens) / orig_tokens if orig_tokens > 0 else 0.0 - result = validate(orig_path, comp_path) - - return (comp_path.name, orig_tokens, comp_tokens, saved, result.is_valid) - - -def print_table(rows): - print("\n| File | Original | Compressed | Saved % | Valid |") - print("|------|----------|------------|---------|-------|") - for r in rows: - print(f"| {r[0]} | {r[1]} | {r[2]} | {r[3]:.1f}% | {'✅' if r[4] else '❌'} |") - - -def main(): - # Direct file pair: python3 benchmark.py original.md compressed.md - if len(sys.argv) == 3: - orig = Path(sys.argv[1]).resolve() - comp = Path(sys.argv[2]).resolve() - if not orig.exists(): - print(f"❌ Not found: {orig}") - sys.exit(1) - if not comp.exists(): - print(f"❌ Not found: {comp}") - sys.exit(1) - print_table([benchmark_pair(orig, comp)]) - return - - # Glob mode: repo_root/tests/caveman-compress/ - # __file__ lives at /skills/caveman-compress/scripts/benchmark.py - # Walk up four dirs: scripts → caveman-compress → skills → repo_root. - tests_dir = Path(__file__).resolve().parents[3] / "tests" / "caveman-compress" - if not tests_dir.exists(): - print(f"❌ Tests dir not found: {tests_dir}") - sys.exit(1) - - rows = [] - for orig in sorted(tests_dir.glob("*.original.md")): - comp = orig.with_name(orig.stem.removesuffix(".original") + ".md") - if comp.exists(): - rows.append(benchmark_pair(orig, comp)) - - if not rows: - print("No compressed file pairs found.") - return - - print_table(rows) - - -if __name__ == "__main__": - main() diff --git a/.agents/skills/caveman-compress/scripts/cli.py b/.agents/skills/caveman-compress/scripts/cli.py deleted file mode 100644 index c314f8721..000000000 --- a/.agents/skills/caveman-compress/scripts/cli.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python3 -""" -Caveman Compress CLI - -Usage: - caveman -""" - -import sys - -# Force UTF-8 on stdout/stderr before any code can print. Windows consoles -# default to cp1252 and crash on the ❌ glyphs in error/validation branches, -# masking the real error and leaving the user with a half-compressed file. -for _stream in (sys.stdout, sys.stderr): - reconfigure = getattr(_stream, "reconfigure", None) - if callable(reconfigure): - try: - reconfigure(encoding="utf-8", errors="replace") - except Exception: - pass - -from pathlib import Path - -from .compress import compress_file -from .detect import detect_file_type, should_compress - - -def print_usage(): - print("Usage: caveman ") - - -def main(): - if len(sys.argv) != 2: - print_usage() - sys.exit(1) - - filepath = Path(sys.argv[1]) - - # Check file exists - if not filepath.exists(): - print(f"❌ File not found: {filepath}") - sys.exit(1) - - if not filepath.is_file(): - print(f"❌ Not a file: {filepath}") - sys.exit(1) - - filepath = filepath.resolve() - - # Detect file type - file_type = detect_file_type(filepath) - - print(f"Detected: {file_type}") - - # Check if compressible - if not should_compress(filepath): - print("Skipping: file is not natural language (code/config)") - sys.exit(0) - - print("Starting caveman compression...\n") - - try: - success = compress_file(filepath) - - if success: - print("\nCompression completed successfully") - backup_path = filepath.with_name(filepath.stem + ".original.md") - print(f"Compressed: {filepath}") - print(f"Original: {backup_path}") - sys.exit(0) - else: - print("\n❌ Compression failed after retries") - sys.exit(2) - - except KeyboardInterrupt: - print("\nInterrupted by user") - sys.exit(130) - - except Exception as e: - print(f"\n❌ Error: {e}") - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/.agents/skills/caveman-compress/scripts/compress.py b/.agents/skills/caveman-compress/scripts/compress.py deleted file mode 100644 index 7adc4fbaf..000000000 --- a/.agents/skills/caveman-compress/scripts/compress.py +++ /dev/null @@ -1,254 +0,0 @@ -#!/usr/bin/env python3 -""" -Caveman Memory Compression Orchestrator - -Usage: - python scripts/compress.py -""" - -import os -import re -import subprocess -from pathlib import Path -from typing import List - -OUTER_FENCE_REGEX = re.compile( - r"\A\s*(`{3,}|~{3,})[^\n]*\n(.*)\n\1\s*\Z", re.DOTALL -) - -# Filenames and paths that almost certainly hold secrets or PII. Compressing -# them ships raw bytes to the Anthropic API — a third-party data boundary that -# developers on sensitive codebases cannot cross. detect.py already skips .env -# by extension, but credentials.md / secrets.txt / ~/.aws/credentials would -# slip through the natural-language filter. This is a hard refuse before read. -SENSITIVE_BASENAME_REGEX = re.compile( - r"(?ix)^(" - r"\.env(\..+)?" - r"|\.netrc" - r"|credentials(\..+)?" - r"|secrets?(\..+)?" - r"|passwords?(\..+)?" - r"|id_(rsa|dsa|ecdsa|ed25519)(\.pub)?" - r"|authorized_keys" - r"|known_hosts" - r"|.*\.(pem|key|p12|pfx|crt|cer|jks|keystore|asc|gpg)" - r")$" -) - -SENSITIVE_PATH_COMPONENTS = frozenset({".ssh", ".aws", ".gnupg", ".kube", ".docker"}) - -SENSITIVE_NAME_TOKENS = ( - "secret", "credential", "password", "passwd", - "apikey", "accesskey", "token", "privatekey", -) - - -def is_sensitive_path(filepath: Path) -> bool: - """Heuristic denylist for files that must never be shipped to a third-party API.""" - name = filepath.name - if SENSITIVE_BASENAME_REGEX.match(name): - return True - lowered_parts = {p.lower() for p in filepath.parts} - if lowered_parts & SENSITIVE_PATH_COMPONENTS: - return True - # Normalize separators so "api-key" and "api_key" both match "apikey". - lower = re.sub(r"[_\-\s.]", "", name.lower()) - return any(tok in lower for tok in SENSITIVE_NAME_TOKENS) - - -def strip_llm_wrapper(text: str) -> str: - """Strip outer ```markdown ... ``` fence when it wraps the entire output.""" - m = OUTER_FENCE_REGEX.match(text) - if m: - return m.group(2) - return text - -from .detect import should_compress -from .validate import validate - -MAX_RETRIES = 2 - - -# ---------- Claude Calls ---------- - - -def call_claude(prompt: str) -> str: - api_key = os.environ.get("ANTHROPIC_API_KEY") - if api_key: - try: - import anthropic - - client = anthropic.Anthropic(api_key=api_key) - msg = client.messages.create( - model=os.environ.get("CAVEMAN_MODEL", "claude-sonnet-4-5"), - max_tokens=8192, - messages=[{"role": "user", "content": prompt}], - ) - return strip_llm_wrapper(msg.content[0].text.strip()) - except ImportError: - pass # anthropic not installed, fall back to CLI - # Fallback: use claude CLI (handles desktop auth) - try: - result = subprocess.run( - ["claude", "--print"], - input=prompt, - text=True, - capture_output=True, - check=True, - ) - return strip_llm_wrapper(result.stdout.strip()) - except subprocess.CalledProcessError as e: - raise RuntimeError(f"Claude call failed:\n{e.stderr}") - - -def build_compress_prompt(original: str) -> str: - return f""" -Compress this markdown into caveman format. - -STRICT RULES: -- Do NOT modify anything inside ``` code blocks -- Do NOT modify anything inside inline backticks -- Preserve ALL URLs exactly -- Preserve ALL headings exactly -- Preserve file paths and commands -- Return ONLY the compressed markdown body — do NOT wrap the entire output in a ```markdown fence or any other fence. Inner code blocks from the original stay as-is; do not add a new outer fence around the whole file. - -Only compress natural language. - -TEXT: -{original} -""" - - -def build_fix_prompt(original: str, compressed: str, errors: List[str]) -> str: - errors_str = "\n".join(f"- {e}" for e in errors) - return f"""You are fixing a caveman-compressed markdown file. Specific validation errors were found. - -CRITICAL RULES: -- DO NOT recompress or rephrase the file -- ONLY fix the listed errors — leave everything else exactly as-is -- The ORIGINAL is provided as reference only (to restore missing content) -- Preserve caveman style in all untouched sections - -ERRORS TO FIX: -{errors_str} - -HOW TO FIX: -- Missing URL: find it in ORIGINAL, restore it exactly where it belongs in COMPRESSED -- Code block mismatch: find the exact code block in ORIGINAL, restore it in COMPRESSED -- Heading mismatch: restore the exact heading text from ORIGINAL into COMPRESSED -- Do not touch any section not mentioned in the errors - -ORIGINAL (reference only): -{original} - -COMPRESSED (fix this): -{compressed} - -Return ONLY the fixed compressed file. No explanation. -""" - - -# ---------- Core Logic ---------- - - -def compress_file(filepath: Path) -> bool: - # Resolve and validate path - filepath = filepath.resolve() - MAX_FILE_SIZE = 500_000 # 500KB - if not filepath.exists(): - raise FileNotFoundError(f"File not found: {filepath}") - if filepath.stat().st_size > MAX_FILE_SIZE: - raise ValueError(f"File too large to compress safely (max 500KB): {filepath}") - - # Refuse files that look like they contain secrets or PII. Compressing ships - # the raw bytes to the Anthropic API — a third-party boundary — so we fail - # loudly rather than silently exfiltrate credentials or keys. Override is - # intentional: the user must rename the file if the heuristic is wrong. - if is_sensitive_path(filepath): - raise ValueError( - f"Refusing to compress {filepath}: filename looks sensitive " - "(credentials, keys, secrets, or known private paths). " - "Compression sends file contents to the Anthropic API. " - "Rename the file if this is a false positive." - ) - - print(f"Processing: {filepath}") - - if not should_compress(filepath): - print("Skipping (not natural language)") - return False - - original_text = filepath.read_text(errors="ignore") - backup_path = filepath.with_name(filepath.stem + ".original.md") - - if not original_text.strip(): - print("❌ Refusing to compress: file is empty or whitespace-only.") - return False - - # Check if backup already exists to prevent accidental overwriting - if backup_path.exists(): - print(f"⚠️ Backup file already exists: {backup_path}") - print("The original backup may contain important content.") - print("Aborting to prevent data loss. Please remove or rename the backup file if you want to proceed.") - return False - - # Step 1: Compress - print("Compressing with Claude...") - compressed = call_claude(build_compress_prompt(original_text)) - - if compressed is None or not compressed.strip(): - print("❌ Compression aborted: Claude returned an empty response.") - print(" Original file is untouched (no backup created).") - return False - - if compressed.strip() == original_text.strip(): - print("❌ Compression aborted: output is identical to input.") - print(" Likely causes: Claude refused, returned the prompt verbatim, or the file is") - print(" already in caveman form. Original file is untouched (no backup created).") - return False - - # Save original as backup, then verify the backup readback before - # touching the input file. If the filesystem dropped bytes (encoding, - # antivirus, disk full), unlink the bad backup and abort instead of - # leaving the user with a corrupt backup + compressed primary. - backup_path.write_text(original_text) - backup_readback = backup_path.read_text(errors="ignore") - if backup_readback != original_text: - print(f"❌ Backup write verification failed: {backup_path}") - print(" In-memory original differs from on-disk backup. Aborting before touching the input file.") - try: - backup_path.unlink() - except OSError: - pass - return False - filepath.write_text(compressed) - - # Step 2: Validate + Retry - for attempt in range(MAX_RETRIES): - print(f"\nValidation attempt {attempt + 1}") - - result = validate(backup_path, filepath) - - if result.is_valid: - print("Validation passed") - break - - print("❌ Validation failed:") - for err in result.errors: - print(f" - {err}") - - if attempt == MAX_RETRIES - 1: - # Restore original on failure - filepath.write_text(original_text) - backup_path.unlink(missing_ok=True) - print("❌ Failed after retries — original restored") - return False - - print("Fixing with Claude...") - compressed = call_claude( - build_fix_prompt(original_text, compressed, result.errors) - ) - filepath.write_text(compressed) - - return True diff --git a/.agents/skills/caveman-compress/scripts/detect.py b/.agents/skills/caveman-compress/scripts/detect.py deleted file mode 100644 index 8d5f6d7e7..000000000 --- a/.agents/skills/caveman-compress/scripts/detect.py +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env python3 -"""Detect whether a file is natural language (compressible) or code/config (skip).""" - -import json -import re -from pathlib import Path - -# Extensions that are natural language and compressible -COMPRESSIBLE_EXTENSIONS = {".md", ".txt", ".markdown", ".rst", ".typ", ".typst", ".tex"} - -# Extensions that are code/config and should be skipped -SKIP_EXTENSIONS = { - ".py", ".js", ".ts", ".tsx", ".jsx", ".json", ".yaml", ".yml", - ".toml", ".env", ".lock", ".css", ".scss", ".html", ".xml", - ".sql", ".sh", ".bash", ".zsh", ".go", ".rs", ".java", ".c", - ".cpp", ".h", ".hpp", ".rb", ".php", ".swift", ".kt", ".lua", - ".dockerfile", ".makefile", ".csv", ".ini", ".cfg", -} - -# Patterns that indicate a line is code -CODE_PATTERNS = [ - re.compile(r"^\s*(import |from .+ import |require\(|const |let |var )"), - re.compile(r"^\s*(def |class |function |async function |export )"), - re.compile(r"^\s*(if\s*\(|for\s*\(|while\s*\(|switch\s*\(|try\s*\{)"), - re.compile(r"^\s*[\}\]\);]+\s*$"), # closing braces/brackets - re.compile(r"^\s*@\w+"), # decorators/annotations - re.compile(r'^\s*"[^"]+"\s*:\s*'), # JSON-like key-value - re.compile(r"^\s*\w+\s*=\s*[{\[\(\"']"), # assignment with literal -] - - -def _is_code_line(line: str) -> bool: - """Check if a line looks like code.""" - return any(p.match(line) for p in CODE_PATTERNS) - - -def _is_json_content(text: str) -> bool: - """Check if content is valid JSON.""" - try: - json.loads(text) - return True - except (json.JSONDecodeError, ValueError): - return False - - -def _is_yaml_content(lines: list[str]) -> bool: - """Heuristic: check if content looks like YAML.""" - yaml_indicators = 0 - for line in lines[:30]: - stripped = line.strip() - if stripped.startswith("---"): - yaml_indicators += 1 - elif re.match(r"^\w[\w\s]*:\s", stripped): - yaml_indicators += 1 - elif stripped.startswith("- ") and ":" in stripped: - yaml_indicators += 1 - # If most non-empty lines look like YAML - non_empty = sum(1 for l in lines[:30] if l.strip()) - return non_empty > 0 and yaml_indicators / non_empty > 0.6 - - -def detect_file_type(filepath: Path) -> str: - """Classify a file as 'natural_language', 'code', 'config', or 'unknown'. - - Returns: - One of: 'natural_language', 'code', 'config', 'unknown' - """ - ext = filepath.suffix.lower() - - # Extension-based classification - if ext in COMPRESSIBLE_EXTENSIONS: - return "natural_language" - if ext in SKIP_EXTENSIONS: - return "code" if ext not in {".json", ".yaml", ".yml", ".toml", ".ini", ".cfg", ".env"} else "config" - - # Extensionless files (like CLAUDE.md, TODO) — check content - if not ext: - try: - text = filepath.read_text(errors="ignore") - except (OSError, PermissionError): - return "unknown" - - lines = text.splitlines()[:50] - - if _is_json_content(text[:10000]): - return "config" - if _is_yaml_content(lines): - return "config" - - code_lines = sum(1 for l in lines if l.strip() and _is_code_line(l)) - non_empty = sum(1 for l in lines if l.strip()) - if non_empty > 0 and code_lines / non_empty > 0.4: - return "code" - - return "natural_language" - - return "unknown" - - -def should_compress(filepath: Path) -> bool: - """Return True if the file is natural language and should be compressed.""" - if not filepath.is_file(): - return False - # Skip backup files - if filepath.name.endswith(".original.md"): - return False - return detect_file_type(filepath) == "natural_language" - - -if __name__ == "__main__": - import sys - - if len(sys.argv) < 2: - print("Usage: python detect.py [file2] ...") - sys.exit(1) - - for path_str in sys.argv[1:]: - p = Path(path_str).resolve() - file_type = detect_file_type(p) - compress = should_compress(p) - print(f" {p.name:30s} type={file_type:20s} compress={compress}") diff --git a/.agents/skills/caveman-compress/scripts/validate.py b/.agents/skills/caveman-compress/scripts/validate.py deleted file mode 100644 index dc07307b1..000000000 --- a/.agents/skills/caveman-compress/scripts/validate.py +++ /dev/null @@ -1,213 +0,0 @@ -#!/usr/bin/env python3 -import re -from collections import Counter -from pathlib import Path - -URL_REGEX = re.compile(r"https?://[^\s)]+") -FENCE_OPEN_REGEX = re.compile(r"^(\s{0,3})(`{3,}|~{3,})(.*)$") -HEADING_REGEX = re.compile(r"^(#{1,6})\s+(.*)", re.MULTILINE) -BULLET_REGEX = re.compile(r"^\s*[-*+]\s+", re.MULTILINE) - -# crude but effective path detection -# Requires either a path prefix (./ ../ / or drive letter) or a slash/backslash within the match -PATH_REGEX = re.compile(r"(?:\./|\.\./|/|[A-Za-z]:\\)[\w\-/\\\.]+|[\w\-\.]+[/\\][\w\-/\\\.]+") - - -class ValidationResult: - def __init__(self): - self.is_valid = True - self.errors = [] - self.warnings = [] - - def add_error(self, msg): - self.is_valid = False - self.errors.append(msg) - - def add_warning(self, msg): - self.warnings.append(msg) - - -def read_file(path: Path) -> str: - return path.read_text(errors="ignore") - - -# ---------- Extractors ---------- - - -def extract_headings(text): - return [(level, title.strip()) for level, title in HEADING_REGEX.findall(text)] - - -def extract_code_blocks(text): - """Line-based fenced code block extractor. - - Handles ``` and ~~~ fences with variable length (CommonMark: closing - fence must use same char and be at least as long as opening). Supports - nested fences (e.g. an outer 4-backtick block wrapping inner 3-backtick - content). - """ - blocks = [] - lines = text.split("\n") - i = 0 - n = len(lines) - while i < n: - m = FENCE_OPEN_REGEX.match(lines[i]) - if not m: - i += 1 - continue - fence_char = m.group(2)[0] - fence_len = len(m.group(2)) - open_line = lines[i] - block_lines = [open_line] - i += 1 - closed = False - while i < n: - close_m = FENCE_OPEN_REGEX.match(lines[i]) - if ( - close_m - and close_m.group(2)[0] == fence_char - and len(close_m.group(2)) >= fence_len - and close_m.group(3).strip() == "" - ): - block_lines.append(lines[i]) - closed = True - i += 1 - break - block_lines.append(lines[i]) - i += 1 - if closed: - blocks.append("\n".join(block_lines)) - # Unclosed fences are silently skipped — they indicate malformed markdown - # and including them would cause false-positive validation failures. - return blocks - - -def extract_urls(text): - return set(URL_REGEX.findall(text)) - - -def extract_paths(text): - return set(PATH_REGEX.findall(text)) - - -def count_bullets(text): - return len(BULLET_REGEX.findall(text)) - - -def extract_inline_codes(text): - text_without_fences = re.sub(r"^```[\s\S]*?^```", "", text, flags=re.MULTILINE) - text_without_fences = re.sub(r"^~~~[\s\S]*?^~~~", "", text_without_fences, flags=re.MULTILINE) - return re.findall(r"`([^`]+)`", text_without_fences) - - -# ---------- Validators ---------- - - -def validate_headings(orig, comp, result): - h1 = extract_headings(orig) - h2 = extract_headings(comp) - - if len(h1) != len(h2): - result.add_error(f"Heading count mismatch: {len(h1)} vs {len(h2)}") - - if h1 != h2: - result.add_warning("Heading text/order changed") - - -def validate_code_blocks(orig, comp, result): - c1 = extract_code_blocks(orig) - c2 = extract_code_blocks(comp) - - if c1 != c2: - result.add_error("Code blocks not preserved exactly") - - -def validate_urls(orig, comp, result): - u1 = extract_urls(orig) - u2 = extract_urls(comp) - - if u1 != u2: - result.add_error(f"URL mismatch: lost={u1 - u2}, added={u2 - u1}") - - -def validate_paths(orig, comp, result): - p1 = extract_paths(orig) - p2 = extract_paths(comp) - - if p1 != p2: - result.add_warning(f"Path mismatch: lost={p1 - p2}, added={p2 - p1}") - - -def validate_bullets(orig, comp, result): - b1 = count_bullets(orig) - b2 = count_bullets(comp) - - if b1 == 0: - return - - diff = abs(b1 - b2) / b1 - - if diff > 0.15: - result.add_warning(f"Bullet count changed too much: {b1} -> {b2}") - - -def validate_inline_codes(orig, comp, result): - c1 = Counter(extract_inline_codes(orig)) - c2 = Counter(extract_inline_codes(comp)) - - if c1 != c2: - lost = set(c1.keys()) - set(c2.keys()) - added = set(c2.keys()) - set(c1.keys()) - for code, count in c1.items(): - if code in c2 and c2[code] < count: - lost.add(f"{code} (lost {count - c2[code]} of {count} occurrences)") - if lost: - result.add_error(f"Inline code lost: {lost}") - if added: - result.add_warning(f"Inline code added: {added}") - - -# ---------- Main ---------- - - -def validate(original_path: Path, compressed_path: Path) -> ValidationResult: - result = ValidationResult() - - orig = read_file(original_path) - comp = read_file(compressed_path) - - validate_headings(orig, comp, result) - validate_code_blocks(orig, comp, result) - validate_urls(orig, comp, result) - validate_paths(orig, comp, result) - validate_bullets(orig, comp, result) - validate_inline_codes(orig, comp, result) - - return result - - -# ---------- CLI ---------- - -if __name__ == "__main__": - import sys - - if len(sys.argv) != 3: - print("Usage: python validate.py ") - sys.exit(1) - - orig = Path(sys.argv[1]).resolve() - comp = Path(sys.argv[2]).resolve() - - res = validate(orig, comp) - - print(f"\nValid: {res.is_valid}") - - if res.errors: - print("\nErrors:") - for e in res.errors: - print(f" - {e}") - - if res.warnings: - print("\nWarnings:") - for w in res.warnings: - print(f" - {w}") diff --git a/.agents/skills/caveman-help/README.md b/.agents/skills/caveman-help/README.md deleted file mode 100644 index 5841256fa..000000000 --- a/.agents/skills/caveman-help/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# caveman-help - -Quick-reference card. One shot, no mode change. - -## What it does - -Prints a cheat sheet of all caveman modes, sibling skills, deactivation triggers, and how to set the default mode via env var or config file. One-shot display — does not flip the active mode, write flag files, or persist anything. Use when you forget the slash commands. - -## How to invoke - -``` -/caveman-help -``` - -Also triggers on "caveman help", "what caveman commands", "how do I use caveman". - -## Example output - -``` -Modes: - /caveman full (default) - /caveman lite lighter - /caveman ultra extreme - /caveman wenyan classical Chinese - -Skills: - /caveman-commit terse Conventional Commits - /caveman-review one-line PR comments - /caveman-stats session token savings - -Deactivate: - "stop caveman" or "normal mode" -``` - -## See also - -- [`SKILL.md`](./SKILL.md) — full reference card -- [Caveman README](../../README.md) — repo overview diff --git a/.agents/skills/caveman-help/SKILL.md b/.agents/skills/caveman-help/SKILL.md deleted file mode 100644 index 252221f38..000000000 --- a/.agents/skills/caveman-help/SKILL.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -name: caveman-help -description: > - Quick-reference card for all caveman modes, skills, and commands. - One-shot display, not a persistent mode. Trigger: /caveman-help, - "caveman help", "what caveman commands", "how do I use caveman". ---- - -# Caveman Help - -Display this reference card when invoked. One-shot — do NOT change mode, write flag files, or persist anything. Output in caveman style. - -## Modes - -| Mode | Trigger | What change | -|------|---------|-------------| -| **Lite** | `/caveman lite` | Drop filler. Keep sentence structure. | -| **Full** | `/caveman` | Drop articles, filler, pleasantries, hedging. Fragments OK. Default. | -| **Ultra** | `/caveman ultra` | Extreme compression. Bare fragments. Tables over prose. | -| **Wenyan-Lite** | `/caveman wenyan-lite` | Classical Chinese style, light compression. | -| **Wenyan-Full** | `/caveman wenyan` | Full 文言文. Maximum classical terseness. | -| **Wenyan-Ultra** | `/caveman wenyan-ultra` | Extreme. Ancient scholar on a budget. | - -Mode stick until changed or session end. - -## Skills - -| Skill | Trigger | What it do | -|-------|---------|-----------| -| **caveman-commit** | `/caveman-commit` | Terse commit messages. Conventional Commits. ≤50 char subject. | -| **caveman-review** | `/caveman-review` | One-line PR comments: `L42: bug: user null. Add guard.` | -| **caveman-compress** | `/caveman-compress ` | Compress .md files to caveman prose. Saves ~46% input tokens. | -| **caveman-help** | `/caveman-help` | This card. | - -## Deactivate - -Say "stop caveman" or "normal mode". Resume anytime with `/caveman`. - -## Configure Default Mode - -Default mode = `full`. Change it: - -**Environment variable** (highest priority): -```bash -export CAVEMAN_DEFAULT_MODE=ultra -``` - -**Config file** (`~/.config/caveman/config.json`): -```json -{ "defaultMode": "lite" } -``` - -Set `"off"` to disable auto-activation on session start. User can still activate manually with `/caveman`. - -Resolution: env var > config file > `full`. - -## More - -Full docs: https://github.com/JuliusBrussee/caveman diff --git a/.agents/skills/caveman-review/README.md b/.agents/skills/caveman-review/README.md deleted file mode 100644 index acf519fe7..000000000 --- a/.agents/skills/caveman-review/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# caveman-review - -One-line PR comments. Location, problem, fix. No throat-clearing. - -## What it does - -Generates code review comments in `L: . .` format. One line per finding. Severity emoji: 🔴 bug, 🟡 risk, 🔵 nit, ❓ question. Drops "I noticed that...", hedging, and restating what the diff already shows. Keeps exact line numbers, backticked symbols, and concrete fixes. - -Auto-clarity: drops terse mode for CVE-class security findings, architectural disagreements, and onboarding contexts where the author needs the *why*. Resumes terse for the rest. - -Output only — does not approve, request changes, or run linters. - -## How to invoke - -``` -/caveman-review -``` - -Also triggers on "review this PR", "code review", "review the diff". - -## Example output - -``` -L42: 🔴 bug: user can be null after .find(). Add guard before .email. -L88-140: 🔵 nit: 50-line fn does 4 things. Extract validate/normalize/persist. -L23: 🟡 risk: no retry on 429. Wrap in withBackoff(3). -L107: ❓ q: why drop the cache here? Reads on next request will miss. -``` - -## See also - -- [`SKILL.md`](./SKILL.md) — full LLM-facing instructions -- [Caveman README](../../README.md) — repo overview diff --git a/.agents/skills/caveman-review/SKILL.md b/.agents/skills/caveman-review/SKILL.md deleted file mode 100644 index 48f4adbfc..000000000 --- a/.agents/skills/caveman-review/SKILL.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -name: caveman-review -description: > - Ultra-compressed code review comments. Cuts noise from PR feedback while preserving - the actionable signal. Each comment is one line: location, problem, fix. Use when user - says "review this PR", "code review", "review the diff", "/review", or invokes - /caveman-review. Auto-triggers when reviewing pull requests. ---- - -Write code review comments terse and actionable. One line per finding. Location, problem, fix. No throat-clearing. - -## Rules - -**Format:** `L: . .` — or `:L: ...` when reviewing multi-file diffs. - -**Severity prefix (optional, when mixed):** -- `🔴 bug:` — broken behavior, will cause incident -- `🟡 risk:` — works but fragile (race, missing null check, swallowed error) -- `🔵 nit:` — style, naming, micro-optim. Author can ignore -- `❓ q:` — genuine question, not a suggestion - -**Drop:** -- "I noticed that...", "It seems like...", "You might want to consider..." -- "This is just a suggestion but..." — use `nit:` instead -- "Great work!", "Looks good overall but..." — say it once at the top, not per comment -- Restating what the line does — the reviewer can read the diff -- Hedging ("perhaps", "maybe", "I think") — if unsure use `q:` - -**Keep:** -- Exact line numbers -- Exact symbol/function/variable names in backticks -- Concrete fix, not "consider refactoring this" -- The *why* if the fix isn't obvious from the problem statement - -## Examples - -❌ "I noticed that on line 42 you're not checking if the user object is null before accessing the email property. This could potentially cause a crash if the user is not found in the database. You might want to add a null check here." - -✅ `L42: 🔴 bug: user can be null after .find(). Add guard before .email.` - -❌ "It looks like this function is doing a lot of things and might benefit from being broken up into smaller functions for readability." - -✅ `L88-140: 🔵 nit: 50-line fn does 4 things. Extract validate/normalize/persist.` - -❌ "Have you considered what happens if the API returns a 429? I think we should probably handle that case." - -✅ `L23: 🟡 risk: no retry on 429. Wrap in withBackoff(3).` - -## Auto-Clarity - -Drop terse mode for: security findings (CVE-class bugs need full explanation + reference), architectural disagreements (need rationale, not just a one-liner), and onboarding contexts where the author is new and needs the "why". In those cases write a normal paragraph, then resume terse for the rest. - -## Boundaries - -Reviews only — does not write the code fix, does not approve/request-changes, does not run linters. Output the comment(s) ready to paste into the PR. "stop caveman-review" or "normal mode": revert to verbose review style. \ No newline at end of file diff --git a/.agents/skills/caveman-stats/README.md b/.agents/skills/caveman-stats/README.md deleted file mode 100644 index 30b28f90d..000000000 --- a/.agents/skills/caveman-stats/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# caveman-stats - -Real session token receipts. No AI estimation. - -## What it does - -Reads the current Claude Code session log directly and reports actual input/output token usage plus estimated savings versus a non-caveman baseline. Numbers come from the JSONL session log on disk — the model itself does not compute or estimate them. Output is injected by the `caveman-mode-tracker` hook, which intercepts `/caveman-stats` and returns the formatted stats as a blocked-decision reason. - -Each run also writes a lifetime-savings suffix file used by the statusline badge (`⛏ 12.4k`). - -## How to invoke - -``` -/caveman-stats -``` - -## Example output - -``` -Session: 47 turns -Input: 12,304 tokens -Output: 3,891 tokens (caveman) -Baseline: 11,247 tokens (estimated without caveman) -Saved: 7,356 tokens (~65%) -``` - -## See also - -- [`SKILL.md`](./SKILL.md) — hook contract and mechanics -- [Caveman README](../../README.md) — repo overview diff --git a/.agents/skills/caveman-stats/SKILL.md b/.agents/skills/caveman-stats/SKILL.md deleted file mode 100644 index a7348ac62..000000000 --- a/.agents/skills/caveman-stats/SKILL.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: caveman-stats -description: > - Show real token usage and estimated savings for the current session. - Reads directly from the Claude Code session log — no AI estimation. - Triggers on /caveman-stats. Output is injected by the mode-tracker hook; - the model itself does not compute the numbers. ---- - -This skill is delivered by `hooks/caveman-stats.js` (read by `hooks/caveman-mode-tracker.js` on `/caveman-stats`). The model does not need to do anything when this skill fires — the hook returns `decision: "block"` with the formatted stats as the reason. The user sees the numbers immediately. diff --git a/.agents/skills/caveman/README.md b/.agents/skills/caveman/README.md deleted file mode 100644 index d749b8343..000000000 --- a/.agents/skills/caveman/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# caveman - -Talk like smart caveman. Same brain, fewer tokens. - -## What it does - -Compress every model response to caveman-style prose. Drops articles, filler, pleasantries, and hedging. Keeps every technical detail, code block, error string, and symbol exact. Cuts ~65-75% of output tokens with full accuracy preserved. Mode persists for the whole session until changed or stopped. - -Six intensity levels: - -| Level | What change | -|-------|-------------| -| `lite` | Drop filler/hedging. Sentences stay full. Professional but tight. | -| `full` | Default. Drop articles, fragments OK, short synonyms. | -| `ultra` | Bare fragments. Abbreviations (DB, auth, fn). Arrows for causality. | -| `wenyan-lite` | Classical Chinese register, light compression. | -| `wenyan-full` | Maximum 文言文. 80-90% character reduction. | -| `wenyan-ultra` | Extreme classical compression. | - -Auto-clarity rule: caveman drops to normal prose for security warnings, irreversible-action confirmations, multi-step sequences where fragment ambiguity risks misread, and when user repeats a question. Resumes after the clear part. - -## How to invoke - -``` -/caveman # full mode (default) -/caveman lite # lighter compression -/caveman ultra # extreme compression -/caveman wenyan # classical Chinese -stop caveman # back to normal prose -``` - -## Example output - -Question: "Why does my React component re-render?" - -Normal prose: -> Your component re-renders because you create a new object reference each render. Wrapping it in `useMemo` will fix the issue. - -Caveman (full): -> New object ref each render. Inline object prop = new ref = re-render. Wrap in `useMemo`. - -Caveman (ultra): -> Inline obj prop → new ref → re-render. `useMemo`. - -## See also - -- [`SKILL.md`](./SKILL.md) — full LLM-facing instructions -- [Caveman README](../../README.md) — repo overview, install, benchmarks diff --git a/.agents/skills/caveman/SKILL.md b/.agents/skills/caveman/SKILL.md deleted file mode 100644 index 073d6bb10..000000000 --- a/.agents/skills/caveman/SKILL.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -name: caveman -description: > - Ultra-compressed communication mode. Cuts token usage ~75% by speaking like caveman - while keeping full technical accuracy. Supports intensity levels: lite, full (default), ultra, - wenyan-lite, wenyan-full, wenyan-ultra. - Use when user says "caveman mode", "talk like caveman", "use caveman", "less tokens", - "be brief", or invokes /caveman. Also auto-triggers when token efficiency is requested. ---- - -Respond terse like smart caveman. All technical substance stay. Only fluff die. - -## Persistence - -ACTIVE EVERY RESPONSE. No revert after many turns. No filler drift. Still active if unsure. Off only: "stop caveman" / "normal mode". - -Default: **full**. Switch: `/caveman lite|full|ultra`. - -## Rules - -Drop: articles (a/an/the), filler (just/really/basically/actually/simply), pleasantries (sure/certainly/of course/happy to), hedging. Fragments OK. Short synonyms (big not extensive, fix not "implement a solution for"). Technical terms exact. Code blocks unchanged. Errors quoted exact. - -Pattern: `[thing] [action] [reason]. [next step].` - -Not: "Sure! I'd be happy to help you with that. The issue you're experiencing is likely caused by..." -Yes: "Bug in auth middleware. Token expiry check use `<` not `<=`. Fix:" - -## Intensity - -| Level | What change | -|-------|------------| -| **lite** | No filler/hedging. Keep articles + full sentences. Professional but tight | -| **full** | Drop articles, fragments OK, short synonyms. Classic caveman | -| **ultra** | Abbreviate prose words (DB/auth/config/req/res/fn/impl), strip conjunctions, arrows for causality (X → Y), one word when one word enough. Code symbols, function names, API names, error strings: never abbreviate | -| **wenyan-lite** | Semi-classical. Drop filler/hedging but keep grammar structure, classical register | -| **wenyan-full** | Maximum classical terseness. Fully 文言文. 80-90% character reduction. Classical sentence patterns, verbs precede objects, subjects often omitted, classical particles (之/乃/為/其) | -| **wenyan-ultra** | Extreme abbreviation while keeping classical Chinese feel. Maximum compression, ultra terse | - -Example — "Why React component re-render?" -- lite: "Your component re-renders because you create a new object reference each render. Wrap it in `useMemo`." -- full: "New object ref each render. Inline object prop = new ref = re-render. Wrap in `useMemo`." -- ultra: "Inline obj prop → new ref → re-render. `useMemo`." -- wenyan-lite: "組件頻重繪,以每繪新生對象參照故。以 useMemo 包之。" -- wenyan-full: "物出新參照,致重繪。useMemo .Wrap之。" -- wenyan-ultra: "新參照→重繪。useMemo Wrap。" - -Example — "Explain database connection pooling." -- lite: "Connection pooling reuses open connections instead of creating new ones per request. Avoids repeated handshake overhead." -- full: "Pool reuse open DB connections. No new connection per request. Skip handshake overhead." -- ultra: "Pool = reuse DB conn. Skip handshake → fast under load." -- wenyan-full: "池reuse open connection。不每req新開。skip handshake overhead。" -- wenyan-ultra: "池reuse conn。skip handshake → fast。" - -## Auto-Clarity - -Drop caveman when: -- Security warnings -- Irreversible action confirmations -- Multi-step sequences where fragment order or omitted conjunctions risk misread -- Compression itself creates technical ambiguity (e.g., `"migrate table drop column backup first"` — order unclear without articles/conjunctions) -- User asks to clarify or repeats question - -Resume caveman after clear part done. - -Example — destructive op: -> **Warning:** This will permanently delete all rows in the `users` table and cannot be undone. -> ```sql -> DROP TABLE users; -> ``` -> Caveman resume. Verify backup exist first. - -## Boundaries - -Code/commits/PRs: write normal. "stop caveman" or "normal mode": revert. Level persist until changed or session end. \ No newline at end of file diff --git a/public/images/AlgorithmComparison-1024.webp b/public/images/AlgorithmComparison-1024.webp deleted file mode 100644 index b699bb244..000000000 Binary files a/public/images/AlgorithmComparison-1024.webp and /dev/null differ diff --git a/public/images/AlgorithmComparison-320.webp b/public/images/AlgorithmComparison-320.webp deleted file mode 100644 index 527cdbdfe..000000000 Binary files a/public/images/AlgorithmComparison-320.webp and /dev/null differ diff --git a/public/images/AlgorithmComparison-640.webp b/public/images/AlgorithmComparison-640.webp deleted file mode 100644 index 64e60b60f..000000000 Binary files a/public/images/AlgorithmComparison-640.webp and /dev/null differ diff --git a/public/images/EulerCycle-1024.webp b/public/images/EulerCycle-1024.webp deleted file mode 100644 index b6ef50333..000000000 Binary files a/public/images/EulerCycle-1024.webp and /dev/null differ diff --git a/public/images/EulerCycle-320.webp b/public/images/EulerCycle-320.webp deleted file mode 100644 index e2863b1e8..000000000 Binary files a/public/images/EulerCycle-320.webp and /dev/null differ diff --git a/public/images/EulerCycle-640.webp b/public/images/EulerCycle-640.webp deleted file mode 100644 index d1844861d..000000000 Binary files a/public/images/EulerCycle-640.webp and /dev/null differ diff --git a/public/images/EulerExample-1024.webp b/public/images/EulerExample-1024.webp deleted file mode 100644 index cd25461ae..000000000 Binary files a/public/images/EulerExample-1024.webp and /dev/null differ diff --git a/public/images/EulerExample-320.webp b/public/images/EulerExample-320.webp deleted file mode 100644 index 1ee8f818f..000000000 Binary files a/public/images/EulerExample-320.webp and /dev/null differ diff --git a/public/images/EulerExample-640.webp b/public/images/EulerExample-640.webp deleted file mode 100644 index 4b726e4f7..000000000 Binary files a/public/images/EulerExample-640.webp and /dev/null differ diff --git a/public/images/GitHub_Logo-1024.webp b/public/images/GitHub_Logo-1024.webp deleted file mode 100644 index 2eeaad855..000000000 Binary files a/public/images/GitHub_Logo-1024.webp and /dev/null differ diff --git a/public/images/GitHub_Logo-320.webp b/public/images/GitHub_Logo-320.webp deleted file mode 100644 index 1f3c74936..000000000 Binary files a/public/images/GitHub_Logo-320.webp and /dev/null differ diff --git a/public/images/GitHub_Logo-640.webp b/public/images/GitHub_Logo-640.webp deleted file mode 100644 index 0ff162727..000000000 Binary files a/public/images/GitHub_Logo-640.webp and /dev/null differ diff --git a/public/images/GunControl-1024.webp b/public/images/GunControl-1024.webp deleted file mode 100644 index bdbf203ba..000000000 Binary files a/public/images/GunControl-1024.webp and /dev/null differ diff --git a/public/images/GunControl-320.webp b/public/images/GunControl-320.webp deleted file mode 100644 index 671e42e69..000000000 Binary files a/public/images/GunControl-320.webp and /dev/null differ diff --git a/public/images/GunControl-640.webp b/public/images/GunControl-640.webp deleted file mode 100644 index 4a9504a74..000000000 Binary files a/public/images/GunControl-640.webp and /dev/null differ diff --git a/public/images/HSV-1024.webp b/public/images/HSV-1024.webp deleted file mode 100644 index f33c772ca..000000000 Binary files a/public/images/HSV-1024.webp and /dev/null differ diff --git a/public/images/HSV-320.webp b/public/images/HSV-320.webp deleted file mode 100644 index bc5cc8fee..000000000 Binary files a/public/images/HSV-320.webp and /dev/null differ diff --git a/public/images/HSV-640.webp b/public/images/HSV-640.webp deleted file mode 100644 index 574167fc2..000000000 Binary files a/public/images/HSV-640.webp and /dev/null differ diff --git a/public/images/Hangman-1024.webp b/public/images/Hangman-1024.webp deleted file mode 100644 index 1d9e8f242..000000000 Binary files a/public/images/Hangman-1024.webp and /dev/null differ diff --git a/public/images/Hangman-320.webp b/public/images/Hangman-320.webp deleted file mode 100644 index 58e96a8db..000000000 Binary files a/public/images/Hangman-320.webp and /dev/null differ diff --git a/public/images/Hangman-640.webp b/public/images/Hangman-640.webp deleted file mode 100644 index 96f879550..000000000 Binary files a/public/images/Hangman-640.webp and /dev/null differ diff --git a/public/images/KnightsTour-1024.webp b/public/images/KnightsTour-1024.webp deleted file mode 100644 index 9f2fb5b7d..000000000 Binary files a/public/images/KnightsTour-1024.webp and /dev/null differ diff --git a/public/images/KnightsTour-320.webp b/public/images/KnightsTour-320.webp deleted file mode 100644 index f2c1f1031..000000000 Binary files a/public/images/KnightsTour-320.webp and /dev/null differ diff --git a/public/images/KnightsTour-640.webp b/public/images/KnightsTour-640.webp deleted file mode 100644 index fd6222d9b..000000000 Binary files a/public/images/KnightsTour-640.webp and /dev/null differ diff --git a/public/images/LI-Logo-1024.webp b/public/images/LI-Logo-1024.webp deleted file mode 100644 index 54fc56e95..000000000 Binary files a/public/images/LI-Logo-1024.webp and /dev/null differ diff --git a/public/images/LI-Logo-320.webp b/public/images/LI-Logo-320.webp deleted file mode 100644 index 305710b9f..000000000 Binary files a/public/images/LI-Logo-320.webp and /dev/null differ diff --git a/public/images/LI-Logo-640.webp b/public/images/LI-Logo-640.webp deleted file mode 100644 index 408232987..000000000 Binary files a/public/images/LI-Logo-640.webp and /dev/null differ diff --git a/public/images/MJB-1024.webp b/public/images/MJB-1024.webp deleted file mode 100644 index 1327d61e2..000000000 Binary files a/public/images/MJB-1024.webp and /dev/null differ diff --git a/public/images/MJB-320.webp b/public/images/MJB-320.webp deleted file mode 100644 index e9e6407d1..000000000 Binary files a/public/images/MJB-320.webp and /dev/null differ diff --git a/public/images/MJB-640.webp b/public/images/MJB-640.webp deleted file mode 100644 index a44ca2a9a..000000000 Binary files a/public/images/MJB-640.webp and /dev/null differ diff --git a/public/images/Me-1024.webp b/public/images/Me-1024.webp deleted file mode 100644 index a7eb28531..000000000 Binary files a/public/images/Me-1024.webp and /dev/null differ diff --git a/public/images/Me-320.webp b/public/images/Me-320.webp deleted file mode 100644 index 5915b1717..000000000 Binary files a/public/images/Me-320.webp and /dev/null differ diff --git a/public/images/Me-640.webp b/public/images/Me-640.webp deleted file mode 100644 index a29388109..000000000 Binary files a/public/images/Me-640.webp and /dev/null differ diff --git a/public/images/Palish-1024.webp b/public/images/Palish-1024.webp deleted file mode 100644 index 5756f756c..000000000 Binary files a/public/images/Palish-1024.webp and /dev/null differ diff --git a/public/images/Palish-320.webp b/public/images/Palish-320.webp deleted file mode 100644 index 913e9fddb..000000000 Binary files a/public/images/Palish-320.webp and /dev/null differ diff --git a/public/images/Palish-640.webp b/public/images/Palish-640.webp deleted file mode 100644 index 3579733f2..000000000 Binary files a/public/images/Palish-640.webp and /dev/null differ diff --git a/public/images/Resume-1024.webp b/public/images/Resume-1024.webp deleted file mode 100644 index 2821a3777..000000000 Binary files a/public/images/Resume-1024.webp and /dev/null differ diff --git a/public/images/Resume-320.webp b/public/images/Resume-320.webp deleted file mode 100644 index b725a8a24..000000000 Binary files a/public/images/Resume-320.webp and /dev/null differ diff --git a/public/images/Resume-640.webp b/public/images/Resume-640.webp deleted file mode 100644 index 784585f34..000000000 Binary files a/public/images/Resume-640.webp and /dev/null differ diff --git a/public/images/SRS-1024.webp b/public/images/SRS-1024.webp deleted file mode 100644 index 927b9cae8..000000000 Binary files a/public/images/SRS-1024.webp and /dev/null differ diff --git a/public/images/SRS-320.webp b/public/images/SRS-320.webp deleted file mode 100644 index 1ce51a1f6..000000000 Binary files a/public/images/SRS-320.webp and /dev/null differ diff --git a/public/images/SRS-640.webp b/public/images/SRS-640.webp deleted file mode 100644 index ed6893791..000000000 Binary files a/public/images/SRS-640.webp and /dev/null differ diff --git a/public/images/ShowingOff-1024.webp b/public/images/ShowingOff-1024.webp deleted file mode 100644 index 39b04d438..000000000 Binary files a/public/images/ShowingOff-1024.webp and /dev/null differ diff --git a/public/images/ShowingOff-320.webp b/public/images/ShowingOff-320.webp deleted file mode 100644 index fb8732ae6..000000000 Binary files a/public/images/ShowingOff-320.webp and /dev/null differ diff --git a/public/images/ShowingOff-640.webp b/public/images/ShowingOff-640.webp deleted file mode 100644 index cc7eae1f4..000000000 Binary files a/public/images/ShowingOff-640.webp and /dev/null differ diff --git a/public/images/StateMachine-1024.webp b/public/images/StateMachine-1024.webp deleted file mode 100644 index 7261b2885..000000000 Binary files a/public/images/StateMachine-1024.webp and /dev/null differ diff --git a/public/images/StateMachine-320.webp b/public/images/StateMachine-320.webp deleted file mode 100644 index 3fe026c2b..000000000 Binary files a/public/images/StateMachine-320.webp and /dev/null differ diff --git a/public/images/StateMachine-640.webp b/public/images/StateMachine-640.webp deleted file mode 100644 index e07a24ec3..000000000 Binary files a/public/images/StateMachine-640.webp and /dev/null differ diff --git a/public/images/TextAnalyzer-1024.webp b/public/images/TextAnalyzer-1024.webp deleted file mode 100644 index 7eb983486..000000000 Binary files a/public/images/TextAnalyzer-1024.webp and /dev/null differ diff --git a/public/images/TextAnalyzer-320.webp b/public/images/TextAnalyzer-320.webp deleted file mode 100644 index e22dd2618..000000000 Binary files a/public/images/TextAnalyzer-320.webp and /dev/null differ diff --git a/public/images/TextAnalyzer-640.webp b/public/images/TextAnalyzer-640.webp deleted file mode 100644 index 5d256f208..000000000 Binary files a/public/images/TextAnalyzer-640.webp and /dev/null differ diff --git a/public/images/Twitter-Logo-1024.webp b/public/images/Twitter-Logo-1024.webp deleted file mode 100644 index 8d4ca4762..000000000 Binary files a/public/images/Twitter-Logo-1024.webp and /dev/null differ diff --git a/public/images/Twitter-Logo-320.webp b/public/images/Twitter-Logo-320.webp deleted file mode 100644 index d3d7d04a6..000000000 Binary files a/public/images/Twitter-Logo-320.webp and /dev/null differ diff --git a/public/images/Twitter-Logo-640.webp b/public/images/Twitter-Logo-640.webp deleted file mode 100644 index f165a657c..000000000 Binary files a/public/images/Twitter-Logo-640.webp and /dev/null differ diff --git a/public/images/W&H-1024.webp b/public/images/W&H-1024.webp deleted file mode 100644 index 0f2cdf0fc..000000000 Binary files a/public/images/W&H-1024.webp and /dev/null differ diff --git a/public/images/W&H-320.webp b/public/images/W&H-320.webp deleted file mode 100644 index f80fe41d5..000000000 Binary files a/public/images/W&H-320.webp and /dev/null differ diff --git a/public/images/W&H-640.webp b/public/images/W&H-640.webp deleted file mode 100644 index 460576f90..000000000 Binary files a/public/images/W&H-640.webp and /dev/null differ diff --git a/public/images/background1-1024.webp b/public/images/background1-1024.webp deleted file mode 100644 index 9dc3625c0..000000000 Binary files a/public/images/background1-1024.webp and /dev/null differ diff --git a/public/images/background1-320.webp b/public/images/background1-320.webp deleted file mode 100644 index 11854c233..000000000 Binary files a/public/images/background1-320.webp and /dev/null differ diff --git a/public/images/background1-640.webp b/public/images/background1-640.webp deleted file mode 100644 index 72c9e58ea..000000000 Binary files a/public/images/background1-640.webp and /dev/null differ diff --git a/public/images/background2-1024.webp b/public/images/background2-1024.webp deleted file mode 100644 index f66625f83..000000000 Binary files a/public/images/background2-1024.webp and /dev/null differ diff --git a/public/images/background2-320.webp b/public/images/background2-320.webp deleted file mode 100644 index b1c9c475a..000000000 Binary files a/public/images/background2-320.webp and /dev/null differ diff --git a/public/images/background2-640.webp b/public/images/background2-640.webp deleted file mode 100644 index 267d529e0..000000000 Binary files a/public/images/background2-640.webp and /dev/null differ diff --git a/public/images/background3-1024.webp b/public/images/background3-1024.webp deleted file mode 100644 index cc4610351..000000000 Binary files a/public/images/background3-1024.webp and /dev/null differ diff --git a/public/images/background3-320.webp b/public/images/background3-320.webp deleted file mode 100644 index f161d2500..000000000 Binary files a/public/images/background3-320.webp and /dev/null differ diff --git a/public/images/background3-640.webp b/public/images/background3-640.webp deleted file mode 100644 index cb383c02e..000000000 Binary files a/public/images/background3-640.webp and /dev/null differ diff --git a/public/images/itchio-logo-black-1024.webp b/public/images/itchio-logo-black-1024.webp deleted file mode 100644 index 111076b63..000000000 Binary files a/public/images/itchio-logo-black-1024.webp and /dev/null differ diff --git a/public/images/itchio-logo-black-320.webp b/public/images/itchio-logo-black-320.webp deleted file mode 100644 index 8f4f37223..000000000 Binary files a/public/images/itchio-logo-black-320.webp and /dev/null differ diff --git a/public/images/itchio-logo-black-640.webp b/public/images/itchio-logo-black-640.webp deleted file mode 100644 index f3a3ad06b..000000000 Binary files a/public/images/itchio-logo-black-640.webp and /dev/null differ diff --git a/public/images/skin2-1024.webp b/public/images/skin2-1024.webp deleted file mode 100644 index 23953dbe7..000000000 Binary files a/public/images/skin2-1024.webp and /dev/null differ diff --git a/public/images/skin2-320.webp b/public/images/skin2-320.webp deleted file mode 100644 index db29d22cb..000000000 Binary files a/public/images/skin2-320.webp and /dev/null differ diff --git a/public/images/skin2-640.webp b/public/images/skin2-640.webp deleted file mode 100644 index a4f9435b6..000000000 Binary files a/public/images/skin2-640.webp and /dev/null differ