dsh-caveman 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: caveman-commit
3
+ description: >
4
+ Ultra-compressed commit message generator. Cuts noise from commit messages while preserving
5
+ intent and reasoning. Conventional Commits format. Subject ≤50 chars, body only when "why"
6
+ isn't obvious. Use when user says "write a commit", "commit message", "generate commit",
7
+ "/commit", or invokes /caveman-commit. Auto-triggers when staging changes.
8
+ ---
9
+
10
+ Write commit messages terse and exact. Conventional Commits format. No fluff. Why over what.
11
+
12
+ ## Rules
13
+
14
+ **Subject line:**
15
+ - `<type>(<scope>): <imperative summary>` — `<scope>` optional
16
+ - Types: `feat`, `fix`, `refactor`, `perf`, `docs`, `test`, `chore`, `build`, `ci`, `style`, `revert`
17
+ - Imperative mood: "add", "fix", "remove" — not "added", "adds", "adding"
18
+ - ≤50 chars when possible, hard cap 72
19
+ - No trailing period
20
+ - Match project convention for capitalization after the colon
21
+
22
+ **Body (only if needed):**
23
+ - Skip entirely when subject is self-explanatory
24
+ - Add body only for: non-obvious *why*, breaking changes, migration notes, linked issues
25
+ - Wrap at 72 chars
26
+ - Bullets `-` not `*`
27
+ - Reference issues/PRs at end: `Closes #42`, `Refs #17`
28
+
29
+ **What NEVER goes in:**
30
+ - "This commit does X", "I", "we", "now", "currently" — the diff says what
31
+ - "As requested by..." — use Co-authored-by trailer
32
+ - "Generated with Claude Code" or any AI attribution — unless the user's own rule requires an `Assisted-by`/AI-attribution trailer, then add it as a trailer
33
+ - Emoji (unless project convention requires)
34
+ - Restating the file name when scope already says it
35
+
36
+ ## Examples
37
+
38
+ Diff: new endpoint for user profile with body explaining the why
39
+ - ❌ "feat: add a new endpoint to get user profile information from the database"
40
+ - ✅
41
+ ```
42
+ feat(api): add GET /users/:id/profile
43
+
44
+ Mobile client needs profile data without the full user payload
45
+ to reduce LTE bandwidth on cold-launch screens.
46
+
47
+ Closes #128
48
+ ```
49
+
50
+ Diff: breaking API change
51
+ - ✅
52
+ ```
53
+ feat(api)!: rename /v1/orders to /v1/checkout
54
+
55
+ BREAKING CHANGE: clients on /v1/orders must migrate to /v1/checkout
56
+ before 2026-06-01. Old route returns 410 after that date.
57
+ ```
58
+
59
+ ## Auto-Clarity
60
+
61
+ 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.
62
+
63
+ ## Boundaries
64
+
65
+ 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.
@@ -0,0 +1,163 @@
1
+ <p align="center">
2
+ <img src="https://em-content.zobj.net/source/apple/391/rock_1faa8.png" width="80" />
3
+ </p>
4
+
5
+ <h1 align="center">caveman-compress</h1>
6
+
7
+ <p align="center">
8
+ <strong>shrink memory file. save token every session.</strong>
9
+ </p>
10
+
11
+ ---
12
+
13
+ A Claude Code skill that compresses your project memory files (`CLAUDE.md`, todos, preferences) into caveman format — so every session loads fewer tokens automatically.
14
+
15
+ Claude read `CLAUDE.md` on every session start. If file big, cost big. Caveman make file small. Cost go down forever.
16
+
17
+ ## What It Do
18
+
19
+ ```
20
+ /caveman-compress CLAUDE.md
21
+ ```
22
+
23
+ ```
24
+ CLAUDE.md ← compressed (Claude reads this — fewer tokens every session)
25
+ CLAUDE.original.md ← human-readable backup (you edit this)
26
+ ```
27
+
28
+ Original never lost. You can read and edit `.original.md`. Run skill again to re-compress after edits.
29
+
30
+ ## Benchmarks
31
+
32
+ Real results on real project files:
33
+
34
+ | File | Original | Compressed | Saved |
35
+ |------|----------:|----------:|------:|
36
+ | `claude-md-preferences.md` | 706 | 285 | **59.6%** |
37
+ | `project-notes.md` | 1145 | 535 | **53.3%** |
38
+ | `claude-md-project.md` | 1122 | 636 | **43.3%** |
39
+ | `todo-list.md` | 627 | 388 | **38.1%** |
40
+ | `mixed-with-code.md` | 888 | 560 | **36.9%** |
41
+ | **Average** | **898** | **481** | **46%** |
42
+
43
+ All validations passed ✅ — headings, code blocks, URLs, file paths preserved exactly.
44
+
45
+ ## Before / After
46
+
47
+ <table>
48
+ <tr>
49
+ <td width="50%">
50
+
51
+ ### 📄 Original (706 tokens)
52
+
53
+ > "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."
54
+
55
+ </td>
56
+ <td width="50%">
57
+
58
+ ### <img src="../../docs/assets/dancing-rock.svg" width="20" height="20" alt="rock"/> Caveman (285 tokens)
59
+
60
+ > "Prefer TypeScript strict mode always. No `any` unless unavoidable — comment why if used. Proper types catch bugs early."
61
+
62
+ </td>
63
+ </tr>
64
+ </table>
65
+
66
+ **Same instructions. 60% fewer tokens. Every. Single. Session.**
67
+
68
+ ## Security
69
+
70
+ `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.
71
+
72
+ ## Install
73
+
74
+ Compress is built in with the `caveman` plugin. Install `caveman` once, then use `/caveman-compress`.
75
+
76
+ If you need local files, the compress skill lives at:
77
+
78
+ ```bash
79
+ caveman-compress/
80
+ ```
81
+
82
+ **Requires:** Python 3.10+
83
+
84
+ ## Usage
85
+
86
+ ```
87
+ /caveman-compress <filepath>
88
+ ```
89
+
90
+ Examples:
91
+ ```
92
+ /caveman-compress CLAUDE.md
93
+ /caveman-compress docs/preferences.md
94
+ /caveman-compress todos.md
95
+ ```
96
+
97
+ ### What files work
98
+
99
+ | Type | Compress? |
100
+ |------|-----------|
101
+ | `.md`, `.txt`, `.rst`, `.typ`, `.typst`, `.tex` | ✅ Yes |
102
+ | Extensionless natural language | ✅ Yes |
103
+ | `.py`, `.js`, `.ts`, `.json`, `.yaml` | ❌ Skip (code/config) |
104
+ | `*.original.md` | ❌ Skip (backup files) |
105
+
106
+ ## How It Work
107
+
108
+ ```
109
+ /caveman-compress CLAUDE.md
110
+
111
+ detect file type (no tokens)
112
+
113
+ Claude compresses (tokens — one call)
114
+
115
+ validate output (no tokens)
116
+ checks: headings, code blocks, URLs, file paths, bullets
117
+
118
+ if errors: Claude fixes cherry-picked issues only (tokens — targeted fix)
119
+ does NOT recompress — only patches broken parts
120
+
121
+ retry up to 2 times
122
+
123
+ write compressed → CLAUDE.md
124
+ write original → CLAUDE.original.md
125
+ ```
126
+
127
+ Only two things use tokens: initial compression + targeted fix if validation fails. Everything else is local Python.
128
+
129
+ ## What Is Preserved
130
+
131
+ Caveman compress natural language. It never touch:
132
+
133
+ - Code blocks (` ``` ` fenced or indented)
134
+ - Inline code (`` `backtick content` ``)
135
+ - URLs and links
136
+ - File paths (`/src/components/...`)
137
+ - Commands (`npm install`, `git commit`)
138
+ - Technical terms, library names, API names
139
+ - Headings (exact text preserved)
140
+ - Tables (structure preserved, cell text compressed)
141
+ - Dates, version numbers, numeric values
142
+
143
+ ## Why This Matter
144
+
145
+ `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.
146
+
147
+ Caveman cut that by ~46% on average. Same instructions. Same accuracy. Less waste.
148
+
149
+ ```
150
+ ┌────────────────────────────────────────────┐
151
+ │ TOKEN SAVINGS PER FILE █████ 46% │
152
+ │ SESSIONS THAT BENEFIT ██████████ 100% │
153
+ │ INFORMATION PRESERVED ██████████ 100% │
154
+ │ SETUP TIME █ 1x │
155
+ └────────────────────────────────────────────┘
156
+ ```
157
+
158
+ ## Part of Caveman
159
+
160
+ This skill is part of the [caveman](https://github.com/JuliusBrussee/caveman) toolkit — making Claude use fewer tokens without losing accuracy.
161
+
162
+ - **caveman** — make Claude *speak* like caveman (cuts response tokens ~65%)
163
+ - **caveman-compress** — make Claude *read* less (cuts context tokens ~46%)
@@ -0,0 +1,31 @@
1
+ # Security
2
+
3
+ ## Snyk High Risk Rating
4
+
5
+ `caveman-compress` receives a Snyk High Risk rating due to static analysis heuristics. This document explains what the skill does and does not do.
6
+
7
+ ### What triggers the rating
8
+
9
+ 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.
10
+
11
+ 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.
12
+
13
+ ### What the skill does NOT do
14
+
15
+ - Does not execute user file content as code
16
+ - Does not make network requests except to Anthropic's API (via SDK or CLI)
17
+ - Does not access files outside the path the user provides
18
+ - Does not use shell=True or string interpolation in subprocess calls
19
+ - Does not collect or transmit any data beyond the file being compressed
20
+
21
+ ### Auth behavior
22
+
23
+ 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.
24
+
25
+ ### File size limit
26
+
27
+ Files larger than 500KB are rejected before any API call is made.
28
+
29
+ ### Reporting a vulnerability
30
+
31
+ If you believe you've found a genuine security issue, please open a GitHub issue with the label `security`.
@@ -0,0 +1,111 @@
1
+ ---
2
+ name: caveman-compress
3
+ description: >
4
+ Compress natural language memory files (CLAUDE.md, todos, preferences) into caveman format
5
+ to save input tokens. Preserves all technical substance, code, URLs, and structure.
6
+ Compressed version overwrites the original file. Human-readable backup saved as FILE.original.md.
7
+ Trigger: /caveman-compress FILEPATH or "compress memory file"
8
+ ---
9
+
10
+ # Caveman Compress
11
+
12
+ ## Purpose
13
+
14
+ 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`.
15
+
16
+ ## Trigger
17
+
18
+ `/caveman-compress <filepath>` or when user asks to compress a memory file.
19
+
20
+ ## Process
21
+
22
+ 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.
23
+
24
+ 2. From the directory containing this SKILL.md, run:
25
+
26
+ python3 -m scripts <absolute_filepath>
27
+
28
+ 3. The CLI will:
29
+ - detect file type (no tokens)
30
+ - call Claude to compress
31
+ - validate output (no tokens)
32
+ - if errors: cherry-pick fix with Claude (targeted fixes only, no recompression)
33
+ - retry up to 2 times
34
+ - if still failing after 2 retries: report error to user, leave original file untouched
35
+
36
+ 4. Return result to user
37
+
38
+ ## Compression Rules
39
+
40
+ ### Remove
41
+ - Articles: a, an, the
42
+ - Filler: just, really, basically, actually, simply, essentially, generally
43
+ - Pleasantries: "sure", "certainly", "of course", "happy to", "I'd recommend"
44
+ - Hedging: "it might be worth", "you could consider", "it would be good to"
45
+ - Redundant phrasing: "in order to" → "to", "make sure to" → "ensure", "the reason is because" → "because"
46
+ - Connective fluff: "however", "furthermore", "additionally", "in addition"
47
+
48
+ ### Preserve EXACTLY (never modify)
49
+ - Code blocks (fenced ``` and indented)
50
+ - Inline code (`backtick content`)
51
+ - URLs and links (full URLs, markdown links)
52
+ - File paths (`/src/components/...`, `./config.yaml`)
53
+ - Commands (`npm install`, `git commit`, `docker build`)
54
+ - Technical terms (library names, API names, protocols, algorithms)
55
+ - Proper nouns (project names, people, companies)
56
+ - Dates, version numbers, numeric values
57
+ - Environment variables (`$HOME`, `NODE_ENV`)
58
+
59
+ ### Preserve Structure
60
+ - All markdown headings (keep exact heading text, compress body below)
61
+ - Bullet point hierarchy (keep nesting level)
62
+ - Numbered lists (keep numbering)
63
+ - Tables (compress cell text, keep structure)
64
+ - Frontmatter/YAML headers in markdown files
65
+
66
+ ### Compress
67
+ - Use short synonyms: "big" not "extensive", "fix" not "implement a solution for", "use" not "utilize"
68
+ - Fragments OK: "Run tests before commit" not "You should always run tests before committing"
69
+ - Drop "you should", "make sure to", "remember to" — just state the action
70
+ - Merge redundant bullets that say the same thing differently
71
+ - Keep one example where multiple examples show the same pattern
72
+
73
+ CRITICAL RULE:
74
+ Anything inside ``` ... ``` must be copied EXACTLY.
75
+ Do not:
76
+ - remove comments
77
+ - remove spacing
78
+ - reorder lines
79
+ - shorten commands
80
+ - simplify anything
81
+
82
+ Inline code (`...`) must be preserved EXACTLY.
83
+ Do not modify anything inside backticks.
84
+
85
+ If file contains code blocks:
86
+ - Treat code blocks as read-only regions
87
+ - Only compress text outside them
88
+ - Do not merge sections around code
89
+
90
+ ## Pattern
91
+
92
+ Original:
93
+ > 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.
94
+
95
+ Compressed:
96
+ > Run tests before push to main. Catch bugs early, prevent broken prod deploys.
97
+
98
+ Original:
99
+ > 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.
100
+
101
+ Compressed:
102
+ > Microservices architecture. API gateway route all requests to services. Auth service manage user sessions + JWT tokens.
103
+
104
+ ## Boundaries
105
+
106
+ - ONLY compress natural language files (.md, .txt, .typ, .typst, .tex, extensionless)
107
+ - NEVER modify: .py, .js, .ts, .json, .yaml, .yml, .toml, .env, .lock, .css, .html, .xml, .sql, .sh
108
+ - If file has mixed content (prose + code), compress ONLY the prose sections
109
+ - If unsure whether something is code or prose, leave it unchanged
110
+ - Original file is backed up as FILE.original.md before overwriting
111
+ - Never compress FILE.original.md (skip it)
@@ -0,0 +1,9 @@
1
+ """Caveman compress scripts.
2
+
3
+ This package provides tools to compress natural language markdown files
4
+ into caveman format to save input tokens.
5
+ """
6
+
7
+ __all__ = ["cli", "compress", "detect", "validate"]
8
+
9
+ __version__ = "1.0.0"
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ main()
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env python3
2
+ from pathlib import Path
3
+ import sys
4
+
5
+ # Support both direct execution and module import
6
+ try:
7
+ from .validate import validate
8
+ except ImportError:
9
+ sys.path.insert(0, str(Path(__file__).parent))
10
+ from validate import validate
11
+
12
+ try:
13
+ import tiktoken
14
+ _enc = tiktoken.get_encoding("o200k_base")
15
+ except ImportError:
16
+ _enc = None
17
+
18
+
19
+ def count_tokens(text):
20
+ if _enc is None:
21
+ return len(text.split()) # fallback: word count
22
+ return len(_enc.encode(text))
23
+
24
+
25
+ def benchmark_pair(orig_path: Path, comp_path: Path):
26
+ orig_text = orig_path.read_text()
27
+ comp_text = comp_path.read_text()
28
+
29
+ orig_tokens = count_tokens(orig_text)
30
+ comp_tokens = count_tokens(comp_text)
31
+ saved = 100 * (orig_tokens - comp_tokens) / orig_tokens if orig_tokens > 0 else 0.0
32
+ result = validate(orig_path, comp_path)
33
+
34
+ return (comp_path.name, orig_tokens, comp_tokens, saved, result.is_valid)
35
+
36
+
37
+ def print_table(rows):
38
+ print("\n| File | Original | Compressed | Saved % | Valid |")
39
+ print("|------|----------|------------|---------|-------|")
40
+ for r in rows:
41
+ print(f"| {r[0]} | {r[1]} | {r[2]} | {r[3]:.1f}% | {'✅' if r[4] else '❌'} |")
42
+
43
+
44
+ def main():
45
+ # Direct file pair: python3 benchmark.py original.md compressed.md
46
+ if len(sys.argv) == 3:
47
+ orig = Path(sys.argv[1]).resolve()
48
+ comp = Path(sys.argv[2]).resolve()
49
+ if not orig.exists():
50
+ print(f"❌ Not found: {orig}")
51
+ sys.exit(1)
52
+ if not comp.exists():
53
+ print(f"❌ Not found: {comp}")
54
+ sys.exit(1)
55
+ print_table([benchmark_pair(orig, comp)])
56
+ return
57
+
58
+ # Glob mode: repo_root/tests/caveman-compress/
59
+ # __file__ lives at <repo_root>/skills/caveman-compress/scripts/benchmark.py
60
+ # Walk up four dirs: scripts → caveman-compress → skills → repo_root.
61
+ tests_dir = Path(__file__).resolve().parents[3] / "tests" / "caveman-compress"
62
+ if not tests_dir.exists():
63
+ print(f"❌ Tests dir not found: {tests_dir}")
64
+ sys.exit(1)
65
+
66
+ rows = []
67
+ for orig in sorted(tests_dir.glob("*.original.md")):
68
+ comp = orig.with_name(orig.stem.removesuffix(".original") + ".md")
69
+ if comp.exists():
70
+ rows.append(benchmark_pair(orig, comp))
71
+
72
+ if not rows:
73
+ print("No compressed file pairs found.")
74
+ return
75
+
76
+ print_table(rows)
77
+
78
+
79
+ if __name__ == "__main__":
80
+ main()
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Caveman Compress CLI
4
+
5
+ Usage:
6
+ caveman <filepath>
7
+ """
8
+
9
+ import sys
10
+
11
+ # Force UTF-8 on stdout/stderr before any code can print. Windows consoles
12
+ # default to cp1252 and crash on the ❌ glyphs in error/validation branches,
13
+ # masking the real error and leaving the user with a half-compressed file.
14
+ for _stream in (sys.stdout, sys.stderr):
15
+ reconfigure = getattr(_stream, "reconfigure", None)
16
+ if callable(reconfigure):
17
+ try:
18
+ reconfigure(encoding="utf-8", errors="replace")
19
+ except Exception:
20
+ pass
21
+
22
+ from pathlib import Path
23
+
24
+ from .compress import backup_dir_for, compress_file
25
+ from .detect import detect_file_type, should_compress
26
+
27
+
28
+ def print_usage():
29
+ print("Usage: caveman <filepath>")
30
+
31
+
32
+ def main():
33
+ if len(sys.argv) != 2:
34
+ print_usage()
35
+ sys.exit(1)
36
+
37
+ filepath = Path(sys.argv[1])
38
+
39
+ # Check file exists
40
+ if not filepath.exists():
41
+ print(f"❌ File not found: {filepath}")
42
+ sys.exit(1)
43
+
44
+ if not filepath.is_file():
45
+ print(f"❌ Not a file: {filepath}")
46
+ sys.exit(1)
47
+
48
+ filepath = filepath.resolve()
49
+
50
+ # Detect file type
51
+ file_type = detect_file_type(filepath)
52
+
53
+ print(f"Detected: {file_type}")
54
+
55
+ # Check if compressible
56
+ if not should_compress(filepath):
57
+ print("Skipping: file is not natural language (code/config)")
58
+ sys.exit(0)
59
+
60
+ print("Starting caveman compression...\n")
61
+
62
+ try:
63
+ success = compress_file(filepath)
64
+
65
+ if success:
66
+ print("\nCompression completed successfully")
67
+ backup_path = backup_dir_for(filepath) / (filepath.stem + ".original.md")
68
+ print(f"Compressed: {filepath}")
69
+ print(f"Original: {backup_path}")
70
+ sys.exit(0)
71
+ else:
72
+ print("\n❌ Compression failed after retries")
73
+ sys.exit(2)
74
+
75
+ except KeyboardInterrupt:
76
+ print("\nInterrupted by user")
77
+ sys.exit(130)
78
+
79
+ except Exception as e:
80
+ print(f"\n❌ Error: {e}")
81
+ sys.exit(1)
82
+
83
+
84
+ if __name__ == "__main__":
85
+ main()