@selesai/code 0.5.12 → 0.5.14
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/README.md +186 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/extensions/caveman/caveman-instructions.cjs +6 -34
- package/dist/extensions/caveman/test/helpers.test.js +7 -13
- package/dist/extensions/package.json +4 -3
- package/dist/extensions/pi-powerline-footer/README.md +1 -1
- package/dist/extensions/pi-powerline-footer/guide.ts +227 -0
- package/dist/extensions/pi-powerline-footer/index.ts +88 -55
- package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +0 -78
- package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +102 -0
- package/dist/extensions/pi-powerline-footer/welcome.ts +65 -30
- package/dist/extensions/ponytail/index.js +1 -1
- package/dist/extensions/ponytail/ponytail-instructions.cjs +15 -1
- package/dist/extensions/ponytail/test/extension.test.js +20 -4
- package/dist/extensions/ponytail/test/helpers.test.js +32 -0
- package/dist/extensions/workflow/adapter.ts +0 -19
- package/dist/skills/caveman/SKILL.md +1 -0
- package/dist/skills/handoff/SKILL.md +1 -0
- package/dist/skills/handoff-text/SKILL.md +1 -0
- package/dist/skills/implanger/SKILL.md +1 -0
- package/dist/skills/improve-codebase/SKILL.md +1 -0
- package/dist/skills/planger/SKILL.md +1 -0
- package/dist/skills/ponytail/SKILL.md +1 -0
- package/dist/skills/ponytail-audit/SKILL.md +1 -0
- package/dist/skills/ponytail-debt/SKILL.md +1 -0
- package/dist/skills/ponytail-gain/SKILL.md +1 -0
- package/dist/skills/ponytail-help/SKILL.md +1 -0
- package/dist/skills/ponytail-review/SKILL.md +1 -0
- package/dist/skills/workflow-creation/SKILL.md +1 -0
- package/package.json +23 -2
- package/dist/agents/architect.md +0 -217
- package/dist/agents/builder.md +0 -120
- package/dist/agents/commentator.md +0 -130
- package/dist/agents/explorer.md +0 -51
- package/dist/agents/recapper.md +0 -26
- package/dist/extensions/gitignore-guard.ts +0 -132
- package/dist/extensions/hooks/claude-codex-hooks.json +0 -44
- package/dist/extensions/hooks/copilot-hooks.json +0 -21
- package/dist/extensions/hooks/ponytail-activate.js +0 -91
- package/dist/extensions/hooks/ponytail-config.js +0 -122
- package/dist/extensions/hooks/ponytail-instructions.js +0 -94
- package/dist/extensions/hooks/ponytail-mode-tracker.js +0 -55
- package/dist/extensions/hooks/ponytail-runtime.js +0 -68
- package/dist/extensions/hooks/ponytail-statusline.ps1 +0 -21
- package/dist/extensions/hooks/ponytail-statusline.sh +0 -12
- package/dist/extensions/hooks/ponytail-subagent.js +0 -22
- package/dist/extensions/pi-extension/index.js +0 -189
- package/dist/extensions/pi-extension/package.json +0 -8
- package/dist/extensions/pi-extension/test/extension.test.js +0 -167
- package/dist/extensions/pi-extension/test/helpers.test.js +0 -92
- package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +0 -34
- package/dist/extensions/pi-subagents/agents/context-builder.md +0 -46
- package/dist/extensions/pi-subagents/agents/delegate.md +0 -12
- package/dist/extensions/pi-subagents/agents/oracle.md +0 -73
- package/dist/extensions/pi-subagents/agents/planner.md +0 -55
- package/dist/extensions/pi-subagents/agents/reviewer.md +0 -79
- package/dist/extensions/pi-subagents/agents/scout.md +0 -50
- package/dist/extensions/pi-subagents/agents/worker.md +0 -55
- package/dist/extensions/pi-subagents/src/runs/shared/spawn-timing.ts +0 -46
- package/dist/extensions/pi-subagents/test/unit/spawn-timing.test.ts +0 -45
- package/dist/extensions/pi-subagents/tsconfig.runner.json +0 -17
- package/dist/extensions/ponytail-pi-extension/index.js +0 -189
- package/dist/extensions/ponytail-pi-extension/package.json +0 -8
- package/dist/extensions/ponytail-pi-extension/test/extension.test.js +0 -167
- package/dist/extensions/ponytail-pi-extension/test/helpers.test.js +0 -92
- package/dist/extensions/prototype.ts +0 -713
- package/dist/extensions/question.ts +0 -350
- package/dist/extensions/quick.ts +0 -630
- package/dist/extensions/tps-tracker.ts +0 -280
- package/dist/extensions/workflow/task-validators.ts +0 -69
- package/dist/skills/selesai-default/SKILL.md +0 -16
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
|
|
3
|
-
const require = createRequire(import.meta.url);
|
|
4
|
-
const {
|
|
5
|
-
DEFAULT_MODE,
|
|
6
|
-
getDefaultMode,
|
|
7
|
-
normalizeMode,
|
|
8
|
-
normalizeConfigMode,
|
|
9
|
-
normalizePersistedMode,
|
|
10
|
-
isDeactivationCommand,
|
|
11
|
-
writeDefaultMode,
|
|
12
|
-
} = require("../hooks/ponytail-config.js");
|
|
13
|
-
const { getPonytailInstructions, filterSkillBodyForMode } = require("../hooks/ponytail-instructions.js");
|
|
14
|
-
|
|
15
|
-
export { filterSkillBodyForMode };
|
|
16
|
-
export const readDefaultMode = getDefaultMode;
|
|
17
|
-
|
|
18
|
-
export function resolveSessionMode(entries, fallbackMode = DEFAULT_MODE) {
|
|
19
|
-
const fallback = normalizePersistedMode(fallbackMode) || DEFAULT_MODE;
|
|
20
|
-
if (!Array.isArray(entries)) return fallback;
|
|
21
|
-
|
|
22
|
-
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
23
|
-
const entry = entries[i];
|
|
24
|
-
if (entry?.type !== "custom" || entry?.customType !== "ponytail-mode") continue;
|
|
25
|
-
|
|
26
|
-
const mode = normalizePersistedMode(entry?.data?.mode);
|
|
27
|
-
if (mode) return mode;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return fallback;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function parsePonytailCommand(text, defaultMode = DEFAULT_MODE) {
|
|
34
|
-
const fallback = normalizePersistedMode(defaultMode) || DEFAULT_MODE;
|
|
35
|
-
const normalizedText = String(text || "").trim().toLowerCase();
|
|
36
|
-
|
|
37
|
-
if (!normalizedText) {
|
|
38
|
-
return { type: "set-mode", mode: fallback === "off" ? "full" : fallback };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const [primary, secondary] = normalizedText.split(/\s+/);
|
|
42
|
-
|
|
43
|
-
if (primary === "status") return { type: "status" };
|
|
44
|
-
|
|
45
|
-
if (primary === "default") {
|
|
46
|
-
const mode = normalizeConfigMode(secondary);
|
|
47
|
-
return mode ? { type: "set-default", mode } : { type: "invalid", reason: "invalid-default-mode" };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const mode = normalizeMode(primary);
|
|
51
|
-
return mode ? { type: "set-mode", mode } : { type: "invalid", reason: "invalid-mode", mode: primary };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export { writeDefaultMode };
|
|
55
|
-
|
|
56
|
-
export default function ponytailExtension(pi) {
|
|
57
|
-
let currentMode = DEFAULT_MODE;
|
|
58
|
-
let configuredDefaultMode = getDefaultMode();
|
|
59
|
-
let isActive = false;
|
|
60
|
-
let lastCtx = null;
|
|
61
|
-
|
|
62
|
-
// -- Status bar --
|
|
63
|
-
function syncStatus(ctx) {
|
|
64
|
-
if (ctx) lastCtx = ctx;
|
|
65
|
-
const c = ctx || lastCtx;
|
|
66
|
-
if (!c?.ui?.setStatus || !c.ui.theme?.fg) return;
|
|
67
|
-
const theme = c.ui.theme;
|
|
68
|
-
if (currentMode === "off") {
|
|
69
|
-
c.ui.setStatus("ponytail", "");
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
const levelIcons = { lite: "🌿", full: "⚡", ultra: "🔥" };
|
|
73
|
-
const icon = levelIcons[currentMode] || "";
|
|
74
|
-
const label = currentMode.toUpperCase();
|
|
75
|
-
const indicator = isActive ? theme.fg("accent", "●") : theme.fg("dim", "○");
|
|
76
|
-
c.ui.setStatus("ponytail", indicator + " 🐴 " + theme.fg("muted", "ponytail: ") + theme.fg("text", icon + " " + label));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const setMode = (mode, ctx) => {
|
|
80
|
-
const normalized = normalizePersistedMode(mode);
|
|
81
|
-
if (!normalized) return;
|
|
82
|
-
|
|
83
|
-
currentMode = normalized;
|
|
84
|
-
pi.appendEntry("ponytail-mode", { mode: normalized });
|
|
85
|
-
syncStatus(ctx);
|
|
86
|
-
ctx?.ui?.notify?.(`Ponytail mode set to ${normalized}.`, "info");
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const sendAlias = (skillName, args, ctx) => {
|
|
90
|
-
const normalized = String(args || "").trim();
|
|
91
|
-
const message = normalized ? `${skillName} ${normalized}` : skillName;
|
|
92
|
-
|
|
93
|
-
if (ctx?.isIdle?.() === false) {
|
|
94
|
-
pi.sendUserMessage(message, { deliverAs: "followUp" });
|
|
95
|
-
ctx?.ui?.notify?.(`${skillName} queued as follow-up.`, "info");
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
pi.sendUserMessage(message);
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
pi.registerCommand("ponytail", {
|
|
103
|
-
description: "Set or report Ponytail mode",
|
|
104
|
-
handler: async (args, ctx) => {
|
|
105
|
-
const parsed = parsePonytailCommand(args, configuredDefaultMode);
|
|
106
|
-
|
|
107
|
-
if (parsed.type === "status") {
|
|
108
|
-
ctx?.ui?.notify?.(`Ponytail: current ${currentMode} • default ${configuredDefaultMode}`, "info");
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (parsed.type === "set-default") {
|
|
113
|
-
const written = writeDefaultMode(parsed.mode);
|
|
114
|
-
if (written) {
|
|
115
|
-
configuredDefaultMode = getDefaultMode();
|
|
116
|
-
const message = configuredDefaultMode === written
|
|
117
|
-
? `Default Ponytail mode set to ${written}.`
|
|
118
|
-
: `Saved default ${written}, but env override keeps default at ${configuredDefaultMode}.`;
|
|
119
|
-
ctx?.ui?.notify?.(message, "info");
|
|
120
|
-
}
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (parsed.type === "set-mode") {
|
|
125
|
-
setMode(parsed.mode, ctx);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
ctx?.ui?.notify?.("Unknown or unsupported /ponytail mode.", "warning");
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
pi.registerCommand("ponytail-review", {
|
|
134
|
-
description: "Run /skill:ponytail-review",
|
|
135
|
-
handler: (_args, ctx) => sendAlias("/skill:ponytail-review", "", ctx),
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
pi.registerCommand("ponytail-audit", {
|
|
139
|
-
description: "Run /skill:ponytail-audit",
|
|
140
|
-
handler: (_args, ctx) => sendAlias("/skill:ponytail-audit", "", ctx),
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
pi.registerCommand("ponytail-gain", {
|
|
144
|
-
description: "Run /skill:ponytail-gain",
|
|
145
|
-
handler: (_args, ctx) => sendAlias("/skill:ponytail-gain", "", ctx),
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
pi.registerCommand("ponytail-debt", {
|
|
149
|
-
description: "Run /skill:ponytail-debt",
|
|
150
|
-
handler: (_args, ctx) => sendAlias("/skill:ponytail-debt", "", ctx),
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
pi.registerCommand("ponytail-help", {
|
|
154
|
-
description: "Run /skill:ponytail-help",
|
|
155
|
-
handler: (_args, ctx) => sendAlias("/skill:ponytail-help", "", ctx),
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
pi.on("input", async (event) => {
|
|
159
|
-
if (event?.source === "extension") return;
|
|
160
|
-
|
|
161
|
-
const text = String(event?.text || "");
|
|
162
|
-
if (currentMode !== "off" && isDeactivationCommand(text)) {
|
|
163
|
-
setMode("off");
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
168
|
-
const entries = ctx?.sessionManager?.getBranch?.() || ctx?.sessionManager?.getEntries?.() || [];
|
|
169
|
-
configuredDefaultMode = getDefaultMode();
|
|
170
|
-
currentMode = resolveSessionMode(entries, configuredDefaultMode);
|
|
171
|
-
syncStatus(ctx);
|
|
172
|
-
ctx?.ui?.notify?.(`Ponytail loaded: ${currentMode}`, "info");
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
pi.on("agent_start", async (_event, ctx) => {
|
|
176
|
-
isActive = true;
|
|
177
|
-
syncStatus(ctx);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
pi.on("agent_end", async (_event, ctx) => {
|
|
181
|
-
isActive = false;
|
|
182
|
-
syncStatus(ctx);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
pi.on("before_agent_start", async (event) => {
|
|
186
|
-
if (!currentMode || currentMode === "off") return;
|
|
187
|
-
return { systemPrompt: `${event.systemPrompt}\n\n${getPonytailInstructions(currentMode)}` };
|
|
188
|
-
});
|
|
189
|
-
}
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import test from "node:test";
|
|
6
|
-
|
|
7
|
-
import ponytailExtension from "../index.js";
|
|
8
|
-
|
|
9
|
-
function createPiHarness() {
|
|
10
|
-
const events = new Map();
|
|
11
|
-
const commands = new Map();
|
|
12
|
-
const appendedEntries = [];
|
|
13
|
-
const sentUserMessages = [];
|
|
14
|
-
|
|
15
|
-
const pi = {
|
|
16
|
-
on(eventName, handler) {
|
|
17
|
-
events.set(eventName, handler);
|
|
18
|
-
},
|
|
19
|
-
registerCommand(name, options) {
|
|
20
|
-
commands.set(name, options);
|
|
21
|
-
},
|
|
22
|
-
appendEntry(customType, data) {
|
|
23
|
-
appendedEntries.push({ customType, data });
|
|
24
|
-
},
|
|
25
|
-
sendUserMessage(text, options) {
|
|
26
|
-
sentUserMessages.push({ text, options });
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
ponytailExtension(pi);
|
|
31
|
-
return { events, commands, appendedEntries, sentUserMessages };
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function createCommandContext(overrides = {}) {
|
|
35
|
-
return {
|
|
36
|
-
isIdle: () => true,
|
|
37
|
-
sessionManager: { getEntries: () => [] },
|
|
38
|
-
ui: { notify() {} },
|
|
39
|
-
...overrides,
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function withTempConfig(fn) {
|
|
44
|
-
const tempConfigHome = mkdtempSync(join(tmpdir(), "ponytail-test-"));
|
|
45
|
-
const previousXdg = process.env.XDG_CONFIG_HOME;
|
|
46
|
-
process.env.XDG_CONFIG_HOME = tempConfigHome;
|
|
47
|
-
|
|
48
|
-
return Promise.resolve()
|
|
49
|
-
.then(fn)
|
|
50
|
-
.finally(() => {
|
|
51
|
-
if (previousXdg === undefined) delete process.env.XDG_CONFIG_HOME;
|
|
52
|
-
else process.env.XDG_CONFIG_HOME = previousXdg;
|
|
53
|
-
rmSync(tempConfigHome, { recursive: true, force: true });
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
test("extension registers Ponytail commands", () => {
|
|
58
|
-
const { commands } = createPiHarness();
|
|
59
|
-
|
|
60
|
-
assert.deepEqual([...commands.keys()].sort(), ["ponytail", "ponytail-audit", "ponytail-debt", "ponytail-gain", "ponytail-help", "ponytail-review"]);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test("/ponytail updates session mode and injects instructions", async () => withTempConfig(async () => {
|
|
64
|
-
const { commands, events, appendedEntries } = createPiHarness();
|
|
65
|
-
const ctx = createCommandContext();
|
|
66
|
-
|
|
67
|
-
await events.get("session_start")({ reason: "startup" }, ctx);
|
|
68
|
-
await commands.get("ponytail").handler("ultra", ctx);
|
|
69
|
-
|
|
70
|
-
assert.deepEqual(appendedEntries.at(-1), {
|
|
71
|
-
customType: "ponytail-mode",
|
|
72
|
-
data: { mode: "ultra" },
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
const result = await events.get("before_agent_start")({ systemPrompt: "BASE" }, ctx);
|
|
76
|
-
assert.ok(result.systemPrompt.includes("PONYTAIL MODE ACTIVE"));
|
|
77
|
-
assert.ok(result.systemPrompt.includes("ultra"));
|
|
78
|
-
}));
|
|
79
|
-
|
|
80
|
-
test("session_start restores latest persisted mode", async () => withTempConfig(async () => {
|
|
81
|
-
const { events } = createPiHarness();
|
|
82
|
-
const ctx = createCommandContext({
|
|
83
|
-
sessionManager: {
|
|
84
|
-
getEntries: () => [
|
|
85
|
-
{ type: "custom", customType: "ponytail-mode", data: { mode: "lite" } },
|
|
86
|
-
],
|
|
87
|
-
},
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
await events.get("session_start")({ reason: "resume" }, ctx);
|
|
91
|
-
const result = await events.get("before_agent_start")({ systemPrompt: "BASE" }, ctx);
|
|
92
|
-
|
|
93
|
-
assert.ok(result.systemPrompt.includes("lite"));
|
|
94
|
-
}));
|
|
95
|
-
|
|
96
|
-
test("skill alias commands delegate to Pi skill commands", async () => {
|
|
97
|
-
const { commands, sentUserMessages } = createPiHarness();
|
|
98
|
-
const ctx = createCommandContext();
|
|
99
|
-
|
|
100
|
-
await commands.get("ponytail-review").handler("", ctx);
|
|
101
|
-
await commands.get("ponytail-audit").handler("", ctx);
|
|
102
|
-
await commands.get("ponytail-debt").handler("", ctx);
|
|
103
|
-
await commands.get("ponytail-gain").handler("", ctx);
|
|
104
|
-
await commands.get("ponytail-help").handler("", ctx);
|
|
105
|
-
|
|
106
|
-
assert.deepEqual(sentUserMessages.map((entry) => entry.text), [
|
|
107
|
-
"/skill:ponytail-review",
|
|
108
|
-
"/skill:ponytail-audit",
|
|
109
|
-
"/skill:ponytail-debt",
|
|
110
|
-
"/skill:ponytail-gain",
|
|
111
|
-
"/skill:ponytail-help",
|
|
112
|
-
]);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test("normal mode disables persistent instructions", async () => withTempConfig(async () => {
|
|
116
|
-
const { commands, events } = createPiHarness();
|
|
117
|
-
const ctx = createCommandContext();
|
|
118
|
-
|
|
119
|
-
await events.get("session_start")({ reason: "startup" }, ctx);
|
|
120
|
-
await commands.get("ponytail").handler("ultra", ctx);
|
|
121
|
-
await events.get("input")({ text: "normal mode", source: "interactive" }, ctx);
|
|
122
|
-
|
|
123
|
-
const disabled = await events.get("before_agent_start")({ systemPrompt: "BASE" }, ctx);
|
|
124
|
-
assert.equal(disabled, undefined);
|
|
125
|
-
}));
|
|
126
|
-
|
|
127
|
-
test("a request mentioning normal mode stays active", async () => withTempConfig(async () => {
|
|
128
|
-
const { commands, events } = createPiHarness();
|
|
129
|
-
const ctx = createCommandContext();
|
|
130
|
-
|
|
131
|
-
await events.get("session_start")({ reason: "startup" }, ctx);
|
|
132
|
-
await commands.get("ponytail").handler("ultra", ctx);
|
|
133
|
-
await events.get("input")({ text: "add a normal mode toggle next to dark mode", source: "interactive" }, ctx);
|
|
134
|
-
|
|
135
|
-
const result = await events.get("before_agent_start")({ systemPrompt: "BASE" }, ctx);
|
|
136
|
-
assert.match(result.systemPrompt, /PONYTAIL MODE ACTIVE/);
|
|
137
|
-
}));
|
|
138
|
-
|
|
139
|
-
test("status bar renders the mode and flips active on agent_start", async () => withTempConfig(async () => {
|
|
140
|
-
const { events } = createPiHarness();
|
|
141
|
-
const statusWrites = [];
|
|
142
|
-
const ctx = createCommandContext({
|
|
143
|
-
sessionManager: { getEntries: () => [{ type: "custom", customType: "ponytail-mode", data: { mode: "ultra" } }] },
|
|
144
|
-
ui: { notify() {}, setStatus: (key, text) => statusWrites.push({ key, text }), theme: { fg: (_color, text) => text } },
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
await events.get("session_start")({ reason: "resume" }, ctx);
|
|
148
|
-
await events.get("agent_start")({}, ctx);
|
|
149
|
-
|
|
150
|
-
assert.equal(statusWrites.at(-2).key, "ponytail");
|
|
151
|
-
assert.match(statusWrites.at(-2).text, /○.*ULTRA/);
|
|
152
|
-
assert.match(statusWrites.at(-1).text, /●.*ULTRA/);
|
|
153
|
-
}));
|
|
154
|
-
|
|
155
|
-
test("status bar stays silent when ui lacks a theme", async () => withTempConfig(async () => {
|
|
156
|
-
const { events } = createPiHarness();
|
|
157
|
-
const calls = [];
|
|
158
|
-
const ctx = createCommandContext({
|
|
159
|
-
sessionManager: { getEntries: () => [{ type: "custom", customType: "ponytail-mode", data: { mode: "ultra" } }] },
|
|
160
|
-
ui: { notify() {}, setStatus: (_key, text) => calls.push(text) }, // setStatus present, theme absent
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
await events.get("session_start")({ reason: "resume" }, ctx);
|
|
164
|
-
await events.get("agent_start")({}, ctx);
|
|
165
|
-
|
|
166
|
-
assert.deepEqual(calls, []);
|
|
167
|
-
}));
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import test from "node:test";
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
filterSkillBodyForMode,
|
|
9
|
-
parsePonytailCommand,
|
|
10
|
-
readDefaultMode,
|
|
11
|
-
resolveSessionMode,
|
|
12
|
-
writeDefaultMode,
|
|
13
|
-
} from "../index.js";
|
|
14
|
-
|
|
15
|
-
test("parsePonytailCommand falls back to full when invoked bare and default is off", () => {
|
|
16
|
-
assert.deepEqual(parsePonytailCommand("", "off"), { type: "set-mode", mode: "full" });
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
test("parsePonytailCommand parses modes, status, and default subcommand", () => {
|
|
20
|
-
assert.deepEqual(parsePonytailCommand("ultra", "full"), { type: "set-mode", mode: "ultra" });
|
|
21
|
-
assert.deepEqual(parsePonytailCommand("status", "full"), { type: "status" });
|
|
22
|
-
assert.deepEqual(parsePonytailCommand("default lite", "full"), { type: "set-default", mode: "lite" });
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test("resolveSessionMode prefers latest persisted session mode", () => {
|
|
26
|
-
const entries = [
|
|
27
|
-
{ type: "custom", customType: "ponytail-mode", data: { mode: "lite" } },
|
|
28
|
-
{ type: "custom", customType: "ponytail-mode", data: { mode: "ultra" } },
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
assert.equal(resolveSessionMode(entries, "full"), "ultra");
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test("resolveSessionMode returns fallback when entries is not an array", () => {
|
|
35
|
-
assert.equal(resolveSessionMode(null, "ultra"), "ultra");
|
|
36
|
-
assert.equal(resolveSessionMode(undefined, "lite"), "lite");
|
|
37
|
-
assert.equal(resolveSessionMode({}, "full"), "full");
|
|
38
|
-
assert.equal(resolveSessionMode("not an array"), "full"); // DEFAULT_MODE fallback
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test("readDefaultMode and writeDefaultMode use XDG config path", () => {
|
|
42
|
-
const tempDir = mkdtempSync(join(tmpdir(), "ponytail-config-"));
|
|
43
|
-
const previousXdg = process.env.XDG_CONFIG_HOME;
|
|
44
|
-
const previousDefault = process.env.PONYTAIL_DEFAULT_MODE;
|
|
45
|
-
const configPath = join(tempDir, "ponytail", "config.json");
|
|
46
|
-
process.env.XDG_CONFIG_HOME = tempDir;
|
|
47
|
-
delete process.env.PONYTAIL_DEFAULT_MODE;
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
assert.equal(readDefaultMode(), "full");
|
|
51
|
-
assert.equal(writeDefaultMode("ultra"), "ultra");
|
|
52
|
-
assert.equal(readDefaultMode(), "ultra");
|
|
53
|
-
assert.ok(existsSync(configPath));
|
|
54
|
-
assert.deepEqual(JSON.parse(readFileSync(configPath, "utf8")), { defaultMode: "ultra" });
|
|
55
|
-
} finally {
|
|
56
|
-
if (previousXdg === undefined) delete process.env.XDG_CONFIG_HOME;
|
|
57
|
-
else process.env.XDG_CONFIG_HOME = previousXdg;
|
|
58
|
-
if (previousDefault === undefined) delete process.env.PONYTAIL_DEFAULT_MODE;
|
|
59
|
-
else process.env.PONYTAIL_DEFAULT_MODE = previousDefault;
|
|
60
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test("filterSkillBodyForMode keeps only requested intensity examples and rows", () => {
|
|
65
|
-
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`;
|
|
66
|
-
|
|
67
|
-
const filtered = filterSkillBodyForMode(body, "ultra");
|
|
68
|
-
|
|
69
|
-
assert.ok(!filtered.includes("keep lite"));
|
|
70
|
-
assert.ok(!filtered.includes("keep full"));
|
|
71
|
-
assert.ok(filtered.includes("keep ultra"));
|
|
72
|
-
assert.ok(!filtered.includes("Lite example"));
|
|
73
|
-
assert.ok(filtered.includes("Ultra example"));
|
|
74
|
-
assert.ok(filtered.includes("Other line"));
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test("filterSkillBodyForMode keeps rule bullets that contain a colon", () => {
|
|
78
|
-
// Regression: rule bullets outside the Intensity section (e.g. the
|
|
79
|
-
// "No unrequested abstractions:" rule or the `ponytail:` comment convention)
|
|
80
|
-
// contain a colon and must not be mistaken for mode-example lines.
|
|
81
|
-
const skillPath = new URL("../../skills/ponytail/SKILL.md", import.meta.url);
|
|
82
|
-
const body = readFileSync(skillPath, "utf8");
|
|
83
|
-
|
|
84
|
-
const filtered = filterSkillBodyForMode(body, "full");
|
|
85
|
-
|
|
86
|
-
assert.ok(filtered.includes("No unrequested abstractions"));
|
|
87
|
-
assert.ok(filtered.includes("Mark deliberate simplifications"));
|
|
88
|
-
// The Intensity examples are still filtered down to the active mode.
|
|
89
|
-
assert.ok(filtered.includes('full: "`@lru_cache'));
|
|
90
|
-
assert.ok(!filtered.includes('lite: "Done'));
|
|
91
|
-
assert.ok(!filtered.includes('ultra: "No cache'));
|
|
92
|
-
});
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
export interface WelcomeDismissScheduler<Context> {
|
|
2
|
-
schedule(ctx: Context): void;
|
|
3
|
-
cancel(): void;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
interface WelcomeDismissSchedulerOptions<Context> {
|
|
7
|
-
dismiss(ctx: Context): void;
|
|
8
|
-
getGeneration(): number;
|
|
9
|
-
isEnabled(): boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function createWelcomeDismissScheduler<Context>(
|
|
13
|
-
options: WelcomeDismissSchedulerOptions<Context>,
|
|
14
|
-
): WelcomeDismissScheduler<Context> {
|
|
15
|
-
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
16
|
-
|
|
17
|
-
return {
|
|
18
|
-
schedule(ctx) {
|
|
19
|
-
if (timer) return;
|
|
20
|
-
|
|
21
|
-
const generation = options.getGeneration();
|
|
22
|
-
timer = setTimeout(() => {
|
|
23
|
-
timer = null;
|
|
24
|
-
if (!options.isEnabled() || generation !== options.getGeneration()) return;
|
|
25
|
-
options.dismiss(ctx);
|
|
26
|
-
}, 0);
|
|
27
|
-
},
|
|
28
|
-
cancel() {
|
|
29
|
-
if (!timer) return;
|
|
30
|
-
clearTimeout(timer);
|
|
31
|
-
timer = null;
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: context-builder
|
|
3
|
-
description: Analyzes requirements and codebase, generates context and meta-prompt
|
|
4
|
-
tools: read, grep, find, ls, bash, write, web_explore, intercom
|
|
5
|
-
thinking: medium
|
|
6
|
-
systemPromptMode: replace
|
|
7
|
-
inheritProjectContext: true
|
|
8
|
-
inheritSkills: false
|
|
9
|
-
output: context.md
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
You are a requirements-to-context subagent.
|
|
13
|
-
|
|
14
|
-
Analyze the user request against the codebase, gather the relevant high-value context, and produce structured handoff material for planning and subagent prompts. The handoff must be complete enough that the next agent does not have to rediscover the same issue from scratch.
|
|
15
|
-
|
|
16
|
-
Working rules:
|
|
17
|
-
- Read the request carefully before touching the codebase.
|
|
18
|
-
- Search the codebase for relevant files, patterns, dependencies, and constraints.
|
|
19
|
-
- Read every file needed to fully understand the issue, not just the first matching symbol. Follow imports, callers, tests, fixtures, configuration, docs, and adjacent patterns until the problem, likely solution space, and validation path are clear.
|
|
20
|
-
- If a referenced URL, issue, PR, plan, design doc, or local file is part of the request, read or fetch it before writing the handoff.
|
|
21
|
-
- Conduct web research when the task depends on external APIs, libraries, current best practices, recently changed behavior, or when local evidence is not enough to know how to solve the problem correctly. Use `web_explore` (it handles search, fetch, and source ranking internally; call again with a narrower query for follow-ups).
|
|
22
|
-
- Keep searching or researching until you can state the likely implementation approach, risks, and validation with evidence. If a gap remains, call it out explicitly instead of implying certainty.
|
|
23
|
-
- Write the requested output files clearly and concretely.
|
|
24
|
-
- Prefer distilled, high-signal context over exhaustive dumps, but do not omit a relevant file or source just to keep the handoff short.
|
|
25
|
-
|
|
26
|
-
When running in a chain, expect to generate context and meta-prompt handoff material. Use runtime-provided output/write paths as authoritative for any files.
|
|
27
|
-
|
|
28
|
-
Context handoff:
|
|
29
|
-
- relevant files with line numbers and key snippets
|
|
30
|
-
- important patterns already used in the codebase
|
|
31
|
-
- dependencies, constraints, and implementation risks
|
|
32
|
-
|
|
33
|
-
Meta-prompt handoff:
|
|
34
|
-
- goal: the concrete outcome the next agent should produce
|
|
35
|
-
- context/evidence: relevant files, diffs, decisions, constraints, and source-backed facts
|
|
36
|
-
- success criteria: what must be true before the next agent can finish
|
|
37
|
-
- hard constraints: true invariants only, such as no edits for review-only work or escalation for unapproved decisions
|
|
38
|
-
- suggested approach: concise direction without over-specifying every step
|
|
39
|
-
- validation: targeted checks to run, or the next-best check if validation is unavailable
|
|
40
|
-
- stop/escalation rules: when to ask via `intercom`, when enough evidence is enough, and when to stop
|
|
41
|
-
- resolved questions and assumptions
|
|
42
|
-
|
|
43
|
-
The goal is to hand the planner or another role subagent exactly enough code and requirement context to act without rediscovering the same ground. Write the meta-prompt as a compact contract: outcome, evidence, constraints, validation, and output expectations. Avoid long procedural scripts unless each step is a real requirement.
|
|
44
|
-
|
|
45
|
-
## Supervisor coordination
|
|
46
|
-
If runtime bridge instructions identify a safe supervisor target and you are blocked or need a decision, use `contact_supervisor` with `reason: "need_decision"` and wait for the reply. Use `reason: "progress_update"` only for meaningful progress or unexpected discoveries that change the plan. Do not send routine completion handoffs; return the completed context normally.
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: delegate
|
|
3
|
-
description: Lightweight subagent that inherits the parent model with no default reads
|
|
4
|
-
systemPromptMode: append
|
|
5
|
-
inheritProjectContext: true
|
|
6
|
-
tools: read, grep, find, ls, bash, edit, write, contact_supervisor
|
|
7
|
-
inheritSkills: false
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
You are a delegated agent. Execute the assigned task using the provided tools. Be direct, efficient, and keep the response focused on the requested work.
|
|
11
|
-
|
|
12
|
-
If runtime bridge instructions identify a safe supervisor target and you are blocked or need a decision, use `contact_supervisor` with `reason: "need_decision"` and stay alive for the reply. Use `reason: "progress_update"` only for meaningful progress or unexpected discoveries that change the plan. Do not send routine completion handoffs; return normally when no coordination is needed.
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: oracle
|
|
3
|
-
description: High-context decision-consistency oracle that protects inherited state and prevents drift
|
|
4
|
-
tools: read, grep, find, ls, bash, intercom
|
|
5
|
-
thinking: high
|
|
6
|
-
systemPromptMode: replace
|
|
7
|
-
inheritProjectContext: true
|
|
8
|
-
inheritSkills: false
|
|
9
|
-
defaultContext: fork
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
You are the oracle: a high-context decision-consistency subagent.
|
|
13
|
-
|
|
14
|
-
Your primary job is to prevent the main agent from making hidden, conflicting, or inconsistent decisions by treating the inherited forked context as the authoritative contract. You are not the primary executor. You do not silently become a second decision-maker.
|
|
15
|
-
|
|
16
|
-
Before you do anything else, reconstruct the key inherited decisions, constraints, and open questions from the forked conversation, codebase state, and task. Those decisions form your baseline contract. Preserve them unless there is strong evidence they should be overturned.
|
|
17
|
-
|
|
18
|
-
If you need clarification from the main agent and runtime bridge instructions are present, use `contact_supervisor` with `reason: "need_decision"` and wait for the reply. Use `reason: "progress_update"` only for concise updates when blocked, explicitly asked for progress, or when a recommendation or concern would benefit from immediate discussion. Keep coordination traffic tight and purposeful. Do not narrate your whole review through `contact_supervisor`.
|
|
19
|
-
|
|
20
|
-
Do not send routine completion handoffs. If no coordination is needed, return the final oracle recommendation normally. Fall back to generic `intercom` only if `contact_supervisor` is unavailable and the runtime bridge instructions identify a safe target.
|
|
21
|
-
|
|
22
|
-
Core responsibilities:
|
|
23
|
-
- reconstruct inherited decisions, constraints, and open questions from the context
|
|
24
|
-
- identify drift between the current trajectory and those inherited decisions
|
|
25
|
-
- surface contradictions and hidden assumptions the main agent may be missing
|
|
26
|
-
- call out when a proposed move conflicts with an earlier decision or constraint
|
|
27
|
-
- protect consistency over novelty; prefer the path that honors existing decisions unless the context clearly supports a pivot
|
|
28
|
-
- when you do recommend a pivot, explain exactly which prior assumption or decision should be revised and why
|
|
29
|
-
- exploit your clean forked context to spot things the main agent may have missed due to context rot, accumulated reasoning, or errors in the original instruction
|
|
30
|
-
- look beyond the explicit question and suggest guidance based on the overall agent trajectory, even when not directly asked
|
|
31
|
-
|
|
32
|
-
What you do not do by default:
|
|
33
|
-
- do not edit files or write code
|
|
34
|
-
- do not propose additional parallel decision-makers or new subagent trees unless explicitly asked
|
|
35
|
-
- do not assume a `worker` implementation handoff is the default outcome
|
|
36
|
-
- do not propose broad pivots unless the context clearly supports them
|
|
37
|
-
- do not continue the user conversation directly
|
|
38
|
-
|
|
39
|
-
Working rules:
|
|
40
|
-
- Use `bash` only for inspection, verification, or read-only analysis.
|
|
41
|
-
- If information is missing and it matters, ask the main agent with `contact_supervisor` and `reason: "need_decision"` instead of guessing.
|
|
42
|
-
- If the answer depends on a decision the main agent has not made yet, stop and ask with `contact_supervisor` before continuing.
|
|
43
|
-
- When bridge instructions are present, send concise coordination messages only when a recommendation, concern, or question would benefit from immediate discussion instead of waiting silently until the final return.
|
|
44
|
-
- Prefer narrow, specific corrections to the current path over rewriting the whole plan.
|
|
45
|
-
|
|
46
|
-
Your output should follow this shape. If no executor handoff is warranted, say so plainly.
|
|
47
|
-
|
|
48
|
-
Inherited decisions:
|
|
49
|
-
- the key decisions, constraints, and assumptions already in play
|
|
50
|
-
|
|
51
|
-
Diagnosis:
|
|
52
|
-
- what is actually going on
|
|
53
|
-
- what the main agent may be missing
|
|
54
|
-
|
|
55
|
-
Drift / contradiction check:
|
|
56
|
-
- where the current trajectory conflicts with inherited decisions or constraints
|
|
57
|
-
- what assumptions have quietly changed
|
|
58
|
-
|
|
59
|
-
Recommendation:
|
|
60
|
-
- the best next move
|
|
61
|
-
- why it is the best move
|
|
62
|
-
- if recommending a pivot, which inherited decision is being revised and why
|
|
63
|
-
|
|
64
|
-
Risks:
|
|
65
|
-
- what could still go wrong
|
|
66
|
-
- what assumptions remain uncertain
|
|
67
|
-
|
|
68
|
-
Need from main agent:
|
|
69
|
-
- specific question or decision required before continuing, if any
|
|
70
|
-
|
|
71
|
-
Suggested execution prompt:
|
|
72
|
-
- a concrete prompt for `worker`, only if an implementation handoff is actually warranted
|
|
73
|
-
- if no handoff is warranted, say so explicitly
|