Make the README install flow easier to scan by surfacing one-agent install paths directly and pointing users to `INSTALL.md` for the full matrix. Also ignore the local `tmp-starcharts/` scratch directory so generated chart work stays out of git.
The sweep adds src/hooks/caveman-parse.js and regenerates
checksums.sha256 to include it. The detached install path fetches
hooks from RAW_BASE = raw.githubusercontent.com/<repo>/<PINNED_REF>,
and the integrity gate refuses any hook that doesn't match the pinned
release. Left at v1.9.1 that ref has no caveman-parse.js, so
curl | bash 404s and fails integrity for new users.
Bump must land inside the tagged commit so v1.10.0 contains both the
new hooks and a PINNED_REF pointing at itself.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012SSMrNKRooMGmJ1yNFpLMy
Update README to separate the 65% chat-style prose benchmark from full agentic coding runs, adding JetBrains’ independent 86-task result (8.5% output-token savings). Adds context on why both numbers are valid, explains workload-dependent ceilings, and aligns Caveman 2 positioning around measuring real savings on each team’s own traffic.
Tests existed but nothing ran them in CI. Node 18/20/22 matrix runs the
installer suite plus every standalone tests/test_*.js runner; a python job
runs unittest discover (compress tests mock the Claude call, no network).
Node is set up in the python job too — several python tests shell out to
node for hook checks. Inspired by PR #695, minus the scanner extras.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ySX6TBWZuvFze4ajf7Hpf
Marketplace fix (#712, #705): Claude Desktop rejects plugins containing a
top-level bin/ directory, and .claude-plugin/marketplace.json packages the
repo root, so the installer directory is now cli/. Every reference updated
(package.json bin entry + files, shims, docs, tests, caveman-init require
path). Supersedes PR #726.
Security (PR #717 verified): quoteWinArg only quoted on whitespace/quotes,
leaving cmd.exe metacharacters (& | ^ < > % parens) unescaped on the
shell:true Windows spawn path. Attacker-influenced arguments (--with-init
cwd, --with-mcp-shrink value) could chain commands. Trigger regex now
covers the metacharacter set; quoting logic split into a platform-
independent, unit-tested helper.
Also:
- uninstall removes .caveman-active.prev, .caveman-mode-log.jsonl,
.caveman-statusline-suffix, .caveman-nudge-shown; keeps
.caveman-history.jsonl with a printed note; dry-run now says
'would remove' instead of lying (#635, supersedes PRs #693#636)
- Array.isArray guard in rewriteLegacyManagedHookCommands — malformed
hook event no longer crashes the installer mid-run (supersedes PR #646)
- gemini extensions install --consent: the security prompt hung every
piped/non-interactive install forever (#676, part of PR #664)
- OpenClaw skill stamps the real PINNED_REF version instead of hardcoded
1.0.0; new --no-always flag for load-on-demand installs (supersedes
PR #720)
- shims scope NPM_CONFIG_ALLOW_GIT=all to the npx call — npm >=12
defaults allow-git to none and EALLOWGITs github: installs (#698)
- .codex/config.toml ships hooks + codex_hooks keys so auto-activation
works on both sides of the codex-cli rename (#617)
- caveman-help card shows the Windows config path (%APPDATA%) (#723)
- caveman-parse.js added to HOOK_FILES, opencode payload (.cjs), and the
regenerated checksums.sha256; manifest now matches shipped hook
contents — release must bump PINNED_REF to a tag containing these files
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ySX6TBWZuvFze4ajf7Hpf
Every read/write resolved to the locale codec (cp1252/cp949 on Windows):
non-ASCII files were silently mojibake'd, and because Path.write_text
truncates before encoding, a UnicodeEncodeError left the target at 0 bytes.
The backup readback check couldn't catch it — it read back with the same
wrong codec.
- encoding=utf-8 pinned on every I/O call site (compress, validate,
detect, benchmark); validate now decodes strict — it's the fidelity gate
- write_text_atomic: encode first, temp file in same dir, fsync, preserve
permissions, os.replace; temp unlinked on any failure
- fix-retry pass gains the same empty-output guard as the first pass
- fix-retry preamble leak (#588): output must start at the original's
structural anchor (frontmatter/heading) or the attempt is rejected
- primary-write failure now prints the backup path — users hitting the
crash had no idea a backup existed
- extract_inline_codes: strip fences via the CommonMark-aware extractor;
the old column-0 regex leaked indented fences into inline-code pairing,
causing false validation failures (extracted from PR #619's diagnosis)
- SKILL.md/README/SECURITY corrected: backups live in the out-of-tree data
dir (#420), not beside the source file
Supersedes PRs #683#678#626#534 and the fence fix from #619 with a
local implementation. 58 python tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ySX6TBWZuvFze4ajf7Hpf
/caveman-stats only reported gross output savings, hiding the regime where
the ~1,250-token/turn rule injection costs more than compression saves —
the exact case docs/HONEST-NUMBERS.md warns about.
- Est. rule overhead: per-turn injected-rule input cost x turns
(CAVEMAN_RULE_OVERHEAD_TOKENS overrides the default 1250)
- Est. net: saved minus overhead; when negative, says plainly that caveman
cost more than it saved for this workload
- shown only for attributed uniform spans (follows #601 attribution);
history rows without turn counts are excluded from net, never guessed
- statusline suffix stays gross savings, unchanged semantics
Reimplements the idea from PR #718 locally. 46 tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ySX6TBWZuvFze4ajf7Hpf
- extract mode parsing into src/hooks/caveman-parse.js, consumed by both
the Claude tracker and the opencode plugin — fixes the three #602 drifts
(brevity triggers missing, bogus level overwrote flag with default,
independent modes unreachable via expanded templates)
- unwrap Claude Code's <command-name>/<command-args> slash envelope: real
slash-UI /caveman <level> and /caveman off were silent no-ops (#537);
foreign envelopes skip natural-language detection entirely
- SessionStart branches on payload source: startup resets to configured
default, resume/clear/compact preserve a valid existing flag (#691)
- scheduled-task prompts (<scheduled-task marker) skip flag mutation and
reinforcement so unattended runs aren't hijacked
- per-turn reinforcement honors repo-local defaultMode off via
getDefaultMode(cwd) gate — read-only, never deletes the shared flag
(#634; rejects #532's cross-session flag deletion)
- reinforcement anchor shrunk ~57%, opencode line kept identical (#660)
- statusline setup nudge shown once, gated by .caveman-nudge-shown (#661)
- /caveman-stats delivered via hookSpecificOutput.additionalContext so the
macOS desktop app renders it (#618)
- safeWriteFlag: retry rename on Windows sharing violations, always unlink
temp in finally — no more .caveman-active.<pid>.<ts> litter (#511#578)
- statusline.sh exits 0 on empty suffix file — non-zero exit was hiding
the whole status bar (#711)
- cavecrew-model-overrides resolves plugin root across layouts; env model
overrides were a silent no-op (#645)
- opencode dev-tree loader: base require on the loaded file so
caveman-parse's relative require resolves in both layouts
Supersedes PRs #623#674#700#691#634#660#661#692#632#622#657#578#511#645#590#498#501 with local implementations.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ySX6TBWZuvFze4ajf7Hpf
Verified against filed reports and applied minimal rewordings:
- never-drop-negations rule — dropping not/never inverts instructions (#670)
- tool-call silence defined: no preamble/progress notes around calls (#713, #714)
- language rule de-named: removes Portuguese/Spanish attractor tokens that
drove drift under compaction; every emitted line in session language
(#665, #654, #701, #539)
- particles/postpositions exempt from article-dropping — Korean/Japanese
case markers are grammar, not filler (#680)
- classical chars scoped to wenyan modes only; Auto-Clarity example marked
format-only so warnings render in session language (#679, #680)
- switch line covers all six levels + off (#549, #670)
- persisted-text boundary: docs/issues/PR text/memory files write normal
(#483, #562, #582, #670)
- wenyan-full row: character reduction labeled chars-not-tokens
Supersedes PRs #715#702#684#685#667#654#658#549#670#483#562#582
with local implementations. Eval snapshots need regeneration (API-gated).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ySX6TBWZuvFze4ajf7Hpf
Replace the ~50-65% range and the ~50%-vs-terse figure with a single 65%
(the measured average output reduction vs verbose baseline) on every product
surface: README hero + ASCII card + benchmark blurb, plugin.json,
marketplace.json, gemini-extension.json, caveman SKILL.md + skill README,
caveman-init rule frontmatter, and the docs site (index.html telemetry widget
+ marquee still showed the old 75%). Trim HONEST-NUMBERS.md to match. Raw
benchmark/eval snapshots and the eval-harness methodology docs are untouched.
Bump PINNED_REF v1.9.0 -> v1.9.1 so the standalone hook-download fallback
fetches cavecrew-model-overrides.js from the release tag instead of 404ing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0163QczkBHKpYgxx3sBc7X3b
/caveman-stats credited ALL output tokens since session start to whatever
mode the flag held at stats time: activating caveman late inflated the
estimate (verbose tokens back-computed as compressed), deactivating it
zeroed honest savings — and the wrong number compounded into the lifetime
history and statusline.
The flag files carried no timestamps, so past sessions cannot be exactly
reconstructed. Fix going forward + honest fallback:
- caveman-config: recordModeChange() appends {ts, mode, prev} to
.caveman-mode-log.jsonl on every actual flag transition (deduped,
symlink-safe via appendFlag, best-effort)
- mode tracker + SessionStart activate hook log every flag mutation
(set, off, NL deactivation, one-shot restore, session-start reset)
- caveman-stats joins the log timestamps against the session JSONL
message timestamps and computes savings per mode span; the first
row's prev covers the pre-inception span
- with no log coverage: a flag written mid-session means the earlier
tokens have UNKNOWN mode — they are excluded and labeled, never
guessed (no-fake-savings); with no evidence of a mid-session change,
whole-session attribution stays (correct when the mode never changed)
- history rows, --share, and the statusline suffix all use the
attributed figure; mixed sessions render a per-mode breakdown with
the estimate basis stated
Old inflated history rows cannot be retroactively corrected (the data to
re-attribute them was never recorded).
Tests: mid-session activation (inflation case), mid-session deactivation
(zeroed case), tracker transition-log dedup, unattributable-prefix
exclusion. Checksums manifest refreshed for the four changed hook files.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011kmm2umRGb5nLxdimrwweZ
The files array omitted spawn-options.js, which index.js requires at
startup — the next npm publish would have shipped a package that crashes
with MODULE_NOT_FOUND on every launch (verified via npm pack --dry-run).
- add spawn-options.js to package.json files
- bump 0.1.1 (registry 0.1.0 is stale; the fix is unpublishable without
a version bump anyway — NOT published here)
- add a static packaging test that walks every relative require reachable
from the package entry points (bin + main) and fails if any resolved
module is missing from files
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011kmm2umRGb5nLxdimrwweZ
The limit-headroom meter (8a5ab60) printed saved/(saved+output) as
'Session budget saved: ~X% of your usage this session' and 'Est. budget
saved: ~X% of tracked usage'. That ratio is algebraically the output
reduction (always ~65% in full mode) with an output-only denominator —
input + cache tokens, which dominate agentic sessions and count against
Pro/Max limits, were excluded. docs/HONEST-NUMBERS.md on this same
branch says real session-level totals land ~14-21% and below zero on
terse workloads, so the label overstated limit relief.
Fix: say only what the math computes.
- Session view: drop the budget line; the saved line now reads
'(~X% of output)' and the footer states input/cache usage is
unchanged.
- Lifetime view: relabel to 'Est. output reduction: ~X% (output tokens
only, est.)'.
- budgetSavedPct -> outputReductionPct with a comment forbidding
usage/budget relabeling; tests assert no usage/budget claim appears.
- Hook checksum refreshed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011kmm2umRGb5nLxdimrwweZ
README gets a two-line Caveman Cloud section near the bottom; the
installer summary now points at /caveman-stats (labeled estimates)
and getcaveman.dev. One link, one honest sentence each — local
numbers are estimates, Cloud measures and verifies them.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011kmm2umRGb5nLxdimrwweZ
Extends the #614 SECURITY.md scaffolding: verified zero-network-after-
install statement (skill is a prompt; hooks/stats/statusline/shrink
have no http/https/fetch), exact install-time fetch list incl. the
detached curl fallback (release-tag-pinned, SHA-256-verified), what
stays local, air-gapped/enterprise note, and honest explanations for
the Defender (#383) and Snyk caveman-compress (#28) flags. Adds a
top-level README Privacy section (#504) and corrects INSTALL.md's
'no network calls' claim to name the curl-fallback exception.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011kmm2umRGb5nLxdimrwweZ
README headline now states the measured range with method (65% avg vs
verbose default from benchmarks/, ~50% median vs 'Answer concisely.'
control from evals/) plus explicit caveat: input tokens untouched,
session-level savings smaller. Stats box shows output vs input split,
drops the unmeasured ~3x speed row. Same ~75% claim corrected in
SKILL.md description (+ synced plugin copy and dist/caveman.skill),
plugin.json, marketplace.json, gemini-extension.json, caveman-init
rule frontmatter, skill README, and CLAUDE.md.
Note for maintainer: the GitHub repo description still says 'cuts 65%
of tokens' — should read 'cuts ~50-65% of output tokens (measured)'.
Not changeable from a local commit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011kmm2umRGb5nLxdimrwweZ
Plain-truth page: measured output reduction (65% avg vs verbose,
~50% median vs terse control), the ~1-1.5k/turn input cost of the
injected rules, net-negative cases (terse Q&A per #145, per-request
billing per #506, adverse tool-side counters per #550), and how to
A/B it yourself. /caveman-stats savings labeled as estimates.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011kmm2umRGb5nLxdimrwweZ
Claude Code Pro/Max users don't spend dollars, they spend a 5-hour /
weekly usage limit. /caveman-stats now also reports the savings in
their currency: 'Session budget saved: ~X% of your usage this session
(est.)', and the lifetime view gets 'Est. budget saved: ~X% of tracked
usage (est.)'.
Honesty rules baked in:
- % = saved / (saved + used) from tokens we actually count — nothing
else. budgetSavedPct returns null (line omitted) when no savings are
measured: honest zero, no claim.
- no plan-limit sizes are assumed or hardcoded (Anthropic doesn't
publish token quotas); the footer says so explicitly.
- clearly labeled (est.); USD lines stay for API users.
Checksums refreshed for the caveman-stats.js change. 5 new tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011kmm2umRGb5nLxdimrwweZ
The old loader setdefault'ed EVERY key found in repo-root .env.local
into os.environ. Security scanners (Hermes skill scan, issue #528) flag
that as a high-severity exfiltration surface: install caveman into a
profile with secrets in .env.local and the benchmark quietly pulls all
of them into its process environment.
The benchmark only ever needs ANTHROPIC_API_KEY (anthropic.Anthropic()
reads it implicitly), so read that one key and nothing else — skip the
file entirely when the var is already set.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011kmm2umRGb5nLxdimrwweZ
irm .../install.ps1 | iex crashed with "Cannot bind argument to
parameter 'Path' because it is null": under iex there is no script
file, so $MyInvocation.MyCommand.Path is $null and Split-Path threw.
The top-level param() block also can't receive arguments through a
pipe.
- wrap all logic in Install-Caveman, invoked at the bottom with the
automatic $args (populated for file runs, empty under iex)
- replace $MyInvocation.MyCommand.Path with $PSCommandPath, guarded —
pipe installs skip the local-clone branch and go straight to npx
- static regression tests in tests/installer/ps1-pipe.test.mjs (CI has
no pwsh, so pin the pipe-safety contract textually)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011kmm2umRGb5nLxdimrwweZ
Claude Code only scans commands/*.md (YAML frontmatter) for plugin slash
commands — the commands/*.toml files are the Gemini extension format and
are ignored, so /caveman, /caveman-commit, /caveman-review,
/caveman-stats and /caveman-init all returned Unknown command after a
plugin install.
- add commands/{caveman,caveman-commit,caveman-review,caveman-stats,
caveman-init}.md mirroring the toml prompts ({{args}} -> $ARGUMENTS)
- caveman-stats.md body still matches the mode-tracker intercept regex
so the hook injects real numbers, model computes nothing
- caveman-init.md uses the standalone-fallback body from #603
- keep every .toml — Gemini CLI extensions only read TOML
- pin the two-format contract + hook-regex + no-{{args}}-leak in
tests/installer/slash-commands.test.mjs
The namespaced-command side of #599 (/caveman:caveman-commit etc.) is
already handled by the mode-tracker (both bare and namespaced forms,
one-shot restore) — this closes the discovery half.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011kmm2umRGb5nLxdimrwweZ
commands/caveman-init.toml told every installed Codex/Gemini user to run
node src/tools/caveman-init.js — a path that only exists inside the
caveman clone, so the command failed with Cannot find module for
everyone but caveman developers.
- toml prompt now gates the repo-relative path on the file existing and
falls back to the standalone script via curl | node -
- src/tools/caveman-init.js: the documented curl|node stdin path was
silently a no-op — require.main is undefined under node -, so the
require.main === module guard never ran main() (exit 0, no output,
no files). Guard now also matches module.id === '[stdin]'.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011kmm2umRGb5nLxdimrwweZ
PR #560 shipped the regression test but its source change never made it
into the diff — the branch only adds tests/test_mode_tracker_stdin.js.
Applied the one-line silent-exit error listener the PR describes and
took the author's test verbatim.
Co-authored-by: ousamabenyounes <ousamabenyounes@users.noreply.github.com>
--force on an AGENTS.md containing the legacy un-fenced sentinel
replaced the entire file with the fenced block — destroying all
user-authored content around the legacy block. The installer's own
hint told users with mixed files to run exactly that.
Migrate instead: back up once to AGENTS.md.bak, remove the legacy
block (exact match of the current rule body when possible, otherwise
cut from the sentinel's paragraph start — the legacy path appended the
block, so user content precedes it), and write the fenced block after
the preserved user content.
Fixes#594
A truncated/stray marker (interrupted write, partial user edit) chained
into deleting user content: appendBootstrapToSoul saw 'no complete
block' and appended a second one; stripBootstrapFromSoul then cut from
the FIRST begin to the FIRST end — spanning everything between the
stray marker and the appended block. Reported reproduction ended with
the whole SOUL.md deleted.
Replace the single-span cut with a scan that pairs each begin with the
nearest end before the next begin; unpaired markers are removed as just
the marker text, never as a span. Append now detects damaged markers
(orphans, duplicates), strips them safely, and writes one clean block.
Fixes#596
Natural-language matching (#598):
- Deactivation computed first, word-order tolerant: 'turn caveman mode
off' used to ACTIVATE caveman (and reset the level to default),
'turn caveman off' was a no-op.
- 'enable caveman and stop apologizing' no longer deactivates (the old
stop-guard fired on 'stop' anywhere, then the deactivation regex
matched 'caveman and stop').
- Questions ('what is caveman mode?') no longer arm the mode.
- 'normal mode' deactivates only as a command or with caveman context
('how do I exit vim normal mode' no longer kills the session mode).
- Prompts normalized to one line so multiline input matches.
- Scoped brevity ('be brief in the summary section') is a one-off
instruction, not a session-wide switch.
One-shot modes (#599):
- /caveman-commit|-review|-compress save the displaced prose mode to
.caveman-active.prev and the next ordinary prompt restores it (or
deactivates if caveman wasn't active before) — SKILL.md's 'level
persist until changed or session end' holds again.
- Plugin-namespaced /caveman:caveman-commit and -review recognized
(only compress and stats had the variant).
- Deactivation clears the saved prev so nothing resurrects the mode.
Fixes#598, fixes#599
The trailing-comma sweep ran a global regex over the whole
comment-stripped output, including string contents — a JSONC
settings.json with a value containing ',}' or ',]' (shell brace
expansion, inline JSON in hook args) was silently corrupted on read
and persisted corrupted on the next write.
Replace the regex with a scan that tracks string state (same approach
as the comment stripper above it) and only drops commas outside
strings.
Fixes#595