@kulapard/pi-caveman 0.4.1 → 0.4.3
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 +14 -44
- package/CHANGELOG.md +18 -1
- package/extensions/caveman-state.ts +49 -0
- package/extensions/caveman.ts +22 -5
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -1,55 +1,25 @@
|
|
|
1
1
|
# AGENTS.md — pi-caveman conventions
|
|
2
2
|
|
|
3
|
-
Project memory
|
|
3
|
+
Project memory. Non-obvious only.
|
|
4
4
|
|
|
5
5
|
## Architecture (do not rebuild)
|
|
6
6
|
|
|
7
|
-
- `extensions/caveman.ts`
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
|
|
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
|
-
- 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.
|
|
21
|
-
- Pi 0.80.2 has **no `agents/` subagent mechanism**. The `agents/cavecrew-*.md`
|
|
22
|
-
files are reference personas only and cavecrew is optional/out-of-scope.
|
|
7
|
+
- `extensions/caveman.ts` = Pi extension. `extensions/caveman-core.ts` = pure SDK-free logic (`normalizeMode`, `modeInstructions`, `VALID_MODES`, regexes). Unit-testable without fake SDK.
|
|
8
|
+
- Mode state **session-scoped**: `pi.appendEntry("caveman-mode", …)`, restored from `ctx.sessionManager.getBranch()` on `session_start`. New session always `off`. No cross-session config/env.
|
|
9
|
+
- Activation = `before_agent_start` appends `modeInstructions(mode)`. Statusline = `ctx.ui.setStatus("caveman", …)` guarded by `hasUI`.
|
|
10
|
+
- Extension `modeInstructions` = **canonical** activator. `skills/caveman/SKILL.md` = fallback for hosts loading skills but not extension. If both active, model sees both rule sets — intentional redundancy, no de-dupe.
|
|
11
|
+
- Pi 0.80.2 has **no `agents/` subagent mechanism**. `agents/cavecrew-*.md` = reference personas only; cavecrew optional/out-of-scope.
|
|
23
12
|
|
|
24
13
|
## Invariants
|
|
25
14
|
|
|
26
|
-
- **SDK import
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
would break the tests — `tests/extension.test.mjs` asserts this invariant.
|
|
30
|
-
- **Verbatim preservation**: caveman-compress never alters code blocks, inline
|
|
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 that was created during the
|
|
34
|
-
same invocation (stale backups are rejected before compression starts).
|
|
35
|
-
- `caveman-compress` is **prompt-only**: the Pi agent performs the compression
|
|
36
|
-
with its own model and file tools, driven by `SKILL.md`. There is no Python and
|
|
37
|
-
no external model CLI; coverage is the doc-guard test `tests/compress-docs.test.mjs`.
|
|
15
|
+
- **SDK import `import type` only** in `extensions/*.ts`. JS tests run via `--experimental-strip-types`, erases type-only imports. Value import from `@earendil-works/pi-coding-agent` breaks tests — `tests/extension.test.mjs` asserts this.
|
|
16
|
+
- **Verbatim preservation**: caveman-compress never changes code blocks, inline code, URLs, paths, commands, exact error strings. Skill instructs self-validate against original. Mismatch unfixable → restore from `.original` backup created in same invocation (stale backups rejected before compression).
|
|
17
|
+
- `caveman-compress` is **prompt-only**: Pi agent compresses with own model + file tools, driven by `SKILL.md`. No Python, no external model CLI. Coverage = `tests/compress-docs.test.mjs`.
|
|
38
18
|
|
|
39
19
|
## Changelog
|
|
40
20
|
|
|
41
|
-
Every notable change must update `CHANGELOG.md` under `[Unreleased]`. Before
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
- `npm test` runs `pretest` (`npm run typecheck` → `tsc --noEmit`) first, then the
|
|
47
|
-
`node --test` suites under `tests/`. Typecheck failures fail the test run.
|
|
48
|
-
- The JS test glob (`tests/**/*.test.mjs`) is expanded by the Node `--test` runner,
|
|
49
|
-
not the shell. Directory-recursion (`--test tests/`) does **not** work on the
|
|
50
|
-
current Node — keep the glob.
|
|
51
|
-
- Several tests are **phantom-reference guards** (`tests/stats-docs.test.mjs`,
|
|
52
|
-
`tests/cavecrew-docs.test.mjs`, `tests/compress-docs.test.mjs`): they assert the
|
|
53
|
-
docs do **not** mention a Claude-Code hooks layer, a plugin install path, the
|
|
54
|
-
`⛏` badge, Claude-only subagent presets, or broken asset paths. Do not
|
|
55
|
-
reintroduce those references.
|
|
21
|
+
Every notable change must update `CHANGELOG.md` under `[Unreleased]`. Before finishing task, run `npm run changelog:check` to confirm changelog updated for files changed. CI runs same check on every PR.
|
|
22
|
+
|
|
23
|
+
- `npm test` runs `pretest` (`npm run typecheck` → `tsc --noEmit`), then `node --test tests/**/*.test.mjs`. Typecheck failures fail test run.
|
|
24
|
+
- Node `--test` glob expanded by runner, not shell. Directory-recursion `--test tests/` does **not** work on current Node — keep glob.
|
|
25
|
+
- Tests are **phantom-reference guards** (`tests/stats-docs.test.mjs`, `tests/cavecrew-docs.test.mjs`, `tests/compress-docs.test.mjs`): docs must not mention Claude-Code hooks layer, plugin install path, `⛏` badge, Claude-only subagent presets, broken asset paths. Do not reintroduce.
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Caveman mode is now project-scoped: `.pi/caveman-mode.json` in the working
|
|
13
|
+
directory persists the mode across new sessions, while session entries still
|
|
14
|
+
override it for the current session. Falls back to `off` when no state exists.
|
|
15
|
+
- Status bar now appends live context usage (`ctx:XX%`) when a caveman mode is
|
|
16
|
+
active and Pi exposes usage data. Updates on mode changes and at each turn end.
|
|
17
|
+
|
|
18
|
+
## [0.4.2] - 2026-06-29
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Compressed `AGENTS.md` with `caveman-compress` to reduce session token load.
|
|
23
|
+
- Added `*.original` and `*.original.*` to `.gitignore` so caveman-compress
|
|
24
|
+
backups are not committed accidentally.
|
|
25
|
+
|
|
10
26
|
## [0.4.1] - 2026-06-29
|
|
11
27
|
|
|
12
28
|
### Changed
|
|
@@ -104,7 +120,8 @@ port of [caveman](https://github.com/JuliusBrussee/caveman).
|
|
|
104
120
|
token, automatic provenance, a tag-equals-version guard, and a concurrency
|
|
105
121
|
group).
|
|
106
122
|
|
|
107
|
-
[Unreleased]: https://github.com/kulapard/pi-caveman/compare/v0.4.
|
|
123
|
+
[Unreleased]: https://github.com/kulapard/pi-caveman/compare/v0.4.2...HEAD
|
|
124
|
+
[0.4.2]: https://github.com/kulapard/pi-caveman/compare/v0.4.1...v0.4.2
|
|
108
125
|
[0.4.1]: https://github.com/kulapard/pi-caveman/compare/v0.4.0...v0.4.1
|
|
109
126
|
[0.4.0]: https://github.com/kulapard/pi-caveman/compare/v0.3.0...v0.4.0
|
|
110
127
|
[0.3.0]: https://github.com/kulapard/pi-caveman/compare/v0.2.0...v0.3.0
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Project-scoped persistence for caveman mode.
|
|
2
|
+
// This is a workaround until Pi exposes SettingsManager to extensions
|
|
3
|
+
// (tracked upstream as pi-coding-agent issue #4981).
|
|
4
|
+
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { normalizeMode, type StoredMode } from "./caveman-core.ts";
|
|
8
|
+
|
|
9
|
+
const STATE_DIR = ".pi";
|
|
10
|
+
const STATE_FILE = "caveman-mode.json";
|
|
11
|
+
|
|
12
|
+
function statePath(cwd: string): string {
|
|
13
|
+
return join(cwd, STATE_DIR, STATE_FILE);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Load the project-scoped caveman mode default. Returns `undefined` when no
|
|
18
|
+
* state file exists or it cannot be read, so the caller can fall back to the
|
|
19
|
+
* session-scoped default (`off`).
|
|
20
|
+
*/
|
|
21
|
+
export function loadProjectMode(cwd: string): StoredMode | undefined {
|
|
22
|
+
const path = statePath(cwd);
|
|
23
|
+
if (!existsSync(path)) return undefined;
|
|
24
|
+
try {
|
|
25
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
26
|
+
return normalizeMode(raw.mode);
|
|
27
|
+
} catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Persist the caveman mode for this project directory. Returns `true` on
|
|
34
|
+
* success; failures are silent so that session operation is never blocked by
|
|
35
|
+
* disk issues.
|
|
36
|
+
*/
|
|
37
|
+
export function saveProjectMode(cwd: string, mode: StoredMode): boolean {
|
|
38
|
+
try {
|
|
39
|
+
const dir = join(cwd, STATE_DIR);
|
|
40
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
41
|
+
writeFileSync(
|
|
42
|
+
statePath(cwd),
|
|
43
|
+
JSON.stringify({ mode, updatedAt: Date.now() }, null, 2),
|
|
44
|
+
);
|
|
45
|
+
return true;
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
package/extensions/caveman.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
normalizeMode,
|
|
11
11
|
type StoredMode,
|
|
12
12
|
} from "./caveman-core.ts";
|
|
13
|
+
import { loadProjectMode, saveProjectMode } from "./caveman-state.ts";
|
|
13
14
|
|
|
14
15
|
const HELP_TEXT = `# Caveman for Pi
|
|
15
16
|
|
|
@@ -22,15 +23,22 @@ Commands:
|
|
|
22
23
|
- /caveman-compress <file> — compress prose file via caveman-compress skill.
|
|
23
24
|
- /caveman-stats — load stats skill/help.
|
|
24
25
|
|
|
25
|
-
Mode persists
|
|
26
|
-
Code, commands, API names, file paths, and
|
|
26
|
+
Mode persists across sessions in the same project via \`.pi/caveman-mode.json\`, and
|
|
27
|
+
survives /reload via session state. Code, commands, API names, file paths, and
|
|
28
|
+
exact errors stay verbatim.`;
|
|
27
29
|
|
|
28
30
|
export default function cavemanExtension(pi: ExtensionAPI) {
|
|
29
31
|
let mode: StoredMode = "off";
|
|
30
32
|
|
|
31
33
|
function setStatus(ctx?: ExtensionContext) {
|
|
32
34
|
if (!ctx?.hasUI) return;
|
|
33
|
-
ctx.
|
|
35
|
+
const usage = ctx.getContextUsage?.();
|
|
36
|
+
const suffix =
|
|
37
|
+
usage?.percent != null ? ` ctx:${Math.round(usage.percent)}%` : "";
|
|
38
|
+
ctx.ui.setStatus(
|
|
39
|
+
"caveman",
|
|
40
|
+
mode === "off" ? undefined : `caveman:${mode}${suffix}`,
|
|
41
|
+
);
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
function persistMode(nextMode: StoredMode, ctx?: ExtensionContext) {
|
|
@@ -40,7 +48,7 @@ export default function cavemanExtension(pi: ExtensionAPI) {
|
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
pi.on("session_start", (_event, ctx) => {
|
|
43
|
-
mode = "off";
|
|
51
|
+
mode = loadProjectMode(ctx.cwd) ?? "off";
|
|
44
52
|
for (const entry of ctx.sessionManager.getBranch() as Array<{
|
|
45
53
|
type?: string;
|
|
46
54
|
customType?: string;
|
|
@@ -73,6 +81,7 @@ export default function cavemanExtension(pi: ExtensionAPI) {
|
|
|
73
81
|
return;
|
|
74
82
|
}
|
|
75
83
|
persistMode(nextMode, ctx);
|
|
84
|
+
saveProjectMode(ctx.cwd, nextMode);
|
|
76
85
|
ctx.ui.notify(
|
|
77
86
|
nextMode === "off" ? "Caveman disabled" : `Caveman ${nextMode} enabled`,
|
|
78
87
|
"info",
|
|
@@ -137,14 +146,22 @@ export default function cavemanExtension(pi: ExtensionAPI) {
|
|
|
137
146
|
if (event.source !== "extension") {
|
|
138
147
|
const text = (event.text ?? "").trim();
|
|
139
148
|
if (DEACTIVATION_RE.test(text)) {
|
|
140
|
-
if (mode !== "off")
|
|
149
|
+
if (mode !== "off") {
|
|
150
|
+
persistMode("off", ctx);
|
|
151
|
+
saveProjectMode(ctx.cwd, "off");
|
|
152
|
+
}
|
|
141
153
|
} else if (mode === "off" && ACTIVATION_RE.test(text)) {
|
|
142
154
|
persistMode("full", ctx);
|
|
155
|
+
saveProjectMode(ctx.cwd, "full");
|
|
143
156
|
}
|
|
144
157
|
}
|
|
145
158
|
return { action: "continue" as const };
|
|
146
159
|
});
|
|
147
160
|
|
|
161
|
+
pi.on("turn_end", (_event, ctx) => {
|
|
162
|
+
setStatus(ctx);
|
|
163
|
+
});
|
|
164
|
+
|
|
148
165
|
pi.on("before_agent_start", (event) => {
|
|
149
166
|
if (mode === "off") return undefined;
|
|
150
167
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kulapard/pi-caveman",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
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",
|