@selesai/code 0.13.34 → 0.13.37
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/CHANGELOG.md +22 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +2 -0
- package/dist/core/remote-catalog-provider.js +2 -2
- package/dist/core/remote-catalog-provider.test.js +20 -1
- package/dist/extensions/capability-gateway/index.ts +2 -1
- package/dist/extensions/jev/decisions.ts +14 -1
- package/dist/extensions/jev-ask-tool.test.ts +70 -5
- package/dist/extensions/jev-ask-tool.ts +375 -10
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +6 -4
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +26 -1
- package/dist/extensions/pi-subagents/src/runs/shared/jev-subagent-routing.ts +255 -0
- package/dist/extensions/pi-subagents/test/unit/jev-subagent-routing.test.ts +117 -0
- package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +3 -1
- package/dist/extensions/ponytail/index.js +7 -4
- package/dist/extensions/ponytail/ponytail-config.cjs +52 -26
- package/dist/extensions/ponytail/ponytail-instructions.cjs +3 -17
- package/dist/extensions/ponytail/test/extension.test.js +37 -16
- package/dist/extensions/ponytail/test/helpers.test.js +54 -51
- package/dist/skills/ponytail/SKILL.md +5 -7
- package/dist/skills/ponytail-help/SKILL.md +10 -14
- package/docs/settings.md +22 -4
- package/package.json +1 -1
|
@@ -42,13 +42,17 @@ function createCommandContext(overrides = {}) {
|
|
|
42
42
|
function withTempConfig(fn) {
|
|
43
43
|
const tempConfigHome = mkdtempSync(join(tmpdir(), "ponytail-test-"));
|
|
44
44
|
const previousXdg = process.env.XDG_CONFIG_HOME;
|
|
45
|
+
const previousAgentDir = process.env.SELESAI_CODING_AGENT_DIR;
|
|
45
46
|
process.env.XDG_CONFIG_HOME = tempConfigHome;
|
|
47
|
+
process.env.SELESAI_CODING_AGENT_DIR = tempConfigHome;
|
|
46
48
|
|
|
47
49
|
return Promise.resolve()
|
|
48
50
|
.then(fn)
|
|
49
51
|
.finally(() => {
|
|
50
52
|
if (previousXdg === undefined) delete process.env.XDG_CONFIG_HOME;
|
|
51
53
|
else process.env.XDG_CONFIG_HOME = previousXdg;
|
|
54
|
+
if (previousAgentDir === undefined) delete process.env.SELESAI_CODING_AGENT_DIR;
|
|
55
|
+
else process.env.SELESAI_CODING_AGENT_DIR = previousAgentDir;
|
|
52
56
|
rmSync(tempConfigHome, { recursive: true, force: true });
|
|
53
57
|
});
|
|
54
58
|
}
|
|
@@ -65,15 +69,32 @@ describe("ponytail extension", () => {
|
|
|
65
69
|
const ctx = createCommandContext();
|
|
66
70
|
|
|
67
71
|
await events.get("session_start")({ reason: "startup" }, ctx);
|
|
68
|
-
await commands.get("ponytail").handler("
|
|
72
|
+
await commands.get("ponytail").handler("lite", ctx);
|
|
69
73
|
|
|
70
74
|
expect(appendedEntries.at(-1)).toEqual({
|
|
71
75
|
customType: "ponytail-mode",
|
|
72
|
-
data: { mode: "
|
|
76
|
+
data: { mode: "lite" },
|
|
73
77
|
});
|
|
74
78
|
|
|
75
79
|
const result = await events.get("before_agent_start")({ systemPrompt: "BASE" }, ctx);
|
|
76
|
-
expect(result.systemPrompt).toMatch(/^BASE\n\nPONYTAIL
|
|
80
|
+
expect(result.systemPrompt).toMatch(/^BASE\n\nPONYTAIL MODE ACTIVE — level: lite\n/);
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
it("/ponytail <mode> carries into brand-new sessions", async () => withTempConfig(async () => {
|
|
84
|
+
const first = createPiHarness();
|
|
85
|
+
await first.events.get("session_start")({ reason: "startup" }, createCommandContext());
|
|
86
|
+
await first.commands.get("ponytail").handler("lite", createCommandContext());
|
|
87
|
+
|
|
88
|
+
// Fresh extension instance + empty session, like a new chat or handoff.
|
|
89
|
+
const next = createPiHarness();
|
|
90
|
+
await next.events.get("session_start")({ reason: "new" }, createCommandContext());
|
|
91
|
+
const result = await next.events.get("before_agent_start")({ systemPrompt: "BASE" }, createCommandContext());
|
|
92
|
+
expect(result.systemPrompt).toMatch(/PONYTAIL MODE ACTIVE — level: lite\n/);
|
|
93
|
+
|
|
94
|
+
await next.commands.get("ponytail").handler("off", createCommandContext());
|
|
95
|
+
const after = createPiHarness();
|
|
96
|
+
await after.events.get("session_start")({ reason: "new" }, createCommandContext());
|
|
97
|
+
expect(await after.events.get("before_agent_start")({ systemPrompt: "BASE" }, createCommandContext())).toBeUndefined();
|
|
77
98
|
}));
|
|
78
99
|
|
|
79
100
|
it("session_start restores latest persisted mode", async () => withTempConfig(async () => {
|
|
@@ -89,7 +110,7 @@ describe("ponytail extension", () => {
|
|
|
89
110
|
await events.get("session_start")({ reason: "resume" }, ctx);
|
|
90
111
|
const result = await events.get("before_agent_start")({ systemPrompt: "BASE" }, ctx);
|
|
91
112
|
|
|
92
|
-
expect(result.systemPrompt).toMatch(/PONYTAIL
|
|
113
|
+
expect(result.systemPrompt).toMatch(/PONYTAIL MODE ACTIVE — level: lite\n/);
|
|
93
114
|
}));
|
|
94
115
|
|
|
95
116
|
it("review mode retains its independent skill prompt", async () => withTempConfig(async () => {
|
|
@@ -140,7 +161,7 @@ describe("ponytail extension", () => {
|
|
|
140
161
|
const ctx = createCommandContext();
|
|
141
162
|
|
|
142
163
|
await events.get("session_start")({ reason: "startup" }, ctx);
|
|
143
|
-
await commands.get("ponytail").handler("
|
|
164
|
+
await commands.get("ponytail").handler("lite", ctx);
|
|
144
165
|
await events.get("input")({ text: "normal mode", source: "interactive" }, ctx);
|
|
145
166
|
|
|
146
167
|
const disabled = await events.get("before_agent_start")({ systemPrompt: "BASE" }, ctx);
|
|
@@ -152,11 +173,11 @@ describe("ponytail extension", () => {
|
|
|
152
173
|
const ctx = createCommandContext();
|
|
153
174
|
|
|
154
175
|
await events.get("session_start")({ reason: "startup" }, ctx);
|
|
155
|
-
await commands.get("ponytail").handler("
|
|
176
|
+
await commands.get("ponytail").handler("lite", ctx);
|
|
156
177
|
await events.get("input")({ text: "add a normal mode toggle next to dark mode", source: "interactive" }, ctx);
|
|
157
178
|
|
|
158
179
|
const result = await events.get("before_agent_start")({ systemPrompt: "BASE" }, ctx);
|
|
159
|
-
expect(result.systemPrompt).toMatch(/PONYTAIL
|
|
180
|
+
expect(result.systemPrompt).toMatch(/PONYTAIL MODE ACTIVE — level: lite\n/);
|
|
160
181
|
}));
|
|
161
182
|
|
|
162
183
|
it("never writes a Ponytail statusline", async () => withTempConfig(async () => {
|
|
@@ -179,7 +200,7 @@ describe("ponytail extension", () => {
|
|
|
179
200
|
|
|
180
201
|
await events.get("session_start")({ reason: "startup" }, ctx);
|
|
181
202
|
await commands.get("ponytail").handler("status", ctx);
|
|
182
|
-
expect(notifications.at(-1).msg).toContain("Ponytail: current
|
|
203
|
+
expect(notifications.at(-1).msg).toContain("Ponytail: current lite • default lite");
|
|
183
204
|
}));
|
|
184
205
|
|
|
185
206
|
it("/ponytail set-default persists the default mode", async () => withTempConfig(async () => {
|
|
@@ -194,7 +215,7 @@ describe("ponytail extension", () => {
|
|
|
194
215
|
|
|
195
216
|
it("/ponytail set-default with env override reports the override", async () => withTempConfig(async () => {
|
|
196
217
|
const previous = process.env.PONYTAIL_DEFAULT_MODE;
|
|
197
|
-
process.env.PONYTAIL_DEFAULT_MODE = "
|
|
218
|
+
process.env.PONYTAIL_DEFAULT_MODE = "full";
|
|
198
219
|
try {
|
|
199
220
|
const { commands, events } = createPiHarness();
|
|
200
221
|
const notifications = [];
|
|
@@ -202,7 +223,7 @@ describe("ponytail extension", () => {
|
|
|
202
223
|
|
|
203
224
|
await events.get("session_start")({ reason: "startup" }, ctx);
|
|
204
225
|
await commands.get("ponytail").handler("default lite", ctx);
|
|
205
|
-
expect(notifications.at(-1).msg).toContain("env override keeps default at
|
|
226
|
+
expect(notifications.at(-1).msg).toContain("env override keeps default at full");
|
|
206
227
|
} finally {
|
|
207
228
|
if (previous === undefined) delete process.env.PONYTAIL_DEFAULT_MODE;
|
|
208
229
|
else process.env.PONYTAIL_DEFAULT_MODE = previous;
|
|
@@ -224,11 +245,11 @@ describe("ponytail extension", () => {
|
|
|
224
245
|
const ctx = createCommandContext();
|
|
225
246
|
|
|
226
247
|
await events.get("session_start")({ reason: "startup" }, ctx);
|
|
227
|
-
await commands.get("ponytail").handler("
|
|
248
|
+
await commands.get("ponytail").handler("lite", ctx);
|
|
228
249
|
await events.get("input")({ text: "normal mode", source: "extension" }, ctx);
|
|
229
250
|
|
|
230
251
|
const result = await events.get("before_agent_start")({ systemPrompt: "BASE" }, ctx);
|
|
231
|
-
expect(result.systemPrompt).toMatch(/PONYTAIL
|
|
252
|
+
expect(result.systemPrompt).toMatch(/PONYTAIL MODE ACTIVE — level: lite\n/);
|
|
232
253
|
}));
|
|
233
254
|
|
|
234
255
|
it("setMode ignores invalid modes", async () => withTempConfig(async () => {
|
|
@@ -252,7 +273,7 @@ describe("ponytail extension", () => {
|
|
|
252
273
|
|
|
253
274
|
await events.get("session_start")({ reason: "resume" }, ctx);
|
|
254
275
|
const result = await events.get("before_agent_start")({ systemPrompt: "BASE" }, ctx);
|
|
255
|
-
expect(result.systemPrompt).toMatch(/PONYTAIL
|
|
276
|
+
expect(result.systemPrompt).toMatch(/PONYTAIL MODE ACTIVE — level: lite\n/);
|
|
256
277
|
}));
|
|
257
278
|
|
|
258
279
|
it("session_start tolerates a missing sessionManager", async () => withTempConfig(async () => {
|
|
@@ -261,7 +282,7 @@ describe("ponytail extension", () => {
|
|
|
261
282
|
|
|
262
283
|
await events.get("session_start")({ reason: "resume" }, ctx);
|
|
263
284
|
const result = await events.get("before_agent_start")({ systemPrompt: "BASE" }, ctx);
|
|
264
|
-
expect(result.systemPrompt).toMatch(/PONYTAIL MODE ACTIVE — level:
|
|
285
|
+
expect(result.systemPrompt).toMatch(/PONYTAIL MODE ACTIVE — level: lite\n/); // default is lite
|
|
265
286
|
}));
|
|
266
287
|
|
|
267
288
|
it("input handler tolerates missing text", async () => withTempConfig(async () => {
|
|
@@ -269,11 +290,11 @@ describe("ponytail extension", () => {
|
|
|
269
290
|
const ctx = createCommandContext();
|
|
270
291
|
|
|
271
292
|
await events.get("session_start")({ reason: "startup" }, ctx);
|
|
272
|
-
await commands.get("ponytail").handler("
|
|
293
|
+
await commands.get("ponytail").handler("lite", ctx);
|
|
273
294
|
await events.get("input")({ source: "interactive" }, ctx);
|
|
274
295
|
|
|
275
296
|
const result = await events.get("before_agent_start")({ systemPrompt: "BASE" }, ctx);
|
|
276
|
-
expect(result.systemPrompt).toMatch(/PONYTAIL
|
|
297
|
+
expect(result.systemPrompt).toMatch(/PONYTAIL MODE ACTIVE — level: lite\n/);
|
|
277
298
|
}));
|
|
278
299
|
|
|
279
300
|
it("setMode notifies when ctx has a UI", async () => withTempConfig(async () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
@@ -21,11 +21,11 @@ describe("ponytail helpers", () => {
|
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
it("parsePonytailCommand falls back to DEFAULT_MODE for invalid defaults", () => {
|
|
24
|
-
expect(parsePonytailCommand("", "bogus")).toEqual({ type: "set-mode", mode: "
|
|
24
|
+
expect(parsePonytailCommand("", "bogus")).toEqual({ type: "set-mode", mode: "lite" });
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
it("parsePonytailCommand parses modes, status, and default subcommand", () => {
|
|
28
|
-
expect(parsePonytailCommand("
|
|
28
|
+
expect(parsePonytailCommand("lite", "full")).toEqual({ type: "set-mode", mode: "lite" });
|
|
29
29
|
expect(parsePonytailCommand("status", "full")).toEqual({ type: "status" });
|
|
30
30
|
expect(parsePonytailCommand("default lite", "full")).toEqual({ type: "set-default", mode: "lite" });
|
|
31
31
|
});
|
|
@@ -42,44 +42,59 @@ describe("ponytail helpers", () => {
|
|
|
42
42
|
it("resolveSessionMode prefers latest persisted session mode", () => {
|
|
43
43
|
const entries = [
|
|
44
44
|
{ type: "custom", customType: "ponytail-mode", data: { mode: "lite" } },
|
|
45
|
-
{ type: "custom", customType: "ponytail-mode", data: { mode: "
|
|
45
|
+
{ type: "custom", customType: "ponytail-mode", data: { mode: "lite" } },
|
|
46
46
|
];
|
|
47
47
|
|
|
48
|
-
expect(resolveSessionMode(entries, "full")).toBe("
|
|
48
|
+
expect(resolveSessionMode(entries, "full")).toBe("lite");
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
it("resolveSessionMode returns fallback when entries is not an array", () => {
|
|
52
|
-
expect(resolveSessionMode(null, "
|
|
52
|
+
expect(resolveSessionMode(null, "lite")).toBe("lite");
|
|
53
53
|
expect(resolveSessionMode(undefined, "lite")).toBe("lite");
|
|
54
54
|
expect(resolveSessionMode({}, "full")).toBe("full");
|
|
55
|
-
expect(resolveSessionMode("not an array")).toBe("
|
|
55
|
+
expect(resolveSessionMode("not an array")).toBe("lite"); // DEFAULT_MODE fallback
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
it("resolveSessionMode ignores non-ponytail entries and invalid modes", () => {
|
|
59
59
|
const entries = [
|
|
60
|
-
{ type: "custom", customType: "other", data: { mode: "
|
|
60
|
+
{ type: "custom", customType: "other", data: { mode: "lite" } },
|
|
61
61
|
{ type: "custom", customType: "ponytail-mode", data: { mode: "bogus" } },
|
|
62
62
|
];
|
|
63
63
|
expect(resolveSessionMode(entries, "lite")).toBe("lite");
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
it("readDefaultMode and writeDefaultMode use
|
|
66
|
+
it("readDefaultMode and writeDefaultMode use ponytailMode in agent settings.json", () => {
|
|
67
67
|
const tempDir = mkdtempSync(join(tmpdir(), "ponytail-config-"));
|
|
68
|
-
const
|
|
68
|
+
const previousAgentDir = process.env.SELESAI_CODING_AGENT_DIR;
|
|
69
69
|
const previousDefault = process.env.PONYTAIL_DEFAULT_MODE;
|
|
70
|
-
const
|
|
71
|
-
process.env.
|
|
70
|
+
const settingsPath = join(tempDir, "settings.json");
|
|
71
|
+
process.env.SELESAI_CODING_AGENT_DIR = tempDir;
|
|
72
72
|
delete process.env.PONYTAIL_DEFAULT_MODE;
|
|
73
73
|
|
|
74
74
|
try {
|
|
75
|
-
expect(readDefaultMode()).toBe("
|
|
76
|
-
expect(writeDefaultMode("
|
|
77
|
-
expect(readDefaultMode()).toBe("
|
|
78
|
-
expect(
|
|
79
|
-
|
|
75
|
+
expect(readDefaultMode()).toBe("lite");
|
|
76
|
+
expect(writeDefaultMode("lite")).toBe("lite");
|
|
77
|
+
expect(readDefaultMode()).toBe("lite");
|
|
78
|
+
expect(JSON.parse(readFileSync(settingsPath, "utf8"))).toEqual({ ponytailMode: "lite" });
|
|
79
|
+
|
|
80
|
+
// Other settings are preserved.
|
|
81
|
+
writeFileSync(settingsPath, JSON.stringify({ theme: "dark", ponytailMode: "lite" }));
|
|
82
|
+
writeDefaultMode("full");
|
|
83
|
+
expect(JSON.parse(readFileSync(settingsPath, "utf8"))).toEqual({ theme: "dark", ponytailMode: "full" });
|
|
84
|
+
|
|
85
|
+
// Removed/unknown modes are ignored, not honored.
|
|
86
|
+
writeFileSync(settingsPath, JSON.stringify({ ponytailMode: "ultra" }));
|
|
87
|
+
expect(readDefaultMode()).toBe("lite");
|
|
88
|
+
expect(writeDefaultMode("ultra")).toBeNull();
|
|
89
|
+
|
|
90
|
+
// An unparseable settings.json is never overwritten.
|
|
91
|
+
writeFileSync(settingsPath, "{ not json");
|
|
92
|
+
expect(() => writeDefaultMode("lite")).toThrow();
|
|
93
|
+
expect(readFileSync(settingsPath, "utf8")).toBe("{ not json");
|
|
94
|
+
expect(readDefaultMode()).toBe("lite");
|
|
80
95
|
} finally {
|
|
81
|
-
if (
|
|
82
|
-
else process.env.
|
|
96
|
+
if (previousAgentDir === undefined) delete process.env.SELESAI_CODING_AGENT_DIR;
|
|
97
|
+
else process.env.SELESAI_CODING_AGENT_DIR = previousAgentDir;
|
|
83
98
|
if (previousDefault === undefined) delete process.env.PONYTAIL_DEFAULT_MODE;
|
|
84
99
|
else process.env.PONYTAIL_DEFAULT_MODE = previousDefault;
|
|
85
100
|
rmSync(tempDir, { recursive: true, force: true });
|
|
@@ -97,11 +112,11 @@ describe("ponytail helpers", () => {
|
|
|
97
112
|
}
|
|
98
113
|
});
|
|
99
114
|
|
|
100
|
-
it("readDefaultMode falls back to
|
|
115
|
+
it("readDefaultMode falls back to lite when env is invalid", () => {
|
|
101
116
|
const previous = process.env.PONYTAIL_DEFAULT_MODE;
|
|
102
117
|
process.env.PONYTAIL_DEFAULT_MODE = "bogus";
|
|
103
118
|
try {
|
|
104
|
-
expect(readDefaultMode()).toBe("
|
|
119
|
+
expect(readDefaultMode()).toBe("lite");
|
|
105
120
|
} finally {
|
|
106
121
|
if (previous === undefined) delete process.env.PONYTAIL_DEFAULT_MODE;
|
|
107
122
|
else process.env.PONYTAIL_DEFAULT_MODE = previous;
|
|
@@ -113,45 +128,33 @@ describe("ponytail helpers", () => {
|
|
|
113
128
|
});
|
|
114
129
|
|
|
115
130
|
it("filterSkillBodyForMode keeps only requested intensity examples and rows", () => {
|
|
116
|
-
const body = `---\nname: ponytail\n---\n| **lite** | keep lite |\n| **full** | keep full |\n
|
|
131
|
+
const body = `---\nname: ponytail\n---\n| **lite** | keep lite |\n| **full** | keep full |\n- lite: \"Lite example\"\n- full: \"Full example\"\nOther line`;
|
|
117
132
|
|
|
118
|
-
const filtered = filterSkillBodyForMode(body, "
|
|
133
|
+
const filtered = filterSkillBodyForMode(body, "lite");
|
|
119
134
|
|
|
120
|
-
expect(filtered).
|
|
135
|
+
expect(filtered).toContain("keep lite");
|
|
121
136
|
expect(filtered).not.toContain("keep full");
|
|
122
|
-
expect(filtered).toContain("
|
|
123
|
-
expect(filtered).not.toContain("
|
|
124
|
-
expect(filtered).toContain("Ultra example");
|
|
137
|
+
expect(filtered).toContain("Lite example");
|
|
138
|
+
expect(filtered).not.toContain("Full example");
|
|
125
139
|
expect(filtered).toContain("Other line");
|
|
126
140
|
});
|
|
127
141
|
|
|
128
|
-
it("
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
expect(compact.lite).toMatch(/Build the ask; name a lazier alternative/);
|
|
143
|
-
expect(compact.full).toMatch(/Enforce ladder; shortest diff/);
|
|
144
|
-
expect(compact.ultra).toMatch(/YAGNI first; delete; challenge the rest/);
|
|
145
|
-
|
|
146
|
-
const fullSkill = getPonytailInstructions("full");
|
|
147
|
-
expect(fullSkill).toMatch(/## The ladder/);
|
|
148
|
-
expect(fullSkill.length).toBeGreaterThan(compact.full.length);
|
|
142
|
+
it("every mode gets the full skill prompt, filtered to its own intensity row", () => {
|
|
143
|
+
const lite = getPonytailInstructions("lite");
|
|
144
|
+
const full = getPonytailInstructions("full");
|
|
145
|
+
|
|
146
|
+
expect(lite).toMatch(/^PONYTAIL MODE ACTIVE — level: lite\n\n/);
|
|
147
|
+
expect(full).toMatch(/^PONYTAIL MODE ACTIVE — level: full\n\n/);
|
|
148
|
+
for (const text of [lite, full]) expect(text).toMatch(/## The ladder/);
|
|
149
|
+
expect(lite).toMatch(/\| \*\*lite\*\* \|/);
|
|
150
|
+
expect(lite).not.toMatch(/\| \*\*full\*\* \|/);
|
|
151
|
+
expect(full).toMatch(/\| \*\*full\*\* \|/);
|
|
152
|
+
expect(full).not.toMatch(/\| \*\*lite\*\* \|/);
|
|
149
153
|
});
|
|
150
154
|
|
|
151
|
-
it("review instructions remain independent
|
|
155
|
+
it("review instructions remain independent", () => {
|
|
152
156
|
const expected = "PONYTAIL MODE ACTIVE — level: review. Behavior defined by /ponytail-review skill.";
|
|
153
157
|
expect(getPonytailInstructions("review")).toBe(expected);
|
|
154
|
-
expect(getPonytailInstructions("review", { compact: true })).toBe(expected);
|
|
155
158
|
});
|
|
156
159
|
|
|
157
160
|
it("filterSkillBodyForMode keeps rule bullets that contain a colon", () => {
|
|
@@ -184,7 +187,7 @@ describe("ponytail helpers", () => {
|
|
|
184
187
|
});
|
|
185
188
|
|
|
186
189
|
it("getPonytailInstructions falls back when the skill file is missing", () => {
|
|
187
|
-
const instructions = getPonytailInstructions("full"
|
|
190
|
+
const instructions = getPonytailInstructions("full");
|
|
188
191
|
// With the real skill file present this returns the file-based instructions;
|
|
189
192
|
// the fallback path is exercised by deleting the skill file temporarily.
|
|
190
193
|
expect(instructions.length).toBeGreaterThan(0);
|
|
@@ -6,7 +6,7 @@ description: >
|
|
|
6
6
|
minimal. Channels a senior dev who has seen everything: question whether the
|
|
7
7
|
task needs to exist at all (YAGNI), reach for the standard library before
|
|
8
8
|
custom code, native platform features before dependencies, one line before
|
|
9
|
-
fifty. Supports intensity levels: lite, full
|
|
9
|
+
fifty. Supports intensity levels: lite, full. Use on ANY
|
|
10
10
|
coding task: writing, adding, refactoring, fixing, reviewing, or designing
|
|
11
11
|
code, and choosing libraries or dependencies. Also use whenever the user
|
|
12
12
|
says "ponytail", "be lazy", "lazy mode", "simplest solution", "minimal
|
|
@@ -14,7 +14,7 @@ description: >
|
|
|
14
14
|
over-engineering, bloat, boilerplate, or unnecessary dependencies. Do NOT
|
|
15
15
|
use for non-coding requests (general knowledge, prose, translation,
|
|
16
16
|
summaries, recipes).
|
|
17
|
-
argument-hint: "[lite|full
|
|
17
|
+
argument-hint: "[off|lite|full]"
|
|
18
18
|
license: MIT
|
|
19
19
|
---
|
|
20
20
|
|
|
@@ -27,8 +27,8 @@ code is the code never written.
|
|
|
27
27
|
## Persistence
|
|
28
28
|
|
|
29
29
|
ACTIVE EVERY RESPONSE. No drift back to over-building. Still active if
|
|
30
|
-
unsure. Off only: "stop ponytail" / "normal mode".
|
|
31
|
-
Switch: `/ponytail lite|full
|
|
30
|
+
unsure. Off only: "stop ponytail" / "normal mode".
|
|
31
|
+
Switch: `/ponytail off|lite|full` (your choice is remembered across sessions).
|
|
32
32
|
|
|
33
33
|
## The ladder
|
|
34
34
|
|
|
@@ -80,13 +80,11 @@ Pattern: `[code] → skipped: [X], add when [Y].`
|
|
|
80
80
|
| Level | What change |
|
|
81
81
|
|-------|------------|
|
|
82
82
|
| **lite** | Build what's asked, but name the lazier alternative in one line. User picks. |
|
|
83
|
-
| **full** | The ladder enforced. Stdlib and native first. Shortest diff, shortest explanation.
|
|
84
|
-
| **ultra** | YAGNI extremist. Deletion before addition. Ship the one-liner and challenge the rest of the requirement in the same breath. |
|
|
83
|
+
| **full** | The ladder enforced. Stdlib and native first. Shortest diff, shortest explanation. |
|
|
85
84
|
|
|
86
85
|
Example: "Add a cache for these API responses."
|
|
87
86
|
- lite: "Done, cache added. FYI: `functools.lru_cache` covers this in one line if you'd rather not own a cache class."
|
|
88
87
|
- full: "`@lru_cache(maxsize=1000)` on the fetch function. Skipped custom cache class, add when lru_cache measurably falls short."
|
|
89
|
-
- ultra: "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."
|
|
90
88
|
|
|
91
89
|
## When NOT to be lazy
|
|
92
90
|
|
|
@@ -17,10 +17,10 @@ write flag files, or persist anything.
|
|
|
17
17
|
| Level | Trigger | What change |
|
|
18
18
|
|-------|---------|-------------|
|
|
19
19
|
| **Lite** | `/ponytail lite` | Build what's asked, name the lazier alternative in one line. |
|
|
20
|
-
| **Full** | `/ponytail` | The ladder enforced: YAGNI → stdlib → native → one line → minimum.
|
|
21
|
-
| **
|
|
20
|
+
| **Full** | `/ponytail` or `/ponytail full` | The ladder enforced: YAGNI → stdlib → native → one line → minimum. |
|
|
21
|
+
| **Off** | `/ponytail off` | Ponytail disabled. |
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
Your choice is remembered: new sessions (and handoffs) start in the last mode you picked.
|
|
24
24
|
|
|
25
25
|
## Skills
|
|
26
26
|
|
|
@@ -44,20 +44,16 @@ Say "stop ponytail" or "normal mode". Resume anytime with `/ponytail`.
|
|
|
44
44
|
|
|
45
45
|
## Configure Default Mode
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
**Environment variable** (highest priority):
|
|
50
|
-
```bash
|
|
51
|
-
export PONYTAIL_DEFAULT_MODE=ultra
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
**Config file** (`~/.config/ponytail/config.json`, Windows: `%APPDATA%\ponytail\config.json`):
|
|
47
|
+
Off until you pick a mode. Picking one with `/ponytail <mode>` saves it to the
|
|
48
|
+
agent `settings.json` (`~/.selesai/agent/settings.json`):
|
|
55
49
|
```json
|
|
56
|
-
{ "
|
|
50
|
+
{ "ponytailMode": "lite" }
|
|
57
51
|
```
|
|
58
52
|
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
**Environment variable** (overrides settings.json):
|
|
54
|
+
```bash
|
|
55
|
+
export PONYTAIL_DEFAULT_MODE=lite
|
|
56
|
+
```
|
|
61
57
|
|
|
62
58
|
Resolution: env var > config file > `full`.
|
|
63
59
|
|
package/docs/settings.md
CHANGED
|
@@ -50,7 +50,7 @@ Use `/trust` in interactive mode to save a project trust decision for future ses
|
|
|
50
50
|
|
|
51
51
|
The bundled `jev-advisory-routing` extension uses the Jev decisions model for bounded opt-in
|
|
52
52
|
routing. The host-side routes stay off until they are enabled in `jevAdvisory`; the agent's own
|
|
53
|
-
`ask` route is on by default. All
|
|
53
|
+
`ask` route is on by default. All routes share one Jev provider/model pair:
|
|
54
54
|
|
|
55
55
|
- `memory` — only after an explicit durable-memory cue (for example “the convention we
|
|
56
56
|
decided” or “don't repeat the past failure”), Jev chooses one read-only local
|
|
@@ -67,6 +67,21 @@ routing. The host-side routes stay off until they are enabled in `jevAdvisory`;
|
|
|
67
67
|
of the agent's loadout before each run (it returns after `/tokenin add`, no reload needed), and a call
|
|
68
68
|
that slips through reads no file and runs no command.
|
|
69
69
|
|
|
70
|
+
The same route also offers `jev_find`, a file finder: ripgrep gathers up to 48 candidate files
|
|
71
|
+
(those matching `pattern`, or every file under `path` filtered by `glob`, ranked by question words),
|
|
72
|
+
Jev judges each file's best-matching lines against the agent's `question` in parallel batches of 16,
|
|
73
|
+
and the agent gets ranked `path:lines (relevance)` pointers — never file contents. It respects
|
|
74
|
+
`.gitignore`, stays inside the working directory, skips secret-named files, and still returns the
|
|
75
|
+
unranked candidates when Jev is unavailable.
|
|
76
|
+
- `subagent` — sets up a single-child `subagent` call from the parent agent (off by default). Jev only
|
|
77
|
+
fills what the parent left open, and can only narrow: (1) when `agent` is omitted or generic
|
|
78
|
+
(`genericAgents`, default `["delegate"]`), it picks an agent by function from enabled native agents
|
|
79
|
+
inside the capability ceiling; (2) it may drop tools from that agent's declared `tools` for this run
|
|
80
|
+
(never `read` or supervision tools, and never adds one); (3) when `tiers` is set and the parent
|
|
81
|
+
passed no `model`, it picks `simple`, `complex`, or `reasoning` and launches that tier's model. Jev
|
|
82
|
+
sees only the task and agent descriptions. Any abstention launches exactly what was asked; an
|
|
83
|
+
agentless task that Jev cannot route is rejected with a request to name an agent.
|
|
84
|
+
|
|
70
85
|
| Setting | Type | Default | Description |
|
|
71
86
|
|---------|------|---------|-------------|
|
|
72
87
|
| `jevAdvisory.provider` | string | `"tokenin"` | Provider serving the Jev decisions deployment |
|
|
@@ -74,12 +89,15 @@ routing. The host-side routes stay off until they are enabled in `jevAdvisory`;
|
|
|
74
89
|
| `jevAdvisory.baseUrl` | string | inherited | Base URL override; defaults to any registered model of `provider` |
|
|
75
90
|
| `jevAdvisory.routes.memory.enabled` | boolean | `false` | Enable the memory-lookup route |
|
|
76
91
|
| `jevAdvisory.routes.recommendations.enabled` | boolean | `false` | Enable skill/workflow and verification recommendations |
|
|
77
|
-
| `jevAdvisory.routes.ask.enabled` | boolean | `true` | Offer the agent the `ask_jev`
|
|
78
|
-
|
|
|
92
|
+
| `jevAdvisory.routes.ask.enabled` | boolean | `true` | Offer the agent the `ask_jev` and `jev_find` tools; set `false` to turn both off |
|
|
93
|
+
| `jevAdvisory.routes.subagent.enabled` | boolean | `false` | Let Jev choose the agent, tools, and model tier of a subagent call |
|
|
94
|
+
| `jevAdvisory.routes.subagent.tiers` | object | unset | `{ "simple"?, "complex"?, "reasoning"? }` → `provider/id` model; tier routing needs at least two |
|
|
95
|
+
| `jevAdvisory.routes.subagent.genericAgents` | string[] | `["delegate"]` | Agents Jev may replace with a specialist |
|
|
96
|
+
| `<route>.timeoutMs` | number | `8000` | Route request timeout (ms); memory is capped at 750ms; `ask` defaults to `15000`, `subagent` to `5000` |
|
|
79
97
|
| `<route>.minConfidence` | number | `0.6` | Below this Jev confidence the route abstains |
|
|
80
98
|
| `<route>.contextTurns` | number | `4` | Prior user turns sent as recommendation context; memory sends only the current bounded prompt |
|
|
81
99
|
| `<route>.contextChars` | number | `4000` | Character budget for recommendation context |
|
|
82
|
-
| `<route>.payloadBytes` | number | `8192` | Hard cap on the serialized decision request; `ask` defaults to `32768` |
|
|
100
|
+
| `<route>.payloadBytes` | number | `8192` | Hard cap on the serialized decision request; `ask` defaults to `32768`, `subagent` to `16384` |
|
|
83
101
|
|
|
84
102
|
`ask` ignores `minConfidence`, `contextTurns`, and `contextChars`: it returns every confidence to the
|
|
85
103
|
agent, and its context is whatever the agent put in the request.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@selesai/code",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.37",
|
|
4
4
|
"description": "Maintained, extension-first Pi coding agent with built-in workflows, subagents, web research, questions, skills, and an enhanced terminal UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|