@hheei/omp-optimizer 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.
@@ -0,0 +1,52 @@
1
+ /** Persistence contract for optimizer values across fresh store instances. */
2
+
3
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
4
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import * as path from "node:path";
7
+ import { createOptimizerStore } from "./persist.ts";
8
+
9
+ let tmpAgentDir: string;
10
+ let statePath: string;
11
+
12
+ beforeAll(() => {
13
+ tmpAgentDir = mkdtempSync(path.join(tmpdir(), "omp-optimizer-persist-test-"));
14
+ statePath = path.join(tmpAgentDir, "optimizer.json");
15
+ });
16
+
17
+ afterAll(() => {
18
+ rmSync(tmpAgentDir, { recursive: true, force: true });
19
+ });
20
+
21
+ describe("optimizer persistence", () => {
22
+ test("returns undefined before anything is saved", () => {
23
+ expect(createOptimizerStore(statePath).load("caveman")).toBeUndefined();
24
+ });
25
+
26
+ test("round-trips a value across fresh store instances", () => {
27
+ createOptimizerStore(statePath).save("caveman", "lite");
28
+ expect(createOptimizerStore(statePath).load("caveman")).toBe("lite");
29
+ });
30
+
31
+ test("persists each tool independently in one shared file", () => {
32
+ const store = createOptimizerStore(statePath);
33
+ store.save("ponytail", "full");
34
+ store.save("rtk", "off");
35
+ expect(store.load("caveman")).toBe("lite");
36
+ expect(store.load("ponytail")).toBe("full");
37
+ expect(store.load("rtk")).toBe("off");
38
+ });
39
+
40
+ test("overwriting one tool leaves the others intact", () => {
41
+ const store = createOptimizerStore(statePath);
42
+ store.save("caveman", "ultra");
43
+ expect(store.load("caveman")).toBe("ultra");
44
+ expect(store.load("ponytail")).toBe("full");
45
+ });
46
+
47
+ test("drops legacy TOON state on the next write", () => {
48
+ writeFileSync(statePath, JSON.stringify({ caveman: "lite", toon: "on" }));
49
+ createOptimizerStore(statePath).save("rtk", "on");
50
+ expect(JSON.parse(readFileSync(statePath, "utf8"))).toEqual({ caveman: "lite", rtk: "on" });
51
+ });
52
+ });
package/src/persist.ts ADDED
@@ -0,0 +1,58 @@
1
+ /** Disk-backed persistence for optimizer values. */
2
+
3
+ import * as fs from "node:fs";
4
+ import * as path from "node:path";
5
+ import { getAgentDir, logger } from "@oh-my-pi/pi-utils";
6
+ import type { OptimizerTool } from "./status.ts";
7
+
8
+ type OptimizerFileConfig = Partial<Record<OptimizerTool, string>>;
9
+
10
+ const OPTIMIZER_TOOLS: readonly OptimizerTool[] = ["caveman", "rtk", "ponytail", "t2s"];
11
+
12
+ export interface OptimizerStore {
13
+ load(tool: OptimizerTool): string | undefined;
14
+ save(tool: OptimizerTool, value: string): void;
15
+ }
16
+
17
+ export function createOptimizerStore(statePath: string): OptimizerStore {
18
+ const readState = (): OptimizerFileConfig => {
19
+ try {
20
+ if (!fs.existsSync(statePath)) return {};
21
+ const raw = JSON.parse(fs.readFileSync(statePath, "utf8")) as unknown;
22
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
23
+
24
+ const config: OptimizerFileConfig = {};
25
+ for (const tool of OPTIMIZER_TOOLS) {
26
+ const value = (raw as Record<string, unknown>)[tool];
27
+ if (typeof value === "string") config[tool] = value;
28
+ }
29
+ return config;
30
+ } catch {
31
+ return {};
32
+ }
33
+ };
34
+
35
+ return {
36
+ load: (tool: OptimizerTool) => readState()[tool],
37
+ save: (tool: OptimizerTool, value: string) => {
38
+ try {
39
+ fs.mkdirSync(path.dirname(statePath), { recursive: true });
40
+ fs.writeFileSync(statePath, JSON.stringify({ ...readState(), [tool]: value }, null, 2), "utf8");
41
+ } catch (error) {
42
+ logger.warn("omp-optimizer: failed to persist state", { tool, error: String(error) });
43
+ }
44
+ },
45
+ };
46
+ }
47
+
48
+ function defaultStore(): OptimizerStore {
49
+ return createOptimizerStore(path.join(getAgentDir(), "optimizer.json"));
50
+ }
51
+
52
+ export function loadOptValue(tool: OptimizerTool): string | undefined {
53
+ return defaultStore().load(tool);
54
+ }
55
+
56
+ export function saveOptValue(tool: OptimizerTool, value: string): void {
57
+ defaultStore().save(tool, value);
58
+ }
@@ -0,0 +1,195 @@
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 "./ponytail.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"];
23
+ for (const l of expected) expect(LEVELS).toContain(l);
24
+ });
25
+
26
+ it("has no micro level (caveman-only)", () => {
27
+ expect(LEVELS).not.toContain("micro" as Level);
28
+ });
29
+ });
30
+
31
+ // ── STOP_ALIASES ──────────────────────────────────────────────────────────────
32
+
33
+ describe("STOP_ALIASES", () => {
34
+ it("includes off, stop, quit", () => {
35
+ expect(STOP_ALIASES.has("off")).toBe(true);
36
+ expect(STOP_ALIASES.has("stop")).toBe(true);
37
+ expect(STOP_ALIASES.has("quit")).toBe(true);
38
+ });
39
+
40
+ it("does not include active levels", () => {
41
+ expect(STOP_ALIASES.has("full")).toBe(false);
42
+ expect(STOP_ALIASES.has("ultra")).toBe(false);
43
+ });
44
+ });
45
+
46
+ // ── STATUS_LABELS ─────────────────────────────────────────────────────────────
47
+
48
+ describe("STATUS_LABELS", () => {
49
+ it("has a label for every non-off level", () => {
50
+ const nonOff = LEVELS.filter((l) => l !== "off") as Exclude<Level, "off">[];
51
+ for (const l of nonOff) {
52
+ expect(STATUS_LABELS[l]).toBeTruthy();
53
+ }
54
+ });
55
+
56
+ it("levels are uppercase", () => {
57
+ expect(STATUS_LABELS.lite).toBe("LITE");
58
+ expect(STATUS_LABELS.full).toBe("FULL");
59
+ expect(STATUS_LABELS.ultra).toBe("ULTRA");
60
+ });
61
+ });
62
+
63
+ // ── resolveLevel ──────────────────────────────────────────────────────────────
64
+
65
+ describe("resolveLevel", () => {
66
+ it("resolves valid levels", () => {
67
+ expect(resolveLevel("lite")).toBe("lite");
68
+ expect(resolveLevel("full")).toBe("full");
69
+ expect(resolveLevel("ultra")).toBe("ultra");
70
+ expect(resolveLevel("off")).toBe("off");
71
+ });
72
+
73
+ it("maps stop aliases to off", () => {
74
+ expect(resolveLevel("stop")).toBe("off");
75
+ expect(resolveLevel("quit")).toBe("off");
76
+ });
77
+
78
+ it("is case-insensitive", () => {
79
+ expect(resolveLevel("FULL")).toBe("full");
80
+ expect(resolveLevel("Ultra")).toBe("ultra");
81
+ expect(resolveLevel("STOP")).toBe("off");
82
+ });
83
+
84
+ it("trims whitespace", () => {
85
+ expect(resolveLevel(" full ")).toBe("full");
86
+ });
87
+
88
+ it("returns null for unknown input", () => {
89
+ expect(resolveLevel("unknown")).toBeNull();
90
+ expect(resolveLevel("")).toBeNull();
91
+ expect(resolveLevel("config")).toBeNull(); // config handled separately
92
+ expect(resolveLevel("micro")).toBeNull(); // no micro level
93
+ });
94
+ });
95
+
96
+ // ── numeric levels ────────────────────────────────────────────────────────────
97
+
98
+ describe("numeric levels", () => {
99
+ it("maps 1/2/3 to lite/full/ultra", () => {
100
+ expect(resolveLevel("1")).toBe("lite");
101
+ expect(resolveLevel("2")).toBe("full");
102
+ expect(resolveLevel("3")).toBe("ultra");
103
+ });
104
+
105
+ it("maps 0 to off", () => {
106
+ expect(resolveLevel("0")).toBe("off");
107
+ });
108
+
109
+ it("LEVEL_NUMBERS only covers 1-3", () => {
110
+ expect(Object.keys(LEVEL_NUMBERS).sort()).toEqual(["1", "2", "3"]);
111
+ });
112
+
113
+ it("rejects out-of-range numbers", () => {
114
+ expect(resolveLevel("4")).toBeNull();
115
+ expect(resolveLevel("9")).toBeNull();
116
+ });
117
+
118
+ it("trims whitespace around numbers", () => {
119
+ expect(resolveLevel(" 2 ")).toBe("full");
120
+ });
121
+ });
122
+
123
+ // ── buildHelp ─────────────────────────────────────────────────────────────────
124
+
125
+ describe("buildHelp", () => {
126
+ it("lists numeric shortcuts", () => {
127
+ const help = buildHelp("off");
128
+ expect(help).toContain("1");
129
+ expect(help).toContain("lite");
130
+ expect(help).toContain("2");
131
+ expect(help).toContain("full");
132
+ expect(help).toContain("3");
133
+ expect(help).toContain("ultra");
134
+ });
135
+
136
+ it("shows current level when active", () => {
137
+ expect(buildHelp("ultra")).toContain("ULTRA");
138
+ });
139
+
140
+ it("shows off when disabled", () => {
141
+ expect(buildHelp("off")).toContain("off");
142
+ });
143
+
144
+ it("mentions config", () => {
145
+ expect(buildHelp("off")).toContain("config");
146
+ });
147
+ });
148
+
149
+ // ── toggleLevel ───────────────────────────────────────────────────────────────
150
+
151
+ describe("toggleLevel", () => {
152
+ it("off → full", () => {
153
+ expect(toggleLevel("off")).toBe("full");
154
+ });
155
+
156
+ it("full → off", () => {
157
+ expect(toggleLevel("full")).toBe("off");
158
+ });
159
+
160
+ it("any non-off level → off", () => {
161
+ const nonOff = LEVELS.filter((l) => l !== "off") as Level[];
162
+ for (const l of nonOff) {
163
+ expect(toggleLevel(l)).toBe("off");
164
+ }
165
+ });
166
+ });
167
+
168
+ // ── buildPrompt ───────────────────────────────────────────────────────────────
169
+
170
+ describe("buildPrompt", () => {
171
+ it("returns empty string for off", () => {
172
+ expect(buildPrompt("off")).toBe("");
173
+ });
174
+
175
+ it("includes BASE ladder for all active levels", () => {
176
+ for (const l of ["lite", "full", "ultra"] as Level[]) {
177
+ expect(buildPrompt(l)).toContain("PONYTAIL MODE ACTIVE");
178
+ expect(buildPrompt(l)).toContain("first rung that holds");
179
+ }
180
+ });
181
+
182
+ it("includes SAFETY clause for all active levels", () => {
183
+ for (const l of ["lite", "full", "ultra"] as Level[]) {
184
+ expect(buildPrompt(l)).toContain("When NOT to be lazy");
185
+ }
186
+ });
187
+
188
+ it("each level has distinct intensity instructions", () => {
189
+ const lite = buildPrompt("lite");
190
+ const ultra = buildPrompt("ultra");
191
+ expect(lite).toContain("name the lazier alternative");
192
+ expect(ultra).toContain("YAGNI extremist");
193
+ expect(lite).not.toContain("YAGNI extremist");
194
+ });
195
+ });
@@ -0,0 +1,145 @@
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 type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
13
+ import {
14
+ createMode,
15
+ resolveLevel as resolveLevelGeneric,
16
+ toggleLevel as toggleLevelGeneric,
17
+ } from "./mode.ts";
18
+ import type { OptimizerHandle, OptimizerStatus } from "./status.ts";
19
+
20
+ // ── Levels ────────────────────────────────────────────────────────────────────
21
+
22
+ export const LEVELS = ["off", "lite", "full", "ultra"] as const;
23
+
24
+ export type Level = (typeof LEVELS)[number];
25
+
26
+ export const STOP_ALIASES = new Set(["off", "stop", "quit", "0"]);
27
+
28
+ // Numeric shortcuts: /opt ponytail 1|2|3
29
+ export const LEVEL_NUMBERS: Record<string, Level> = {
30
+ "1": "lite",
31
+ "2": "full",
32
+ "3": "ultra",
33
+ };
34
+
35
+ // ── Status labels ─────────────────────────────────────────────────────────────
36
+
37
+ export const STATUS_LABELS: Record<Exclude<Level, "off">, string> = {
38
+ lite: "LITE",
39
+ full: "FULL",
40
+ ultra: "ULTRA",
41
+ };
42
+
43
+ // ── Prompt fragments ──────────────────────────────────────────────────────────
44
+
45
+ const BASE = `\
46
+ PONYTAIL MODE ACTIVE. You are a lazy senior developer. Lazy means efficient, \
47
+ not careless. The best code is the code never written.
48
+
49
+ Before writing any code, stop at the first rung that holds:
50
+ 1. Does this need to exist at all? Speculative need = skip it, say so in one line. (YAGNI)
51
+ 2. Stdlib does it? Use it.
52
+ 3. Native platform feature covers it? Use it (\`<input type="date">\` over a picker lib, CSS over JS, DB constraint over app code).
53
+ 4. Already-installed dependency solves it? Use it. Never add a new one for what a few lines can do.
54
+ 5. Can it be one line? One line.
55
+ 6. Only then: the minimum code that works.
56
+
57
+ The ladder is a reflex, not a research project. Two rungs work → take the higher one and move on.
58
+
59
+ Rules:
60
+ - No unrequested abstractions: no interface with one impl, no factory for one product, no config for a value that never changes.
61
+ - No boilerplate, no scaffolding "for later". Deletion over addition. Boring over clever. Fewest files possible.
62
+ - Complex request? Ship the lazy version and question it in the same response. Never stall on an answer you can default.
63
+ - Two same-size stdlib options? Take the one correct on edge cases. Lazy means less code, not the flimsier algorithm.
64
+ - Mark deliberate simplifications with a \`ponytail:\` comment. A shortcut with a known ceiling names the ceiling and the upgrade path.`;
65
+
66
+ const INTENSITY: Record<Exclude<Level, "off">, string> = {
67
+ lite: `\
68
+ Build what's asked, but name the lazier alternative in one line. User picks.
69
+ Example: "Done, cache added. FYI: \`functools.lru_cache\` covers this in one line if you'd rather not own a cache class."`,
70
+
71
+ full: `\
72
+ The ladder enforced. Stdlib and native first. Shortest diff, shortest explanation.
73
+ Example: "\`@lru_cache(maxsize=1000)\` on the fetch function. Skipped custom cache class, add when lru_cache measurably falls short."`,
74
+
75
+ ultra: `\
76
+ YAGNI extremist. Deletion before addition. Ship the one-liner and challenge the rest of the requirement in the same breath.
77
+ 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."`,
78
+ };
79
+
80
+ const SAFETY = `\
81
+ When NOT to be lazy: never simplify away input validation at trust boundaries, \
82
+ error handling that prevents data loss, security, accessibility, or anything \
83
+ explicitly requested. Hardware is never the spec ideal — leave the calibration knob.
84
+ Lazy code without its check is unfinished: non-trivial logic leaves ONE runnable check behind \
85
+ (an assert-based self-check or one small test file; no frameworks). Trivial one-liners need no test.
86
+ Output: code first, then at most three short lines — what was skipped, when to add it.
87
+ Boundaries: ponytail governs what you build, not how you talk. "stop ponytail" / "normal mode" reverts.`;
88
+
89
+ /**
90
+ * Build the system prompt injection for a given level.
91
+ * Returns empty string when level is "off".
92
+ */
93
+ export function buildPrompt(level: Level): string {
94
+ if (level === "off") return "";
95
+ return [BASE, "", `Intensity: ${INTENSITY[level]}`, "", SAFETY].join("\n");
96
+ }
97
+
98
+ // ── Level resolution ──────────────────────────────────────────────────────────
99
+
100
+ /**
101
+ * Resolve a raw command arg to a Level, or return null if unrecognised.
102
+ * Handles stop aliases (stop/quit → "off") and valid level names.
103
+ */
104
+ export function resolveLevel(arg: string): Level | null {
105
+ return resolveLevelGeneric(arg, LEVELS, LEVEL_NUMBERS, STOP_ALIASES);
106
+ }
107
+
108
+ /**
109
+ * Help text shown when /opt ponytail is run with no argument.
110
+ */
111
+ export function buildHelp(current: Level): string {
112
+ const statusLine = current === "off" ? "off" : `${STATUS_LABELS[current]} (${current})`;
113
+ return [
114
+ `Ponytail mode: ${statusLine}`,
115
+ "",
116
+ "Usage: /optimizer ponytail <level>",
117
+ " 1 lite - name the lazier alternative, you pick",
118
+ " 2 full - the ladder enforced (default)",
119
+ " 3 ultra - YAGNI extremist",
120
+ " 0 off - disable (aliases: off, stop, quit)",
121
+ "",
122
+ " config - open settings dialog",
123
+ ].join("\n");
124
+ }
125
+
126
+ /**
127
+ * Toggle: off → full, anything else → off.
128
+ */
129
+ export function toggleLevel(current: Level): Level {
130
+ return toggleLevelGeneric(current);
131
+ }
132
+
133
+ // ── Pi extension ────────────────────────────────────────────────────────────
134
+
135
+ export function ponytail(pi: ExtensionAPI, status: OptimizerStatus): OptimizerHandle {
136
+ return createMode(pi, status, {
137
+ name: "ponytail",
138
+ help: "ponytail — lazy senior dev (minimal code)",
139
+ levels: LEVELS,
140
+ buildPrompt,
141
+ resolve: resolveLevel,
142
+ notify: (level) =>
143
+ level === "off" ? "Ponytail mode off." : `Ponytail: ${STATUS_LABELS[level]}`,
144
+ });
145
+ }