@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 +46 -0
- package/LICENSE +21 -0
- package/README.md +139 -0
- package/agents/cavecrew-builder.md +52 -0
- package/agents/cavecrew-investigator.md +61 -0
- package/agents/cavecrew-reviewer.md +52 -0
- package/extensions/caveman-core.ts +94 -0
- package/extensions/caveman.ts +155 -0
- package/package.json +57 -0
- package/skills/cavecrew/README.md +49 -0
- package/skills/cavecrew/SKILL.md +91 -0
- package/skills/caveman/README.md +48 -0
- package/skills/caveman/SKILL.md +78 -0
- package/skills/caveman-commit/README.md +44 -0
- package/skills/caveman-commit/SKILL.md +65 -0
- package/skills/caveman-compress/README.md +166 -0
- package/skills/caveman-compress/SECURITY.md +31 -0
- package/skills/caveman-compress/SKILL.md +111 -0
- package/skills/caveman-compress/scripts/__init__.py +9 -0
- package/skills/caveman-compress/scripts/__main__.py +3 -0
- package/skills/caveman-compress/scripts/benchmark.py +80 -0
- package/skills/caveman-compress/scripts/cli.py +85 -0
- package/skills/caveman-compress/scripts/compress.py +341 -0
- package/skills/caveman-compress/scripts/detect.py +169 -0
- package/skills/caveman-compress/scripts/validate.py +213 -0
- package/skills/caveman-help/README.md +38 -0
- package/skills/caveman-help/SKILL.md +51 -0
- package/skills/caveman-review/README.md +33 -0
- package/skills/caveman-review/SKILL.md +55 -0
- package/skills/caveman-stats/README.md +36 -0
- package/skills/caveman-stats/SKILL.md +31 -0
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
|
+
}
|