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
7 changes: 7 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Overwritten by CI on push to main when sources change. Edits here lost.
`.github/workflows/sync-skill.yml` triggers on main push when `skills/caveman/SKILL.md` or `rules/caveman-activate.md` changes.

What it does:

1. Copies `skills/caveman/SKILL.md` to all agent-specific SKILL.md locations
2. Rebuilds `caveman.skill` as a ZIP of `skills/caveman/`
3. Rebuilds all agent rule files from `rules/caveman-activate.md`, prepending agent-specific frontmatter (Cursor needs `alwaysApply: true`, Windsurf needs `trigger: always_on`)
Expand Down Expand Up @@ -88,12 +89,14 @@ All hooks honor `CLAUDE_CONFIG_DIR` for non-default Claude Code config locations
### `hooks/caveman-config.js` — shared module

Exports:

- `getDefaultMode()` — resolves default mode from `CAVEMAN_DEFAULT_MODE` env var, then `$XDG_CONFIG_HOME/caveman/config.json` / `~/.config/caveman/config.json` / `%APPDATA%\caveman\config.json`, then `'full'`
- `safeWriteFlag(flagPath, content)` — symlink-safe flag write. Refuses if flag target or its immediate parent is a symlink. Opens with `O_NOFOLLOW` where supported. Atomic temp + rename. Creates with `0600`. Protects against local attackers replacing the predictable flag path with a symlink to clobber files writable by the user. Used by both write hooks. Silent-fails on all filesystem errors.

### `hooks/caveman-activate.js` — SessionStart hook

Runs once per Claude Code session start. Three things:

1. Writes the active mode to `$CLAUDE_CONFIG_DIR/.caveman-active` via `safeWriteFlag` (creates if missing)
2. Emits caveman ruleset as hidden stdout — Claude Code injects SessionStart hook stdout as system context, invisible to user
3. Checks `settings.json` for statusline config; if missing, appends nudge to offer setup on first interaction
Expand All @@ -105,7 +108,9 @@ Silent-fails on all filesystem errors — never blocks session start.
Reads JSON from stdin. Three responsibilities:

**1. Slash-command activation.** If prompt starts with `/caveman`, writes mode to flag file via `safeWriteFlag`:

- `/caveman` → configured default (see `caveman-config.js`, defaults to `full`)
- `/caveman status` → read-only query, emits current mode via `hookSpecificOutput`, no flag write
- `/caveman lite` → `lite`
- `/caveman ultra` → `ultra`
- `/caveman wenyan` or `/caveman wenyan-full` → `wenyan`
Expand All @@ -122,6 +127,7 @@ Reads JSON from stdin. Three responsibilities:
### `hooks/caveman-statusline.sh` — Statusline badge

Reads flag file at `$CLAUDE_CONFIG_DIR/.caveman-active`. Outputs colored badge string for Claude Code statusline:

- `full` or empty → `[CAVEMAN]` (orange)
- anything else → `[CAVEMAN:<MODE_UPPERCASED>]` (orange)

Expand Down Expand Up @@ -181,6 +187,7 @@ For agents without hook systems, minimal always-on snippet lives in README under
## Evals

`evals/` has three-arm harness:

- `__baseline__` — no system prompt
- `__terse__` — `Answer concisely.`
- `<skill>` — `Answer concisely.\n\n{SKILL.md}`
Expand Down
15 changes: 14 additions & 1 deletion hooks/caveman-mode-tracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,20 @@ process.stdin.on('end', () => {
} else if (cmd === '/caveman-compress' || cmd === '/caveman:caveman-compress') {
mode = 'compress';
} else if (cmd === '/caveman' || cmd === '/caveman:caveman') {
if (arg === 'lite') mode = 'lite';
if (arg === 'status') {
// Status query — don't change mode, just report current state
const current = readFlag(flagPath);
const statusMsg = current
? 'CAVEMAN STATUS: Active mode is "' + current + '". Tell the user their current caveman mode is: ' + current
: 'CAVEMAN STATUS: Caveman is not active. Tell the user caveman mode is currently off.';
process.stdout.write(JSON.stringify({
hookSpecificOutput: {
hookEventName: "UserPromptSubmit",
additionalContext: statusMsg
}
}));
return; // skip all other processing — status is read-only
} else if (arg === 'lite') mode = 'lite';
else if (arg === 'ultra') mode = 'ultra';
else if (arg === 'wenyan-lite') mode = 'wenyan-lite';
else if (arg === 'wenyan' || arg === 'wenyan-full') mode = 'wenyan';
Expand Down
4 changes: 4 additions & 0 deletions skills/caveman-help/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ Mode stick until changed or session end.
| **caveman-compress** | `/caveman:compress <file>` | Compress .md files to caveman prose. Saves ~46% input tokens. |
| **caveman-help** | `/caveman-help` | This card. |

## Check Active Mode

`/caveman status` — show which mode active right now (lite/full/ultra/wenyan-*/off).

## Deactivate

Say "stop caveman" or "normal mode". Resume anytime with `/caveman`.
Expand Down
2 changes: 1 addition & 1 deletion skills/caveman/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Respond terse like smart caveman. All technical substance stay. Only fluff die.

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`.
Default: **full**. Switch: `/caveman lite|full|ultra`. Check: `/caveman status`.

## Rules

Expand Down