@xynogen/pix-optimizer 1.0.7 → 1.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/README.md CHANGED
@@ -4,9 +4,10 @@ Token-optimization suite for Pi Coding Agent. Three tools wired into one
4
4
  extension via `src/index.ts`, fronted by a single `/opt` command and one
5
5
  shared status-bar cell:
6
6
 
7
- - **Caveman** (`⛏`) — terse-output system prompt
8
- - **RTK** (`⚔`) — prefixes shell commands with `rtk` + injects RTK prompt
9
- - **TOON** (`✂`) — jq + TOON guidance for dense JSON (skill lives in pix-skills)
7
+ - **Caveman** (`󰜐`) — terse-output system prompt
8
+ - **RTK** (`󰓥`) — prefixes shell commands with `rtk` + injects RTK prompt
9
+ - **TOON** (`󰗀`) — jq + TOON guidance for dense JSON (skill lives in pix-skills)
10
+ - **Ponytail** (`󰆐`) — lazy-senior-dev system prompt (minimal code, YAGNI)
10
11
 
11
12
  ## Command
12
13
 
@@ -17,16 +18,17 @@ One command routes to every tool:
17
18
  /opt caveman <level> → set caveman level (1/2/3/lite/full/ultra/micro/off/config)
18
19
  /opt rtk [on|off] → toggle RTK rewriting
19
20
  /opt toon [on|off] → toggle jq+TOON guidance
21
+ /opt ponytail <level> → set ponytail level (1/2/3/lite/full/ultra/off/config)
20
22
  ```
21
23
 
22
24
  ## Status bar
23
25
 
24
- A single cell always shows all three icons in a fixed order (`⛏ ⚔ ✂`), color-
26
+ A single cell always shows the enabled icons in a fixed order (`󰜐 󰓥 󰗀 󰆐`), color-
25
27
  coded by state: **accent** when the tool is enabled, **dim** when disabled.
26
28
 
27
29
  ## Features
28
30
 
29
- ### Caveman Mode (`⛏`)
31
+ ### Caveman Mode (`󰜐`)
30
32
 
31
33
  Cuts ~75% of output tokens while keeping full technical accuracy.
32
34
 
@@ -40,7 +42,7 @@ Cuts ~75% of output tokens while keeping full technical accuracy.
40
42
  `/opt caveman config` opens a settings dialog. Default level for new sessions
41
43
  and status-bar visibility are saved to `~/.pi/agent/caveman.json`.
42
44
 
43
- ### RTK Tool Rewriting (`⚔`)
45
+ ### RTK Tool Rewriting (`󰓥`)
44
46
 
45
47
  Two layers, both active automatically:
46
48
 
@@ -61,7 +63,7 @@ Two layers, both active automatically:
61
63
  cargo install rtk-ai
62
64
  ```
63
65
 
64
- ### TOON / JSON Compression (`✂`)
66
+ ### TOON / JSON Compression (`󰗀`)
65
67
 
66
68
  Guidance for handling information-dense JSON via `jq` (query/reshape) and
67
69
  `toon` (compress). The system-prompt nudge is injected **only when the user
@@ -78,6 +80,26 @@ The `toon-json` skill (full workflow + when-NOT-to-use guidance) is bundled in
78
80
  npm i -g @toon-format/cli
79
81
  ```
80
82
 
83
+ ### Ponytail Mode (`󰆐`)
84
+
85
+ "Lazy senior dev" mode. Governs **what** the agent builds (minimal code,
86
+ YAGNI), orthogonal to Caveman which governs **how** it talks — they pair. Before
87
+ writing code the agent stops at the first rung that holds: does this need to
88
+ exist → stdlib → native platform → installed dep → one line → minimum that
89
+ works. Validation, error handling, security, and accessibility are never cut.
90
+
91
+ | # | Name | Description |
92
+ |---|-------|--------------------------------------|
93
+ | 1 | lite | Name the lazier alternative, you pick |
94
+ | 2 | full | The ladder enforced (default) |
95
+ | 3 | ultra | YAGNI extremist |
96
+
97
+ `/opt ponytail config` opens a settings dialog. Default level for new sessions
98
+ and status-bar visibility are saved to `~/.pi/agent/ponytail.json`.
99
+
100
+ **No install required** — pure prompt injection, no external binary or PATH
101
+ dependency (unlike RTK and TOON).
102
+
81
103
  ## Installation
82
104
 
83
105
  ```bash
@@ -88,15 +110,16 @@ pi install npm:@xynogen/pix-optimizer
88
110
 
89
111
  | File | Role |
90
112
  |-------------------|-----------------------------------------------------------|
91
- | `src/index.ts` | Wires the three tools + shared status, registers `/opt` |
113
+ | `src/index.ts` | Wires the four tools + shared status, registers `/opt` |
92
114
  | `src/opt.ts` | The `/opt` router: parse, complete, dispatch |
93
115
  | `src/status.ts` | Shared status-bar cell + `OptimizerHandle` contract |
94
116
  | `src/caveman.ts` | Caveman logic, levels, prompt, settings dialog |
95
117
  | `src/rtk.ts` | RTK prompt + bash command rewriting |
96
118
  | `src/json.ts` | jq+TOON guidance, heuristics, system-prompt injection |
119
+ | `src/ponytail.ts` | Ponytail logic, levels, prompt, settings dialog |
97
120
 
98
121
  Each tool registers its own lifecycle hooks and exposes an `OptimizerHandle`
99
- that `/opt` dispatches to. All three share one `OptimizerStatus`.
122
+ that `/opt` dispatches to. All four share one `OptimizerStatus`.
100
123
 
101
124
  ## Development
102
125
 
@@ -116,9 +139,14 @@ This package was built by merging two upstream Pi community packages:
116
139
  approach: prompt injection + live bash command rewriting that handles chained commands
117
140
  (`&&`, `||`, `;`, `|`).
118
141
 
119
- Both upstreams are MIT licensed. Neither codebase was copied directly — the logic was
142
+ - **Ponytail mode** — ruleset adapted from [`git:github.com/DietrichGebert/ponytail`](https://github.com/DietrichGebert/ponytail),
143
+ the "lazy senior dev" skill. Reimplemented here as a native `/opt` tool with three intensity
144
+ levels, a settings dialog, and per-session persistence — no external hooks or files. The
145
+ ruleset (the YAGNI ladder + safety carve-outs) is rewritten as a system-prompt fragment.
146
+
147
+ All upstreams are MIT licensed. No codebase was copied directly — the logic was
120
148
  rewritten and combined into a single extension with a unified `/opt` command and shared status bar.
121
- This package does not sync back to either upstream.
149
+ This package does not sync back to any upstream.
122
150
 
123
151
  ## Full distro
124
152
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xynogen/pix-optimizer",
3
- "version": "1.0.7",
4
- "description": "Performance optimization suite for Pi Coding Agent - caveman mode + RTK tool rewriting + jq/TOON JSON compression",
3
+ "version": "1.1.0",
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",
7
7
  "scripts": {
@@ -30,6 +30,7 @@
30
30
  "toon",
31
31
  "jq",
32
32
  "json",
33
+ "ponytail",
33
34
  "performance"
34
35
  ],
35
36
  "author": "xynogen",
package/src/index.ts CHANGED
@@ -2,11 +2,12 @@
2
2
  * pix-optimizer — token-optimization suite for Pi Coding Agent.
3
3
  *
4
4
  * Three tools, combined into one extension + one command:
5
- * - caveman: terse-output system prompt
6
- * - rtk: prefixes shell commands with `rtk` + injects RTK prompt
7
- * - toon: jq + TOON guidance for dense JSON (+ bundled skill)
5
+ * - caveman: terse-output system prompt
6
+ * - rtk: prefixes shell commands with `rtk` + injects RTK prompt
7
+ * - toon: jq + TOON guidance for dense JSON (+ bundled skill)
8
+ * - ponytail: lazy-senior-dev system prompt (minimal code, YAGNI)
8
9
  *
9
- * They share ONE status-bar cell (⛏ ⚔ ✂, only enabled tools shown) and ONE
10
+ * They share ONE status-bar cell (󰜐 󰓥 󰗀 󰆐, only enabled tools shown) and ONE
10
11
  * command (/opt <tool> [args]). index.ts wires lifecycle hooks via each
11
12
  * module, then registers the merged command from their handles.
12
13
  */
@@ -15,6 +16,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
15
16
  import { caveman } from "./caveman.ts";
16
17
  import { json } from "./json.ts";
17
18
  import { registerOptCommand } from "./opt.ts";
19
+ import { ponytail } from "./ponytail.ts";
18
20
  import { rtk } from "./rtk.ts";
19
21
  import {
20
22
  type OptimizerHandle,
@@ -32,6 +34,7 @@ export default function optimizer(pi: ExtensionAPI) {
32
34
  caveman: caveman(pi, status),
33
35
  rtk: rtk(pi, status),
34
36
  toon: json(pi, status),
37
+ ponytail: ponytail(pi, status),
35
38
  };
36
39
 
37
40
  registerOptCommand(pi, handles);
package/src/opt.test.ts CHANGED
@@ -13,7 +13,12 @@ function fakeHandles(): Record<OptimizerTool, OptimizerHandle> {
13
13
  .filter((v) => v.startsWith(prefix.trim().toLowerCase()))
14
14
  .map((v) => ({ value: v, label: v, description: v })),
15
15
  });
16
- return { caveman: mk("caveman"), rtk: mk("rtk"), toon: mk("toon") };
16
+ return {
17
+ caveman: mk("caveman"),
18
+ rtk: mk("rtk"),
19
+ toon: mk("toon"),
20
+ ponytail: mk("ponytail"),
21
+ };
17
22
  }
18
23
 
19
24
  describe("parseInvocation", () => {
@@ -49,7 +54,12 @@ describe("completeInvocation", () => {
49
54
  it("returns all tool names for empty prefix", () => {
50
55
  const handles = fakeHandles();
51
56
  const out = completeInvocation("", handles);
52
- expect(out?.map((i) => i.value)).toEqual(["caveman", "rtk", "toon"]);
57
+ expect(out?.map((i) => i.value)).toEqual([
58
+ "caveman",
59
+ "rtk",
60
+ "toon",
61
+ "ponytail",
62
+ ]);
53
63
  });
54
64
 
55
65
  it("delegates to the tool completer after the name", () => {
@@ -70,6 +80,7 @@ describe("buildOptHelp", () => {
70
80
  expect(help).toContain("/opt caveman help");
71
81
  expect(help).toContain("/opt rtk help");
72
82
  expect(help).toContain("/opt toon help");
83
+ expect(help).toContain("/opt ponytail help");
73
84
  expect(help).toContain("Usage: /opt <tool> [args]");
74
85
  });
75
86
  });
package/src/opt.ts CHANGED
@@ -81,7 +81,7 @@ export function registerOptCommand(
81
81
  handles: Record<OptimizerTool, OptimizerHandle>,
82
82
  ): void {
83
83
  pi.registerCommand("opt", {
84
- description: "pix-optimizer: caveman / rtk / toon token tools",
84
+ description: "pix-optimizer: caveman / rtk / toon / ponytail tools",
85
85
  getArgumentCompletions: (prefix: string) =>
86
86
  completeInvocation(prefix, handles),
87
87
  handler: async (args, ctx) => {
@@ -0,0 +1,236 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import {
3
+ buildHelp,
4
+ buildPrompt,
5
+ DEFAULT_CONFIG,
6
+ LEVEL_NUMBERS,
7
+ LEVELS,
8
+ type Level,
9
+ parseConfig,
10
+ resolveLevel,
11
+ STATUS_LABELS,
12
+ STOP_ALIASES,
13
+ toggleLevel,
14
+ } from "./ponytail.ts";
15
+
16
+ // ── LEVELS ────────────────────────────────────────────────────────────────────
17
+
18
+ describe("LEVELS", () => {
19
+ it("contains off as first entry", () => {
20
+ expect(LEVELS[0]).toBe("off");
21
+ });
22
+
23
+ it("contains all expected levels", () => {
24
+ const expected: Level[] = ["off", "lite", "full", "ultra"];
25
+ for (const l of expected) expect(LEVELS).toContain(l);
26
+ });
27
+
28
+ it("has no micro level (caveman-only)", () => {
29
+ expect(LEVELS).not.toContain("micro" as Level);
30
+ });
31
+ });
32
+
33
+ // ── STOP_ALIASES ──────────────────────────────────────────────────────────────
34
+
35
+ describe("STOP_ALIASES", () => {
36
+ it("includes off, stop, quit", () => {
37
+ expect(STOP_ALIASES.has("off")).toBe(true);
38
+ expect(STOP_ALIASES.has("stop")).toBe(true);
39
+ expect(STOP_ALIASES.has("quit")).toBe(true);
40
+ });
41
+
42
+ it("does not include active levels", () => {
43
+ expect(STOP_ALIASES.has("full")).toBe(false);
44
+ expect(STOP_ALIASES.has("ultra")).toBe(false);
45
+ });
46
+ });
47
+
48
+ // ── STATUS_LABELS ─────────────────────────────────────────────────────────────
49
+
50
+ describe("STATUS_LABELS", () => {
51
+ it("has a label for every non-off level", () => {
52
+ const nonOff = LEVELS.filter((l) => l !== "off") as Exclude<Level, "off">[];
53
+ for (const l of nonOff) {
54
+ expect(STATUS_LABELS[l]).toBeTruthy();
55
+ }
56
+ });
57
+
58
+ it("levels are uppercase", () => {
59
+ expect(STATUS_LABELS.lite).toBe("LITE");
60
+ expect(STATUS_LABELS.full).toBe("FULL");
61
+ expect(STATUS_LABELS.ultra).toBe("ULTRA");
62
+ });
63
+ });
64
+
65
+ // ── resolveLevel ──────────────────────────────────────────────────────────────
66
+
67
+ describe("resolveLevel", () => {
68
+ it("resolves valid levels", () => {
69
+ expect(resolveLevel("lite")).toBe("lite");
70
+ expect(resolveLevel("full")).toBe("full");
71
+ expect(resolveLevel("ultra")).toBe("ultra");
72
+ expect(resolveLevel("off")).toBe("off");
73
+ });
74
+
75
+ it("maps stop aliases to off", () => {
76
+ expect(resolveLevel("stop")).toBe("off");
77
+ expect(resolveLevel("quit")).toBe("off");
78
+ });
79
+
80
+ it("is case-insensitive", () => {
81
+ expect(resolveLevel("FULL")).toBe("full");
82
+ expect(resolveLevel("Ultra")).toBe("ultra");
83
+ expect(resolveLevel("STOP")).toBe("off");
84
+ });
85
+
86
+ it("trims whitespace", () => {
87
+ expect(resolveLevel(" full ")).toBe("full");
88
+ });
89
+
90
+ it("returns null for unknown input", () => {
91
+ expect(resolveLevel("unknown")).toBeNull();
92
+ expect(resolveLevel("")).toBeNull();
93
+ expect(resolveLevel("config")).toBeNull(); // config handled separately
94
+ expect(resolveLevel("micro")).toBeNull(); // no micro level
95
+ });
96
+ });
97
+
98
+ // ── numeric levels ────────────────────────────────────────────────────────────
99
+
100
+ describe("numeric levels", () => {
101
+ it("maps 1/2/3 to lite/full/ultra", () => {
102
+ expect(resolveLevel("1")).toBe("lite");
103
+ expect(resolveLevel("2")).toBe("full");
104
+ expect(resolveLevel("3")).toBe("ultra");
105
+ });
106
+
107
+ it("maps 0 to off", () => {
108
+ expect(resolveLevel("0")).toBe("off");
109
+ });
110
+
111
+ it("LEVEL_NUMBERS only covers 1-3", () => {
112
+ expect(Object.keys(LEVEL_NUMBERS).sort()).toEqual(["1", "2", "3"]);
113
+ });
114
+
115
+ it("rejects out-of-range numbers", () => {
116
+ expect(resolveLevel("4")).toBeNull();
117
+ expect(resolveLevel("9")).toBeNull();
118
+ });
119
+
120
+ it("trims whitespace around numbers", () => {
121
+ expect(resolveLevel(" 2 ")).toBe("full");
122
+ });
123
+ });
124
+
125
+ // ── buildHelp ─────────────────────────────────────────────────────────────────
126
+
127
+ describe("buildHelp", () => {
128
+ it("lists numeric shortcuts", () => {
129
+ const help = buildHelp("off");
130
+ expect(help).toContain("1");
131
+ expect(help).toContain("lite");
132
+ expect(help).toContain("2");
133
+ expect(help).toContain("full");
134
+ expect(help).toContain("3");
135
+ expect(help).toContain("ultra");
136
+ });
137
+
138
+ it("shows current level when active", () => {
139
+ expect(buildHelp("ultra")).toContain("ULTRA");
140
+ });
141
+
142
+ it("shows off when disabled", () => {
143
+ expect(buildHelp("off")).toContain("off");
144
+ });
145
+
146
+ it("mentions config", () => {
147
+ expect(buildHelp("off")).toContain("config");
148
+ });
149
+ });
150
+
151
+ // ── toggleLevel ───────────────────────────────────────────────────────────────
152
+
153
+ describe("toggleLevel", () => {
154
+ it("off → full", () => {
155
+ expect(toggleLevel("off")).toBe("full");
156
+ });
157
+
158
+ it("full → off", () => {
159
+ expect(toggleLevel("full")).toBe("off");
160
+ });
161
+
162
+ it("any non-off level → off", () => {
163
+ const nonOff = LEVELS.filter((l) => l !== "off") as Level[];
164
+ for (const l of nonOff) {
165
+ expect(toggleLevel(l)).toBe("off");
166
+ }
167
+ });
168
+ });
169
+
170
+ // ── buildPrompt ───────────────────────────────────────────────────────────────
171
+
172
+ describe("buildPrompt", () => {
173
+ it("returns empty string for off", () => {
174
+ expect(buildPrompt("off")).toBe("");
175
+ });
176
+
177
+ it("includes BASE ladder for all active levels", () => {
178
+ for (const l of ["lite", "full", "ultra"] as Level[]) {
179
+ expect(buildPrompt(l)).toContain("PONYTAIL MODE ACTIVE");
180
+ expect(buildPrompt(l)).toContain("first rung that holds");
181
+ }
182
+ });
183
+
184
+ it("includes SAFETY clause for all active levels", () => {
185
+ for (const l of ["lite", "full", "ultra"] as Level[]) {
186
+ expect(buildPrompt(l)).toContain("When NOT to be lazy");
187
+ }
188
+ });
189
+
190
+ it("each level has distinct intensity instructions", () => {
191
+ const lite = buildPrompt("lite");
192
+ const ultra = buildPrompt("ultra");
193
+ expect(lite).toContain("name the lazier alternative");
194
+ expect(ultra).toContain("YAGNI extremist");
195
+ expect(lite).not.toContain("YAGNI extremist");
196
+ });
197
+ });
198
+
199
+ // ── parseConfig ───────────────────────────────────────────────────────────────
200
+
201
+ describe("parseConfig", () => {
202
+ it("parses valid config", () => {
203
+ const cfg = parseConfig({ defaultLevel: "ultra", showStatus: false });
204
+ expect(cfg.defaultLevel).toBe("ultra");
205
+ expect(cfg.showStatus).toBe(false);
206
+ });
207
+
208
+ it("falls back to defaults for invalid defaultLevel", () => {
209
+ const cfg = parseConfig({ defaultLevel: "invalid", showStatus: true });
210
+ expect(cfg.defaultLevel).toBe(DEFAULT_CONFIG.defaultLevel);
211
+ });
212
+
213
+ it("default level is full", () => {
214
+ expect(DEFAULT_CONFIG.defaultLevel).toBe("full");
215
+ });
216
+
217
+ it("falls back to defaults for missing fields", () => {
218
+ const cfg = parseConfig({});
219
+ expect(cfg.defaultLevel).toBe(DEFAULT_CONFIG.defaultLevel);
220
+ expect(cfg.showStatus).toBe(DEFAULT_CONFIG.showStatus);
221
+ });
222
+
223
+ it("falls back to defaults for null/undefined", () => {
224
+ const cfg = parseConfig(null);
225
+ expect(cfg.defaultLevel).toBe(DEFAULT_CONFIG.defaultLevel);
226
+ expect(cfg.showStatus).toBe(DEFAULT_CONFIG.showStatus);
227
+ });
228
+
229
+ it("accepts all valid levels as defaultLevel", () => {
230
+ for (const l of LEVELS) {
231
+ expect(
232
+ parseConfig({ defaultLevel: l, showStatus: true }).defaultLevel,
233
+ ).toBe(l);
234
+ }
235
+ });
236
+ });
@@ -0,0 +1,439 @@
1
+ /**
2
+ * ponytail.ts — pure logic + Pi extension
3
+ *
4
+ * "Lazy senior dev" mode: governs WHAT the agent builds (minimal code, YAGNI),
5
+ * orthogonal to caveman which governs HOW it talks. Pure helpers exported for
6
+ * tests; ponytail(pi, status) is the extension entry, wired by index.ts.
7
+ *
8
+ * Ruleset adapted from DietrichGebert/ponytail (MIT), the "lazy senior dev"
9
+ * skill. We inject it as a system-prompt fragment — no external hooks/files.
10
+ */
11
+
12
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
13
+ import { homedir } from "node:os";
14
+ import { join } from "node:path";
15
+ import type {
16
+ ExtensionAPI,
17
+ ExtensionCommandContext,
18
+ ExtensionContext,
19
+ } from "@earendil-works/pi-coding-agent";
20
+ import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
21
+ import {
22
+ Container,
23
+ type SettingItem,
24
+ SettingsList,
25
+ Text,
26
+ } from "@earendil-works/pi-tui";
27
+ import type { OptimizerHandle, OptimizerStatus } from "./status.ts";
28
+
29
+ // ── Levels ────────────────────────────────────────────────────────────────────
30
+
31
+ export const LEVELS = ["off", "lite", "full", "ultra"] as const;
32
+
33
+ export type Level = (typeof LEVELS)[number];
34
+
35
+ export const STOP_ALIASES = new Set(["off", "stop", "quit", "0"]);
36
+
37
+ // Numeric shortcuts: /opt ponytail 1|2|3
38
+ export const LEVEL_NUMBERS: Record<string, Level> = {
39
+ "1": "lite",
40
+ "2": "full",
41
+ "3": "ultra",
42
+ };
43
+
44
+ const PONYTAIL_COMMAND_OPTIONS = [
45
+ { value: "1", label: "1 (lite)", description: "Name the lazier alternative" },
46
+ { value: "2", label: "2 (full)", description: "The ladder enforced" },
47
+ { value: "3", label: "3 (ultra)", description: "YAGNI extremist" },
48
+ { value: "lite", label: "lite", description: "Name the lazier alternative" },
49
+ { value: "full", label: "full", description: "The ladder enforced" },
50
+ { value: "ultra", label: "ultra", description: "YAGNI extremist" },
51
+ { value: "off", label: "off", description: "Disable ponytail mode" },
52
+ { value: "stop", label: "stop", description: "Disable ponytail mode" },
53
+ { value: "quit", label: "quit", description: "Disable ponytail mode" },
54
+ { value: "config", label: "config", description: "Open settings dialog" },
55
+ ] as const;
56
+
57
+ // ── Status labels ─────────────────────────────────────────────────────────────
58
+
59
+ export const STATUS_LABELS: Record<Exclude<Level, "off">, string> = {
60
+ lite: "LITE",
61
+ full: "FULL",
62
+ ultra: "ULTRA",
63
+ };
64
+
65
+ // ── Prompt fragments ──────────────────────────────────────────────────────────
66
+
67
+ const BASE = `\
68
+ PONYTAIL MODE ACTIVE. You are a lazy senior developer. Lazy means efficient, \
69
+ not careless. The best code is the code never written.
70
+
71
+ Before writing any code, stop at the first rung that holds:
72
+ 1. Does this need to exist at all? Speculative need = skip it, say so in one line. (YAGNI)
73
+ 2. Stdlib does it? Use it.
74
+ 3. Native platform feature covers it? Use it (\`<input type="date">\` over a picker lib, CSS over JS, DB constraint over app code).
75
+ 4. Already-installed dependency solves it? Use it. Never add a new one for what a few lines can do.
76
+ 5. Can it be one line? One line.
77
+ 6. Only then: the minimum code that works.
78
+
79
+ The ladder is a reflex, not a research project. Two rungs work → take the higher one and move on.
80
+
81
+ Rules:
82
+ - No unrequested abstractions: no interface with one impl, no factory for one product, no config for a value that never changes.
83
+ - No boilerplate, no scaffolding "for later". Deletion over addition. Boring over clever. Fewest files possible.
84
+ - Complex request? Ship the lazy version and question it in the same response. Never stall on an answer you can default.
85
+ - Two same-size stdlib options? Take the one correct on edge cases. Lazy means less code, not the flimsier algorithm.
86
+ - Mark deliberate simplifications with a \`ponytail:\` comment. A shortcut with a known ceiling names the ceiling and the upgrade path.`;
87
+
88
+ const INTENSITY: Record<Exclude<Level, "off">, string> = {
89
+ lite: `\
90
+ Build what's asked, but name the lazier alternative in one line. User picks.
91
+ Example: "Done, cache added. FYI: \`functools.lru_cache\` covers this in one line if you'd rather not own a cache class."`,
92
+
93
+ full: `\
94
+ The ladder enforced. Stdlib and native first. Shortest diff, shortest explanation.
95
+ Example: "\`@lru_cache(maxsize=1000)\` on the fetch function. Skipped custom cache class, add when lru_cache measurably falls short."`,
96
+
97
+ ultra: `\
98
+ YAGNI extremist. Deletion before addition. Ship the one-liner and challenge the rest of the requirement in the same breath.
99
+ Example: "No cache until a profiler says so. When it does: \`@lru_cache\`. A hand-rolled TTL cache class is a bug farm with a hit rate."`,
100
+ };
101
+
102
+ const SAFETY = `\
103
+ When NOT to be lazy: never simplify away input validation at trust boundaries, \
104
+ error handling that prevents data loss, security, accessibility, or anything \
105
+ explicitly requested. Hardware is never the spec ideal — leave the calibration knob.
106
+ Lazy code without its check is unfinished: non-trivial logic leaves ONE runnable check behind \
107
+ (an assert-based self-check or one small test file; no frameworks). Trivial one-liners need no test.
108
+ Output: code first, then at most three short lines — what was skipped, when to add it.
109
+ Boundaries: ponytail governs what you build, not how you talk. "stop ponytail" / "normal mode" reverts.`;
110
+
111
+ /**
112
+ * Build the system prompt injection for a given level.
113
+ * Returns empty string when level is "off".
114
+ */
115
+ export function buildPrompt(level: Level): string {
116
+ if (level === "off") return "";
117
+ return [BASE, "", `Intensity: ${INTENSITY[level]}`, "", SAFETY].join("\n");
118
+ }
119
+
120
+ // ── Level resolution ──────────────────────────────────────────────────────────
121
+
122
+ /**
123
+ * Resolve a raw command arg to a Level, or return null if unrecognised.
124
+ * Handles stop aliases (stop/quit → "off") and valid level names.
125
+ */
126
+ export function resolveLevel(arg: string): Level | null {
127
+ const a = arg.trim().toLowerCase();
128
+ if (STOP_ALIASES.has(a)) return "off";
129
+ if (LEVEL_NUMBERS[a]) return LEVEL_NUMBERS[a];
130
+ if (LEVELS.includes(a as Level)) return a as Level;
131
+ return null;
132
+ }
133
+
134
+ /**
135
+ * Help text shown when /opt ponytail is run with no argument.
136
+ */
137
+ export function buildHelp(current: Level): string {
138
+ const statusLine =
139
+ current === "off" ? "off" : `${STATUS_LABELS[current]} (${current})`;
140
+ return [
141
+ `Ponytail mode: ${statusLine}`,
142
+ "",
143
+ "Usage: /opt ponytail <level>",
144
+ " 1 lite - name the lazier alternative, you pick",
145
+ " 2 full - the ladder enforced (default)",
146
+ " 3 ultra - YAGNI extremist",
147
+ " 0 off - disable (aliases: off, stop, quit)",
148
+ "",
149
+ " config - open settings dialog",
150
+ ].join("\n");
151
+ }
152
+
153
+ /**
154
+ * Toggle: off → full, anything else → off.
155
+ */
156
+ export function toggleLevel(current: Level): Level {
157
+ return current === "off" ? "full" : "off";
158
+ }
159
+
160
+ // ── Config ────────────────────────────────────────────────────────────────────
161
+
162
+ export interface PonytailConfig {
163
+ /** Level to apply on new sessions. "off" means don't auto-enable. */
164
+ defaultLevel: Level;
165
+ /** Whether to show the status bar indicator. */
166
+ showStatus: boolean;
167
+ }
168
+
169
+ export const DEFAULT_CONFIG: PonytailConfig = {
170
+ defaultLevel: "full",
171
+ showStatus: true,
172
+ };
173
+
174
+ const CONFIG_PATH = join(homedir(), ".pi", "agent", "ponytail.json");
175
+
176
+ export function parseConfig(raw: unknown): PonytailConfig {
177
+ const parsed = raw as Record<string, unknown>;
178
+ return {
179
+ defaultLevel: LEVELS.includes(parsed?.defaultLevel as Level)
180
+ ? (parsed.defaultLevel as Level)
181
+ : DEFAULT_CONFIG.defaultLevel,
182
+ showStatus:
183
+ typeof parsed?.showStatus === "boolean"
184
+ ? parsed.showStatus
185
+ : DEFAULT_CONFIG.showStatus,
186
+ };
187
+ }
188
+
189
+ let _saveQueue: Promise<void> = Promise.resolve();
190
+
191
+ async function loadConfig(): Promise<PonytailConfig> {
192
+ try {
193
+ const raw = await readFile(CONFIG_PATH, "utf8");
194
+ return parseConfig(JSON.parse(raw));
195
+ } catch {
196
+ return { ...DEFAULT_CONFIG };
197
+ }
198
+ }
199
+
200
+ async function saveConfig(config: PonytailConfig): Promise<void> {
201
+ const snapshot = `${JSON.stringify(config, null, 2)}\n`;
202
+ _saveQueue = _saveQueue.then(async () => {
203
+ await mkdir(join(homedir(), ".pi", "agent"), { recursive: true });
204
+ await writeFile(CONFIG_PATH, snapshot, "utf8");
205
+ });
206
+ return _saveQueue;
207
+ }
208
+
209
+ // ── Pi extension ────────────────────────────────────────────────────────────
210
+
211
+ export function ponytail(
212
+ pi: ExtensionAPI,
213
+ status: OptimizerStatus,
214
+ ): OptimizerHandle {
215
+ let level: Level = "off";
216
+ let config: PonytailConfig = { ...DEFAULT_CONFIG };
217
+ let configLoadPromise: Promise<void> | null = null;
218
+
219
+ const ensureConfigLoaded = async () => {
220
+ if (!configLoadPromise) {
221
+ configLoadPromise = (async () => {
222
+ config = await loadConfig();
223
+ if (level === "off" && config.defaultLevel !== "off") {
224
+ level = config.defaultLevel;
225
+ }
226
+ })();
227
+ }
228
+ await configLoadPromise;
229
+ };
230
+
231
+ // -- Status: report into the shared optimizer indicator. --
232
+
233
+ function syncStatus(ctx: Pick<ExtensionContext, "ui">) {
234
+ const on = level !== "off" && config.showStatus;
235
+ status.set("ponytail", on, ctx);
236
+ }
237
+
238
+ // Inject ponytail prompt via before_agent_start
239
+ pi.on("before_agent_start", async (event, _ctx) => {
240
+ const prompt = buildPrompt(level);
241
+ if (!prompt) return undefined;
242
+ const existing = event.systemPrompt ?? "";
243
+ return { systemPrompt: `${prompt}\n\n${existing}` };
244
+ });
245
+
246
+ // -- Restore state on session load --
247
+
248
+ pi.on("session_start", async (_event, ctx) => {
249
+ await ensureConfigLoaded();
250
+
251
+ let sessionLevel: Level | null = null;
252
+ for (const entry of ctx.sessionManager.getEntries()) {
253
+ if (entry.type === "custom" && entry.customType === "ponytail-level") {
254
+ sessionLevel = (entry.data as { level: Level })?.level ?? null;
255
+ }
256
+ }
257
+
258
+ if (sessionLevel !== null) {
259
+ level = sessionLevel;
260
+ } else if (config.defaultLevel !== "off") {
261
+ level = config.defaultLevel;
262
+ pi.appendEntry("ponytail-level", { level });
263
+ }
264
+
265
+ syncStatus(ctx);
266
+ });
267
+
268
+ pi.on("agent_start", async (_event, ctx) => {
269
+ syncStatus(ctx);
270
+ });
271
+ pi.on("agent_end", async (_event, ctx) => {
272
+ syncStatus(ctx);
273
+ });
274
+ pi.on("session_shutdown", async () => {});
275
+
276
+ // -- Subcommand handler (dispatched by the merged /opt router) --
277
+
278
+ async function run(
279
+ args: string,
280
+ ctx: ExtensionCommandContext,
281
+ ): Promise<void> {
282
+ const arg = args.trim().toLowerCase();
283
+
284
+ // No argument → show help
285
+ if (!arg) {
286
+ ctx.ui.notify(buildHelp(level), "info");
287
+ return;
288
+ }
289
+
290
+ if (arg === "config") {
291
+ await openConfig(ctx);
292
+ return;
293
+ }
294
+
295
+ const resolved = resolveLevel(arg);
296
+ if (resolved === null) {
297
+ ctx.ui.notify(
298
+ `Unknown: "${arg}". Use 1/2/3, ${LEVELS.join(", ")}, stop, quit, or config`,
299
+ "error",
300
+ );
301
+ return;
302
+ }
303
+ level = resolved;
304
+
305
+ pi.appendEntry("ponytail-level", { level });
306
+ syncStatus(ctx);
307
+
308
+ ctx.ui.notify(
309
+ level === "off"
310
+ ? "Ponytail mode off."
311
+ : `Ponytail: ${STATUS_LABELS[level]}`,
312
+ "info",
313
+ );
314
+ }
315
+
316
+ function complete(prefix: string) {
317
+ const normalized = prefix.trim().toLowerCase();
318
+ const items = PONYTAIL_COMMAND_OPTIONS.filter((item) =>
319
+ item.value.startsWith(normalized),
320
+ );
321
+ return items.length > 0 ? items.map((i) => ({ ...i })) : null;
322
+ }
323
+
324
+ // -- config: interactive SettingsList --
325
+
326
+ async function openConfig(ctx: ExtensionContext) {
327
+ await ensureConfigLoaded();
328
+
329
+ await ctx.ui.custom((_tui, theme, _kb, done) => {
330
+ const items: SettingItem[] = [
331
+ {
332
+ id: "defaultLevel",
333
+ label: "Default level for new sessions",
334
+ currentValue: config.defaultLevel,
335
+ values: [...LEVELS],
336
+ },
337
+ {
338
+ id: "showStatus",
339
+ label: "Show status bar",
340
+ currentValue: config.showStatus ? "on" : "off",
341
+ values: ["on", "off"],
342
+ },
343
+ ];
344
+
345
+ const container = new Container();
346
+ container.addChild(
347
+ new Text(theme.fg("accent", theme.bold(" Ponytail Config")), 0, 0),
348
+ );
349
+ container.addChild(
350
+ new Text(theme.fg("dim", " Saved to ~/.pi/agent/ponytail.json"), 0, 0),
351
+ );
352
+ container.addChild(
353
+ new Text(
354
+ theme.fg("dim", " Default level applies to future sessions."),
355
+ 0,
356
+ 0,
357
+ ),
358
+ );
359
+ container.addChild(new Text("", 0, 0));
360
+
361
+ const applySettingChange = (id: string, newValue: string) => {
362
+ if (id === "defaultLevel" && LEVELS.includes(newValue as Level)) {
363
+ config.defaultLevel = newValue as Level;
364
+ } else if (id === "showStatus") {
365
+ config.showStatus = newValue === "on";
366
+ }
367
+ saveConfig(config);
368
+ syncStatus(ctx);
369
+ };
370
+
371
+ const settingsList = new SettingsList(
372
+ items,
373
+ Math.min(items.length + 2, 10),
374
+ getSettingsListTheme(),
375
+ applySettingChange,
376
+ () => done(undefined),
377
+ );
378
+
379
+ container.addChild(settingsList);
380
+ container.addChild(
381
+ new Text(
382
+ theme.fg("dim", " ←→/hl/tab change • ↑↓/jk move • esc close"),
383
+ 0,
384
+ 0,
385
+ ),
386
+ );
387
+
388
+ const cycleSelectedValue = (direction: -1 | 1) => {
389
+ const selectedIndex = (
390
+ settingsList as unknown as { selectedIndex: number }
391
+ ).selectedIndex;
392
+ const item = items[selectedIndex];
393
+ if (!item?.values?.length) return;
394
+ const currentIndex = item.values.indexOf(item.currentValue);
395
+ const nextIndex =
396
+ (currentIndex + direction + item.values.length) % item.values.length;
397
+ const newValue = item.values[nextIndex]!;
398
+ item.currentValue = newValue;
399
+ settingsList.updateValue(item.id, newValue);
400
+ applySettingChange(item.id, newValue);
401
+ };
402
+
403
+ return {
404
+ render: (w: number) => container.render(w),
405
+ invalidate: () => container.invalidate(),
406
+ handleInput: (data: string) => {
407
+ if (data === "j") data = "\u001b[B";
408
+ else if (data === "k") data = "\u001b[A";
409
+ else if (data === "h") {
410
+ cycleSelectedValue(-1);
411
+ _tui.requestRender();
412
+ return;
413
+ } else if (data === "l" || data === "\u001b[C" || data === "\t") {
414
+ cycleSelectedValue(1);
415
+ _tui.requestRender();
416
+ return;
417
+ } else if (data === "\u001b[D") {
418
+ cycleSelectedValue(-1);
419
+ _tui.requestRender();
420
+ return;
421
+ } else if (data === "\u001b" || data === "q") {
422
+ done(undefined);
423
+ return;
424
+ }
425
+
426
+ settingsList.handleInput(data);
427
+ _tui.requestRender();
428
+ },
429
+ };
430
+ });
431
+ }
432
+
433
+ return {
434
+ name: "ponytail",
435
+ help: "ponytail <1|2|3|lite|full|ultra|off|config> — lazy senior dev (minimal code)",
436
+ run,
437
+ complete,
438
+ };
439
+ }
@@ -11,26 +11,36 @@ const tag = (c: string, t: string) => `<${c}>${t}</${c}>`;
11
11
 
12
12
  describe("renderStatus", () => {
13
13
  it("shows ALL icons in order, accent when enabled", () => {
14
- expect(renderStatus({ caveman: true, rtk: true, toon: true }, tag)).toBe(
15
- `<accent>${TOOL_ICONS.caveman}</accent> <accent>${TOOL_ICONS.rtk}</accent> <accent>${TOOL_ICONS.toon}</accent> `,
14
+ expect(
15
+ renderStatus(
16
+ { caveman: true, rtk: true, toon: true, ponytail: true },
17
+ tag,
18
+ ),
19
+ ).toBe(
20
+ `<accent>${TOOL_ICONS.caveman}</accent> <accent>${TOOL_ICONS.rtk}</accent> <accent>${TOOL_ICONS.toon}</accent> <accent>${TOOL_ICONS.ponytail}</accent> `,
16
21
  );
17
22
  });
18
23
 
19
24
  it("dims disabled tools but still shows them", () => {
20
- expect(renderStatus({ caveman: false, rtk: true, toon: true }, tag)).toBe(
21
- `<dim>${TOOL_ICONS.caveman}</dim> <accent>${TOOL_ICONS.rtk}</accent> <accent>${TOOL_ICONS.toon}</accent> `,
25
+ expect(
26
+ renderStatus(
27
+ { caveman: false, rtk: true, toon: true, ponytail: true },
28
+ tag,
29
+ ),
30
+ ).toBe(
31
+ `<dim>${TOOL_ICONS.caveman}</dim> <accent>${TOOL_ICONS.rtk}</accent> <accent>${TOOL_ICONS.toon}</accent> <accent>${TOOL_ICONS.ponytail}</accent> `,
22
32
  );
23
33
  });
24
34
 
25
35
  it("all dim when nothing enabled (cell never empty)", () => {
26
36
  expect(renderStatus({}, tag)).toBe(
27
- `<dim>${TOOL_ICONS.caveman}</dim> <dim>${TOOL_ICONS.rtk}</dim> <dim>${TOOL_ICONS.toon}</dim> `,
37
+ `<dim>${TOOL_ICONS.caveman}</dim> <dim>${TOOL_ICONS.rtk}</dim> <dim>${TOOL_ICONS.toon}</dim> <dim>${TOOL_ICONS.ponytail}</dim> `,
28
38
  );
29
39
  });
30
40
 
31
41
  it("preserves fixed order regardless of insertion order", () => {
32
42
  expect(renderStatus({ toon: true, caveman: true }, tag)).toBe(
33
- `<accent>${TOOL_ICONS.caveman}</accent> <dim>${TOOL_ICONS.rtk}</dim> <accent>${TOOL_ICONS.toon}</accent> `,
43
+ `<accent>${TOOL_ICONS.caveman}</accent> <dim>${TOOL_ICONS.rtk}</dim> <accent>${TOOL_ICONS.toon}</accent> <dim>${TOOL_ICONS.ponytail}</dim> `,
34
44
  );
35
45
  });
36
46
  });
@@ -55,9 +65,9 @@ describe("OptimizerStatus", () => {
55
65
  status.set("rtk", true, ctx as never);
56
66
  const last = ctx.calls.at(-1)!;
57
67
  expect(last.key).toBe(STATUS_KEY);
58
- // caveman + toon still unset (dim), rtk accent.
68
+ // caveman + toon + ponytail still unset (dim), rtk accent.
59
69
  expect(last.text).toBe(
60
- `<dim>${TOOL_ICONS.caveman}</dim> <accent>${TOOL_ICONS.rtk}</accent> <dim>${TOOL_ICONS.toon}</dim> `,
70
+ `<dim>${TOOL_ICONS.caveman}</dim> <accent>${TOOL_ICONS.rtk}</accent> <dim>${TOOL_ICONS.toon}</dim> <dim>${TOOL_ICONS.ponytail}</dim> `,
61
71
  );
62
72
  });
63
73
 
@@ -68,7 +78,7 @@ describe("OptimizerStatus", () => {
68
78
  status.set("toon", true, ctx as never);
69
79
  const last = ctx.calls.at(-1)!;
70
80
  expect(last.text).toBe(
71
- `<accent>${TOOL_ICONS.caveman}</accent> <dim>${TOOL_ICONS.rtk}</dim> <accent>${TOOL_ICONS.toon}</accent> `,
81
+ `<accent>${TOOL_ICONS.caveman}</accent> <dim>${TOOL_ICONS.rtk}</dim> <accent>${TOOL_ICONS.toon}</accent> <dim>${TOOL_ICONS.ponytail}</dim> `,
72
82
  );
73
83
  });
74
84
 
@@ -79,7 +89,7 @@ describe("OptimizerStatus", () => {
79
89
  status.set("rtk", false, ctx as never);
80
90
  const last = ctx.calls.at(-1)!;
81
91
  expect(last.text).toBe(
82
- `<dim>${TOOL_ICONS.caveman}</dim> <dim>${TOOL_ICONS.rtk}</dim> <dim>${TOOL_ICONS.toon}</dim> `,
92
+ `<dim>${TOOL_ICONS.caveman}</dim> <dim>${TOOL_ICONS.rtk}</dim> <dim>${TOOL_ICONS.toon}</dim> <dim>${TOOL_ICONS.ponytail}</dim> `,
83
93
  );
84
94
  });
85
95
  });
package/src/status.ts CHANGED
@@ -7,8 +7,8 @@
7
7
  * the cell renders only the icons whose tool is currently enabled, in a fixed
8
8
  * order, accent-colored. When everything is off the cell is empty.
9
9
  *
10
- * all on: ⛏ ⚔ ✂
11
- * caveman off: ⚔ ✂
10
+ * all on: 󰜐 󰓥 󰗀 󰆐
11
+ * caveman off: 󰓥 󰗀 󰆐
12
12
  * all off: (empty)
13
13
  */
14
14
 
@@ -44,16 +44,22 @@ export interface OptimizerHandle {
44
44
  export const STATUS_KEY = "pix-optimizer";
45
45
 
46
46
  /** Tools that participate in the shared indicator, in render order. */
47
- export type OptimizerTool = "caveman" | "rtk" | "toon";
47
+ export type OptimizerTool = "caveman" | "rtk" | "toon" | "ponytail";
48
48
 
49
49
  export const TOOL_ICONS: Record<OptimizerTool, string> = {
50
- caveman: "⛏",
51
- rtk: "⚔",
52
- toon: "✂",
50
+ caveman: "󰜐",
51
+ rtk: "󰓥",
52
+ toon: "󰗀",
53
+ ponytail: "󰆐",
53
54
  };
54
55
 
55
56
  /** Fixed left-to-right order of icons in the cell. */
56
- const TOOL_ORDER: readonly OptimizerTool[] = ["caveman", "rtk", "toon"];
57
+ const TOOL_ORDER: readonly OptimizerTool[] = [
58
+ "caveman",
59
+ "rtk",
60
+ "toon",
61
+ "ponytail",
62
+ ];
57
63
 
58
64
  /** Theme color for enabled icons. */
59
65
  const ENABLED_COLOR: ThemeColor = "accent";