pi-soly 0.2.1 → 0.4.0

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.
@@ -0,0 +1,345 @@
1
+ // =============================================================================
2
+ // index.ts — pi-switch extension entry
3
+ // =============================================================================
4
+ //
5
+ // Wires the agent switcher into pi:
6
+ // - Header bar above chat (Claude Code-style, dim, persistent)
7
+ // - Ctrl+Shift+S to cycle (Shift+Tab is taken by pi's thinking-level cycler)
8
+ // - /agent slash command: show current + available, or set explicitly
9
+ // - Persists current agent to .soly/agent (shared with soly) or ~/.pi-switch/agent
10
+ // - Exposes `globalThis.__PI_SWITCH_AGENT__` for other extensions to read
11
+ // - Injects a system-prompt section so the LLM knows when to use which agent
12
+ // =============================================================================
13
+
14
+ import type { ExtensionAPI, ExtensionUIContext } from "@earendil-works/pi-coding-agent";
15
+ import { Text } from "@earendil-works/pi-tui";
16
+ import * as fs from "node:fs";
17
+ import * as os from "node:os";
18
+ import * as path from "node:path";
19
+ import {
20
+ DEFAULT_AGENT,
21
+ BUILTIN_AGENTS,
22
+ availableAgents,
23
+ nextAgent,
24
+ parseAgentName,
25
+ formatAgentBadge,
26
+ formatAgentSwitchNotify,
27
+ formatHeaderLine,
28
+ groupedAvailableAgents,
29
+ getAgentMeta,
30
+ loadAgent,
31
+ saveAgent,
32
+ } from "./core.ts";
33
+ import { buildPiSwitchSection, recommendAgent } from "./prompt.ts";
34
+
35
+ const GLOBAL_KEY = "__PI_SWITCH_AGENT__";
36
+
37
+ export default function piSwitchExtension(pi: ExtensionAPI) {
38
+ let cwd = "";
39
+ let currentAgent: string = DEFAULT_AGENT;
40
+ let cycle: string[] = [DEFAULT_AGENT];
41
+ let lastUi: ExtensionUIContext | null = null;
42
+
43
+ function refreshCycle(): void {
44
+ cycle = availableAgents();
45
+ if (!cycle.includes(currentAgent)) {
46
+ currentAgent = DEFAULT_AGENT;
47
+ }
48
+ }
49
+
50
+ function publish(): void {
51
+ (globalThis as Record<string, unknown>)[GLOBAL_KEY] = currentAgent;
52
+ }
53
+
54
+ function rerender(): void {
55
+ if (!lastUi) return;
56
+ try {
57
+ setHeaderBar(lastUi, () => formatHeaderLine(currentAgent));
58
+ const badge = formatAgentBadge(currentAgent);
59
+ lastUi.setStatus("pi-switch", badge ?? undefined);
60
+ } catch { /* no ui yet */ }
61
+ }
62
+
63
+ function setAgent(next: string): void {
64
+ const prev = currentAgent;
65
+ if (next === prev) return;
66
+ currentAgent = next;
67
+ publish();
68
+ if (cwd) saveAgent(cwd, next);
69
+ rerender();
70
+ if (lastUi) {
71
+ lastUi.notify(formatAgentSwitchNotify(prev, next), "info");
72
+ }
73
+ }
74
+
75
+ // ----- session_start: load persisted agent + set initial header -----
76
+ pi.on("session_start", async (_event, ctx) => {
77
+ cwd = ctx.cwd;
78
+ lastUi = ctx.ui;
79
+ publish();
80
+ const restored = loadAgent(cwd);
81
+ if (restored) currentAgent = restored;
82
+ refreshCycle();
83
+ publish();
84
+ rerender();
85
+ });
86
+
87
+ // ----- before_agent_start: inject system-prompt section -----
88
+ pi.on("before_agent_start", async (event, ctx) => {
89
+ lastUi = ctx.ui;
90
+ // Re-render the header on each turn (in case agent was changed via /agent)
91
+ rerender();
92
+ return {
93
+ systemPrompt: event.systemPrompt + buildPiSwitchSection(),
94
+ };
95
+ });
96
+
97
+ // ----- Ctrl+Shift+S: cycle to next agent -----
98
+ pi.registerShortcut("ctrl+shift+s", {
99
+ description: "Cycle pi-switch agent: worker → oracle → user-defined…",
100
+ handler: (sctx) => {
101
+ lastUi = sctx.ui;
102
+ refreshCycle();
103
+ const next = nextAgent(currentAgent, cycle);
104
+ setAgent(next);
105
+ },
106
+ });
107
+
108
+ // ----- /agent slash command (also handles subcommands: create, doctor) -----
109
+ pi.registerCommand("agent", {
110
+ description: "show or set the active subagent, or `create <name>` to scaffold, or `doctor` to diagnose",
111
+ handler: async (args, ctx) => {
112
+ lastUi = ctx.ui;
113
+ refreshCycle();
114
+ const parts = args.trim().split(/\s+/);
115
+ const subcommand = parts[0]?.toLowerCase();
116
+ const arg = parts[1];
117
+
118
+ // Subcommand: create
119
+ if (subcommand === "create") {
120
+ if (!arg) {
121
+ ctx.ui.notify("pi-switch: usage — `/agent create <name>`", "info");
122
+ return;
123
+ }
124
+ createAgent(arg, { ui: ctx.ui, cwd });
125
+ return;
126
+ }
127
+
128
+ // Subcommand: doctor
129
+ if (subcommand === "doctor") {
130
+ ctx.ui.notify(doctorReport(), "info");
131
+ return;
132
+ }
133
+
134
+ // Subcommand: recommend
135
+ if (subcommand === "recommend") {
136
+ const task = parts.slice(1).join(" ");
137
+ if (!task) {
138
+ ctx.ui.notify("pi-switch: usage — `/agent recommend <task description>`", "info");
139
+ return;
140
+ }
141
+ const rec = recommendAgent(task);
142
+ if (!rec) {
143
+ ctx.ui.notify(`pi-switch: no clear agent match for: "${task}"`, "info");
144
+ return;
145
+ }
146
+ ctx.ui.notify(
147
+ `pi-switch recommendation: ${rec.emoji} ${rec.agent}\n why: ${rec.why}\n → /agent ${rec.agent} to switch`,
148
+ "info",
149
+ );
150
+ return;
151
+ }
152
+
153
+ // Direct agent name → set (handles `/agent <name>` as a single-arg shortcut).
154
+ // Check FIRST: if parts[0] is a valid agent name, treat as set even
155
+ // if it happens to be a "subcommand-looking" word. This way
156
+ // `/agent researcher` always sets to researcher, never falls
157
+ // through to the listing branch.
158
+ if (subcommand && cycle.includes(subcommand)) {
159
+ setAgent(subcommand);
160
+ return;
161
+ }
162
+ // Optional second arg: also set, for explicit `/agent <name>` syntax.
163
+ if (arg) {
164
+ const target = parseAgentName(arg);
165
+ if (!target) {
166
+ ctx.ui.notify(`pi-switch: invalid name "${arg}".`, "error");
167
+ return;
168
+ }
169
+ if (!cycle.includes(target)) {
170
+ ctx.ui.notify(
171
+ `pi-switch: unknown "${target}". available: ${cycle.join(", ")}`,
172
+ "error",
173
+ );
174
+ return;
175
+ }
176
+ setAgent(target);
177
+ return;
178
+ }
179
+
180
+ // No arg: show current + grouped available
181
+ const curMeta = getAgentMeta(currentAgent);
182
+ const groups = groupedAvailableAgents();
183
+ const lines: string[] = [
184
+ `current: ${curMeta.emoji} ${currentAgent} (${curMeta.description})`,
185
+ "",
186
+ "available:",
187
+ "",
188
+ ];
189
+ for (const g of groups) {
190
+ lines.push(`─── ${g.header} ${"─".repeat(Math.max(0, 40 - g.header.length))}`);
191
+ for (const a of g.agents) {
192
+ const meta = getAgentMeta(a);
193
+ const marker = a === currentAgent ? "→" : " ";
194
+ lines.push(` ${marker} ${meta.emoji} ${a.padEnd(16)} ${meta.description}`);
195
+ }
196
+ lines.push("");
197
+ }
198
+ lines.push("cycle with Ctrl+Shift+S, or `/agent <name>`");
199
+ lines.push("subcommands: `/agent create <name>`, `/agent doctor`, `/agent recommend <task>`");
200
+ ctx.ui.notify(lines.join("\n"), "info");
201
+ },
202
+ });
203
+ }
204
+
205
+ // ---------------------------------------------------------------------------
206
+ // Header bar (Claude Code-style, persistent above chat)
207
+ // ---------------------------------------------------------------------------
208
+
209
+ function setHeaderBar(ui: ExtensionUIContext, getLine: () => string): void {
210
+ // ui.setHeader takes a factory. The factory is called fresh on each
211
+ // render of the TUI. We return a Text whose content is read on each
212
+ // render, so updating `currentAgent` automatically reflects in the header.
213
+ ui.setHeader((_tui, _theme) => {
214
+ // The Text component reads getLine() at render time.
215
+ // We use a closure over a getter to read the current value.
216
+ const text = new Text(getLine(), 1, 0);
217
+ // Text satisfies Component & { dispose?() } — cast to satisfy TS.
218
+ return text as unknown as Parameters<typeof ui.setHeader>[0] extends ((t: infer T, th: infer Th) => infer R) ? R : never;
219
+ });
220
+ }
221
+
222
+ // ---------------------------------------------------------------------------
223
+ // /agent create — scaffold a new agent .md file
224
+ // ---------------------------------------------------------------------------
225
+
226
+ /** Template for a new user agent. User edits the system prompt to specialize. */
227
+ function agentTemplate(name: string, description: string): string {
228
+ return `---
229
+ name: ${name}
230
+ description: ${description}
231
+ thinking: medium
232
+ systemPromptMode: replace
233
+ inheritProjectContext: true
234
+ inheritSkills: false
235
+ tools: read, grep, find, ls, bash, edit, write
236
+ defaultContext: fork
237
+ ---
238
+
239
+ You are \`${name}\`. Describe what you specialize in, your process, and
240
+ what you should NOT do. Keep the rest of this frontmatter as-is unless
241
+ you have a specific reason to change it.
242
+
243
+ # Your role
244
+
245
+ <!-- Replace with a one-paragraph description of what you're for. -->
246
+
247
+ # Process
248
+
249
+ 1. Read the user's request carefully.
250
+ 2. Form a hypothesis about the right approach.
251
+ 3. Verify with tools (read, grep, bash) before writing.
252
+ 4. Commit changes in narrow, reviewable diffs.
253
+
254
+ # What you should NOT do
255
+
256
+ - Edit other agents' files
257
+ - Run subagents yourself (you're already a subagent)
258
+ - Skip verification ("trust me bro" is not a process)
259
+ `;
260
+ }
261
+
262
+ function createAgent(
263
+ name: string,
264
+ ctx: {
265
+ ui: {
266
+ notify: (t: string, k?: "info" | "warning" | "error") => void;
267
+ input: (t: string, p?: string) => Promise<string | undefined>;
268
+ };
269
+ cwd: string;
270
+ },
271
+ ): void {
272
+ if (!parseAgentName(name)) {
273
+ ctx.ui.notify(`pi-switch: invalid name "${name}". Use letters/digits/dashes/underscores, ≤64 chars.`, "error");
274
+ return;
275
+ }
276
+ const userDir = path.join(os.homedir(), ".pi", "agent", "agents");
277
+ fs.mkdirSync(userDir, { recursive: true });
278
+ const file = path.join(userDir, `${name}.md`);
279
+ if (fs.existsSync(file)) {
280
+ ctx.ui.notify(`pi-switch: ${file} already exists. edit it directly.`, "warning");
281
+ return;
282
+ }
283
+ // Ask for description
284
+ void ctx.ui.input(`description for "${name}":`, "one-liner that shows in the picker")?.then((desc) => {
285
+ const description = desc?.trim() || `custom agent (${name})`;
286
+ fs.writeFileSync(file, agentTemplate(name, description), "utf-8");
287
+ ctx.ui.notify(
288
+ `pi-switch: created ${file}\n → next Ctrl+Shift+S to see it in the cycle\n → edit the system prompt to specialize`,
289
+ "info",
290
+ );
291
+ });
292
+ }
293
+
294
+ function doctorReport(): string {
295
+ const cycle = availableAgents();
296
+ const userDir = path.join(os.homedir(), ".pi", "agent", "agents");
297
+ const lines: string[] = ["pi-switch doctor:", ""];
298
+
299
+ // Cycle stats
300
+ const builtins = cycle.filter((a) => BUILTIN_AGENTS.includes(a));
301
+ const users = cycle.filter((a) => !BUILTIN_AGENTS.includes(a));
302
+ lines.push(`cycle: ${cycle.length} agents (${builtins.length} built-in, ${users.length} user)`);
303
+ lines.push("");
304
+
305
+ // User dir check
306
+ if (!fs.existsSync(userDir)) {
307
+ lines.push(`user dir: ${userDir} (does not exist — user agents won't be discovered)`);
308
+ } else {
309
+ const files = fs.readdirSync(userDir).filter((f) => f.endsWith(".md"));
310
+ lines.push(`user dir: ${userDir} (${files.length} file(s))`);
311
+
312
+ // Validate each user agent
313
+ const issues: string[] = [];
314
+ for (const f of files) {
315
+ try {
316
+ const raw = fs.readFileSync(path.join(userDir, f), "utf-8");
317
+ if (!raw.startsWith("---\n")) {
318
+ issues.push(`${f}: no YAML frontmatter`);
319
+ continue;
320
+ }
321
+ const m = raw.match(/^---\n([\s\S]*?)\n---/);
322
+ if (!m) { issues.push(`${f}: malformed frontmatter`); continue; }
323
+ const fm = m[1] ?? "";
324
+ if (!/^name:\s*\S/m.test(fm)) issues.push(`${f}: missing 'name:' in frontmatter`);
325
+ else if (!/^description:\s*\S/m.test(fm)) issues.push(`${f}: missing 'description:' in frontmatter`);
326
+ } catch (e) {
327
+ issues.push(`${f}: read error: ${(e as Error).message}`);
328
+ }
329
+ }
330
+ if (issues.length === 0) {
331
+ lines.push("validation: all user agents OK ✓");
332
+ } else {
333
+ lines.push("validation issues:");
334
+ for (const i of issues) lines.push(` - ${i}`);
335
+ }
336
+ }
337
+
338
+ // Persistence check
339
+ const persisted = process.env.PI_SWITCH_HOME || os.homedir();
340
+ const fallbackFile = path.join(persisted, ".pi-switch", "agent");
341
+ lines.push("");
342
+ lines.push(`persistence: ${fs.existsSync(fallbackFile) ? fallbackFile : "no standalone persistence (uses .soly/agent if soly project)"}`);
343
+
344
+ return lines.join("\n");
345
+ }
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "pi-agented",
3
+ "version": "0.2.0",
4
+ "description": "Generic subagent switcher for pi. Header bar above chat, Ctrl+Shift+S to cycle, /agent slash command. Works with any agent in ~/.pi/agent/agents/.",
5
+ "type": "module",
6
+ "main": "index.ts",
7
+ "scripts": {
8
+ "test": "bun test",
9
+ "typecheck": "bun x tsc --noEmit"
10
+ },
11
+ "dependencies": {},
12
+ "peerDependencies": {
13
+ "@earendil-works/pi-coding-agent": "*",
14
+ "@earendil-works/pi-tui": "*"
15
+ },
16
+ "devDependencies": {
17
+ "@earendil-works/pi-coding-agent": "0.78.1",
18
+ "@earendil-works/pi-tui": "0.78.1",
19
+ "@types/node": "^25.9.1",
20
+ "bun-types": "^1.3.14",
21
+ "typebox": "1.1.38",
22
+ "typescript": "^6.0.3"
23
+ },
24
+ "files": [
25
+ "index.ts",
26
+ "core.ts",
27
+ "prompt.ts",
28
+ "README.md"
29
+ ],
30
+ "keywords": [
31
+ "pi",
32
+ "pi-extension",
33
+ "pi-package",
34
+ "subagent",
35
+ "agent-switcher",
36
+ "task-routing"
37
+ ],
38
+ "license": "MIT",
39
+ "pi": {
40
+ "extensions": [
41
+ "./index.ts"
42
+ ]
43
+ },
44
+ "publishConfig": {
45
+ "registry": "https://registry.npmjs.org/"
46
+ },
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "http://git.local.stbl/lowern1ght/pi-soly.framework.git",
50
+ "directory": "packages/pi-switch"
51
+ }
52
+ }
@@ -0,0 +1,134 @@
1
+ // =============================================================================
2
+ // prompt.ts — System-prompt section for the pi-switch extension
3
+ // =============================================================================
4
+
5
+ /** Task-pattern → recommended agent. The LLM reads this and decides
6
+ * whether to invoke /agent before launching subagent(...). Match is
7
+ * by keyword (case-insensitive). First match wins; ties broken by order. */
8
+ export const TASK_AGENT_HINTS: ReadonlyArray<{
9
+ pattern: RegExp;
10
+ agent: string;
11
+ emoji: string;
12
+ why: string;
13
+ }> = [
14
+ // English keywords (use \b — ASCII word boundary works for English)
15
+ { pattern: /\b(research|investigate|look\s*up|find\s*out|explore|survey|compare\s+libraries|what\s+is\s+the\s+best)\b/i,
16
+ agent: "researcher", emoji: "\ud83d\udcda",
17
+ why: "external docs, ecosystem behavior, primary sources" },
18
+ { pattern: /\b(scout|scan|map|find\s+all|where\s+is|locate|explore\s+codebase|skim)\b/i,
19
+ agent: "scout", emoji: "\ud83d\udd0d",
20
+ why: "codebase recon, patterns, file locations" },
21
+ { pattern: /\b(plan|design|architect|outline|structure|break\s*down|steps|order)\b/i,
22
+ agent: "planner", emoji: "\ud83d\udccb",
23
+ why: "decompose into ordered steps, identify risks" },
24
+ { pattern: /\b(review|audit|check|adversarial|critique|find\s+bugs|qa)\b/i,
25
+ agent: "reviewer", emoji: "\ud83d\udc40",
26
+ why: "adversarial review of correctness, security, style" },
27
+ { pattern: /\b(oracle|decision|tradeoff|compare|which\s+approach|is\s+this\s+wise|drift)\b/i,
28
+ agent: "oracle", emoji: "\ud83d\udd2e",
29
+ why: "decision consistency, hidden assumptions, drift detection" },
30
+ { pattern: /\b(debug|bug|fix|crash|error|stack\s*trace|repro|why\s+is\s+this\s+broken)\b/i,
31
+ agent: "soly-debugger", emoji: "\ud83d\udc1e",
32
+ why: "isolated bug investigation with minimal repro" },
33
+ { pattern: /\b(test|tests|coverage|spec|assert)\b/i,
34
+ agent: "soly-tester", emoji: "\ud83e\uddea",
35
+ why: "test-only work, never modifies prod code" },
36
+ { pattern: /\b(refactor|clean\s*up|simplify|extract|rename|restructure|no\s+behavior\s+change)\b/i,
37
+ agent: "soly-refactor", emoji: "\ud83d\udd04",
38
+ why: "pure refactoring, behavior-preserving" },
39
+ { pattern: /\b(document|docs|readme|jsdoc|comment|annotate)\b/i,
40
+ agent: "soly-documenter", emoji: "\ud83d\udcdd",
41
+ why: "doc updates, READMEs, inline annotations" },
42
+ { pattern: /\b(implement|build|write\s+code|add\s+feature|create\s+the)\b/i,
43
+ agent: "worker", emoji: "\u26a1",
44
+ why: "generic implementation with all tools" },
45
+ { pattern: /\b(orchestrate|coordinate|dispatch|chain|run\s+in\s+parallel|first\s+.+\s+then)\b/i,
46
+ agent: "delegate", emoji: "\ud83e\udd1d",
47
+ why: "multi-agent orchestration" },
48
+ // Russian keywords (loose match — Russian words inflect heavily; we match
49
+ // word stems, accepting some false positives as the cost of broader coverage)
50
+ { pattern: /(изуч|исслед|разузн|найди\s+инфу|research|investigate|find\s+out)/i,
51
+ agent: "researcher", emoji: "\ud83d\udcda",
52
+ why: "external docs, ecosystem behavior, primary sources" },
53
+ { pattern: /(где\s+это|где\s+находит|find\s+all|locate)/i,
54
+ agent: "scout", emoji: "\ud83d\udd0d",
55
+ why: "codebase recon, patterns, file locations" },
56
+ { pattern: /(спланир|plan|design|architect)/i,
57
+ agent: "planner", emoji: "\ud83d\udccb",
58
+ why: "decompose into ordered steps, identify risks" },
59
+ { pattern: /(проверь|ревью|аудит|review|audit)/i,
60
+ agent: "reviewer", emoji: "\ud83d\udc40",
61
+ why: "adversarial review of correctness, security, style" },
62
+ { pattern: /(решени|выбор|decision|tradeoff|drift)/i,
63
+ agent: "oracle", emoji: "\ud83d\udd2e",
64
+ why: "decision consistency, hidden assumptions, drift detection" },
65
+ { pattern: /(баг|ошибк|почему\s+(?:падает|ломает)|debug|bug|crash|stack\s*trace|repro)/i,
66
+ agent: "soly-debugger", emoji: "\ud83d\udc1e",
67
+ why: "isolated bug investigation with minimal repro" },
68
+ { pattern: /(тест|покрыт|test|coverage|spec|assert)/i,
69
+ agent: "soly-tester", emoji: "\ud83e\uddea",
70
+ why: "test-only work, never modifies prod code" },
71
+ { pattern: /(рефактор|упрост|refactor|simplify|extract|restructure)/i,
72
+ agent: "soly-refactor", emoji: "\ud83d\udd04",
73
+ why: "pure refactoring, behavior-preserving" },
74
+ { pattern: /(документ|описани|document|readme|jsdoc)/i,
75
+ agent: "soly-documenter", emoji: "\ud83d\udcdd",
76
+ why: "doc updates, READMEs, inline annotations" },
77
+ { pattern: /(реализуй|сделай|напиши|создай|implement|build|add\s+feature|create\s+the)/i,
78
+ agent: "worker", emoji: "\u26a1",
79
+ why: "generic implementation with all tools" },
80
+ { pattern: /(оркестрируй|координируй|orchestrate|coordinate|dispatch|chain)/i,
81
+ agent: "delegate", emoji: "\ud83e\udd1d",
82
+ why: "multi-agent orchestration" },
83
+ ];
84
+
85
+ /** Heuristic: which agent does the task look like? Returns null if no
86
+ * pattern matches (caller should leave the current agent as-is). */
87
+ export function recommendAgent(taskText: string): { agent: string; emoji: string; why: string } | null {
88
+ for (const hint of TASK_AGENT_HINTS) {
89
+ if (hint.pattern.test(taskText)) {
90
+ return { agent: hint.agent, emoji: hint.emoji, why: hint.why };
91
+ }
92
+ }
93
+ return null;
94
+ }
95
+
96
+ export function buildPiSwitchSection(): string {
97
+ return `
98
+
99
+ ## pi-switch — when to use \`/agent\`
100
+
101
+ The \`/agent\` slash command + \`Ctrl+Shift+S\` shortcut cycle through available subagents. Use the right agent for the job:
102
+
103
+ - **Read-only / no edits** (oracle, scout, researcher, planner, reviewer): for analysis, planning, review. They won't modify files.
104
+ - **Write tools** (worker, context-builder, delegate): for implementation.
105
+ - **User-defined** in \`~/.pi/agent/agents/\`: any agent the user has added — drop a markdown file with YAML frontmatter (name, description) and it joins the cycle automatically.
106
+
107
+ The current agent is shown in a header bar above the chat (with emoji + description) and in the footer status line as \`[emoji name]\`. When the agent changes, a multi-line notification appears with the old → new diff and capability summary.
108
+
109
+ When you need a specialist for a sub-task, use the right agent via the parent LLM's \`subagent(...)\` call — the system will use the currently active agent. You can also use \`/agent <name>\` to switch explicitly, but in most cases the orchestrator picks the right agent for each step.
110
+
111
+ **Task → agent heuristics.** Before launching a generic \`subagent(...)\`, scan the request for these keywords and call \`/agent <name>\` first if it matches:
112
+
113
+ | Keywords in request | Suggested agent | Why |
114
+ |---|---|---|
115
+ | research, investigate, look up, find out, explore, compare libraries, what is the best | 📚 researcher | external docs, ecosystem behavior |
116
+ | scout, scan, map, find all, where is, locate, explore codebase, skim | 🔍 scout | codebase recon, patterns, file locations |
117
+ | plan, design, architect, outline, structure, break down, steps, order | 📋 planner | decompose into ordered steps, identify risks |
118
+ | review, audit, check, adversarial, critique, find bugs, qa | 👀 reviewer | adversarial correctness, security, style review |
119
+ | oracle, decision, tradeoff, compare, which approach, is this wise, drift | 🔮 oracle | decision consistency, hidden assumptions |
120
+ | debug, bug, fix, crash, error, stack trace, repro, why is this broken | 🐞 soly-debugger | isolated bug investigation with minimal repro |
121
+ | test, tests, coverage, spec, assert | 🧪 soly-tester | test-only work, never modifies prod code |
122
+ | refactor, clean up, simplify, extract, rename, restructure, no behavior change | 🔄 soly-refactor | pure refactoring, behavior-preserving |
123
+ | document, docs, readme, jsdoc, comment, annotate | 📝 soly-documenter | doc updates, READMEs, inline annotations |
124
+ | implement, build, write code, add feature, create the | ⚡ worker | generic implementation with all tools |
125
+ | orchestrate, coordinate, dispatch, chain, run in parallel | 🤝 delegate | multi-agent orchestration |
126
+
127
+ For multi-step tasks, the orchestrator (you) decides which agents run and in what order. You can chain agents via \`subagent({ chain: [...] })\` or run them in parallel via parallel tasks.
128
+
129
+ DON'T:
130
+ - Launch a worker for analysis (use oracle/scout)
131
+ - Launch an oracle for implementation (it has no write tools)
132
+ - Manually edit \`.soly/agent\` or \`~/.pi-switch/agent\` — use the slash command
133
+ `;
134
+ }