@xynogen/pix-optimizer 1.1.17 → 1.1.19
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/package.json +3 -3
- package/src/caveman.ts +14 -74
- package/src/mode.ts +116 -0
- package/src/opt.ts +14 -5
- package/src/persist.ts +2 -11
- package/src/ponytail.ts +15 -73
- package/src/capability.test.ts +0 -42
- package/src/caveman.test.ts +0 -203
- package/src/json.test.ts +0 -167
- package/src/opt.test.ts +0 -56
- package/src/persist.test.ts +0 -56
- package/src/ponytail.test.ts +0 -195
- package/src/rtk.test.ts +0 -277
- package/src/status.test.ts +0 -93
- package/src/tool-result-filter.test.ts +0 -96
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xynogen/pix-optimizer",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.19",
|
|
4
4
|
"description": "Performance optimization suite for Pi Coding Agent - caveman mode + RTK tool rewriting + jq/TOON JSON compression + ponytail lazy-dev mode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"src",
|
|
12
|
+
"!src/**/*.test.ts",
|
|
12
13
|
"README.md",
|
|
13
14
|
"LICENSE"
|
|
14
15
|
],
|
|
@@ -48,8 +49,7 @@
|
|
|
48
49
|
"access": "public"
|
|
49
50
|
},
|
|
50
51
|
"dependencies": {
|
|
51
|
-
"@xynogen/pix-pretty": "^1.7.
|
|
52
|
-
"@xynogen/pix-data": "^0.3.0"
|
|
52
|
+
"@xynogen/pix-pretty": "^1.7.22"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@earendil-works/pi-coding-agent": "*",
|
package/src/caveman.ts
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
* called by index.ts alongside rtk(pi).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import {
|
|
10
|
+
createMode,
|
|
11
|
+
resolveLevel as resolveLevelGeneric,
|
|
12
|
+
toggleLevel as toggleLevelGeneric,
|
|
13
|
+
} from "./mode.ts";
|
|
14
14
|
import type { OptimizerHandle, OptimizerStatus } from "./status.ts";
|
|
15
15
|
|
|
16
16
|
// ── Levels ────────────────────────────────────────────────────────────────────
|
|
@@ -97,11 +97,7 @@ export function buildPrompt(level: Level): string {
|
|
|
97
97
|
* Handles stop aliases (stop/quit → "off") and valid level names.
|
|
98
98
|
*/
|
|
99
99
|
export function resolveLevel(arg: string): Level | null {
|
|
100
|
-
|
|
101
|
-
if (STOP_ALIASES.has(a)) return "off";
|
|
102
|
-
if (LEVEL_NUMBERS[a]) return LEVEL_NUMBERS[a];
|
|
103
|
-
if (LEVELS.includes(a as Level)) return a as Level;
|
|
104
|
-
return null;
|
|
100
|
+
return resolveLevelGeneric(arg, LEVELS, LEVEL_NUMBERS, STOP_ALIASES);
|
|
105
101
|
}
|
|
106
102
|
|
|
107
103
|
/**
|
|
@@ -127,74 +123,18 @@ export function buildHelp(current: Level): string {
|
|
|
127
123
|
* Toggle: off → full, anything else → off.
|
|
128
124
|
*/
|
|
129
125
|
export function toggleLevel(current: Level): Level {
|
|
130
|
-
return current
|
|
126
|
+
return toggleLevelGeneric(current);
|
|
131
127
|
}
|
|
132
128
|
|
|
133
129
|
// ── Pi extension ────────────────────────────────────────────────────────────
|
|
134
130
|
|
|
135
131
|
export function caveman(pi: ExtensionAPI, status: OptimizerStatus): OptimizerHandle {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
// -- Status: report into the shared optimizer indicator. --
|
|
139
|
-
|
|
140
|
-
function syncStatus(ctx: Pick<ExtensionContext, "ui">) {
|
|
141
|
-
status.set("caveman", level !== "off", ctx);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Inject caveman prompt via before_agent_start
|
|
145
|
-
pi.on("before_agent_start", async (event, _ctx) => {
|
|
146
|
-
const prompt = buildPrompt(level);
|
|
147
|
-
if (!prompt) return undefined;
|
|
148
|
-
const existing = event.systemPrompt ?? "";
|
|
149
|
-
return { systemPrompt: `${prompt}\n\n${existing}` };
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// -- Restore live level from the session log on load --
|
|
153
|
-
|
|
154
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
155
|
-
// Session log first (survives in-session branch nav), then disk (survives
|
|
156
|
-
// a full quit/restart). Disk wins when present so a new session restores
|
|
157
|
-
// the last chosen level instead of defaulting to off.
|
|
158
|
-
for (const entry of ctx.sessionManager.getEntries()) {
|
|
159
|
-
if (entry.type === "custom" && entry.customType === "caveman-level") {
|
|
160
|
-
level = (entry.data as { level: Level })?.level ?? level;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
const saved = loadOptValue("caveman");
|
|
164
|
-
if (saved && LEVELS.includes(saved as Level)) level = saved as Level;
|
|
165
|
-
syncStatus(ctx);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
pi.on("agent_start", async (_event, ctx) => {
|
|
169
|
-
syncStatus(ctx);
|
|
170
|
-
});
|
|
171
|
-
pi.on("agent_end", async (_event, ctx) => {
|
|
172
|
-
syncStatus(ctx);
|
|
173
|
-
});
|
|
174
|
-
pi.on("session_shutdown", async () => {});
|
|
175
|
-
|
|
176
|
-
// -- Overlay value handler (called by the /optimizer overlay) --
|
|
177
|
-
|
|
178
|
-
async function run(value: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
179
|
-
const resolved = resolveLevel(value);
|
|
180
|
-
if (resolved === null) return;
|
|
181
|
-
level = resolved;
|
|
182
|
-
|
|
183
|
-
pi.appendEntry("caveman-level", { level });
|
|
184
|
-
saveOptValue("caveman", level);
|
|
185
|
-
syncStatus(ctx);
|
|
186
|
-
|
|
187
|
-
ctx.ui.notify(
|
|
188
|
-
level === "off" ? "Caveman mode off." : `Caveman: ${STATUS_LABELS[level]}`,
|
|
189
|
-
"info",
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return {
|
|
132
|
+
return createMode(pi, status, {
|
|
194
133
|
name: "caveman",
|
|
195
134
|
help: "caveman — terse output",
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
135
|
+
levels: LEVELS,
|
|
136
|
+
buildPrompt,
|
|
137
|
+
resolve: resolveLevel,
|
|
138
|
+
notify: (level) => (level === "off" ? "Caveman mode off." : `Caveman: ${STATUS_LABELS[level]}`),
|
|
139
|
+
});
|
|
200
140
|
}
|
package/src/mode.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mode.ts — shared plumbing for prompt-injection optimizer modes.
|
|
3
|
+
*
|
|
4
|
+
* caveman.ts and ponytail.ts are the same *kind* of tool: a level enum, a
|
|
5
|
+
* system-prompt fragment injected via before_agent_start, and identical
|
|
6
|
+
* persistence/status wiring. Only the prompt/help/label *content* differs —
|
|
7
|
+
* that stays in each module. This file owns the generic level resolution and
|
|
8
|
+
* the extension factory both call.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
ExtensionAPI,
|
|
13
|
+
ExtensionCommandContext,
|
|
14
|
+
ExtensionContext,
|
|
15
|
+
} from "@earendil-works/pi-coding-agent";
|
|
16
|
+
import { loadOptValue, saveOptValue } from "./persist.ts";
|
|
17
|
+
import type { OptimizerHandle, OptimizerStatus, OptimizerTool } from "./status.ts";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a raw command arg to a level, or null if unrecognised.
|
|
21
|
+
* Handles stop aliases (stop/quit → "off"), numeric shortcuts, and level names.
|
|
22
|
+
*/
|
|
23
|
+
export function resolveLevel<L extends string>(
|
|
24
|
+
arg: string,
|
|
25
|
+
levels: readonly L[],
|
|
26
|
+
levelNumbers: Record<string, L>,
|
|
27
|
+
stopAliases: ReadonlySet<string>,
|
|
28
|
+
): L | null {
|
|
29
|
+
const a = arg.trim().toLowerCase();
|
|
30
|
+
if (stopAliases.has(a)) return "off" as L;
|
|
31
|
+
if (levelNumbers[a]) return levelNumbers[a];
|
|
32
|
+
if (levels.includes(a as L)) return a as L;
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Toggle: off → full, anything else → off. */
|
|
37
|
+
export function toggleLevel<L extends string>(current: L): L {
|
|
38
|
+
return (current === "off" ? "full" : "off") as L;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ModeConfig<L extends string> {
|
|
42
|
+
/** Status-registry key + persistence key + `${name}-level` custom entry. */
|
|
43
|
+
name: OptimizerTool;
|
|
44
|
+
/** One-line overlay summary. */
|
|
45
|
+
help: string;
|
|
46
|
+
/** Cyclable values in display order. */
|
|
47
|
+
levels: readonly L[];
|
|
48
|
+
/** Build the system-prompt fragment for a level ("" when off). */
|
|
49
|
+
buildPrompt(level: L): string;
|
|
50
|
+
/** Resolve an overlay/command value to a level (each module supplies its enum). */
|
|
51
|
+
resolve(value: string): L | null;
|
|
52
|
+
/** Notification shown after a successful change. */
|
|
53
|
+
notify(level: L): string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build the shared extension: prompt injection + session restore + status +
|
|
58
|
+
* overlay handle. Identical across every prompt-mode tool.
|
|
59
|
+
*/
|
|
60
|
+
export function createMode<L extends string>(
|
|
61
|
+
pi: ExtensionAPI,
|
|
62
|
+
status: OptimizerStatus,
|
|
63
|
+
config: ModeConfig<L>,
|
|
64
|
+
): OptimizerHandle {
|
|
65
|
+
const { name, levels, buildPrompt } = config;
|
|
66
|
+
const customType = `${name}-level`;
|
|
67
|
+
let level = "off" as L;
|
|
68
|
+
|
|
69
|
+
function syncStatus(ctx: Pick<ExtensionContext, "ui">) {
|
|
70
|
+
status.set(name, level !== "off", ctx);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
pi.on("before_agent_start", async (event, _ctx) => {
|
|
74
|
+
const prompt = buildPrompt(level);
|
|
75
|
+
if (!prompt) return undefined;
|
|
76
|
+
const existing = event.systemPrompt ?? "";
|
|
77
|
+
return { systemPrompt: `${prompt}\n\n${existing}` };
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
81
|
+
// Session log first (survives in-session branch nav), then disk (survives
|
|
82
|
+
// a full quit/restart). Disk wins when present.
|
|
83
|
+
for (const entry of ctx.sessionManager.getEntries()) {
|
|
84
|
+
if (entry.type === "custom" && entry.customType === customType) {
|
|
85
|
+
level = ((entry.data as { level: L })?.level ?? level) as L;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const saved = loadOptValue(name);
|
|
89
|
+
if (saved && levels.includes(saved as L)) level = saved as L;
|
|
90
|
+
syncStatus(ctx);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
pi.on("agent_start", async (_event, ctx) => syncStatus(ctx));
|
|
94
|
+
pi.on("agent_end", async (_event, ctx) => syncStatus(ctx));
|
|
95
|
+
pi.on("session_shutdown", async () => {});
|
|
96
|
+
|
|
97
|
+
async function run(value: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
98
|
+
const resolved = config.resolve(value);
|
|
99
|
+
if (resolved === null) return;
|
|
100
|
+
level = resolved;
|
|
101
|
+
|
|
102
|
+
pi.appendEntry(customType, { level });
|
|
103
|
+
saveOptValue(name, level);
|
|
104
|
+
syncStatus(ctx);
|
|
105
|
+
|
|
106
|
+
ctx.ui.notify(config.notify(level), "info");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
name,
|
|
111
|
+
help: config.help,
|
|
112
|
+
values: levels,
|
|
113
|
+
current: () => level,
|
|
114
|
+
run,
|
|
115
|
+
};
|
|
116
|
+
}
|
package/src/opt.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
18
|
+
import { matchesKey } from "@earendil-works/pi-tui";
|
|
18
19
|
import { frameLines } from "@xynogen/pix-pretty/modal-frame";
|
|
19
20
|
import type { OptimizerHandle, OptimizerStatus, OptimizerTool } from "./status.ts";
|
|
20
21
|
import { toolIcon } from "./status.ts";
|
|
@@ -166,12 +167,20 @@ export function registerOptCommand(
|
|
|
166
167
|
},
|
|
167
168
|
invalidate: () => {},
|
|
168
169
|
handleInput: (data: string) => {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
// matchesKey handles both legacy bytes and Kitty CSI-u encodings
|
|
171
|
+
// for letters and special keys alike — raw string compares like
|
|
172
|
+
// `data === "k"` silently fail under the Kitty keyboard protocol.
|
|
173
|
+
if (matchesKey(data, "k") || matchesKey(data, "up")) move(-1);
|
|
174
|
+
else if (matchesKey(data, "j") || matchesKey(data, "down")) move(1);
|
|
175
|
+
else if (matchesKey(data, "h") || matchesKey(data, "left")) cycle(-1);
|
|
176
|
+
else if (
|
|
177
|
+
matchesKey(data, "l") ||
|
|
178
|
+
matchesKey(data, "right") ||
|
|
179
|
+
matchesKey(data, "space") ||
|
|
180
|
+
matchesKey(data, "enter")
|
|
181
|
+
)
|
|
173
182
|
cycle(1);
|
|
174
|
-
else if (data
|
|
183
|
+
else if (matchesKey(data, "escape") || matchesKey(data, "q")) {
|
|
175
184
|
done(null);
|
|
176
185
|
return;
|
|
177
186
|
} else return;
|
package/src/persist.ts
CHANGED
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
13
13
|
import { dirname, join } from "node:path";
|
|
14
14
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
15
|
-
import { pixConfig } from "@xynogen/pix-data/pix-config";
|
|
16
15
|
import type { OptimizerTool } from "./status.ts";
|
|
17
16
|
|
|
18
17
|
type OptimizerFileConfig = Partial<Record<OptimizerTool, string>>;
|
|
@@ -32,17 +31,9 @@ function readFile(): OptimizerFileConfig {
|
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
/**
|
|
36
|
-
* Read a single tool's persisted value.
|
|
37
|
-
* Precedence: optimizer.json (runtime toggle) → pix.json → undefined
|
|
38
|
-
*/
|
|
34
|
+
/** Read a single tool's persisted value from optimizer.json. */
|
|
39
35
|
export function loadOptValue(tool: OptimizerTool): string | undefined {
|
|
40
|
-
|
|
41
|
-
if (fromFile !== undefined) return fromFile;
|
|
42
|
-
// Fall back to pix.json
|
|
43
|
-
const pix = pixConfig().optimizer;
|
|
44
|
-
const val = pix[tool as keyof typeof pix];
|
|
45
|
-
return val && val !== "off" ? val : undefined;
|
|
36
|
+
return readFile()[tool];
|
|
46
37
|
}
|
|
47
38
|
|
|
48
39
|
/** Persist a single tool's value, merging into the shared config file. */
|
package/src/ponytail.ts
CHANGED
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
* skill. We inject it as a system-prompt fragment — no external hooks/files.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import type {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
13
|
+
import {
|
|
14
|
+
createMode,
|
|
15
|
+
resolveLevel as resolveLevelGeneric,
|
|
16
|
+
toggleLevel as toggleLevelGeneric,
|
|
17
|
+
} from "./mode.ts";
|
|
18
18
|
import type { OptimizerHandle, OptimizerStatus } from "./status.ts";
|
|
19
19
|
|
|
20
20
|
// ── Levels ────────────────────────────────────────────────────────────────────
|
|
@@ -102,11 +102,7 @@ export function buildPrompt(level: Level): string {
|
|
|
102
102
|
* Handles stop aliases (stop/quit → "off") and valid level names.
|
|
103
103
|
*/
|
|
104
104
|
export function resolveLevel(arg: string): Level | null {
|
|
105
|
-
|
|
106
|
-
if (STOP_ALIASES.has(a)) return "off";
|
|
107
|
-
if (LEVEL_NUMBERS[a]) return LEVEL_NUMBERS[a];
|
|
108
|
-
if (LEVELS.includes(a as Level)) return a as Level;
|
|
109
|
-
return null;
|
|
105
|
+
return resolveLevelGeneric(arg, LEVELS, LEVEL_NUMBERS, STOP_ALIASES);
|
|
110
106
|
}
|
|
111
107
|
|
|
112
108
|
/**
|
|
@@ -131,73 +127,19 @@ export function buildHelp(current: Level): string {
|
|
|
131
127
|
* Toggle: off → full, anything else → off.
|
|
132
128
|
*/
|
|
133
129
|
export function toggleLevel(current: Level): Level {
|
|
134
|
-
return current
|
|
130
|
+
return toggleLevelGeneric(current);
|
|
135
131
|
}
|
|
136
132
|
|
|
137
133
|
// ── Pi extension ────────────────────────────────────────────────────────────
|
|
138
134
|
|
|
139
135
|
export function ponytail(pi: ExtensionAPI, status: OptimizerStatus): OptimizerHandle {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
// -- Status: report into the shared optimizer indicator. --
|
|
143
|
-
|
|
144
|
-
function syncStatus(ctx: Pick<ExtensionContext, "ui">) {
|
|
145
|
-
status.set("ponytail", level !== "off", ctx);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Inject ponytail prompt via before_agent_start
|
|
149
|
-
pi.on("before_agent_start", async (event, _ctx) => {
|
|
150
|
-
const prompt = buildPrompt(level);
|
|
151
|
-
if (!prompt) return undefined;
|
|
152
|
-
const existing = event.systemPrompt ?? "";
|
|
153
|
-
return { systemPrompt: `${prompt}\n\n${existing}` };
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
// -- Restore live level from the session log on load --
|
|
157
|
-
|
|
158
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
159
|
-
// Session log first (in-session branch nav), then disk (survives a full
|
|
160
|
-
// quit/restart). Disk wins when present.
|
|
161
|
-
for (const entry of ctx.sessionManager.getEntries()) {
|
|
162
|
-
if (entry.type === "custom" && entry.customType === "ponytail-level") {
|
|
163
|
-
level = (entry.data as { level: Level })?.level ?? level;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
const saved = loadOptValue("ponytail");
|
|
167
|
-
if (saved && LEVELS.includes(saved as Level)) level = saved as Level;
|
|
168
|
-
syncStatus(ctx);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
pi.on("agent_start", async (_event, ctx) => {
|
|
172
|
-
syncStatus(ctx);
|
|
173
|
-
});
|
|
174
|
-
pi.on("agent_end", async (_event, ctx) => {
|
|
175
|
-
syncStatus(ctx);
|
|
176
|
-
});
|
|
177
|
-
pi.on("session_shutdown", async () => {});
|
|
178
|
-
|
|
179
|
-
// -- Overlay value handler (called by the /optimizer overlay) --
|
|
180
|
-
|
|
181
|
-
async function run(value: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
182
|
-
const resolved = resolveLevel(value);
|
|
183
|
-
if (resolved === null) return;
|
|
184
|
-
level = resolved;
|
|
185
|
-
|
|
186
|
-
pi.appendEntry("ponytail-level", { level });
|
|
187
|
-
saveOptValue("ponytail", level);
|
|
188
|
-
syncStatus(ctx);
|
|
189
|
-
|
|
190
|
-
ctx.ui.notify(
|
|
191
|
-
level === "off" ? "Ponytail mode off." : `Ponytail: ${STATUS_LABELS[level]}`,
|
|
192
|
-
"info",
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return {
|
|
136
|
+
return createMode(pi, status, {
|
|
197
137
|
name: "ponytail",
|
|
198
138
|
help: "ponytail — lazy senior dev (minimal code)",
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
139
|
+
levels: LEVELS,
|
|
140
|
+
buildPrompt,
|
|
141
|
+
resolve: resolveLevel,
|
|
142
|
+
notify: (level) =>
|
|
143
|
+
level === "off" ? "Ponytail mode off." : `Ponytail: ${STATUS_LABELS[level]}`,
|
|
144
|
+
});
|
|
203
145
|
}
|
package/src/capability.test.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, mock } from "bun:test";
|
|
2
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import { canExecute } from "./capability.ts";
|
|
4
|
-
|
|
5
|
-
function executor(impl: ExtensionAPI["exec"]): Pick<ExtensionAPI, "exec"> {
|
|
6
|
-
return { exec: impl };
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
describe("canExecute", () => {
|
|
10
|
-
it("runs the requested capability probe without a shell", async () => {
|
|
11
|
-
const exec = mock(async () => ({
|
|
12
|
-
stdout: "jq-1.7",
|
|
13
|
-
stderr: "",
|
|
14
|
-
code: 0,
|
|
15
|
-
killed: false,
|
|
16
|
-
}));
|
|
17
|
-
|
|
18
|
-
expect(await canExecute(executor(exec), "jq", ["--version"])).toBe(true);
|
|
19
|
-
expect(exec).toHaveBeenCalledWith("jq", ["--version"], { timeout: 3000 });
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it("supports a custom timeout", async () => {
|
|
23
|
-
const exec = mock(async () => ({ stdout: "2.3.0", stderr: "", code: 0, killed: false }));
|
|
24
|
-
|
|
25
|
-
expect(await canExecute(executor(exec), "toon", ["--version"], 1000)).toBe(true);
|
|
26
|
-
expect(exec).toHaveBeenCalledWith("toon", ["--version"], { timeout: 1000 });
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("returns false for a non-zero exit", async () => {
|
|
30
|
-
const exec = mock(async () => ({ stdout: "", stderr: "failed", code: 1, killed: false }));
|
|
31
|
-
|
|
32
|
-
expect(await canExecute(executor(exec), "jq", ["--version"])).toBe(false);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("returns false when the executable cannot be spawned", async () => {
|
|
36
|
-
const exec = mock(async () => {
|
|
37
|
-
throw new Error("ENOENT");
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
expect(await canExecute(executor(exec), "toon", ["--version"])).toBe(false);
|
|
41
|
-
});
|
|
42
|
-
});
|
package/src/caveman.test.ts
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
buildHelp,
|
|
4
|
-
buildPrompt,
|
|
5
|
-
LEVEL_NUMBERS,
|
|
6
|
-
LEVELS,
|
|
7
|
-
type Level,
|
|
8
|
-
resolveLevel,
|
|
9
|
-
STATUS_LABELS,
|
|
10
|
-
STOP_ALIASES,
|
|
11
|
-
toggleLevel,
|
|
12
|
-
} from "./caveman.ts";
|
|
13
|
-
|
|
14
|
-
// ── LEVELS ────────────────────────────────────────────────────────────────────
|
|
15
|
-
|
|
16
|
-
describe("LEVELS", () => {
|
|
17
|
-
it("contains off as first entry", () => {
|
|
18
|
-
expect(LEVELS[0]).toBe("off");
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("contains all expected levels", () => {
|
|
22
|
-
const expected: Level[] = ["off", "lite", "full", "ultra", "micro"];
|
|
23
|
-
for (const l of expected) expect(LEVELS).toContain(l);
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// ── STOP_ALIASES ──────────────────────────────────────────────────────────────
|
|
28
|
-
|
|
29
|
-
describe("STOP_ALIASES", () => {
|
|
30
|
-
it("includes off, stop, quit", () => {
|
|
31
|
-
expect(STOP_ALIASES.has("off")).toBe(true);
|
|
32
|
-
expect(STOP_ALIASES.has("stop")).toBe(true);
|
|
33
|
-
expect(STOP_ALIASES.has("quit")).toBe(true);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("does not include active levels", () => {
|
|
37
|
-
expect(STOP_ALIASES.has("full")).toBe(false);
|
|
38
|
-
expect(STOP_ALIASES.has("ultra")).toBe(false);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
// ── STATUS_LABELS ─────────────────────────────────────────────────────────────
|
|
43
|
-
|
|
44
|
-
describe("STATUS_LABELS", () => {
|
|
45
|
-
it("has a label for every non-off level", () => {
|
|
46
|
-
const nonOff = LEVELS.filter((l) => l !== "off") as Exclude<Level, "off">[];
|
|
47
|
-
for (const l of nonOff) {
|
|
48
|
-
expect(STATUS_LABELS[l]).toBeTruthy();
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("latin levels are uppercase", () => {
|
|
53
|
-
expect(STATUS_LABELS.lite).toBe("LITE");
|
|
54
|
-
expect(STATUS_LABELS.full).toBe("FULL");
|
|
55
|
-
expect(STATUS_LABELS.ultra).toBe("ULTRA");
|
|
56
|
-
expect(STATUS_LABELS.micro).toBe("MICRO");
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// ── resolveLevel ──────────────────────────────────────────────────────────────
|
|
61
|
-
|
|
62
|
-
describe("resolveLevel", () => {
|
|
63
|
-
it("resolves valid levels", () => {
|
|
64
|
-
expect(resolveLevel("lite")).toBe("lite");
|
|
65
|
-
expect(resolveLevel("full")).toBe("full");
|
|
66
|
-
expect(resolveLevel("ultra")).toBe("ultra");
|
|
67
|
-
expect(resolveLevel("micro")).toBe("micro");
|
|
68
|
-
expect(resolveLevel("off")).toBe("off");
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("maps stop aliases to off", () => {
|
|
72
|
-
expect(resolveLevel("stop")).toBe("off");
|
|
73
|
-
expect(resolveLevel("quit")).toBe("off");
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it("is case-insensitive", () => {
|
|
77
|
-
expect(resolveLevel("FULL")).toBe("full");
|
|
78
|
-
expect(resolveLevel("Ultra")).toBe("ultra");
|
|
79
|
-
expect(resolveLevel("STOP")).toBe("off");
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("trims whitespace", () => {
|
|
83
|
-
expect(resolveLevel(" full ")).toBe("full");
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("returns null for unknown input", () => {
|
|
87
|
-
expect(resolveLevel("unknown")).toBeNull();
|
|
88
|
-
expect(resolveLevel("")).toBeNull();
|
|
89
|
-
expect(resolveLevel("config")).toBeNull(); // config handled separately
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// ── numeric levels ────────────────────────────────────────────────────────────
|
|
94
|
-
|
|
95
|
-
describe("numeric levels", () => {
|
|
96
|
-
it("maps 1/2/3 to lite/full/ultra", () => {
|
|
97
|
-
expect(resolveLevel("1")).toBe("lite");
|
|
98
|
-
expect(resolveLevel("2")).toBe("full");
|
|
99
|
-
expect(resolveLevel("3")).toBe("ultra");
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("maps 0 to off", () => {
|
|
103
|
-
expect(resolveLevel("0")).toBe("off");
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("LEVEL_NUMBERS only covers 1-3", () => {
|
|
107
|
-
expect(Object.keys(LEVEL_NUMBERS).sort()).toEqual(["1", "2", "3"]);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("rejects out-of-range numbers", () => {
|
|
111
|
-
expect(resolveLevel("4")).toBeNull();
|
|
112
|
-
expect(resolveLevel("9")).toBeNull();
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it("trims whitespace around numbers", () => {
|
|
116
|
-
expect(resolveLevel(" 2 ")).toBe("full");
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
// ── buildHelp ─────────────────────────────────────────────────────────────────
|
|
121
|
-
|
|
122
|
-
describe("buildHelp", () => {
|
|
123
|
-
it("lists numeric shortcuts", () => {
|
|
124
|
-
const help = buildHelp("off");
|
|
125
|
-
expect(help).toContain("1");
|
|
126
|
-
expect(help).toContain("lite");
|
|
127
|
-
expect(help).toContain("2");
|
|
128
|
-
expect(help).toContain("full");
|
|
129
|
-
expect(help).toContain("3");
|
|
130
|
-
expect(help).toContain("ultra");
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("shows current level when active", () => {
|
|
134
|
-
expect(buildHelp("ultra")).toContain("ULTRA");
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it("shows off when disabled", () => {
|
|
138
|
-
expect(buildHelp("off")).toContain("off");
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it("mentions config", () => {
|
|
142
|
-
expect(buildHelp("off")).toContain("config");
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it("does not mention wenyan", () => {
|
|
146
|
-
expect(buildHelp("off").toLowerCase()).not.toContain("wenyan");
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
// ── toggleLevel ───────────────────────────────────────────────────────────────
|
|
151
|
-
|
|
152
|
-
describe("toggleLevel", () => {
|
|
153
|
-
it("off → full", () => {
|
|
154
|
-
expect(toggleLevel("off")).toBe("full");
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it("full → off", () => {
|
|
158
|
-
expect(toggleLevel("full")).toBe("off");
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it("any non-off level → off", () => {
|
|
162
|
-
const nonOff = LEVELS.filter((l) => l !== "off") as Level[];
|
|
163
|
-
for (const l of nonOff) {
|
|
164
|
-
expect(toggleLevel(l)).toBe("off");
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
// ── buildPrompt ───────────────────────────────────────────────────────────────
|
|
170
|
-
|
|
171
|
-
describe("buildPrompt", () => {
|
|
172
|
-
it("returns empty string for off", () => {
|
|
173
|
-
expect(buildPrompt("off")).toBe("");
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it("returns MICRO_PROMPT for micro (no BASE)", () => {
|
|
177
|
-
const p = buildPrompt("micro");
|
|
178
|
-
expect(p).toContain("Token efficiency");
|
|
179
|
-
expect(p).not.toContain("CAVEMAN MODE");
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it("includes BASE for all standard levels", () => {
|
|
183
|
-
const standard = ["lite", "full", "ultra"] as Level[];
|
|
184
|
-
for (const l of standard) {
|
|
185
|
-
expect(buildPrompt(l)).toContain("CAVEMAN MODE");
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it("includes SAFETY clause for all standard levels", () => {
|
|
190
|
-
const standard = ["lite", "full", "ultra"] as Level[];
|
|
191
|
-
for (const l of standard) {
|
|
192
|
-
expect(buildPrompt(l)).toContain("Auto-clarity");
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it("each level has distinct intensity instructions", () => {
|
|
197
|
-
const lite = buildPrompt("lite");
|
|
198
|
-
const ultra = buildPrompt("ultra");
|
|
199
|
-
expect(lite).toContain("Keep articles");
|
|
200
|
-
expect(ultra).toContain("Abbreviate");
|
|
201
|
-
expect(lite).not.toContain("Abbreviate");
|
|
202
|
-
});
|
|
203
|
-
});
|