@kulapard/pi-caveman 0.1.0 → 0.3.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.
package/AGENTS.md CHANGED
@@ -12,6 +12,12 @@ Project memory for agents working in this repo. Non-obvious conventions only.
12
12
  session always starts `off`. There is **no** cross-session config file or env var.
13
13
  - Activation = `before_agent_start` appends `modeInstructions(mode)` to the
14
14
  system prompt. Statusline = `ctx.ui.setStatus("caveman", …)` guarded by `hasUI`.
15
+ - The extension's `modeInstructions` injection is the **canonical** activator;
16
+ the `caveman` skill (`skills/caveman/SKILL.md`) is a standalone fallback for
17
+ hosts that load skills but not this extension. When both are active the model
18
+ may see both rule sets — mild, intentional redundancy. They are not
19
+ programmatically de-duped: skill loading is model-driven and the skill's prose
20
+ differs from `modeInstructions`, so there is no reliable text to match on.
15
21
  - Pi 0.80.2 has **no `agents/` subagent mechanism**. The `agents/cavecrew-*.md`
16
22
  files are reference personas only and cavecrew is optional/out-of-scope.
17
23
 
@@ -22,12 +28,12 @@ Project memory for agents working in this repo. Non-obvious conventions only.
22
28
  SDK is needed at test time. A value import from `@earendil-works/pi-coding-agent`
23
29
  would break the tests — `tests/extension.test.mjs` asserts this invariant.
24
30
  - **Verbatim preservation**: caveman-compress never alters code blocks, inline
25
- code, URLs, file paths, commands, or exact error strings. `validate.py` enforces
26
- this and `compress.py` aborts + restores the original on any validation failure.
27
- - `caveman-compress` is **model-bound**: `compress.py` `call_claude()` calls the
28
- Anthropic SDK (if `ANTHROPIC_API_KEY` is set) or the `claude --print` CLI. The
29
- deterministic, unit-tested pieces are `detect.py`, `validate.py`, and the pure
30
- helpers; the live model call is never exercised in tests (it is monkeypatched).
31
+ code, URLs, file paths, commands, or exact error strings. The skill instructs
32
+ the agent to self-validate these against the original and, on any mismatch it
33
+ cannot fix, restore from the `.original` backup rather than leave a corrupted file.
34
+ - `caveman-compress` is **prompt-only**: the Pi agent performs the compression
35
+ with its own model and file tools, driven by `SKILL.md`. There is no Python and
36
+ no external model CLI; coverage is the doc-guard test `tests/compress-docs.test.mjs`.
31
37
 
32
38
  ## Tests / validation
33
39
 
@@ -36,9 +42,6 @@ Project memory for agents working in this repo. Non-obvious conventions only.
36
42
  - The JS test glob (`tests/**/*.test.mjs`) is expanded by the Node `--test` runner,
37
43
  not the shell. Directory-recursion (`--test tests/`) does **not** work on the
38
44
  current Node — keep the glob.
39
- - **Python tests are not on PATH.** Create a venv and install pytest:
40
- `python3 -m venv .venv && .venv/bin/pip install pytest`, then run
41
- `npm run test:py` (which calls `.venv/bin/pytest skills/caveman-compress`).
42
45
  - Several tests are **phantom-reference guards** (`tests/stats-docs.test.mjs`,
43
46
  `tests/cavecrew-docs.test.mjs`, `tests/compress-docs.test.mjs`): they assert the
44
47
  docs do **not** mention a Claude-Code hooks layer, a plugin install path, the
package/CHANGELOG.md ADDED
@@ -0,0 +1,71 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.3.0] - 2026-06-29
11
+
12
+ ### Fixed
13
+
14
+ - `/caveman-help` card linked "Full docs" to the upstream `JuliusBrussee/caveman`
15
+ repo; now points to `kulapard/pi-caveman`.
16
+ - `caveman-help` skill table was missing `/caveman-stats`, disagreeing with the
17
+ extension's own `HELP_TEXT`; added the row.
18
+ - `ultra` mode was documented as "Tables over prose" in the README and help
19
+ card, contradicting the injected mode instructions; docs now match behavior
20
+ (abbreviate prose words, arrows for causality).
21
+ - Input handler no longer throws when an input event arrives without `text`
22
+ (`event.text.trim()` → `(event.text ?? "").trim()`).
23
+ - `caveman-compress` docs said the backup is always `FILE.original.md`, but the
24
+ skill preserves the source extension; corrected to `FILE.original.<ext>`.
25
+
26
+ ### Changed
27
+
28
+ - `engines.node` floor raised from `>=18` to `>=22.6.0`, matching the
29
+ `node --experimental-strip-types` requirement of the test/typecheck workflow.
30
+ - Natural-language activation now also triggers on `"be brief"` (added to
31
+ `ACTIVATION_RE`, the `caveman` skill description, and the README) so the
32
+ deterministic regex matches the advertised triggers.
33
+
34
+ ## [0.2.0] - 2026-06-29
35
+
36
+ ### Changed
37
+
38
+ - `caveman-compress` is now a prompt-only skill: the Pi agent performs the
39
+ compression itself (its own model and file tools), driven by `SKILL.md`. The
40
+ `/caveman-compress` command and the compression rules are unchanged.
41
+
42
+ ### Removed
43
+
44
+ - The Python `caveman-compress` toolkit (`scripts/` + the pytest suite) and the
45
+ `test:py` script. The package no longer requires Python or an external
46
+ `claude` CLI / `ANTHROPIC_API_KEY`.
47
+
48
+ ## [0.1.0] - 2026-06-29
49
+
50
+ Initial release — a [Pi](https://github.com/earendil-works/pi-coding-agent)
51
+ port of [caveman](https://github.com/JuliusBrussee/caveman).
52
+
53
+ ### Added
54
+
55
+ - Caveman output-mode extension (`extensions/caveman.ts`): six intensity modes
56
+ (lite, full, ultra, wenyan-lite, wenyan-full, wenyan-ultra; default full),
57
+ slash commands, natural-language activation/deactivation, and a session
58
+ statusline indicator.
59
+ - Skills: `caveman`, `caveman-commit`, `caveman-review`, `caveman-help`,
60
+ `caveman-stats`, `caveman-compress`, and the `cavecrew` subagent suite.
61
+ - npm packaging as `@kulapard/pi-caveman` (scoped, public) with a `files`
62
+ whitelist, a `prepublishOnly` test gate, and repository metadata.
63
+ - GitHub Actions: `ci.yml` (test on push and pull requests) and `publish.yml`
64
+ (publish to npm via Trusted Publishing / OIDC on `v[0-9]*` tags — no stored
65
+ token, automatic provenance, a tag-equals-version guard, and a concurrency
66
+ group).
67
+
68
+ [Unreleased]: https://github.com/kulapard/pi-caveman/compare/v0.3.0...HEAD
69
+ [0.3.0]: https://github.com/kulapard/pi-caveman/compare/v0.2.0...v0.3.0
70
+ [0.2.0]: https://github.com/kulapard/pi-caveman/compare/v0.1.0...v0.2.0
71
+ [0.1.0]: https://github.com/kulapard/pi-caveman/releases/tag/v0.1.0
package/README.md CHANGED
@@ -12,9 +12,9 @@ plus a set of skills under `skills/`.
12
12
 
13
13
  ## Install
14
14
 
15
- pi-caveman publishes to npm as
15
+ pi-caveman is published to npm as
16
16
  [`@kulapard/pi-caveman`](https://www.npmjs.com/package/@kulapard/pi-caveman).
17
- Once the first release is live, install it into a Pi setup with:
17
+ Install it into a Pi setup with:
18
18
 
19
19
  ```bash
20
20
  pi install npm:@kulapard/pi-caveman
@@ -56,12 +56,6 @@ persistent install over a per-session `pi -e`.
56
56
  ```bash
57
57
  npm install # fetch the Pi SDK + TypeScript dev deps
58
58
  npm test # typecheck + extension/manifest/docs unit tests
59
-
60
- # Python tests for the caveman-compress toolkit need pytest in a local venv
61
- # (pytest is not on PATH on a fresh checkout):
62
- python3 -m venv .venv
63
- .venv/bin/pip install pytest
64
- npm run test:py # runs: .venv/bin/pytest skills/caveman-compress
65
59
  ```
66
60
 
67
61
  ## Modes
@@ -73,7 +67,7 @@ session ends.
73
67
  |------|---------|--------|
74
68
  | **lite** | `/caveman lite` | Drop filler. Keep sentence structure. |
75
69
  | **full** | `/caveman` | Drop articles, filler, pleasantries, hedging. Fragments OK. Default. |
76
- | **ultra** | `/caveman ultra` | Extreme compression. Bare fragments. Tables over prose. |
70
+ | **ultra** | `/caveman ultra` | Extreme compression. Bare fragments. Abbreviate prose words; arrows (X → Y). |
77
71
  | **wenyan-lite** | `/caveman wenyan-lite` | Classical Chinese (文言文) style, light compression. |
78
72
  | **wenyan-full** | `/caveman wenyan` | Full 文言文. Maximum classical terseness. |
79
73
  | **wenyan-ultra** | `/caveman wenyan-ultra` | Extreme classical terseness. |
@@ -97,7 +91,7 @@ You don't have to use a slash command. The extension watches your messages and
97
91
  switches mode on phrases like:
98
92
 
99
93
  - **Activate:** "caveman mode", "talk like caveman", "use caveman", "less
100
- tokens", "fewer tokens", "save tokens" → enables **full** mode.
94
+ tokens", "fewer tokens", "save tokens", "be brief" → enables **full** mode.
101
95
  - **Deactivate:** "stop caveman", "normal mode", "disable caveman" → turns it
102
96
  off.
103
97
 
@@ -122,16 +116,12 @@ tool descriptions. pi-caveman does **not** bundle it: that proxy works at the
122
116
  MCP-client layer, independent of Pi, and upstream itself ships it as a separate
123
117
  package — so it does not belong in this extension-plus-skills package.
124
118
 
125
- The Pi-side equivalent is the Python `caveman-compress` toolkit, invoked via the
126
- `/caveman-compress` skill/command, which compresses prose memory files in place
127
- (writing a `FILE.original.md` backup) while preserving code, URLs, and paths
128
- verbatim. Note plainly: `caveman-compress` is **itself Claude-bound** it
129
- performs compression via a live model call (the Anthropic SDK if
130
- `ANTHROPIC_API_KEY` is set, otherwise the `claude --print` CLI). So "the Pi
131
- equivalent" means *invoked via a Pi skill/command*, **not** *model-independent*.
132
- This is the same nature as upstream's MCP shrink (also a model-mediated
133
- transform); only the integration mechanism differs (a Pi skill here vs. MCP
134
- middleware upstream).
119
+ The Pi-side equivalent is the `caveman-compress` skill, invoked via the
120
+ `/caveman-compress` command. It is prompt-only: the Pi agent itself compresses a
121
+ prose memory file in place (writing a `FILE.original.<ext>` backup) using its own
122
+ model and file tools, preserving code, URLs, and paths verbatim. No Python and no
123
+ external Claude CLI are involved compression is performed by the host Pi agent,
124
+ the same way the other skills work.
135
125
 
136
126
  ## Attribution & license
137
127
 
@@ -90,5 +90,5 @@ Auto-clarity:
90
90
  }
91
91
 
92
92
  export const ACTIVATION_RE =
93
- /\b(caveman mode|talk like caveman|use caveman|less tokens|fewer tokens|save tokens)\b/i;
93
+ /\b(caveman mode|talk like caveman|use caveman|less tokens|fewer tokens|save tokens|be brief)\b/i;
94
94
  export const DEACTIVATION_RE = /\b(stop caveman|normal mode|disable caveman)\b/i;
@@ -133,15 +133,14 @@ export default function cavemanExtension(pi: ExtensionAPI) {
133
133
  });
134
134
 
135
135
  pi.on("input", (event, ctx) => {
136
- if (event.source === "extension") return { action: "continue" as const };
137
- const text = event.text.trim();
138
- if (DEACTIVATION_RE.test(text)) {
139
- if (mode !== "off") persistMode("off", ctx);
140
- return { action: "continue" as const };
141
- }
142
- if (mode === "off" && ACTIVATION_RE.test(text)) {
143
- persistMode("full", ctx);
144
- return { action: "continue" as const };
136
+ // Ignore the extension's own echoed input (self-echo guard).
137
+ if (event.source !== "extension") {
138
+ const text = (event.text ?? "").trim();
139
+ if (DEACTIVATION_RE.test(text)) {
140
+ if (mode !== "off") persistMode("off", ctx);
141
+ } else if (mode === "off" && ACTIVATION_RE.test(text)) {
142
+ persistMode("full", ctx);
143
+ }
145
144
  }
146
145
  return { action: "continue" as const };
147
146
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kulapard/pi-caveman",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Caveman for Pi: ultra-compressed agent output that preserves technical substance. Six intensity modes, slash commands, natural-language activation, and a session statusline.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -20,12 +20,10 @@
20
20
  "skills",
21
21
  "agents",
22
22
  "AGENTS.md",
23
- "!**/__pycache__",
24
- "!**/*.pyc",
25
- "!skills/**/tests"
23
+ "CHANGELOG.md"
26
24
  ],
27
25
  "engines": {
28
- "node": ">=18"
26
+ "node": ">=22.6.0"
29
27
  },
30
28
  "keywords": [
31
29
  "pi-package",
@@ -47,11 +45,10 @@
47
45
  "pretest": "npm run typecheck",
48
46
  "test": "node --experimental-strip-types --test tests/**/*.test.mjs",
49
47
  "typecheck": "tsc --noEmit",
50
- "test:py": ".venv/bin/pytest skills/caveman-compress",
51
48
  "prepublishOnly": "npm test"
52
49
  },
53
50
  "devDependencies": {
54
51
  "@earendil-works/pi-coding-agent": "^0.80.2",
55
- "typescript": "^5"
52
+ "typescript": "^6"
56
53
  }
57
54
  }
@@ -5,7 +5,7 @@ description: >
5
5
  while keeping full technical accuracy. Supports intensity levels: lite, full (default), ultra,
6
6
  wenyan-lite, wenyan-full, wenyan-ultra.
7
7
  Use when user says "caveman mode", "talk like caveman", "use caveman", "less tokens",
8
- "be brief", or invokes /caveman. Also auto-triggers when token efficiency is requested.
8
+ "fewer tokens", "save tokens", "be brief", or invokes /caveman. Also auto-triggers when token efficiency is requested.
9
9
  ---
10
10
 
11
11
  Respond terse like smart caveman. All technical substance stay. Only fluff die.
@@ -40,7 +40,7 @@ Real results on real project files:
40
40
  | `mixed-with-code.md` | 888 | 560 | **36.9%** |
41
41
  | **Average** | **898** | **481** | **46%** |
42
42
 
43
- All validations passed ✅ — headings, code blocks, URLs, file paths preserved exactly.
43
+ Headings, code blocks, URLs, and file paths preserved exactly.
44
44
 
45
45
  ## Before / After
46
46
 
@@ -65,23 +65,14 @@ All validations passed ✅ — headings, code blocks, URLs, file paths preserved
65
65
 
66
66
  **Same instructions. ~60% fewer tokens in this example (46% average across the files above). Every. Single. Session.**
67
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
68
  ## Install
73
69
 
74
70
  This skill ships inside the pi-caveman package. Load the package (see the
75
71
  [root README](../../README.md) for the `pi -e … --skill …` / `pi install`
76
72
  mechanism), then use `/caveman-compress` in a Pi session.
77
73
 
78
- The compress toolkit lives at `skills/caveman-compress/` within the package; the
79
- skill instructions run the Python CLI under `scripts/`.
80
-
81
- **Requires:** Python 3.10+. Compression calls a model (the Anthropic SDK if
82
- `ANTHROPIC_API_KEY` is set, otherwise the `claude --print` CLI), so one of those
83
- must be available — see [Security](#security) and the root README's
84
- "Compression vs. upstream MCP shrink" note for why this step is model-bound.
74
+ No extra runtime is required: the Pi agent performs the compression itself with
75
+ its own model and file tools there is no separate tool or language to install.
85
76
 
86
77
  ## Usage
87
78
 
@@ -111,23 +102,20 @@ Examples:
111
102
  ```
112
103
  /caveman-compress AGENTS.md
113
104
 
114
- detect file type (no tokens)
105
+ agent detects file type (prose? else skip)
115
106
 
116
- toolkit calls a model to compress (tokens one call)
107
+ agent backs up original → AGENTS.original.md (verbatim, never overwritten)
117
108
 
118
- validate output (no tokens)
119
- checks: headings, code blocks, URLs, file paths, bullets
109
+ agent rewrites prose to caveman, code/URLs/paths left exact
120
110
 
121
- if errors: model fixes cherry-picked issues only (tokens — targeted fix)
122
- does NOT recompress — only patches broken parts
111
+ agent self-validates: protected tokens byte-identical to original
123
112
 
124
- retry up to 2 times
113
+ if a protected token changed: fix it, or restore from backup and report
125
114
 
126
- write compressed AGENTS.md
127
- write original → AGENTS.original.md
115
+ write compressed AGENTS.md
128
116
  ```
129
117
 
130
- Only two things use tokens: initial compression + targeted fix if validation fails. Everything else is local Python.
118
+ The agent does this with its own model and file tools no external CLI, no separate runtime.
131
119
 
132
120
  ## What Is Preserved
133
121
 
@@ -149,15 +137,6 @@ A memory file (`AGENTS.md` / `CLAUDE.md`) loads on **every session start**. A 10
149
137
 
150
138
  Caveman cut that by ~46% on average. Same instructions. Same accuracy. Less waste.
151
139
 
152
- ```
153
- ┌────────────────────────────────────────────┐
154
- │ TOKEN SAVINGS PER FILE █████ 46% │
155
- │ SESSIONS THAT BENEFIT ██████████ 100% │
156
- │ INFORMATION PRESERVED ██████████ 100% │
157
- │ SETUP TIME █ 1x │
158
- └────────────────────────────────────────────┘
159
- ```
160
-
161
140
  ## Part of Caveman
162
141
 
163
142
  This skill is part of the [caveman](https://github.com/JuliusBrussee/caveman) toolkit — making the agent use fewer tokens without losing accuracy. pi-caveman is the [Pi](https://github.com/earendil-works/pi-coding-agent) port.
@@ -3,7 +3,7 @@ name: caveman-compress
3
3
  description: >
4
4
  Compress natural language memory files (AGENTS.md, CLAUDE.md, todos, preferences) into caveman
5
5
  format 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.
6
+ Compressed version overwrites the original file. Human-readable backup saved as FILE.original.<ext> (same extension as the source).
7
7
  Trigger: /caveman-compress FILEPATH or "compress memory file"
8
8
  ---
9
9
 
@@ -11,7 +11,7 @@ description: >
11
11
 
12
12
  ## Purpose
13
13
 
14
- Compress natural language files (`AGENTS.md`, `CLAUDE.md`, todos, preferences) into caveman-speak to reduce input tokens. Compressed version overwrites original. Human-readable backup saved as `<filename>.original.md`.
14
+ Compress natural language files (`AGENTS.md`, `CLAUDE.md`, todos, preferences) into caveman-speak to reduce input tokens. Compressed version overwrites original. Human-readable backup saved as `<filename>.original.<ext>` (same extension as the source, e.g. `AGENTS.md` → `AGENTS.original.md`, `notes.txt` → `notes.original.txt`).
15
15
 
16
16
  ## Trigger
17
17
 
@@ -19,21 +19,17 @@ Compress natural language files (`AGENTS.md`, `CLAUDE.md`, todos, preferences) i
19
19
 
20
20
  ## Process
21
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.
22
+ You (the Pi agent) perform the compression directly there is no separate tool to run. Given `/caveman-compress <filepath>`:
23
23
 
24
- 2. From the directory containing this SKILL.md, run:
24
+ 1. **Skip backups.** If the path ends in `.original.<ext>` (e.g. `AGENTS.original.md`), stop — never compress a backup file.
25
+ 2. **Check it is compressible** per **Boundaries** below: prose files (`.md`, `.txt`, `.rst`, `.typ`, `.typst`, `.tex`, or extensionless natural language). If it is code/config (`.py`, `.js`, `.ts`, `.json`, `.yaml`, …) or larger than ~500 KB, report it is out of scope and stop.
26
+ 3. **Read** the file's full contents.
27
+ 4. **Back up the original.** Write a verbatim copy to `<filename>.original.<ext>` (e.g. `AGENTS.md` → `AGENTS.original.md`), **only if that backup does not already exist** — never overwrite an existing `.original` backup.
28
+ 5. **Rewrite** the file in place, applying the **Compression Rules** below. Treat code blocks, inline code, URLs, paths, commands, headings, and table structure as read-only regions.
29
+ 6. **Self-validate** against the contents you read in step 3: every protected token — fenced and inline code, URLs, file paths, heading text, table structure, dates/version numbers — must be byte-for-byte identical. If any changed, fix just that region; if you cannot make it identical, restore the file from the `.original` backup and report the failure rather than leave a corrupted file.
30
+ 7. **Report** the result: bytes before/after and the approximate reduction.
25
31
 
26
- python3 -m scripts <absolute_filepath>
27
-
28
- 3. The CLI will:
29
- - detect file type (no tokens)
30
- - call a model to compress (Anthropic SDK if `ANTHROPIC_API_KEY` is set, else the `claude --print` CLI)
31
- - validate output (no tokens)
32
- - if errors: cherry-pick fix via the same model call (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
32
+ Only the rewrite needs the model (you); detection, backup, and validation are mechanical.
37
33
 
38
34
  ## Compression Rules
39
35
 
@@ -103,9 +99,10 @@ Compressed:
103
99
 
104
100
  ## Boundaries
105
101
 
106
- - ONLY compress natural language files (.md, .txt, .typ, .typst, .tex, extensionless)
102
+ - ONLY compress natural language files (.md, .txt, .rst, .typ, .typst, .tex, extensionless)
107
103
  - NEVER modify: .py, .js, .ts, .json, .yaml, .yml, .toml, .env, .lock, .css, .html, .xml, .sql, .sh
104
+ - Skip files larger than ~500 KB (too big to rewrite safely in one pass)
108
105
  - If file has mixed content (prose + code), compress ONLY the prose sections
109
106
  - 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)
107
+ - Original file is backed up as FILE.original.<ext> (same extension as the source) before overwriting
108
+ - Never compress a FILE.original.<ext> backup (skip it)
@@ -16,7 +16,7 @@ Display this reference card when invoked. One-shot — do NOT change mode, write
16
16
  |------|---------|-------------|
17
17
  | **Lite** | `/caveman lite` | Drop filler. Keep sentence structure. |
18
18
  | **Full** | `/caveman` | Drop articles, filler, pleasantries, hedging. Fragments OK. Default. |
19
- | **Ultra** | `/caveman ultra` | Extreme compression. Bare fragments. Tables over prose. |
19
+ | **Ultra** | `/caveman ultra` | Extreme compression. Bare fragments. Abbreviate prose words; arrows (X → Y). |
20
20
  | **Wenyan-Lite** | `/caveman wenyan-lite` | Classical Chinese style, light compression. |
21
21
  | **Wenyan-Full** | `/caveman wenyan` | Full 文言文. Maximum classical terseness. |
22
22
  | **Wenyan-Ultra** | `/caveman wenyan-ultra` | Extreme. Ancient scholar on a budget. |
@@ -30,6 +30,7 @@ Mode stick until changed or session end.
30
30
  | **caveman-commit** | `/caveman-commit` | Terse commit messages. Conventional Commits. ≤50 char subject. |
31
31
  | **caveman-review** | `/caveman-review` | One-line PR comments: `L42: bug: user null. Add guard.` |
32
32
  | **caveman-compress** | `/caveman-compress <file>` | Compress .md files to caveman prose. Saves ~46% input tokens. |
33
+ | **caveman-stats** | `/caveman-stats` | On-demand, model-driven estimate of tokens saved this session. |
33
34
  | **caveman-help** | `/caveman-help` | This card. |
34
35
 
35
36
  ## Deactivate
@@ -48,4 +49,4 @@ Mode set per session. New session start → mode `off`; activate again with `/ca
48
49
 
49
50
  ## More
50
51
 
51
- Full docs: https://github.com/JuliusBrussee/caveman
52
+ Full docs: https://github.com/kulapard/pi-caveman
@@ -1,31 +0,0 @@
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`.
@@ -1,9 +0,0 @@
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"
@@ -1,3 +0,0 @@
1
- from .cli import main
2
-
3
- main()
@@ -1,80 +0,0 @@
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()
@@ -1,85 +0,0 @@
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()