@selesai/code 0.13.35 → 0.13.38

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.
@@ -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
@@ -67,12 +67,14 @@ 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.
70
+ The same route also offers `jev_find`, a behavior finder: ripgrep gathers candidate files (those
71
+ matching `pattern`, or every file under `path` filtered by `glob`); on large trees Jev first judges
72
+ directories and descends only into relevant ones, then judges files, then the declarations (source
73
+ units) inside relevant files. The agent gets a ranked file list with reading leads plus the accepted
74
+ units verbatim with original line numbers (at most 12 Jev requests and 16 KiB of source per call).
75
+ It respects `.gitignore`, stays inside the working directory, skips secret-named files, retries a
76
+ transport failure once, and still returns ripgrep-ranked candidates with keyword-window source when
77
+ Jev is unavailable.
76
78
  - `subagent` — sets up a single-child `subagent` call from the parent agent (off by default). Jev only
77
79
  fills what the parent left open, and can only narrow: (1) when `agent` is omitted or generic
78
80
  (`genericAgents`, default `["delegate"]`), it picks an agent by function from enabled native agents
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@selesai/code",
3
- "version": "0.13.35",
3
+ "version": "0.13.38",
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": {