@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.
- package/CHANGELOG.md +21 -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/jev/decisions.test.ts +18 -0
- package/dist/extensions/jev/decisions.ts +54 -6
- package/dist/extensions/jev-ask-tool.test.ts +371 -25
- package/dist/extensions/jev-ask-tool.ts +826 -167
- package/dist/extensions/jev-find-source.ts +190 -0
- package/dist/extensions/package.json +1 -1
- package/dist/extensions/pi-subagents/agents/researcher.md +4 -5
- package/dist/extensions/pi-subagents/docs/agents.md +1 -7
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +3 -4
- package/dist/extensions/pi-web-agent/src/extension.ts +11 -0
- 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 +8 -6
- package/package.json +1 -1
|
@@ -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
|
@@ -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
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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.
|
|
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": {
|