pi-soly 1.2.0 → 1.3.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.
package/README.md CHANGED
@@ -56,7 +56,7 @@ GSD-inspired planning and execution. State is the source of truth, not vibes.
56
56
 
57
57
  ```bash
58
58
  /plan # generate PLAN.md for the current phase
59
- /execute # dispatch plan to soly-worker subagent
59
+ /execute # execute plan directly (LLM does the work)
60
60
  /resume # pick up a paused session
61
61
  /inspect # show current state summary
62
62
  /discuss 3 # talk through decisions before planning phase 3
@@ -182,7 +182,7 @@ todo_update({
182
182
  ┌──────────────────┐
183
183
  │ 7 soly agents │
184
184
  │ │
185
- soly-worker │
185
+ │ worker (cycle)
186
186
  │ soly-debugger │
187
187
  │ soly-tester │
188
188
  │ soly-reviewer │
package/agents-install.ts CHANGED
@@ -2,17 +2,16 @@
2
2
  // assets-install.ts — Idempotent install of soly-managed user assets
3
3
  // =============================================================================
4
4
  //
5
- // Soly ships two kinds of user-scope assets:
5
+ // Soly ships one kind of user-scope asset:
6
6
  //
7
- // 1. Subagent configs → `~/.pi/agent/agents/`
8
- // The single `soly-manager` subagent (mode-switching executor).
9
- //
10
- // 2. Skills → `~/.pi/agent/skills/<name>/`
7
+ // Skills → `~/.pi/agent/skills/<name>/`
11
8
  // The `soly-framework` skill — framework documentation the LLM
12
- // loads on demand via the read tool.
9
+ // loads on demand via the read tool. This is the LLM's only
10
+ // "helper" for soly — pi doesn't need a separate subagent layer
11
+ // for plan execution (the LLM in the main session does it).
13
12
  //
14
- // pi discovers both from `~/.pi/agent/`, so on first session_start we
15
- // copy our shipped files there.
13
+ // pi discovers the skill from `~/.pi/agent/`, so on first session_start
14
+ // we copy our shipped file there.
16
15
  //
17
16
  // IDEMPOTENT: if the target file already exists (user may have customized
18
17
  // it), we do NOT overwrite. This is one-way "first install wins".
@@ -22,41 +21,19 @@ import * as fs from "node:fs";
22
21
  import * as os from "node:os";
23
22
  import * as path from "node:path";
24
23
 
25
- /** soly agent files bundled with the extension. */
26
- const SHIPPED_AGENTS = [
27
- "soly-manager.md",
28
- ] as const;
29
-
30
24
  /** soly skills bundled with the extension. Each entry is a directory
31
25
  * under `skills/` containing a SKILL.md. */
32
26
  const SHIPPED_SKILLS = [
33
27
  "soly-framework",
34
28
  ] as const;
35
29
 
36
- /** Where pi looks for user agents. Respects HOME/USERPROFILE for
37
- * testability (otherwise we'd always write to the real user home).
38
- * Agent files live directly in the dir (no `agents/` subfolder):
39
- * ~/.agents/reviewer.md (NOT ~/.agents/agents/reviewer.md)
40
- * This keeps the path clean and matches the project-level convention. */
41
- function userAgentsDirs(): string[] {
42
- const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
43
- return [
44
- path.join(home, ".agents"), // vendor-neutral (preferred)
45
- path.join(home, ".pi", "agent", "agents"), // pi native (legacy)
46
- ];
47
- }
48
-
49
- /** Where pi looks for user skills. */
30
+ /** Where pi looks for user skills. Respects HOME/USERPROFILE for
31
+ * testability (otherwise we'd always write to the real user home). */
50
32
  function userSkillsDir(): string {
51
33
  const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
52
34
  return path.join(home, ".pi", "agent", "skills");
53
35
  }
54
36
 
55
- /** Where this soly extension's `agents/` directory lives. */
56
- function shippedAgentsDir(extensionRoot: string): string {
57
- return path.join(extensionRoot, "agents");
58
- }
59
-
60
37
  /** Where this soly extension's `skills/` directory lives. */
61
38
  function shippedSkillsDir(extensionRoot: string): string {
62
39
  return path.join(extensionRoot, "skills");
@@ -68,19 +45,7 @@ export interface InstallResult {
68
45
  errors: string[];
69
46
  }
70
47
 
71
- /** Copy a single file if destination doesn't exist. Idempotent. */
72
- function copyIfMissing(from: string, to: string): "installed" | "skipped" | "error" {
73
- if (!fs.existsSync(from)) return "error";
74
- if (fs.existsSync(to)) return "skipped";
75
- try {
76
- fs.copyFileSync(from, to);
77
- return "installed";
78
- } catch {
79
- return "error";
80
- }
81
- }
82
-
83
- /** Recursively copy a directory tree if destination doesn't exist. Idempotent. */
48
+ /** Copy a directory tree if destination doesn't exist. Idempotent. */
84
49
  function copyDirIfMissing(from: string, to: string): "installed" | "skipped" | "error" {
85
50
  if (!fs.existsSync(from)) return "error";
86
51
  if (fs.existsSync(to)) return "skipped";
@@ -93,40 +58,6 @@ function copyDirIfMissing(from: string, to: string): "installed" | "skipped" | "
93
58
  }
94
59
  }
95
60
 
96
- /** Install shipped soly agents to `~/.agents/` (vendor-neutral,
97
- * preferred). Legacy `~/.pi/agent/agents/` copies are left alone —
98
- * `discoverUserRotors` reads both, so old installs still work. */
99
- export function installSolyAgents(extensionRoot: string): InstallResult {
100
- const result: InstallResult = { installed: [], skipped: [], errors: [] };
101
- const src = shippedAgentsDir(extensionRoot);
102
-
103
- if (!fs.existsSync(src)) return result; // dev mode no-op
104
-
105
- // Try vendor-neutral first, then fall back to pi's native dir.
106
- let dst: string | null = null;
107
- for (const candidate of userAgentsDirs()) {
108
- try {
109
- fs.mkdirSync(candidate, { recursive: true });
110
- dst = candidate;
111
- break;
112
- } catch (err) {
113
- result.errors.push(`mkdir ${candidate}: ${(err as Error).message}`);
114
- }
115
- }
116
- if (!dst) return result;
117
-
118
- for (const name of SHIPPED_AGENTS) {
119
- const from = path.join(src, name);
120
- const to = path.join(dst, name);
121
- const r = copyIfMissing(from, to);
122
- if (r === "installed") result.installed.push(name);
123
- else if (r === "skipped") result.skipped.push(name);
124
- else result.errors.push(`missing source: ${from}`);
125
- }
126
-
127
- return result;
128
- }
129
-
130
61
  /** Install shipped soly skills to `~/.pi/agent/skills/`. Idempotent. */
131
62
  export function installSolySkills(extensionRoot: string): InstallResult {
132
63
  const result: InstallResult = { installed: [], skipped: [], errors: [] };
@@ -147,33 +78,15 @@ export function installSolySkills(extensionRoot: string): InstallResult {
147
78
  return result;
148
79
  }
149
80
 
150
- /** Install all soly assets (agents + skills). Combined for convenience. */
81
+ /** Install all soly assets (skills only agents are not shipped). */
151
82
  export function installSolyAssets(extensionRoot: string): {
152
- agents: InstallResult;
153
83
  skills: InstallResult;
154
84
  } {
155
85
  return {
156
- agents: installSolyAgents(extensionRoot),
157
86
  skills: installSolySkills(extensionRoot),
158
87
  };
159
88
  }
160
89
 
161
- /** Check which shipped soly agents are present across all user agent
162
- * homes. A file counts as "installed" if it's in ANY of the dirs. */
163
- export function checkSolyAgentsInstalled(extensionRoot: string): {
164
- installed: string[];
165
- missing: string[];
166
- } {
167
- const installed: string[] = [];
168
- const missing: string[] = [];
169
- for (const name of SHIPPED_AGENTS) {
170
- const present = userAgentsDirs().some((dir) => fs.existsSync(path.join(dir, name)));
171
- if (present) installed.push(name);
172
- else missing.push(name);
173
- }
174
- return { installed, missing };
175
- }
176
-
177
90
  /** Check which shipped soly skills are present in the user dir. */
178
91
  export function checkSolySkillsInstalled(extensionRoot: string): {
179
92
  installed: string[];
package/commands.ts CHANGED
@@ -31,6 +31,10 @@ import {
31
31
  type SolyState,
32
32
  } from "./core.ts";
33
33
  import type { SolyConfig } from "./config.ts";
34
+ import { migrateSolyDir } from "./migrate.js";
35
+ import { initSolyProject } from "./init.js";
36
+ import { readNotifications, formatNotifications } from "./notifications-log.js";
37
+ import { formatStatus } from "./status.js";
34
38
 
35
39
  /** Minimum ui surface the command handlers actually need. */
36
40
  export interface CommandUI {
@@ -332,6 +336,48 @@ What must the LLM do?
332
336
  });
333
337
 
334
338
  // ============================================================================
339
+ // /soly migrate — move .soly/ → .agents/ atomically
340
+ // ============================================================================
341
+ pi.registerCommand("soly-migrate", {
342
+ description:
343
+ "migrate project state from .soly/ to .agents/ (atomic rename, validates result, suggests git commit)",
344
+ handler: async (args, ctx) => {
345
+ const ui: CommandUI = {
346
+ notify: (t, k) => ctx.ui.notify(t, k ?? "info"),
347
+ select: async (label, options) => {
348
+ const result = await ctx.ui.select(label, options);
349
+ return result === undefined ? null : options.indexOf(result);
350
+ },
351
+ confirm: (title, message) => ctx.ui.confirm(title, message),
352
+ };
353
+ await migrateSolyDir(ctx.cwd, ui, { autoYes: args.includes("--yes") });
354
+ },
355
+ });
356
+
357
+ // /soly init — scaffold new project
358
+ // ============================================================================
359
+ pi.registerCommand("soly-init", {
360
+ description:
361
+ "scaffold a new soly project (interactive: pick template — minimal/web-app/library/cli)",
362
+ handler: async (args, ctx) => {
363
+ const ui: CommandUI = {
364
+ notify: (t, k) => ctx.ui.notify(t, k ?? "info"),
365
+ select: async (label, options) => {
366
+ const result = await ctx.ui.select(label, options);
367
+ return result === undefined ? null : options.indexOf(result);
368
+ },
369
+ confirm: (title, message) => ctx.ui.confirm(title, message),
370
+ input: (label, placeholder) => ctx.ui.input(label, placeholder),
371
+ };
372
+ // Parse args: --template=X, --yes, --name=X
373
+ const template = (args.match(/--template[= ](\S+)/)?.[1] as
374
+ | "minimal" | "web-app" | "library" | "cli" | undefined) ?? undefined;
375
+ const autoYes = args.includes("--yes");
376
+ const projectName = args.match(/--name[= ](\S+)/)?.[1];
377
+ await initSolyProject(ctx.cwd, ui, { template, autoYes, projectName });
378
+ },
379
+ });
380
+
335
381
  // /soly
336
382
  // ============================================================================
337
383
 
package/config.ts CHANGED
@@ -35,12 +35,10 @@ export interface SolyConfig {
35
35
  preferAskPro: boolean;
36
36
  /** When soly pause is invoked, also auto-save HANDOFF.json (currently always true; knob for future). */
37
37
  autoCheckpointOnPause: boolean;
38
- /** Opt-in: install soly-manager agent config to
39
- * ~/.pi/agent/agents/ on session_start. The single soly subagent
40
- * is mode-switching (worker/debugger/tester/reviewer/etc. based on
41
- * the task brief). Off by default — most users don't need a soly-
42
- * specialized subagent since the workflow template already
43
- * contains soly instructions. */
38
+ /** DEPRECATED in 1.3.0. Soly no longer ships a subagent. The LLM
39
+ * executes plans directly using the slash commands + the
40
+ * soly-framework skill. This option is kept as a no-op for
41
+ * backward compat with old config files. */
44
42
  useSolyWorkerSubagents: boolean;
45
43
  };
46
44
  display: {
package/index.ts CHANGED
@@ -88,9 +88,10 @@ export default function solyExtension(pi: ExtensionAPI) {
88
88
  // ============================================================================
89
89
  // Rotor switcher: REMOVED. The rotor cycler is now owned by the
90
90
  // separate `pi-switch` extension (footer pill + Ctrl+Tab + /rotor slash).
91
- // Soly owns a single subagent (soly-manager.md) and the auto-install on
92
- // opt-in. Workflows read the current agent from
93
- // globalThis.__PI_SWITCH_AGENT__ (set by pi-switch).
91
+ // Soly no longer ships a subagent (removed in 1.3.0). The LLM does plan
92
+ // execution directly using slash commands + the soly-framework skill.
93
+ // Workflows read the current rotor from globalThis.__PI_SWITCH_ROTOR__
94
+ // (set by pi-switch).
94
95
  // ============================================================================
95
96
 
96
97
  // Config (per-project + global + defaults). Refreshed on session_start
@@ -354,7 +355,7 @@ export default function solyExtension(pi: ExtensionAPI) {
354
355
  pi.on("session_start", async (event, ctx) => {
355
356
  // Deprecation warning: if the project still uses `.soly/`, nudge the
356
357
  // user toward `.agents/`. One-time per session.
357
- if (isLegacySolyDir(ctx.cwd)) {
358
+ if (activeConfig.agent.useSolyWorkerSubagents && isLegacySolyDir(ctx.cwd)) {
358
359
  notifyDeprecation(
359
360
  ctx.ui,
360
361
  `.soly/ (legacy)`,
@@ -405,21 +406,22 @@ export default function solyExtension(pi: ExtensionAPI) {
405
406
  }
406
407
  }
407
408
 
408
- // Auto-install soly user-scope assets (soly-manager agent + soly-framework
409
- // skill) to ~/.pi/agent/ on first run. Opt-in via config
410
- // `agent.useSolyWorkerSubagents` (default false). Idempotent — respects
411
- // any existing user-customized copies.
409
+ // Auto-install soly user-scope assets (soly-framework skill only,
410
+ // since 1.3.0 we don't ship a subagent) to ~/.pi/agent/skills/ on
411
+ // first run. Opt-in via config `agent.useSolyWorkerSubagents`
412
+ // (kept for backward compat, now a no-op for the skill install).
413
+ // Idempotent — respects any existing user-customized copies.
412
414
  if (activeConfig.agent.useSolyWorkerSubagents) {
413
415
  const extRoot = path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1"));
414
416
  const assets = installSolyAssets(extRoot);
415
- const installed = [...assets.agents.installed, ...assets.skills.installed.map((s) => `skill:${s}`)];
417
+ const installed = assets.skills.installed.map((s) => `skill:${s}`);
416
418
  if (installed.length > 0) {
417
419
  ctx.ui.notify(
418
- `soly: installed (${installed.join(", ")}) — run \`/subagents-doctor\` to verify`,
420
+ `soly: installed (${installed.join(", ")}) — pi will discover on next session`,
419
421
  "info",
420
422
  );
421
423
  }
422
- for (const e of [...assets.agents.errors, ...assets.skills.errors]) {
424
+ for (const e of assets.skills.errors) {
423
425
  ctx.ui.notify(`soly: install error — ${e}`, "warning");
424
426
  }
425
427
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-soly",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Project management framework for pi-coding-agent. Workflows, planning, multi-question picker, agent switcher, live task list — one npm install, zero config.",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -38,7 +38,6 @@
38
38
  "scratchpad.ts",
39
39
  "tools.ts",
40
40
  "agents-install.ts",
41
- "agents",
42
41
  "ask",
43
42
  "skills",
44
43
  "switch",
@@ -27,7 +27,7 @@ The **soly** extension adds project-management workflow to [pi-coding-agent](htt
27
27
  | Command | What it does |
28
28
  |---|---|
29
29
  | `/plan [N]` | Generate or update `PLAN.md` for phase N (or current phase) |
30
- | `/execute [N[.MM]]` | Dispatch plan(s) to `soly-manager` subagent. `N` = all plans in phase. `N.MM` = specific plan. |
30
+ | `/execute [N[.MM]]` | Execute plan(s) in phase N. `N` = all plans. `N.MM` = specific plan. The LLM (you) executes directly. |
31
31
  | `/discuss N` | Discussion-driven scoping for phase N — capture decisions before planning |
32
32
  | `/inspect` | One-screen summary: position, phases, recent decisions |
33
33
  | `/pause` | Save handoff (`HANDOFF.json` + `.continue-here.md`) for later resume |
@@ -211,24 +211,11 @@ Once production commits exist, returning without a committed `SUMMARY.md` is an
211
211
 
212
212
  Switch with `/rotor <name>` or `Ctrl+Tab` (cycles through). Footer pill shows current: `· ⚡ worker` / `▶ 🐢 oracle`.
213
213
 
214
- **Why "rotors"?** Because they *rotate* — `Ctrl+Tab` cycles through them. The word emphasizes the cycling behavior. Subagents (like `soly-manager`) are still called agentsthey're a different concept (spawned for a task, not cycled through).
214
+ **Why "rotors"?** Because they *rotate* — `Ctrl+Tab` cycles through them. The word emphasizes the cycling behavior. As of 1.3.0 there are no soly subagentsonly the cycle rotors.
215
215
 
216
- ## Subagent: soly-manager (single, mode-switching)
216
+ ## Subagent: none (as of 1.3.0)
217
217
 
218
- Spawn via `subagent({ agent: "soly-manager", task: ... })`. The task brief tells it which mode to be in:
219
-
220
- | Task brief mentions | Mode |
221
- |---|---|
222
- | implement, build, write code, add feature, create | **worker** |
223
- | debug, bug, fix, crash, error, repro, broken | **debugger** |
224
- | test, coverage, spec, assert, only modify tests | **tester** |
225
- | review, audit, adversarial, find bugs, qa | **reviewer** |
226
- | refactor, simplify, extract, rename, no behavior change | **refactor** |
227
- | document, readme, jsdoc, comment, intent doc | **documenter** |
228
- | validate, scope, drift, decision, before committing | **oracle** |
229
- | plan, design, outline, structure, decompose | **planner** |
230
-
231
- **soly-manager is ONE agent that switches modes. Don't spawn soly-worker / soly-debugger / etc. — those don't exist anymore.**
218
+ **Soly no longer ships a subagent.** You (the LLM) execute plans directly in the main session using the slash commands above. If you need help, use pi's built-in cycle agents (\`worker\`, \`oracle\`, \`scout\`, \`reviewer\`) or the user's custom agents in \`~/.agents/\`. Don't spawn \`soly-manager\` / \`soly-worker\` / etc. — they don't exist.
232
219
 
233
220
  ## Tools the LLM can call
234
221
 
@@ -358,7 +345,7 @@ If `/execute` complains about illegal partial state:
358
345
 
359
346
  - ❌ Edit `.soly/rules/` files you didn't write — those are project invariants
360
347
  - ❌ Skip the SUMMARY — illegal partial state
361
- - ❌ Spawn `soly-worker` or `soly-debugger` — use `soly-manager` (mode-switches)
348
+ - ❌ Spawn `soly-manager` / `soly-worker` / etc. there are no soly subagents (removed in 1.3.0). Use pi's built-in subagents via the parent LLM's \`subagent(...)\` call.
362
349
  - ❌ Write rules in code comments — use `.soly/rules/*.md` or `.agents/rules/*.md` files
363
350
  - ❌ Edit `.soly/phases/*/PLAN.md` after `status: in_progress` — create a new plan
364
351
  - ❌ Put intent docs anywhere other than `.soly/docs/` (or `.agents/docs/` for vendor-neutral)
package/switch/README.md CHANGED
@@ -25,7 +25,7 @@ Agents are markdown files with YAML frontmatter. pi-subagents (and pi-switch) di
25
25
  |---|---|---|
26
26
  | `~/.pi/agent/npm/node_modules/pi-subagents/agents/*.md` | built-in (worker, oracle, scout, reviewer) | ❌ |
27
27
  | `~/.pi/agent/agents/*.md` | user-defined | ✅ |
28
- | `~/.pi/agent/extensions/pi-soly/agents/*.md` (auto-installed if `useSolyWorkerSubagents: true` in `.soly/config.json`) | soly-manager (mode-switching subagent) | source |
28
+ | `~/.pi/agent/extensions/pi-soly/agents/*.md` (auto-installed if `useSolyWorkerSubagents: true` in `.soly/config.json`) | (removed in 1.3.0 — soly no longer ships a subagent) | |
29
29
 
30
30
  ### Frontmatter schema
31
31
 
@@ -61,7 +61,7 @@ You'll be prompted for a one-liner description. Then edit the file to specialize
61
61
  |---|---|
62
62
  | See current + available | `/agent` |
63
63
  | Cycle | `Ctrl+Tab` (or `F2`) |
64
- | Set explicitly | `/agent soly-manager` |
64
+ | Set explicitly | `(n/a — no soly subagent since 1.3.0)` |
65
65
  | Diagnose | `/agent doctor` |
66
66
  | Recommend for a task | `/agent recommend investigate React Server Components` |
67
67
 
@@ -76,7 +76,7 @@ The LLM's system prompt includes a table mapping task keywords to agents. When t
76
76
  | scout, scan, map, where is, locate, skim | 🔍 scout | codebase recon |
77
77
  | review, audit, check, adversarial, critique, qa | 👀 reviewer | adversarial review |
78
78
  | oracle, decision, tradeoff, which approach, drift | 🔮 oracle | decision consistency |
79
- | implement, build, write code, add feature, debug, fix, test, refactor, document, plan, validate | ⚡ soly-manager | workflow executor, mode-switches from task brief |
79
+ | implement, build, write code, add feature, debug, fix, test, refactor, document, plan, validate | ⚡ worker | do it yourself using slash commands |
80
80
  | (anything else) | ⚡ worker | generic implementation |
81
81
 
82
82
  Same keywords in Russian work (изучи, баг, тест, etc.).
@@ -84,7 +84,7 @@ Same keywords in Russian work (изучи, баг, тест, etc.).
84
84
  ## Integration with other extensions
85
85
 
86
86
  - **pi-soly** reads `globalThis.__PI_SWITCH_AGENT__` to know which cycle agent is active. Falls back to `"worker"` if pi-switch isn't loaded.
87
- - **pi-soly** also auto-installs `soly-manager.md` (single mode-switching subagent) to `~/.pi/agent/agents/` when `useSolyWorkerSubagents: true` in `.soly/config.json`.
87
+ - **pi-soly no longer ships a subagent** (removed in 1.3.0). The LLM in the main session executes plans directly using the slash commands + the `soly-framework` skill.
88
88
 
89
89
  ## Files
90
90
 
package/switch/index.ts CHANGED
@@ -35,6 +35,7 @@ import {
35
35
  saveAgent,
36
36
  } from "./core.ts";
37
37
  import { buildPiSwitchSection, recommendAgent } from "./prompt.ts";
38
+ import { watchRotors, type WatcherHandle } from "./watcher.ts";
38
39
 
39
40
  const GLOBAL_KEY = "__PI_SWITCH_ROTOR__";
40
41
 
@@ -43,6 +44,22 @@ export default function piSwitchExtension(pi: ExtensionAPI) {
43
44
  let currentRotor: string = DEFAULT_ROTOR;
44
45
  let cycle: string[] = [DEFAULT_ROTOR];
45
46
  let lastUi: ExtensionUIContext | null = null;
47
+ let rotorWatcher: WatcherHandle | null = null;
48
+
49
+ function startRotorWatcher(): void {
50
+ // Already running — restart to pick up new cwd
51
+ rotorWatcher?.stop();
52
+ rotorWatcher = watchRotors(cwd, {
53
+ onChange: () => {
54
+ refreshCycle();
55
+ publish();
56
+ rerender();
57
+ },
58
+ onNotify: (msg) => {
59
+ lastUi?.notify(`pi-switch: ${msg}`, "info");
60
+ },
61
+ });
62
+ }
46
63
 
47
64
  function refreshCycle(): void {
48
65
  cycle = availableAgents(cwd);
@@ -87,6 +104,7 @@ export default function piSwitchExtension(pi: ExtensionAPI) {
87
104
  refreshCycle();
88
105
  publish();
89
106
  rerender();
107
+ startRotorWatcher();
90
108
  });
91
109
 
92
110
  // ----- before_agent_start: inject system-prompt section -----
package/switch/prompt.ts CHANGED
@@ -19,7 +19,7 @@ export const TASK_AGENT_HINTS: ReadonlyArray<{
19
19
  agent: "scout", emoji: "\ud83d\udd0d",
20
20
  why: "codebase recon, patterns, file locations" },
21
21
  { pattern: /\b(plan|design|architect|outline|structure|break\s*down|steps|order)\b/i,
22
- agent: "soly-manager", emoji: "\ud83d\udccb",
22
+ agent: "worker", emoji: "\ud83d\udccb",
23
23
  why: "decompose into ordered steps, identify risks" },
24
24
  { pattern: /\b(review|audit|check|adversarial|critique|find\s+bugs|qa)\b/i,
25
25
  agent: "reviewer", emoji: "\ud83d\udc40",
@@ -28,22 +28,22 @@ export const TASK_AGENT_HINTS: ReadonlyArray<{
28
28
  agent: "oracle", emoji: "\ud83d\udd2e",
29
29
  why: "decision consistency, hidden assumptions, drift detection" },
30
30
  { pattern: /\b(debug|bug|fix|crash|error|stack\s*trace|repro|why\s+is\s+this\s+broken)\b/i,
31
- agent: "soly-manager", emoji: "\ud83d\udc1e",
31
+ agent: "worker", emoji: "\ud83d\udc1e",
32
32
  why: "isolated bug investigation with minimal repro" },
33
33
  { pattern: /\b(test|tests|coverage|spec|assert)\b/i,
34
- agent: "soly-manager", emoji: "\ud83e\uddea",
34
+ agent: "worker", emoji: "\ud83e\uddea",
35
35
  why: "test-only work, never modifies prod code" },
36
36
  { pattern: /\b(refactor|clean\s*up|simplify|extract|rename|restructure|no\s+behavior\s+change)\b/i,
37
- agent: "soly-manager", emoji: "\ud83d\udd04",
37
+ agent: "worker", emoji: "\ud83d\udd04",
38
38
  why: "pure refactoring, behavior-preserving" },
39
39
  { pattern: /\b(document|docs|readme|jsdoc|comment|annotate)\b/i,
40
- agent: "soly-manager", emoji: "\ud83d\udcdd",
40
+ agent: "worker", emoji: "\ud83d\udcdd",
41
41
  why: "doc updates, READMEs, inline annotations" },
42
42
  { pattern: /\b(implement|build|write\s+code|add\s+feature|create\s+the)\b/i,
43
43
  agent: "worker", emoji: "\u26a1",
44
44
  why: "generic implementation with all tools" },
45
45
  { pattern: /\b(orchestrate|coordinate|dispatch|chain|run\s+in\s+parallel|first\s+.+\s+then)\b/i,
46
- agent: "soly-manager", emoji: "\ud83e\udd1d",
46
+ agent: "worker", emoji: "\ud83e\udd1d",
47
47
  why: "multi-agent orchestration" },
48
48
  // Russian keywords (loose match — Russian words inflect heavily; we match
49
49
  // word stems, accepting some false positives as the cost of broader coverage)
@@ -54,7 +54,7 @@ export const TASK_AGENT_HINTS: ReadonlyArray<{
54
54
  agent: "scout", emoji: "\ud83d\udd0d",
55
55
  why: "codebase recon, patterns, file locations" },
56
56
  { pattern: /(спланир|plan|design|architect)/i,
57
- agent: "soly-manager", emoji: "\ud83d\udccb",
57
+ agent: "worker", emoji: "\ud83d\udccb",
58
58
  why: "decompose into ordered steps, identify risks" },
59
59
  { pattern: /(проверь|ревью|аудит|review|audit)/i,
60
60
  agent: "reviewer", emoji: "\ud83d\udc40",
@@ -63,22 +63,22 @@ export const TASK_AGENT_HINTS: ReadonlyArray<{
63
63
  agent: "oracle", emoji: "\ud83d\udd2e",
64
64
  why: "decision consistency, hidden assumptions, drift detection" },
65
65
  { pattern: /(баг|ошибк|почему\s+(?:падает|ломает)|debug|bug|crash|stack\s*trace|repro)/i,
66
- agent: "soly-manager", emoji: "\ud83d\udc1e",
66
+ agent: "worker", emoji: "\ud83d\udc1e",
67
67
  why: "isolated bug investigation with minimal repro" },
68
68
  { pattern: /(тест|покрыт|test|coverage|spec|assert)/i,
69
- agent: "soly-manager", emoji: "\ud83e\uddea",
69
+ agent: "worker", emoji: "\ud83e\uddea",
70
70
  why: "test-only work, never modifies prod code" },
71
71
  { pattern: /(рефактор|упрост|refactor|simplify|extract|restructure)/i,
72
- agent: "soly-manager", emoji: "\ud83d\udd04",
72
+ agent: "worker", emoji: "\ud83d\udd04",
73
73
  why: "pure refactoring, behavior-preserving" },
74
74
  { pattern: /(документ|описани|document|readme|jsdoc)/i,
75
- agent: "soly-manager", emoji: "\ud83d\udcdd",
75
+ agent: "worker", emoji: "\ud83d\udcdd",
76
76
  why: "doc updates, READMEs, inline annotations" },
77
77
  { pattern: /(реализуй|сделай|напиши|создай|implement|build|add\s+feature|create\s+the)/i,
78
78
  agent: "worker", emoji: "\u26a1",
79
79
  why: "generic implementation with all tools" },
80
80
  { pattern: /(оркестрируй|координируй|orchestrate|coordinate|dispatch|chain)/i,
81
- agent: "soly-manager", emoji: "\ud83e\udd1d",
81
+ agent: "worker", emoji: "\ud83e\udd1d",
82
82
  why: "multi-agent orchestration" },
83
83
  ];
84
84
 
@@ -108,7 +108,7 @@ The current agent is shown in the footer status line as \`[emoji name]\`.
108
108
 
109
109
  When you need a specialist for a sub-task, use the right agent via the parent LLM's \`subagent(...)\` call.
110
110
 
111
- **Soly subagent:** there is exactly one \`soly-manager\`. It's a workflow executor that switches modes (worker/debugger/tester/reviewer/refactor/documenter/oracle/planner) based on the task brief. For any soly plan execution, spawn \`soly-manager\` via \`subagent(...)\` with the task it picks the right mode itself.
111
+ **No soly subagent.** As of 1.3.0, soly no longer ships a subagent. The LLM in the main session executes plans directly using the slash commands (\`/plan\`, \`/execute\`, etc.) and the \`soly-framework\` skill. Use pi's built-in subagents (\`worker\`, \`oracle\`, \`scout\`, \`reviewer\`) for read-only research.
112
112
 
113
113
  **Task → agent heuristics.** Before launching a generic \`subagent(...)\`, scan the request for these keywords:
114
114
 
@@ -117,14 +117,14 @@ When you need a specialist for a sub-task, use the right agent via the parent LL
117
117
  | scout, scan, map, find all, where is, locate, explore codebase, skim | 🔍 scout | codebase recon, patterns, file locations |
118
118
  | review, audit, check, adversarial, critique, find bugs, qa | 👀 reviewer | adversarial correctness, security, style review |
119
119
  | oracle, decision, tradeoff, compare, which approach, is this wise, drift | 🔮 oracle | decision consistency, hidden assumptions |
120
- | implement, build, write code, add feature, create the, debug, fix, test, refactor, document, plan, validate | ⚡ soly-manager | workflow executor, picks mode from task brief |
121
- | (anything else) | ⚡ worker | generic implementation, all tools |
120
+ | (anything else, including implement, debug, fix, test, refactor, document, plan) | ⚡ worker | generic implementation, all tools prefer to do it yourself |
122
121
 
123
122
  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.
124
123
 
125
124
  DON'T:
126
125
  - Launch a worker for analysis (use oracle/scout/reviewer)
127
126
  - Launch an oracle for implementation (it has no write tools)
127
+ - Spawn \`soly-manager\` / \`soly-worker\` / etc. — there are no soly subagents anymore (as of 1.3.0)
128
128
  - Spawn soly-worker / soly-debugger / soly-tester — there is only \`soly-manager\`
129
129
  - Manually edit \`.soly/agent\` or \`~/.pi-switch/agent\` — use the slash command
130
130
  `;
@@ -21,8 +21,8 @@ describe("buildPiSwitchSection", () => {
21
21
  expect(s).toContain("scout");
22
22
  expect(s).toContain("worker");
23
23
  });
24
- test("mentions soly-manager as the single subagent", () => {
25
- expect(s).toContain("soly-manager");
24
+ test("mentions worker as the main cycle rotor", () => {
25
+ expect(s).toContain("worker");
26
26
  });
27
27
  test("explains user-defined", () => {
28
28
  expect(s).toMatch(/user[- ]?defined/i);
@@ -60,36 +60,36 @@ describe("recommendAgent", () => {
60
60
  expect(recommendAgent("Изучи React Server Components")?.agent).toBe("researcher");
61
61
  expect(recommendAgent("Найди инфу про Zustand")?.agent).toBe("researcher");
62
62
  });
63
- test("debug keywords → soly-manager", () => {
64
- expect(recommendAgent("fix this bug")?.agent).toBe("soly-manager");
65
- expect(recommendAgent("why is this crash happening")?.agent).toBe("soly-manager");
66
- expect(recommendAgent("repro the failing test")?.agent).toBe("soly-manager");
67
- expect(recommendAgent("Почему падает тест?")?.agent).toBe("soly-manager");
63
+ test("debug keywords → worker", () => {
64
+ expect(recommendAgent("fix this bug")?.agent).toBe("worker");
65
+ expect(recommendAgent("why is this crash happening")?.agent).toBe("worker");
66
+ expect(recommendAgent("repro the failing test")?.agent).toBe("worker");
67
+ expect(recommendAgent("Почему падает тест?")?.agent).toBe("worker");
68
68
  });
69
- test("refactor keywords → soly-manager", () => {
70
- expect(recommendAgent("refactor this function")?.agent).toBe("soly-manager");
71
- expect(recommendAgent("simplify the auth flow")?.agent).toBe("soly-manager");
72
- expect(recommendAgent("Упрости эту функцию")?.agent).toBe("soly-manager");
69
+ test("refactor keywords → worker", () => {
70
+ expect(recommendAgent("refactor this function")?.agent).toBe("worker");
71
+ expect(recommendAgent("simplify the auth flow")?.agent).toBe("worker");
72
+ expect(recommendAgent("Упрости эту функцию")?.agent).toBe("worker");
73
73
  });
74
- test("test keywords → soly-manager", () => {
75
- expect(recommendAgent("write tests for the parser")?.agent).toBe("soly-manager");
76
- expect(recommendAgent("improve coverage")?.agent).toBe("soly-manager");
77
- expect(recommendAgent("Напиши тесты для парсера")?.agent).toBe("soly-manager");
74
+ test("test keywords → worker", () => {
75
+ expect(recommendAgent("write tests for the parser")?.agent).toBe("worker");
76
+ expect(recommendAgent("improve coverage")?.agent).toBe("worker");
77
+ expect(recommendAgent("Напиши тесты для парсера")?.agent).toBe("worker");
78
78
  });
79
79
  test("review keywords → reviewer", () => {
80
80
  expect(recommendAgent("review this PR")?.agent).toBe("reviewer");
81
81
  expect(recommendAgent("audit the security")?.agent).toBe("reviewer");
82
82
  expect(recommendAgent("Проверь этот код")?.agent).toBe("reviewer");
83
83
  });
84
- test("docs keywords → soly-manager", () => {
85
- expect(recommendAgent("update the readme")?.agent).toBe("soly-manager");
86
- expect(recommendAgent("add jsdoc to the function")?.agent).toBe("soly-manager");
87
- expect(recommendAgent("Обнови документацию")?.agent).toBe("soly-manager");
84
+ test("docs keywords → worker", () => {
85
+ expect(recommendAgent("update the readme")?.agent).toBe("worker");
86
+ expect(recommendAgent("add jsdoc to the function")?.agent).toBe("worker");
87
+ expect(recommendAgent("Обнови документацию")?.agent).toBe("worker");
88
88
  });
89
- test("plan keywords → soly-manager", () => {
90
- expect(recommendAgent("plan the migration")?.agent).toBe("soly-manager");
91
- expect(recommendAgent("design the API")?.agent).toBe("soly-manager");
92
- expect(recommendAgent("Спланируй миграцию")?.agent).toBe("soly-manager");
89
+ test("plan keywords → worker", () => {
90
+ expect(recommendAgent("plan the migration")?.agent).toBe("worker");
91
+ expect(recommendAgent("design the API")?.agent).toBe("worker");
92
+ expect(recommendAgent("Спланируй миграцию")?.agent).toBe("worker");
93
93
  });
94
94
  test("implement keywords → worker", () => {
95
95
  expect(recommendAgent("implement the feature")?.agent).toBe("worker");
@@ -0,0 +1,147 @@
1
+ // =============================================================================
2
+ // tests/watcher.test.ts — Tests for rotor hot-reload watcher
3
+ // =============================================================================
4
+
5
+ /// <reference types="bun-types" />
6
+ import { describe, test, expect, beforeAll, afterAll } from "bun:test";
7
+ import * as fs from "node:fs";
8
+ import * as os from "node:os";
9
+ import * as path from "node:path";
10
+ import { watchRotors } from "../watcher.js";
11
+
12
+ let tmpRoot: string;
13
+ let fakeHome: string;
14
+ let origHome: string | undefined;
15
+ let origUserProfile: string | undefined;
16
+
17
+ beforeAll(() => {
18
+ tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "rotor-watch-"));
19
+ fakeHome = path.join(tmpRoot, "home");
20
+ fs.mkdirSync(fakeHome, { recursive: true });
21
+ origHome = process.env.HOME;
22
+ origUserProfile = process.env.USERPROFILE;
23
+ process.env.HOME = fakeHome;
24
+ process.env.USERPROFILE = fakeHome;
25
+ });
26
+
27
+ afterAll(() => {
28
+ if (origHome !== undefined) process.env.HOME = origHome;
29
+ if (origUserProfile !== undefined) process.env.USERPROFILE = origUserProfile;
30
+ fs.rmSync(tmpRoot, { recursive: true, force: true });
31
+ });
32
+
33
+ function sleep(ms: number): Promise<void> {
34
+ return new Promise((r) => setTimeout(r, ms));
35
+ }
36
+
37
+ describe("watchRotors", () => {
38
+ test("calls onChange when a rotor .md is added", async () => {
39
+ const projectDir = fs.mkdtempSync(path.join(tmpRoot, "proj-"));
40
+ fs.mkdirSync(path.join(projectDir, ".agents"), { recursive: true });
41
+ let changes = 0;
42
+ const handle = watchRotors(projectDir, {
43
+ home: fakeHome,
44
+ onChange: () => { changes++; },
45
+ });
46
+ try {
47
+ fs.writeFileSync(path.join(projectDir, ".agents", "new-rotor.md"), "---\nname: new-rotor\n---\n# body");
48
+ await sleep(400);
49
+ expect(changes).toBeGreaterThan(0);
50
+ } finally {
51
+ handle.stop();
52
+ fs.rmSync(projectDir, { recursive: true, force: true });
53
+ }
54
+ });
55
+
56
+ test("coalesces multiple rapid changes into one onChange", async () => {
57
+ const projectDir = fs.mkdtempSync(path.join(tmpRoot, "proj-"));
58
+ fs.mkdirSync(path.join(projectDir, ".agents"), { recursive: true });
59
+ let changes = 0;
60
+ const handle = watchRotors(projectDir, {
61
+ home: fakeHome,
62
+ onChange: () => { changes++; },
63
+ });
64
+ try {
65
+ // Burst: 3 quick writes
66
+ fs.writeFileSync(path.join(projectDir, ".agents", "a.md"), "x");
67
+ await sleep(50);
68
+ fs.writeFileSync(path.join(projectDir, ".agents", "a.md"), "xy");
69
+ await sleep(50);
70
+ fs.writeFileSync(path.join(projectDir, ".agents", "a.md"), "xyz");
71
+ await sleep(400); // wait past debounce
72
+ // All three bursts collapse into ~1-2 calls (debounce)
73
+ expect(changes).toBeLessThan(3);
74
+ expect(changes).toBeGreaterThanOrEqual(1);
75
+ } finally {
76
+ handle.stop();
77
+ fs.rmSync(projectDir, { recursive: true, force: true });
78
+ }
79
+ });
80
+
81
+ test("ignores non-.md files", async () => {
82
+ const projectDir = fs.mkdtempSync(path.join(tmpRoot, "proj-"));
83
+ fs.mkdirSync(path.join(projectDir, ".agents"), { recursive: true });
84
+ let changes = 0;
85
+ const handle = watchRotors(projectDir, {
86
+ home: fakeHome,
87
+ onChange: () => { changes++; },
88
+ });
89
+ try {
90
+ fs.writeFileSync(path.join(projectDir, ".agents", "notes.txt"), "ignore me");
91
+ fs.writeFileSync(path.join(projectDir, ".agents", ".hidden.md"), "also ignore");
92
+ await sleep(300);
93
+ expect(changes).toBe(0);
94
+ } finally {
95
+ handle.stop();
96
+ fs.rmSync(projectDir, { recursive: true, force: true });
97
+ }
98
+ });
99
+
100
+ test("calls onNotify with summary", async () => {
101
+ const projectDir = fs.mkdtempSync(path.join(tmpRoot, "proj-"));
102
+ fs.mkdirSync(path.join(projectDir, ".agents"), { recursive: true });
103
+ const notifies: string[] = [];
104
+ const handle = watchRotors(projectDir, {
105
+ home: fakeHome,
106
+ onChange: () => {},
107
+ onNotify: (msg) => { notifies.push(msg); },
108
+ });
109
+ try {
110
+ fs.writeFileSync(path.join(projectDir, ".agents", "x.md"), "x");
111
+ await sleep(1000); // debounce (200) + coalesce (500) + buffer
112
+ expect(notifies.length).toBeGreaterThan(0);
113
+ expect(notifies[0]).toContain("rotors reloaded");
114
+ } finally {
115
+ handle.stop();
116
+ fs.rmSync(projectDir, { recursive: true, force: true });
117
+ }
118
+ });
119
+
120
+ test("stop() prevents further callbacks", async () => {
121
+ const projectDir = fs.mkdtempSync(path.join(tmpRoot, "proj-"));
122
+ fs.mkdirSync(path.join(projectDir, ".agents"), { recursive: true });
123
+ let changes = 0;
124
+ const handle = watchRotors(projectDir, {
125
+ home: fakeHome,
126
+ onChange: () => { changes++; },
127
+ });
128
+ handle.stop();
129
+ fs.writeFileSync(path.join(projectDir, ".agents", "a.md"), "x");
130
+ await sleep(300);
131
+ expect(changes).toBe(0);
132
+ fs.rmSync(projectDir, { recursive: true, force: true });
133
+ });
134
+
135
+ test("handles non-existent dirs gracefully (creates them)", () => {
136
+ const projectDir = fs.mkdtempSync(path.join(tmpRoot, "proj-"));
137
+ // Don't create .agents — watcher should create it
138
+ const handle = watchRotors(projectDir, {
139
+ home: fakeHome,
140
+ onChange: () => {},
141
+ });
142
+ // .agents should now exist (created by watchRotors)
143
+ expect(fs.existsSync(path.join(projectDir, ".agents"))).toBe(true);
144
+ handle.stop();
145
+ fs.rmSync(projectDir, { recursive: true, force: true });
146
+ });
147
+ });
@@ -0,0 +1,112 @@
1
+ // =============================================================================
2
+ // watcher.ts — Hot-reload the rotor cycle when rotor .md files change
3
+ // =============================================================================
4
+ //
5
+ // Watches all known rotor home dirs (project + user) and triggers a cycle
6
+ // refresh + brief notify when a .md file is added/removed/changed. The
7
+ // watcher is debounced (editors save in bursts) and stops cleanly on
8
+ // extension reload.
9
+ //
10
+ // Why: previously, adding a new rotor .md to `.agents/` only took effect on
11
+ // the next Ctrl+Tab. With this watcher, the new rotor appears in the next
12
+ // pill render — no user action required.
13
+ // =============================================================================
14
+
15
+ import * as fs from "node:fs";
16
+ import * as os from "node:os";
17
+ import * as path from "node:path";
18
+ import { rotorHomeDirs } from "./core.js";
19
+
20
+ /** Debounce window for file events (editors save in bursts). */
21
+ const DEBOUNCE_MS = 200;
22
+ /** Coalesce window for the "rotors reloaded" notify. */
23
+ const NOTIFY_COALESCE_MS = 500;
24
+
25
+ export interface WatcherOptions {
26
+ /** Called when rotors change (debounced). */
27
+ onChange: () => void;
28
+ /** Called with a debounced message about what changed. */
29
+ onNotify?: (message: string) => void;
30
+ /** Override HOME for tests. */
31
+ home?: string;
32
+ }
33
+
34
+ export interface WatcherHandle {
35
+ stop: () => void;
36
+ }
37
+
38
+ /** Watch all rotor home dirs for *.md add/remove/change. */
39
+ export function watchRotors(cwd: string | undefined, opts: WatcherOptions): WatcherHandle {
40
+ const home = opts.home ?? process.env.HOME ?? process.env.USERPROFILE ?? os.homedir();
41
+ const dirs = rotorHomeDirs(cwd).map((d) => d.replace(/^~/, home));
42
+
43
+ const watchers: fs.FSWatcher[] = [];
44
+ let debounceTimer: ReturnType<typeof setTimeout> | null = null;
45
+ let notifyTimer: ReturnType<typeof setTimeout> | null = null;
46
+ let pendingReasons: string[] = [];
47
+ let stopped = false;
48
+
49
+ const fire = () => {
50
+ if (stopped) return;
51
+ opts.onChange();
52
+ };
53
+
54
+ const scheduleNotify = (reason: string) => {
55
+ pendingReasons.push(reason);
56
+ if (notifyTimer) clearTimeout(notifyTimer);
57
+ notifyTimer = setTimeout(() => {
58
+ const reasons = [...new Set(pendingReasons)];
59
+ pendingReasons = [];
60
+ notifyTimer = null;
61
+ if (opts.onNotify) {
62
+ const summary =
63
+ reasons.length === 1
64
+ ? reasons[0]!
65
+ : `${reasons.length} changes (${reasons.slice(0, 3).join(", ")}${reasons.length > 3 ? "…" : ""})`;
66
+ opts.onNotify(`rotors reloaded (${summary})`);
67
+ }
68
+ }, NOTIFY_COALESCE_MS);
69
+ };
70
+
71
+ const onEvent = (event: "add" | "change" | "unlink", filename: string | null) => {
72
+ if (stopped) return;
73
+ if (!filename || !filename.endsWith(".md")) return;
74
+ // Skip dotfiles (frontmatter dumps, etc.)
75
+ if (filename.startsWith(".")) return;
76
+ // Coalesce
77
+ if (debounceTimer) clearTimeout(debounceTimer);
78
+ debounceTimer = setTimeout(() => {
79
+ debounceTimer = null;
80
+ scheduleNotify(event);
81
+ fire();
82
+ }, DEBOUNCE_MS);
83
+ };
84
+
85
+ for (const dir of dirs) {
86
+ // Ensure dir exists before watching (fs.watch errors on non-existent)
87
+ try {
88
+ fs.mkdirSync(dir, { recursive: true });
89
+ } catch { /* ignore */ }
90
+ try {
91
+ const w = fs.watch(dir, { persistent: false }, (_eventType, filename) => {
92
+ onEvent(_eventType as "add" | "change" | "unlink", filename);
93
+ });
94
+ watchers.push(w);
95
+ } catch (err) {
96
+ // Some dirs may not exist or be unwatchable. Skip silently.
97
+ // eslint-disable-next-line no-console
98
+ console.error(`[pi-soly] cannot watch ${dir}: ${(err as Error).message}`);
99
+ }
100
+ }
101
+
102
+ return {
103
+ stop: () => {
104
+ stopped = true;
105
+ if (debounceTimer) clearTimeout(debounceTimer);
106
+ if (notifyTimer) clearTimeout(notifyTimer);
107
+ for (const w of watchers) {
108
+ try { w.close(); } catch { /* ignore */ }
109
+ }
110
+ },
111
+ };
112
+ }
@@ -1,124 +0,0 @@
1
- ---
2
- name: soly-manager
3
- description: Soly workflow executor. Handles any soly task end-to-end — plan, execute, debug, test, review, refactor, document. Reads the workflow brief passed by the parent and picks the right role for the task. The single writer/reviewer for soly projects.
4
- thinking: high
5
- systemPromptMode: replace
6
- inheritProjectContext: true
7
- inheritSkills: false
8
- tools: read, grep, find, ls, bash, edit, write
9
- defaultContext: fork
10
- defaultReads: context.md, plan.md
11
- defaultProgress: true
12
- ---
13
-
14
- You are `soly-manager`: the workflow executor for the **soly** project-management extension.
15
-
16
- The parent agent passes you a task with one of these roles. **Pick the right one based on the task brief, not on your name:**
17
-
18
- | Task brief mentions | You are in mode | Your job |
19
- |---|---|---|
20
- | implement, build, write code, add feature, create | **worker** | Write the code, run verification, commit |
21
- | debug, bug, fix, crash, error, repro, broken | **debugger** | Repro → isolate → fix → regression test |
22
- | test, coverage, spec, assert, only modify tests | **tester** | Write tests, run full suite, never touch prod |
23
- | review, audit, adversarial, find bugs, qa | **reviewer** | Read-only review with file:line evidence |
24
- | refactor, simplify, extract, rename, no behavior change | **refactor** | Behavior-preserving structural change |
25
- | document, readme, jsdoc, comment, intent doc | **documenter** | Update docs, never change product behavior |
26
- | validate, scope, drift, decision, before committing | **oracle** | Read-only consistency check, no edits |
27
- | plan, design, outline, structure, decompose | **planner** | Ordered steps with risks; not code |
28
-
29
- **You are one agent that switches modes. You are not seven agents.** The system prompt above is your only persona — the task brief tells you which hat to wear.
30
-
31
- ## Soly-aware defaults (apply in every mode)
32
-
33
- **Path discipline — NON-NEGOTIABLE.** All soly-managed files live under `.soly/`:
34
- - `PLAN.md`, `CONTEXT.md`, `RESEARCH.md`, `SUMMARY.md` → `.soly/phases/<NN>-<slug>/`
35
- - iteration files → `.soly/iterations/`
36
- - handoffs → `.soly/HANDOFF.json`, `.soly/.continue-here.md`
37
- - rules → `.soly/rules/` (NEVER edit — version-controlled)
38
- - All other files (source code, tests) → normal project dirs
39
-
40
- **Close-out order** (when working a plan): production-code commit(s) → SUMMARY commit → `STATUS: done` update.
41
- Once production commits exist, returning without a committed SUMMARY is an **illegal partial-plan state**.
42
-
43
- **Frontmatter contract** for `PLAN.md`: `id`, `title`, `status: pending|in_progress|done`, `phase`, `depends-on`, `parallelizable`. Read frontmatter first.
44
-
45
- **pi-todo integration** (auto-tracks plan sub-tasks if `todo_update` tool is available):
46
- 1. At task start: call `todo_update` with all `status: "pending"`
47
- 2. Set first to `in_progress` before starting
48
- 3. Update as you go: `pending` → `in_progress` → `completed`
49
- 4. Clear list (`todo_update({todos: []})`) after SUMMARY committed
50
- Skip silently if `todo_update` is not available.
51
-
52
- ## Read first (soly-aware order)
53
-
54
- 1. `.soly/STATE.md` — milestone, current position, recent decisions
55
- 2. `.soly/ROADMAP.md` — overall phase plan
56
- 3. The target `PLAN.md` (the contract) if a plan is in scope
57
- 4. `<phase>-CONTEXT.md` if it exists (honor user decisions)
58
- 5. `<phase>-RESEARCH.md` if it exists (use chosen libs/patterns)
59
-
60
- **Iteration context file** (if the parent references one) is a pre-aggregated bundle. If given, read that INSTEAD of the individual files.
61
-
62
- ## Mode-specific discipline
63
-
64
- These are the few hard rules per mode. Follow them or fail loudly.
65
-
66
- ### As worker (implement)
67
- - Atomic edits only — no speculative scaffolding, no TODO comments
68
- - Per task: read `<read_first>` → minimal correct change → verify `<acceptance_criteria>` (HARD GATE; log deviation after 2 failed fix attempts) → run `<verification>` → commit with `<type>(${PHASE}-${PLAN}): <summary>`
69
- - Do NOT edit `.soly/rules/`
70
-
71
- ### As debugger (fix)
72
- - **Reproduce first.** No fix without a repro. If the user gave a stack trace, build a minimal test that triggers it. If they said "X is broken", find one test case that demonstrates it.
73
- - **Isolate.** Git blame, grep, bisect. State the root cause in one sentence before fixing.
74
- - **Fix the cause, not the symptom.** Extra null checks, swallowed errors, type casts mask the bug.
75
- - **Regression test.** If a test would have caught this, write it. Run the full suite.
76
-
77
- ### As tester
78
- - **Hard rule:** you can edit `*.test.*`, `*.spec.*`, `tests/`, `__tests__/`, `test/`. You CANNOT edit anything else. If a test fails because of a prod bug, STOP and report — don't "fix" the prod code.
79
- - Match the project's existing test style. Don't introduce a new style.
80
- - Test behavior, not implementation. Black-box > white-box.
81
-
82
- ### As reviewer (adversarial)
83
- - **Read-only.** Do NOT edit files. Do NOT fix bugs. Do NOT commit. You produce a review with file:line evidence; the parent decides what to do.
84
- - Read spec → read test → read impl → diff them. Where do they disagree?
85
- - Pick 3-4 relevant review angles (correctness, security, performance, maintainability, soly-style).
86
- - Specific over vague: "Line 47: SQL injection. Use parameterized query." not "the code is buggy".
87
-
88
- ### As refactor
89
- - **Behavior preservation is the entire point.** If a test starts failing, you've changed behavior — that's a bug, not a refactor.
90
- - Smallest possible diff. Run tests after EVERY change.
91
- - Don't refactor AND fix a bug. Two concerns = unreviewable.
92
- - If you find a bug, stop, log it, finish the refactor without touching it.
93
-
94
- ### As documenter
95
- - **You do NOT change product code.** You write READMEs, JSDoc, `.soly/docs/`, ADRs.
96
- - Update, don't append. If the README has an "Architecture" section, edit in place.
97
- - Link, don't repeat. 5 lines + a link > 50 lines of pasted explanation.
98
- - Don't add marketing fluff ("this powerful, elegant framework...").
99
-
100
- ### As oracle (validate)
101
- - **Read-only.** No edits, no code, no new workflow trees.
102
- - Check: drift, hidden assumptions, scope creep, missing prerequisites, repeated mistakes, unresolved `depends-on`.
103
- - Sometimes the answer is "this shouldn't be a soly plan at all" — say so.
104
- - Output: inherited decisions → drift check → hidden assumptions → missing prereqs → scope check → recommendation → confidence.
105
-
106
- ### As planner
107
- - Output ordered steps with explicit risks. No code. No "let me also...".
108
- - Each step: description, depends-on, verification (test or command), acceptance criteria.
109
- - If the parent asks for a plan, give a plan. Don't drift into implementation.
110
-
111
- ## Returning
112
-
113
- Your final response should follow this shape:
114
-
115
- ```
116
- Mode: <worker | debugger | tester | reviewer | refactor | documenter | oracle | planner>
117
- Did: <one-sentence summary of what you did or found>
118
- Changed files: <list, or "none" for read-only modes>
119
- Validation: <test/typecheck/build output, or "n/a" for read-only modes>
120
- Open risks / decisions needing approval: <list, or "none">
121
- Recommended next step: <one line>
122
- ```
123
-
124
- Be concise. The parent synthesizes, not you.