@kulapard/pi-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.
package/AGENTS.md ADDED
@@ -0,0 +1,46 @@
1
+ # AGENTS.md — pi-caveman conventions
2
+
3
+ Project memory for agents working in this repo. Non-obvious conventions only.
4
+
5
+ ## Architecture (do not rebuild)
6
+
7
+ - `extensions/caveman.ts` is the Pi extension; `extensions/caveman-core.ts` holds
8
+ the pure, SDK-free logic (`normalizeMode`, `modeInstructions`, `VALID_MODES`,
9
+ the activation/deactivation regexes) so it is unit-testable without a fake SDK.
10
+ - Mode state is **session-scoped**: stored via `pi.appendEntry("caveman-mode", …)`
11
+ and restored from `ctx.sessionManager.getBranch()` on `session_start`. A new
12
+ session always starts `off`. There is **no** cross-session config file or env var.
13
+ - Activation = `before_agent_start` appends `modeInstructions(mode)` to the
14
+ system prompt. Statusline = `ctx.ui.setStatus("caveman", …)` guarded by `hasUI`.
15
+ - Pi 0.80.2 has **no `agents/` subagent mechanism**. The `agents/cavecrew-*.md`
16
+ files are reference personas only and cavecrew is optional/out-of-scope.
17
+
18
+ ## Invariants
19
+
20
+ - **SDK import is `import type` only** in `extensions/*.ts`. The JS tests run via
21
+ `--experimental-strip-types`, which erases type-only imports, so no installed
22
+ SDK is needed at test time. A value import from `@earendil-works/pi-coding-agent`
23
+ would break the tests — `tests/extension.test.mjs` asserts this invariant.
24
+ - **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
+
32
+ ## Tests / validation
33
+
34
+ - `npm test` runs `pretest` (`npm run typecheck` → `tsc --noEmit`) first, then the
35
+ `node --test` suites under `tests/`. Typecheck failures fail the test run.
36
+ - The JS test glob (`tests/**/*.test.mjs`) is expanded by the Node `--test` runner,
37
+ not the shell. Directory-recursion (`--test tests/`) does **not** work on the
38
+ 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
+ - Several tests are **phantom-reference guards** (`tests/stats-docs.test.mjs`,
43
+ `tests/cavecrew-docs.test.mjs`, `tests/compress-docs.test.mjs`): they assert the
44
+ docs do **not** mention a Claude-Code hooks layer, a plugin install path, the
45
+ `⛏` badge, Claude-only subagent presets, or broken asset paths. Do not
46
+ reintroduce those references.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Taras Drapalyuk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,139 @@
1
+ # pi-caveman
2
+
3
+ A [Pi](https://github.com/earendil-works/pi-coding-agent) port of
4
+ [caveman](https://github.com/JuliusBrussee/caveman) — a terse-output mode for the
5
+ Pi coding agent. It makes the agent speak in compressed "caveman" prose (drop
6
+ articles, filler, and hedging; fragments over sentences) to cut output tokens by
7
+ roughly 65–75% **while keeping full technical accuracy**: code, commands, API
8
+ names, file paths, and exact error strings are always preserved verbatim.
9
+
10
+ pi-caveman ships as a Pi package: a single extension (`extensions/caveman.ts`)
11
+ plus a set of skills under `skills/`.
12
+
13
+ ## Install
14
+
15
+ pi-caveman publishes to npm as
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:
18
+
19
+ ```bash
20
+ pi install npm:@kulapard/pi-caveman
21
+ ```
22
+
23
+ You can also load it straight from a checkout on disk (no install needed) — see
24
+ the `pi -e` mechanism below.
25
+
26
+ ### Load the extension directly (confirmed mechanism)
27
+
28
+ The simplest, confirmed way to load it is Pi's `-e` flag, which loads the
29
+ extension file for a session:
30
+
31
+ ```bash
32
+ pi -e /path/to/pi-caveman/extensions/caveman.ts --skill /path/to/pi-caveman/skills
33
+ ```
34
+
35
+ (Run from a clone of this repo, substituting its absolute path.)
36
+
37
+ ### Load via the package manifest
38
+
39
+ The repo's `package.json` carries a `pi` block so Pi can resolve the extension
40
+ and skills from the package directory:
41
+
42
+ ```json
43
+ "pi": {
44
+ "extensions": ["./extensions/caveman.ts"],
45
+ "skills": ["./skills"]
46
+ }
47
+ ```
48
+
49
+ Pi resolves `pi.extensions` relative to the package directory, so the extension
50
+ loads at its current path (no move into `.pi/extensions/` is needed). Point Pi at
51
+ the package directory with `pi install /path/to/pi-caveman` if you prefer a
52
+ persistent install over a per-session `pi -e`.
53
+
54
+ ### First-time setup (for development)
55
+
56
+ ```bash
57
+ npm install # fetch the Pi SDK + TypeScript dev deps
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
+ ```
66
+
67
+ ## Modes
68
+
69
+ Six intensity modes (default **full**). A mode sticks until you change it or the
70
+ session ends.
71
+
72
+ | Mode | Command | Effect |
73
+ |------|---------|--------|
74
+ | **lite** | `/caveman lite` | Drop filler. Keep sentence structure. |
75
+ | **full** | `/caveman` | Drop articles, filler, pleasantries, hedging. Fragments OK. Default. |
76
+ | **ultra** | `/caveman ultra` | Extreme compression. Bare fragments. Tables over prose. |
77
+ | **wenyan-lite** | `/caveman wenyan-lite` | Classical Chinese (文言文) style, light compression. |
78
+ | **wenyan-full** | `/caveman wenyan` | Full 文言文. Maximum classical terseness. |
79
+ | **wenyan-ultra** | `/caveman wenyan-ultra` | Extreme classical terseness. |
80
+
81
+ `/caveman off` disables terse mode.
82
+
83
+ ## Commands
84
+
85
+ | Command | What it does |
86
+ |---------|--------------|
87
+ | `/caveman [mode\|off]` | Enable a mode for this session (or turn it off). |
88
+ | `/caveman-help` | Show the quick-reference card. |
89
+ | `/caveman-commit [notes]` | Generate a terse Conventional Commit message. Does **not** commit. |
90
+ | `/caveman-review [scope]` | One-line-per-finding code review comments. |
91
+ | `/caveman-compress <file>` | Compress a prose file via the caveman-compress skill. |
92
+ | `/caveman-stats` | Load the stats skill (an on-demand, model-driven estimate). |
93
+
94
+ ## Natural-language activation
95
+
96
+ You don't have to use a slash command. The extension watches your messages and
97
+ switches mode on phrases like:
98
+
99
+ - **Activate:** "caveman mode", "talk like caveman", "use caveman", "less
100
+ tokens", "fewer tokens", "save tokens" → enables **full** mode.
101
+ - **Deactivate:** "stop caveman", "normal mode", "disable caveman" → turns it
102
+ off.
103
+
104
+ ## Session-scoped behavior
105
+
106
+ Mode state is **session-scoped**. It is stored as a session entry and restored
107
+ across a `/reload` within the same session, but a **new session always starts
108
+ with caveman off** — by design. There is no cross-session config file or global
109
+ default that auto-enables it.
110
+
111
+ ## Statusline indicator
112
+
113
+ When a UI is attached, the statusline shows the active mode as
114
+ `caveman:<mode>` (for example `caveman:ultra`). When caveman is off the indicator
115
+ is cleared.
116
+
117
+ ## Compression vs. upstream MCP shrink
118
+
119
+ Upstream caveman ships a `caveman-shrink` **MCP middleware** — a stdio proxy that
120
+ sits between an MCP client and an upstream MCP server and compresses the server's
121
+ tool descriptions. pi-caveman does **not** bundle it: that proxy works at the
122
+ MCP-client layer, independent of Pi, and upstream itself ships it as a separate
123
+ package — so it does not belong in this extension-plus-skills package.
124
+
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).
135
+
136
+ ## Attribution & license
137
+
138
+ pi-caveman is a Pi port of [caveman](https://github.com/JuliusBrussee/caveman)
139
+ by Julius Brussee. Licensed under the [MIT License](./LICENSE).
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: cavecrew-builder
3
+ description: >
4
+ Surgical 1-2 file edit. Typo fixes, single-function rewrites, mechanical
5
+ renames, comment removal, format-preserving tweaks. Hard refuses 3+ file
6
+ scope. Returns caveman diff receipt. Use when scope is bounded and
7
+ obvious; do NOT use for new features, new files (unless asked), or
8
+ cross-file refactors.
9
+ ---
10
+
11
+ > **Reference persona — not wired into Pi.** Pi 0.80.2 has no subagent/`agents/`
12
+ > mechanism, so this file is not loaded as a runnable preset. It is a design
13
+ > note: the prompt you would give a delegated "builder" agent, usable only via
14
+ > an external Pi subagent capability (e.g. a future `pi-subagents` package).
15
+ > See `skills/cavecrew/README.md`.
16
+
17
+ Caveman-ultra. Drop articles/filler. Code/paths exact, backticked. No narration.
18
+
19
+ ## Scope
20
+
21
+ 1 file ideal. 2 OK. 3+ → refuse.
22
+ Edit existing only (new file iff user asked).
23
+ No new abstractions. No drive-by refactors. No comment additions.
24
+ Restrict to read/edit/write tools — no `bash`, so cannot shell out, push, or delete.
25
+
26
+ ## Workflow
27
+
28
+ 1. `read` target(s). Never edit blind.
29
+ 2. `edit` smallest diff that work.
30
+ 3. Re-`read` to verify.
31
+ 4. Return receipt.
32
+
33
+ ## Output (receipt)
34
+
35
+ ```
36
+ <path:line-range> — <change ≤10 words>.
37
+ <path:line-range> — <change ≤10 words>.
38
+ verified: <re-read OK | mismatch @ path:line>.
39
+ ```
40
+
41
+ Diff is the artifact. Receipt is the proof. No exploration story.
42
+
43
+ ## Refusals (terminal lines)
44
+
45
+ 3+ files → `too-big. split: <n one-line tasks>.`
46
+ Destructive needed → `needs-confirm. op: <command>.`
47
+ Spec ambiguous → `ambiguous. ask: <one question>.`
48
+ Tests fail post-edit, can't fix in scope → `regressed. revert path:line. cause: <fragment>.`
49
+
50
+ ## Auto-clarity
51
+
52
+ Security or destructive paths → write normal English warning, then resume caveman.
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: cavecrew-investigator
3
+ description: >
4
+ Read-only code locator. Returns file:line table for "where is X defined",
5
+ "what calls Y", "list all uses of Z", "map this directory". Output is
6
+ caveman-compressed so the main thread eats ~60% fewer tokens than a
7
+ prose locate. Refuses to suggest fixes.
8
+ ---
9
+
10
+ > **Reference persona — not wired into Pi.** Pi 0.80.2 has no subagent/`agents/`
11
+ > mechanism, so this file is not loaded as a runnable preset. It is a design
12
+ > note: the prompt you would give a delegated "investigator" agent, usable only
13
+ > via an external Pi subagent capability (e.g. a future `pi-subagents` package).
14
+ > See `skills/cavecrew/README.md`.
15
+
16
+ Caveman-ultra. Drop articles/filler/hedging. Code/symbols/paths exact, backticked. Lead with answer.
17
+
18
+ ## Job
19
+
20
+ Locate. Report. Stop. Never edit, never propose fix.
21
+
22
+ ## Output
23
+
24
+ ```
25
+ <path:line> — `<symbol>` — <≤6 word note>
26
+ <path:line> — `<symbol>` — <≤6 word note>
27
+ ```
28
+
29
+ Group with one-word header when 3+ rows: `Defs:` / `Refs:` / `Callers:` / `Tests:` / `Imports:` / `Sites:`.
30
+ Single hit → one line, no header.
31
+ Zero hits → `No match.`
32
+ Last line → totals: `2 defs, 5 refs.` (omit if 0 or 1).
33
+
34
+ ## Tools
35
+
36
+ `grep` for symbols/strings. `glob` for paths. `read` only specific ranges. `bash` for `git log -S`/`git grep`/`find` when faster.
37
+
38
+ ## Refusals
39
+
40
+ Asked to fix → `Read-only. Spawn cavecrew-builder.`
41
+ Asked to design → `Read-only. Spawn cavecrew-builder or use main thread.`
42
+
43
+ ## Auto-clarity
44
+
45
+ Security warnings, destructive ops → write normal English. Resume after.
46
+
47
+ ## Example
48
+
49
+ Q: "where mode normalized + injected?"
50
+
51
+ ```
52
+ Defs:
53
+ - extensions/caveman-core.ts:26 — `normalizeMode` — raw → StoredMode | undefined
54
+ - extensions/caveman-core.ts:41 — `modeInstructions` — per-mode system-prompt text
55
+ Callers:
56
+ - extensions/caveman.ts:52,78 — `normalizeMode`
57
+ - extensions/caveman.ts:160 — `modeInstructions` (before_agent_start)
58
+ Tests:
59
+ - tests/extension.test.mjs — normalizeMode table + per-mode lines
60
+ 2 defs, 3 callers, 1 test file.
61
+ ```
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: cavecrew-reviewer
3
+ description: >
4
+ Diff/branch/file reviewer. One line per finding, severity-tagged, no praise,
5
+ no scope creep. Output format `path:line: <emoji> <severity>: <problem>. <fix>.`
6
+ Use for "review this PR", "review my diff", "audit this file". Skips
7
+ formatting nits unless they change meaning.
8
+ ---
9
+
10
+ > **Reference persona — not wired into Pi.** Pi 0.80.2 has no subagent/`agents/`
11
+ > mechanism, so this file is not loaded as a runnable preset. It is a design
12
+ > note: the prompt you would give a delegated "reviewer" agent, usable only via
13
+ > an external Pi subagent capability (e.g. a future `pi-subagents` package).
14
+ > See `skills/cavecrew/README.md`.
15
+
16
+ Caveman-ultra. Findings only. No "looks good", no "I'd suggest", no preamble.
17
+
18
+ ## Severity
19
+
20
+ | Emoji | Tier | Use for |
21
+ |---|---|---|
22
+ | 🔴 | bug | Wrong output, crash, security hole, data loss |
23
+ | 🟡 | risk | Edge case, race, leak, perf cliff, missing guard |
24
+ | 🔵 | nit | Style, naming, micro-perf — emit only if user asked thorough |
25
+ | ❓ | question | Need author intent before judging |
26
+
27
+ ## Output
28
+
29
+ ```
30
+ path/to/file.ts:42: 🔴 bug: token expiry uses `<` not `<=`. Off-by-one allows expired tokens 1 tick.
31
+ path/to/file.ts:118: 🟡 risk: pool not closed on error path. Add `try/finally`.
32
+ src/utils.ts:7: ❓ question: why duplicate `.trim()` here?
33
+ totals: 1🔴 1🟡 1❓
34
+ ```
35
+
36
+ Zero findings → `No issues.`
37
+ File order, ascending line numbers within file.
38
+
39
+ ## Boundaries
40
+
41
+ - Review only what's in front of you. No "while we're here".
42
+ - No big-refactor proposals.
43
+ - Need more context → append `(see L<n> in <file>)`. Don't guess.
44
+ - Formatting nits skipped unless they change meaning.
45
+
46
+ ## Tools
47
+
48
+ `bash` only for `git diff`/`git log -p`/`git show`. No mutating commands.
49
+
50
+ ## Auto-clarity
51
+
52
+ Security findings → state risk in plain English first sentence, then caveman fix line.
@@ -0,0 +1,94 @@
1
+ // Pure, SDK-free core logic for the caveman extension.
2
+ // Kept separate from caveman.ts so it can be unit-tested directly without a
3
+ // fake/installed Pi SDK. This module imports no runtime SDK values; any SDK
4
+ // types it ever needs MUST be imported as `import type` so --experimental-strip-types
5
+ // can erase them.
6
+
7
+ export type CavemanMode =
8
+ | "lite"
9
+ | "full"
10
+ | "ultra"
11
+ | "wenyan-lite"
12
+ | "wenyan-full"
13
+ | "wenyan-ultra";
14
+
15
+ export type StoredMode = CavemanMode | "off";
16
+
17
+ export const VALID_MODES = new Set<CavemanMode>([
18
+ "lite",
19
+ "full",
20
+ "ultra",
21
+ "wenyan-lite",
22
+ "wenyan-full",
23
+ "wenyan-ultra",
24
+ ]);
25
+
26
+ // Values offered as argument completions for /caveman: the six real modes plus
27
+ // the `wenyan` alias (→ wenyan-full via normalizeMode) and `off`. These are what
28
+ // a user may type, which is a superset of VALID_MODES, so it is kept as its own
29
+ // list rather than derived from VALID_MODES.
30
+ export const COMPLETION_VALUES: readonly string[] = [
31
+ "lite",
32
+ "full",
33
+ "ultra",
34
+ "wenyan",
35
+ "wenyan-lite",
36
+ "wenyan-full",
37
+ "wenyan-ultra",
38
+ "off",
39
+ ];
40
+
41
+ export function normalizeMode(raw: string | undefined): StoredMode | undefined {
42
+ const value = (raw ?? "").trim().toLowerCase();
43
+ if (!value) return "full";
44
+ if (
45
+ ["off", "stop", "normal", "normal-mode", "disable", "disabled"].includes(
46
+ value,
47
+ )
48
+ )
49
+ return "off";
50
+ if (value === "wenyan" || value === "classical") return "wenyan-full";
51
+ return VALID_MODES.has(value as CavemanMode)
52
+ ? (value as CavemanMode)
53
+ : undefined;
54
+ }
55
+
56
+ export function modeInstructions(mode: CavemanMode): string {
57
+ const base = `
58
+ CAVEMAN MODE ACTIVE. Respond terse like smart caveman. All technical substance stay. Only fluff die.
59
+
60
+ Persistence:
61
+ - Apply to every assistant response until user says "stop caveman", "normal mode", or runs /caveman off.
62
+ - Do not announce mode. No "caveman mode on", no self-reference.
63
+
64
+ Core rules:
65
+ - Drop articles, filler, pleasantries, hedging, tool-call narration.
66
+ - Prefer fragments. Pattern: [thing] [action] [reason]. [next step].
67
+ - Keep technical terms exact. Keep code blocks, inline code, commands, API names, file paths, commit types, and exact error strings verbatim.
68
+ - Preserve user's dominant language; compress style, not language.
69
+ - Avoid decorative tables/emoji unless useful or requested.
70
+ - Do not dump long raw logs unless asked; quote shortest decisive line.
71
+
72
+ Auto-clarity:
73
+ - Use normal precise prose for security warnings, irreversible confirmations, or sequences where terse fragments risk ambiguity.
74
+ - Resume terse style after clear part.`;
75
+
76
+ const perMode: Record<CavemanMode, string> = {
77
+ lite: "Intensity: lite. Remove filler/hedging. Keep articles and full professional sentences.",
78
+ full: "Intensity: full. Drop articles; fragments OK; short synonyms. Classic caveman.",
79
+ ultra:
80
+ "Intensity: ultra. Bare fragments. Use arrows for causality. Abbreviate prose words only; never abbreviate real code symbols, function names, API names, or error strings.",
81
+ "wenyan-lite":
82
+ "Intensity: wenyan-lite. Semi-classical Chinese register when user writes Chinese; otherwise terse lite mode.",
83
+ "wenyan-full":
84
+ "Intensity: wenyan-full. Maximum classical Chinese terseness when user writes Chinese; otherwise terse full mode.",
85
+ "wenyan-ultra":
86
+ "Intensity: wenyan-ultra. Extreme classical Chinese compression when user writes Chinese; otherwise terse ultra mode.",
87
+ };
88
+
89
+ return `${base}\n\n${perMode[mode]}`;
90
+ }
91
+
92
+ export const ACTIVATION_RE =
93
+ /\b(caveman mode|talk like caveman|use caveman|less tokens|fewer tokens|save tokens)\b/i;
94
+ export const DEACTIVATION_RE = /\b(stop caveman|normal mode|disable caveman)\b/i;
@@ -0,0 +1,155 @@
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionContext,
4
+ } from "@earendil-works/pi-coding-agent";
5
+ import {
6
+ ACTIVATION_RE,
7
+ COMPLETION_VALUES,
8
+ DEACTIVATION_RE,
9
+ modeInstructions,
10
+ normalizeMode,
11
+ type StoredMode,
12
+ } from "./caveman-core.ts";
13
+
14
+ const HELP_TEXT = `# Caveman for Pi
15
+
16
+ Commands:
17
+ - /caveman [lite|full|ultra|wenyan|wenyan-lite|wenyan-full|wenyan-ultra] — enable terse mode for this session.
18
+ - /caveman off — disable terse mode.
19
+ - /caveman-help — show this card.
20
+ - /caveman-commit [notes] — generate Conventional Commit message. Does not commit.
21
+ - /caveman-review [scope] — terse review comments.
22
+ - /caveman-compress <file> — compress prose file via caveman-compress skill.
23
+ - /caveman-stats — load stats skill/help.
24
+
25
+ Mode persists in current Pi session and survives /reload via session state.
26
+ Code, commands, API names, file paths, and exact errors stay verbatim.`;
27
+
28
+ export default function cavemanExtension(pi: ExtensionAPI) {
29
+ let mode: StoredMode = "off";
30
+
31
+ function setStatus(ctx?: ExtensionContext) {
32
+ if (!ctx?.hasUI) return;
33
+ ctx.ui.setStatus("caveman", mode === "off" ? undefined : `caveman:${mode}`);
34
+ }
35
+
36
+ function persistMode(nextMode: StoredMode, ctx?: ExtensionContext) {
37
+ mode = nextMode;
38
+ pi.appendEntry("caveman-mode", { mode, timestamp: Date.now() });
39
+ setStatus(ctx);
40
+ }
41
+
42
+ pi.on("session_start", (_event, ctx) => {
43
+ mode = "off";
44
+ for (const entry of ctx.sessionManager.getBranch() as Array<{
45
+ type?: string;
46
+ customType?: string;
47
+ data?: { mode?: unknown };
48
+ }>) {
49
+ if (entry.type !== "custom" || entry.customType !== "caveman-mode")
50
+ continue;
51
+ const restored =
52
+ typeof entry.data?.mode === "string"
53
+ ? normalizeMode(entry.data.mode)
54
+ : undefined;
55
+ if (restored) mode = restored;
56
+ }
57
+ setStatus(ctx);
58
+ });
59
+
60
+ pi.registerCommand("caveman", {
61
+ description: "Enable caveman terse mode: lite, full, ultra, wenyan, or off",
62
+ getArgumentCompletions: (prefix) => {
63
+ const normalizedPrefix = prefix.trim().toLowerCase();
64
+ const items = COMPLETION_VALUES.flatMap((value) =>
65
+ value.startsWith(normalizedPrefix) ? [{ value, label: value }] : [],
66
+ );
67
+ return items.length > 0 ? items : null;
68
+ },
69
+ handler: async (args, ctx) => {
70
+ const nextMode = normalizeMode(args);
71
+ if (!nextMode) {
72
+ ctx.ui.notify(`Unknown caveman mode: ${args || "(empty)"}`, "error");
73
+ return;
74
+ }
75
+ persistMode(nextMode, ctx);
76
+ ctx.ui.notify(
77
+ nextMode === "off" ? "Caveman disabled" : `Caveman ${nextMode} enabled`,
78
+ "info",
79
+ );
80
+ },
81
+ });
82
+
83
+ pi.registerCommand("caveman-help", {
84
+ description: "Show caveman command reference",
85
+ handler: async () => {
86
+ pi.sendMessage({
87
+ customType: "caveman-help",
88
+ content: HELP_TEXT,
89
+ display: true,
90
+ });
91
+ },
92
+ });
93
+
94
+ pi.registerCommand("caveman-commit", {
95
+ description: "Generate terse Conventional Commit message",
96
+ handler: async (args) => {
97
+ const task =
98
+ args?.trim() ||
99
+ "Generate a commit message for current repository changes. Inspect git status and diffs as needed. Do not run git commit.";
100
+ pi.sendUserMessage(`/skill:caveman-commit ${task}`);
101
+ },
102
+ });
103
+
104
+ pi.registerCommand("caveman-review", {
105
+ description: "Generate terse code-review comments",
106
+ handler: async (args) => {
107
+ const task =
108
+ args?.trim() ||
109
+ "Review current repository changes or PR diff. Inspect git diff as needed. Findings only.";
110
+ pi.sendUserMessage(`/skill:caveman-review ${task}`);
111
+ },
112
+ });
113
+
114
+ pi.registerCommand("caveman-compress", {
115
+ description: "Compress prose/memory file into caveman style",
116
+ handler: async (args, ctx) => {
117
+ const target = args?.trim();
118
+ if (!target) {
119
+ ctx.ui.notify("Usage: /caveman-compress <file>", "error");
120
+ return;
121
+ }
122
+ pi.sendUserMessage(`/skill:caveman-compress ${target}`);
123
+ },
124
+ });
125
+
126
+ pi.registerCommand("caveman-stats", {
127
+ description: "Show caveman stats skill/help",
128
+ handler: async (args) => {
129
+ pi.sendUserMessage(
130
+ `/skill:caveman-stats ${args?.trim() || "Show caveman stats if available."}`,
131
+ );
132
+ },
133
+ });
134
+
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 };
145
+ }
146
+ return { action: "continue" as const };
147
+ });
148
+
149
+ pi.on("before_agent_start", (event) => {
150
+ if (mode === "off") return undefined;
151
+ return {
152
+ systemPrompt: `${event.systemPrompt}\n\n${modeInstructions(mode)}`,
153
+ };
154
+ });
155
+ }
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@kulapard/pi-caveman",
3
+ "version": "0.1.0",
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
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/kulapard/pi-caveman.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/kulapard/pi-caveman/issues"
13
+ },
14
+ "homepage": "https://github.com/kulapard/pi-caveman#readme",
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "files": [
19
+ "extensions",
20
+ "skills",
21
+ "agents",
22
+ "AGENTS.md",
23
+ "!**/__pycache__",
24
+ "!**/*.pyc",
25
+ "!skills/**/tests"
26
+ ],
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
30
+ "keywords": [
31
+ "pi-package",
32
+ "pi",
33
+ "caveman",
34
+ "compression",
35
+ "token-efficiency",
36
+ "extension"
37
+ ],
38
+ "pi": {
39
+ "extensions": [
40
+ "./extensions/caveman.ts"
41
+ ],
42
+ "skills": [
43
+ "./skills"
44
+ ]
45
+ },
46
+ "scripts": {
47
+ "pretest": "npm run typecheck",
48
+ "test": "node --experimental-strip-types --test tests/**/*.test.mjs",
49
+ "typecheck": "tsc --noEmit",
50
+ "test:py": ".venv/bin/pytest skills/caveman-compress",
51
+ "prepublishOnly": "npm test"
52
+ },
53
+ "devDependencies": {
54
+ "@earendil-works/pi-coding-agent": "^0.80.2",
55
+ "typescript": "^5"
56
+ }
57
+ }