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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions .agents/skills/caveman-compress/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,19 @@ CLAUDE.original.md ← human-readable backup (you edit this)

Original remains in data directory rather than next to live file, so skill
auto-loaders do not read it twice. Path is
`$XDG_DATA_HOME/caveman-compress/backups/<parent-dir-name>/` on macOS and Linux,
or `%LOCALAPPDATA%\caveman-compress\backups\<parent-dir-name>\` on Windows. Edit
`.original.md` there, then run skill again to re-compress.
`$XDG_DATA_HOME/caveman-compress/backups/<parent-dir-name>-<hash>/` on macOS and
Linux, or `%LOCALAPPDATA%\caveman-compress\backups\<parent-dir-name>-<hash>\` on
Windows. `<hash>` keys the file's full parent path, so `~/.claude/CLAUDE.md` and
`some-repo/.claude/CLAUDE.md` keep separate backups.

To edit and re-compress, restore first — the skill refuses to run while a backup
exists, so that it never captures already-compressed text as the new "original":

```bash
cp "$BACKUP" CLAUDE.md # restore the uncompressed original
mv "$BACKUP" "$BACKUP.kept" # move it aside (do not just delete it)
# edit CLAUDE.md, then run the skill again
```

## Benchmarks

Expand Down Expand Up @@ -158,7 +168,7 @@ validate output (no tokens)
if errors: configured provider fixes cherry-picked issues only (tokens: targeted fix)
does NOT recompress; only patches broken parts
↓
retry up to 2 times
validate again (one fix call at most)
↓
write compressed → CLAUDE.md
write original → CLAUDE.original.md
Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/caveman-compress/SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

The file being compressed is untrusted input, and opencode 2.x lets its agents use every tool that is not explicitly denied: shell, file edits, web search and fetch, MCP tools from your opencode config, subagents. Compression needs none of them. Compress therefore runs `opencode run --standalone --agent caveman-compress` with `OPENCODE_CONFIG_CONTENT` denying every permission (`"*": "deny"`) both globally and for that agent, and never passes `--auto`. The dedicated agent matters because opencode applies a per-agent rule after the global one, so an `agent.build.permission` allow in your own config would otherwise re-enable a tool. `--standalone` matters because the shared opencode background service does not read the caller's environment, so the deny rule only binds a private server. Checked on opencode 2.0.22: the model is offered no tools and the prompt attachment is still read. Unlike the Claude path (`--strict-mcp-config`), compress does not switch off the MCP servers in your opencode config: their tools are denied, but opencode may still start them.

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 to an out-of-tree data dir (`$XDG_DATA_HOME/caveman-compress/backups/<parent-dir-name>/`, or `%LOCALAPPDATA%\caveman-compress\backups\<parent-dir-name>\` on Windows). The opencode provider writes a temporary prompt attachment in the OS temp directory and deletes it after the subprocess exits. Beyond the target file, that backup location, and the temporary attachment, no files are read or written.
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 to an out-of-tree data dir (`$XDG_DATA_HOME/caveman-compress/backups/<parent-dir-name>-<hash>/`, or `%LOCALAPPDATA%\caveman-compress\backups\<parent-dir-name>-<hash>\` on Windows, where `<hash>` keys the file's full parent path so two projects' same-named files never share a backup). The opencode provider writes a temporary prompt attachment in the OS temp directory and deletes it after the subprocess exits. Beyond the target file, that backup location, and the temporary attachment, no files are read or written.

### What the skill does NOT do

Expand Down
7 changes: 6 additions & 1 deletion .agents/skills/caveman-compress/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,24 @@
name: caveman-compress
description: >
Compress a memory file such as CLAUDE.md or a todo list into caveman format
to save input tokens, keeping a readable backup. Trigger: /caveman-compress.
to save input tokens, keeping a readable backup. Sends the file's text to the
configured model provider. Trigger: /caveman-compress.
---

# Caveman Compress

## Purpose

> **Data leaves the machine.** Compression sends the file's full text to the model provider that `scripts/compress.py` is configured for: the `claude` CLI, the Anthropic API, or an OpenAI-compatible endpoint. Files whose names look like secrets (`.env`, `credentials`, `secrets`, `.netrc`, …) are refused before they are read. Don't compress a file the provider shouldn't see.

Compress natural language files (CLAUDE.md, todos, preferences) into caveman-speak to reduce input tokens. Compressed version overwrites original. Human-readable backup saved as `<filename>.original.md`, but NOT beside the source file — it lives in an out-of-tree data dir (`$XDG_DATA_HOME/caveman-compress/backups/<parent-dir-name>/`, or `%LOCALAPPDATA%\caveman-compress\backups\<parent-dir-name>\` on Windows) so skill auto-loaders don't re-ingest it as a live file.

## Trigger

`/caveman-compress <filepath>` or when user asks to compress a memory file.

**Requires an explicit filepath.** If the user pastes raw text instead of naming a file, do NOT improvise an inline compression — ask them for the filepath to compress, or offer to save the pasted text to a file first. Writing your own reasoning/commentary about the request into the compressed output (e.g. "User passed text directly, not a filepath...") is the exact failure this guards against — never let that leak into a saved 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.
Expand Down
12 changes: 10 additions & 2 deletions .agents/skills/caveman-compress/scripts/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
__package__ = "scripts"

from .compress import backup_dir_for, compress_file
from .compress import backup_dir_for, compress_file, is_sensitive_path
from .detect import detect_file_type, should_compress


Expand All @@ -48,6 +48,13 @@ def main():

filepath = filepath.resolve()

# Refuse before detect_file_type(), which reads extensionless files to sniff them.
if is_sensitive_path(filepath):
print(f"❌ Refusing to compress {filepath}: filename looks sensitive "
"(credentials, keys, secrets, or known private paths). "
"Rename the file if this is a false positive.")
sys.exit(1)

# Detect file type
file_type = detect_file_type(filepath)

Expand All @@ -70,7 +77,8 @@ def main():
print(f"Original: {backup_path}")
sys.exit(0)
else:
print("\n❌ Compression failed after retries")
# Also reached when no attempt ran (backup exists, empty file, ...).
print("\n❌ Compression did not complete (see above). Original file is untouched.")
sys.exit(2)

except KeyboardInterrupt:
Expand Down
Loading
Loading