@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.
@@ -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("ultra", ctx);
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: "ultra" },
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 ULTRA:/);
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 LITE:/);
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("ultra", ctx);
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("ultra", ctx);
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 ULTRA:/);
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 full • default full");
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 = "ultra";
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 ultra");
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("ultra", ctx);
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 ULTRA:/);
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 LITE:/);
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: full/);
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("ultra", ctx);
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 ULTRA:/);
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: "full" });
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("ultra", "full")).toEqual({ type: "set-mode", mode: "ultra" });
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: "ultra" } },
45
+ { type: "custom", customType: "ponytail-mode", data: { mode: "lite" } },
46
46
  ];
47
47
 
48
- expect(resolveSessionMode(entries, "full")).toBe("ultra");
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, "ultra")).toBe("ultra");
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("full"); // DEFAULT_MODE fallback
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: "ultra" } },
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 XDG config path", () => {
66
+ it("readDefaultMode and writeDefaultMode use ponytailMode in agent settings.json", () => {
67
67
  const tempDir = mkdtempSync(join(tmpdir(), "ponytail-config-"));
68
- const previousXdg = process.env.XDG_CONFIG_HOME;
68
+ const previousAgentDir = process.env.SELESAI_CODING_AGENT_DIR;
69
69
  const previousDefault = process.env.PONYTAIL_DEFAULT_MODE;
70
- const configPath = join(tempDir, "ponytail", "config.json");
71
- process.env.XDG_CONFIG_HOME = tempDir;
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("full");
76
- expect(writeDefaultMode("ultra")).toBe("ultra");
77
- expect(readDefaultMode()).toBe("ultra");
78
- expect(existsSync(configPath)).toBe(true);
79
- expect(JSON.parse(readFileSync(configPath, "utf8"))).toEqual({ defaultMode: "ultra" });
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 (previousXdg === undefined) delete process.env.XDG_CONFIG_HOME;
82
- else process.env.XDG_CONFIG_HOME = previousXdg;
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 full when env is invalid", () => {
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("full");
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| **ultra** | keep ultra |\n- lite: \"Lite example\"\n- full: \"Full example\"\n- ultra: \"Ultra example\"\nOther line`;
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, "ultra");
133
+ const filtered = filterSkillBodyForMode(body, "lite");
119
134
 
120
- expect(filtered).not.toContain("keep lite");
135
+ expect(filtered).toContain("keep lite");
121
136
  expect(filtered).not.toContain("keep full");
122
- expect(filtered).toContain("keep ultra");
123
- expect(filtered).not.toContain("Lite example");
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("compact Ponytail instructions retain core rules and mode distinctions", () => {
129
- const compact = Object.fromEntries(
130
- ["lite", "full", "ultra"].map((mode) => [mode, getPonytailInstructions(mode, { compact: true })]),
131
- );
132
-
133
- for (const [mode, instructions] of Object.entries(compact)) {
134
- expect(instructions).toMatch(new RegExp(`^PONYTAIL ${mode.toUpperCase()}:`));
135
- expect(instructions.length).toBeGreaterThanOrEqual(300);
136
- expect(instructions.length).toBeLessThanOrEqual(450);
137
- for (const phrase of ["stop ponytail", "Trace first", "root cause", "validation", "data-loss", "security", "accessibility", "runnable check"]) {
138
- expect(instructions).toContain(phrase);
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 in compact mode", () => {
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", { compact: false });
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 (default), ultra. Use on ANY
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|ultra]"
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". Default: **full**.
31
- Switch: `/ponytail lite|full|ultra`.
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. Default. |
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. Default. |
21
- | **Ultra** | `/ponytail ultra` | YAGNI extremist. Deletion before addition. Challenges requirements before building. |
20
+ | **Full** | `/ponytail` or `/ponytail full` | The ladder enforced: YAGNI → stdlib → native → one line → minimum. |
21
+ | **Off** | `/ponytail off` | Ponytail disabled. |
22
22
 
23
- Level sticks until changed or session end.
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
- Default mode = `full`, auto-active every session. Change it:
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
- { "defaultMode": "lite" }
50
+ { "ponytailMode": "lite" }
57
51
  ```
58
52
 
59
- Set `"off"` to disable auto-activation on session start, activate manually
60
- with `/ponytail` when wanted.
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 three share one Jev provider/model pair:
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` tool; set `false` to turn it off |
78
- | `<route>.timeoutMs` | number | `8000` | Route request timeout (ms); memory is capped at 750ms; `ask` defaults to `15000` |
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.34",
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": {