@oh-my-pi/pi-coding-agent 4.2.1 → 4.2.2

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/docs/sdk.md +5 -5
  3. package/examples/sdk/10-settings.ts +2 -2
  4. package/package.json +5 -5
  5. package/src/capability/fs.ts +90 -0
  6. package/src/capability/index.ts +41 -227
  7. package/src/capability/types.ts +1 -11
  8. package/src/cli/args.ts +4 -0
  9. package/src/core/agent-session.ts +4 -4
  10. package/src/core/agent-storage.ts +50 -0
  11. package/src/core/auth-storage.ts +102 -3
  12. package/src/core/bash-executor.ts +1 -1
  13. package/src/core/custom-tools/loader.ts +2 -2
  14. package/src/core/extensions/loader.ts +2 -2
  15. package/src/core/extensions/types.ts +1 -1
  16. package/src/core/hooks/loader.ts +2 -2
  17. package/src/core/mcp/config.ts +2 -2
  18. package/src/core/model-registry.ts +46 -0
  19. package/src/core/sdk.ts +37 -29
  20. package/src/core/settings-manager.ts +152 -135
  21. package/src/core/skills.ts +72 -51
  22. package/src/core/slash-commands.ts +3 -3
  23. package/src/core/system-prompt.ts +10 -10
  24. package/src/core/tools/edit.ts +7 -4
  25. package/src/core/tools/index.test.ts +16 -0
  26. package/src/core/tools/index.ts +21 -8
  27. package/src/core/tools/lsp/index.ts +4 -1
  28. package/src/core/tools/ssh.ts +6 -6
  29. package/src/core/tools/task/commands.ts +3 -5
  30. package/src/core/tools/task/executor.ts +88 -3
  31. package/src/core/tools/task/index.ts +4 -0
  32. package/src/core/tools/task/model-resolver.ts +10 -7
  33. package/src/core/tools/task/worker-protocol.ts +48 -2
  34. package/src/core/tools/task/worker.ts +152 -7
  35. package/src/core/tools/write.ts +7 -4
  36. package/src/discovery/agents-md.ts +13 -19
  37. package/src/discovery/builtin.ts +367 -247
  38. package/src/discovery/claude.ts +181 -290
  39. package/src/discovery/cline.ts +30 -10
  40. package/src/discovery/codex.ts +185 -244
  41. package/src/discovery/cursor.ts +106 -121
  42. package/src/discovery/gemini.ts +72 -97
  43. package/src/discovery/github.ts +7 -10
  44. package/src/discovery/helpers.ts +94 -88
  45. package/src/discovery/index.ts +1 -2
  46. package/src/discovery/mcp-json.ts +15 -18
  47. package/src/discovery/ssh.ts +9 -17
  48. package/src/discovery/vscode.ts +10 -5
  49. package/src/discovery/windsurf.ts +52 -86
  50. package/src/main.ts +5 -1
  51. package/src/modes/interactive/components/extensions/extension-dashboard.ts +24 -11
  52. package/src/modes/interactive/components/extensions/state-manager.ts +19 -15
  53. package/src/modes/interactive/controllers/selector-controller.ts +6 -2
  54. package/src/modes/interactive/interactive-mode.ts +19 -15
  55. package/src/prompts/agents/plan.md +107 -30
  56. package/src/utils/shell.ts +2 -2
  57. package/src/prompts/agents/planner.md +0 -112
@@ -33,22 +33,35 @@ import { applyFilter, createInitialState, filterByProvider, refreshState, toggle
33
33
  import type { DashboardState } from "./types";
34
34
 
35
35
  export class ExtensionDashboard extends Container {
36
- private state: DashboardState;
37
- private mainList: ExtensionList;
38
- private inspector: InspectorPanel;
36
+ private state!: DashboardState;
37
+ private mainList!: ExtensionList;
38
+ private inspector!: InspectorPanel;
39
39
  private settingsManager: SettingsManager | null;
40
40
  private cwd: string;
41
41
  private terminalHeight: number;
42
42
 
43
43
  public onClose?: () => void;
44
44
 
45
- constructor(cwd: string, settingsManager: SettingsManager | null = null, terminalHeight?: number) {
45
+ private constructor(cwd: string, settingsManager: SettingsManager | null, terminalHeight: number) {
46
46
  super();
47
47
  this.cwd = cwd;
48
48
  this.settingsManager = settingsManager;
49
- this.terminalHeight = terminalHeight ?? process.stdout.rows ?? 24;
50
- const disabledIds = settingsManager?.getDisabledExtensions() ?? [];
51
- this.state = createInitialState(cwd, disabledIds);
49
+ this.terminalHeight = terminalHeight;
50
+ }
51
+
52
+ static async create(
53
+ cwd: string,
54
+ settingsManager: SettingsManager | null = null,
55
+ terminalHeight?: number,
56
+ ): Promise<ExtensionDashboard> {
57
+ const dashboard = new ExtensionDashboard(cwd, settingsManager, terminalHeight ?? process.stdout.rows ?? 24);
58
+ await dashboard.init();
59
+ return dashboard;
60
+ }
61
+
62
+ private async init(): Promise<void> {
63
+ const disabledIds = this.settingsManager?.getDisabledExtensions() ?? [];
64
+ this.state = await createInitialState(this.cwd, disabledIds);
52
65
 
53
66
  // Calculate max visible items based on terminal height
54
67
  // Reserve ~10 lines for header, tabs, help text, borders
@@ -150,7 +163,7 @@ export class ExtensionDashboard extends Container {
150
163
 
151
164
  private handleProviderToggle(providerId: string): void {
152
165
  toggleProvider(providerId);
153
- this.refreshFromState();
166
+ void this.refreshFromState();
154
167
  }
155
168
 
156
169
  private handleExtensionToggle(extensionId: string, enabled: boolean): void {
@@ -162,15 +175,15 @@ export class ExtensionDashboard extends Container {
162
175
  this.settingsManager.disableExtension(extensionId);
163
176
  }
164
177
 
165
- this.refreshFromState();
178
+ void this.refreshFromState();
166
179
  }
167
180
 
168
- private refreshFromState(): void {
181
+ private async refreshFromState(): Promise<void> {
169
182
  // Remember current tab ID before refresh
170
183
  const currentTabId = this.state.tabs[this.state.activeTabIndex]?.id;
171
184
 
172
185
  const disabledIds = this.settingsManager?.getDisabledExtensions() ?? [];
173
- this.state = refreshState(this.state, this.cwd, disabledIds);
186
+ this.state = await refreshState(this.state, this.cwd, disabledIds);
174
187
 
175
188
  // Find the same tab in the new (re-sorted) list
176
189
  if (currentTabId) {
@@ -18,7 +18,7 @@ import {
18
18
  enableProvider,
19
19
  getAllProvidersInfo,
20
20
  isProviderEnabled,
21
- loadSync,
21
+ loadCapability,
22
22
  } from "../../../../discovery";
23
23
  import type {
24
24
  DashboardState,
@@ -42,7 +42,7 @@ export interface ExtensionSettingsManager {
42
42
  /**
43
43
  * Load all extensions from all capabilities.
44
44
  */
45
- export function loadAllExtensions(cwd?: string, disabledIds?: string[]): Extension[] {
45
+ export async function loadAllExtensions(cwd?: string, disabledIds?: string[]): Promise<Extension[]> {
46
46
  const extensions: Extension[] = [];
47
47
  const disabledExtensions = new Set<string>(disabledIds ?? []);
48
48
 
@@ -100,7 +100,7 @@ export function loadAllExtensions(cwd?: string, disabledIds?: string[]): Extensi
100
100
 
101
101
  // Load skills
102
102
  try {
103
- const skills = loadSync<Skill>("skills", loadOpts);
103
+ const skills = await loadCapability<Skill>("skills", loadOpts);
104
104
  addItems(skills.all, "skill", {
105
105
  getDescription: (s) => s.frontmatter?.description,
106
106
  getTrigger: (s) => s.frontmatter?.globs?.join(", "),
@@ -111,7 +111,7 @@ export function loadAllExtensions(cwd?: string, disabledIds?: string[]): Extensi
111
111
 
112
112
  // Load rules
113
113
  try {
114
- const rules = loadSync<Rule>("rules", loadOpts);
114
+ const rules = await loadCapability<Rule>("rules", loadOpts);
115
115
  addItems(rules.all, "rule", {
116
116
  getDescription: (r) => r.description,
117
117
  getTrigger: (r) => r.globs?.join(", ") || (r.alwaysApply ? "always" : undefined),
@@ -122,7 +122,7 @@ export function loadAllExtensions(cwd?: string, disabledIds?: string[]): Extensi
122
122
 
123
123
  // Load custom tools
124
124
  try {
125
- const tools = loadSync<CustomTool>("tools", loadOpts);
125
+ const tools = await loadCapability<CustomTool>("tools", loadOpts);
126
126
  addItems(tools.all, "tool", {
127
127
  getDescription: (t) => t.description,
128
128
  });
@@ -132,7 +132,7 @@ export function loadAllExtensions(cwd?: string, disabledIds?: string[]): Extensi
132
132
 
133
133
  // Load extension modules
134
134
  try {
135
- const modules = loadSync<ExtensionModule>("extension-modules", loadOpts);
135
+ const modules = await loadCapability<ExtensionModule>("extension-modules", loadOpts);
136
136
  const nativeModules = modules.all.filter((module) => module._source.provider === "native");
137
137
  addItems(nativeModules, "extension-module");
138
138
  } catch {
@@ -141,7 +141,7 @@ export function loadAllExtensions(cwd?: string, disabledIds?: string[]): Extensi
141
141
 
142
142
  // Load MCP servers
143
143
  try {
144
- const mcps = loadSync<MCPServer>("mcps", loadOpts);
144
+ const mcps = await loadCapability<MCPServer>("mcps", loadOpts);
145
145
  for (const server of mcps.all) {
146
146
  const id = makeExtensionId("mcp", server.name);
147
147
  const isDisabled = disabledExtensions.has(id);
@@ -184,7 +184,7 @@ export function loadAllExtensions(cwd?: string, disabledIds?: string[]): Extensi
184
184
 
185
185
  // Load prompts
186
186
  try {
187
- const prompts = loadSync<Prompt>("prompts", loadOpts);
187
+ const prompts = await loadCapability<Prompt>("prompts", loadOpts);
188
188
  addItems(prompts.all, "prompt", {
189
189
  getDescription: () => undefined,
190
190
  getTrigger: (p) => `/prompts:${p.name}`,
@@ -195,7 +195,7 @@ export function loadAllExtensions(cwd?: string, disabledIds?: string[]): Extensi
195
195
 
196
196
  // Load slash commands
197
197
  try {
198
- const commands = loadSync<SlashCommand>("slash-commands", loadOpts);
198
+ const commands = await loadCapability<SlashCommand>("slash-commands", loadOpts);
199
199
  addItems(commands.all, "slash-command", {
200
200
  getDescription: () => undefined,
201
201
  getTrigger: (c) => `/${c.name}`,
@@ -206,7 +206,7 @@ export function loadAllExtensions(cwd?: string, disabledIds?: string[]): Extensi
206
206
 
207
207
  // Load hooks
208
208
  try {
209
- const hooks = loadSync<Hook>("hooks", loadOpts);
209
+ const hooks = await loadCapability<Hook>("hooks", loadOpts);
210
210
  for (const hook of hooks.all) {
211
211
  const id = makeExtensionId("hook", `${hook.type}:${hook.tool}:${hook.name}`);
212
212
  const isDisabled = disabledExtensions.has(id);
@@ -249,7 +249,7 @@ export function loadAllExtensions(cwd?: string, disabledIds?: string[]): Extensi
249
249
 
250
250
  // Load context files
251
251
  try {
252
- const contextFiles = loadSync<ContextFile>("context-files", loadOpts);
252
+ const contextFiles = await loadCapability<ContextFile>("context-files", loadOpts);
253
253
  for (const file of contextFiles.all) {
254
254
  // Extract filename from path for display
255
255
  const name = file.path.split("/").pop() || file.path;
@@ -511,8 +511,8 @@ export function filterByProvider(extensions: Extension[], providerId: string): E
511
511
  /**
512
512
  * Create initial dashboard state.
513
513
  */
514
- export function createInitialState(cwd?: string, disabledIds?: string[]): DashboardState {
515
- const extensions = loadAllExtensions(cwd, disabledIds);
514
+ export async function createInitialState(cwd?: string, disabledIds?: string[]): Promise<DashboardState> {
515
+ const extensions = await loadAllExtensions(cwd, disabledIds);
516
516
  const tabs = buildProviderTabs(extensions);
517
517
  const tabFiltered = extensions; // "all" tab by default
518
518
  const searchFiltered = tabFiltered;
@@ -546,8 +546,12 @@ export function toggleProvider(providerId: string): boolean {
546
546
  /**
547
547
  * Refresh state after toggle.
548
548
  */
549
- export function refreshState(state: DashboardState, cwd?: string, disabledIds?: string[]): DashboardState {
550
- const extensions = loadAllExtensions(cwd, disabledIds);
549
+ export async function refreshState(
550
+ state: DashboardState,
551
+ cwd?: string,
552
+ disabledIds?: string[],
553
+ ): Promise<DashboardState> {
554
+ const extensions = await loadAllExtensions(cwd, disabledIds);
551
555
  const tabs = buildProviderTabs(extensions);
552
556
 
553
557
  // Get current provider from tabs
@@ -111,9 +111,13 @@ export class SelectorController {
111
111
  * Show the Extension Control Center dashboard.
112
112
  * Replaces /status with a unified view of all providers and extensions.
113
113
  */
114
- showExtensionsDashboard(): void {
114
+ async showExtensionsDashboard(): Promise<void> {
115
+ const dashboard = await ExtensionDashboard.create(
116
+ process.cwd(),
117
+ this.ctx.settingsManager,
118
+ this.ctx.ui.terminal.rows,
119
+ );
115
120
  this.showSelector((done) => {
116
- const dashboard = new ExtensionDashboard(process.cwd(), this.ctx.settingsManager, this.ctx.ui.terminal.rows);
117
121
  dashboard.onClose = () => {
118
122
  done();
119
123
  this.ctx.ui.requestRender();
@@ -112,6 +112,7 @@ export class InteractiveMode implements InteractiveModeContext {
112
112
  public lastStatusText: Text | undefined = undefined;
113
113
  public fileSlashCommands: Set<string> = new Set();
114
114
 
115
+ private pendingSlashCommands: SlashCommand[] = [];
115
116
  private cleanupUnsubscribe?: () => void;
116
117
  private readonly version: string;
117
118
  private readonly changelogMarkdown: string | undefined;
@@ -210,14 +211,6 @@ export class InteractiveMode implements InteractiveModeContext {
210
211
  { name: "exit", description: "Exit the application" },
211
212
  ];
212
213
 
213
- // Load and convert file commands to SlashCommand format
214
- const fileCommands = loadSlashCommands({ cwd: process.cwd() });
215
- this.fileSlashCommands = new Set(fileCommands.map((cmd) => cmd.name));
216
- const fileSlashCommands: SlashCommand[] = fileCommands.map((cmd) => ({
217
- name: cmd.name,
218
- description: cmd.description,
219
- }));
220
-
221
214
  // Convert hook commands to SlashCommand format
222
215
  const hookCommands: SlashCommand[] = (this.session.extensionRunner?.getRegisteredCommands() ?? []).map((cmd) => ({
223
216
  name: cmd.name,
@@ -230,12 +223,8 @@ export class InteractiveMode implements InteractiveModeContext {
230
223
  description: `${loaded.command.description} (${loaded.source})`,
231
224
  }));
232
225
 
233
- // Setup autocomplete
234
- const autocompleteProvider = new CombinedAutocompleteProvider(
235
- [...slashCommands, ...fileSlashCommands, ...hookCommands, ...customCommands],
236
- process.cwd(),
237
- );
238
- this.editor.setAutocompleteProvider(autocompleteProvider);
226
+ // Store pending commands for init() where file commands are loaded async
227
+ this.pendingSlashCommands = [...slashCommands, ...hookCommands, ...customCommands];
239
228
 
240
229
  this.uiHelpers = new UiHelpers(this);
241
230
  this.voiceManager = new VoiceManager(this);
@@ -252,6 +241,21 @@ export class InteractiveMode implements InteractiveModeContext {
252
241
  // Register session manager flush for signal handlers (SIGINT, SIGTERM, SIGHUP)
253
242
  this.cleanupUnsubscribe = registerAsyncCleanup(() => this.sessionManager.flush());
254
243
 
244
+ // Load and convert file commands to SlashCommand format (async)
245
+ const fileCommands = await loadSlashCommands({ cwd: process.cwd() });
246
+ this.fileSlashCommands = new Set(fileCommands.map((cmd) => cmd.name));
247
+ const fileSlashCommands: SlashCommand[] = fileCommands.map((cmd) => ({
248
+ name: cmd.name,
249
+ description: cmd.description,
250
+ }));
251
+
252
+ // Setup autocomplete with all commands
253
+ const autocompleteProvider = new CombinedAutocompleteProvider(
254
+ [...this.pendingSlashCommands, ...fileSlashCommands],
255
+ process.cwd(),
256
+ );
257
+ this.editor.setAutocompleteProvider(autocompleteProvider);
258
+
255
259
  // Get current model info for welcome screen
256
260
  const modelName = this.session.model?.name ?? "Unknown";
257
261
  const providerName = this.session.model?.provider ?? "Unknown";
@@ -560,7 +564,7 @@ export class InteractiveMode implements InteractiveModeContext {
560
564
  }
561
565
 
562
566
  showExtensionsDashboard(): void {
563
- this.selectorController.showExtensionsDashboard();
567
+ void this.selectorController.showExtensionsDashboard();
564
568
  }
565
569
 
566
570
  showModelSelector(options?: { temporaryOnly?: boolean }): void {
@@ -1,54 +1,131 @@
1
1
  ---
2
2
  name: plan
3
- description: Software architect that explores codebase and designs implementation plans (read-only)
3
+ description: Software architect for complex multi-file architectural decisions. NOT for simple tasks, single-file changes, reasoning, or tasks completable in <5 tool calls—execute those directly.
4
4
  tools: read, grep, find, ls, bash
5
- model: default
5
+ spawns: explore
6
+ model: pi/slow, gpt-5.2-codex, gpt-5.2, codex, gpt
6
7
  ---
7
8
 
8
- You are a software architect and planning specialist. Explore the codebase and design implementation plans.
9
+ <role>Senior software architect producing implementation plans. READ-ONLY no file modifications, no state changes.</role>
9
10
 
10
11
  === CRITICAL: READ-ONLY MODE ===
11
- This is a READ-ONLY planning task. You are STRICTLY PROHIBITED from:
12
-
12
+ You are STRICTLY PROHIBITED from:
13
13
  - Creating or modifying files (no Write, Edit, touch, rm, mv, cp)
14
14
  - Creating temporary files anywhere, including /tmp
15
15
  - Using redirect operators (>, >>, |) or heredocs to write files
16
16
  - Running commands that change system state (git add, git commit, npm install, pip install)
17
+ - Use bash ONLY for git status/log/diff; use read/grep/find/ls tools for file and search operations
18
+
19
+ <context>
20
+ Another engineer will execute your plan without re-exploring the codebase. Your plan must be specific enough to implement directly.
21
+ </context>
22
+
23
+ <process>
24
+ ## Phase 1: Understand
25
+
26
+ 1. Parse the task requirements precisely
27
+ 2. Identify ambiguities — list assumptions you're making
28
+ 3. Spawn parallel `explore` agents if the task spans multiple areas
29
+
30
+ ## Phase 2: Explore
31
+
32
+ Investigate thoroughly before designing:
33
+
34
+ 1. Find existing patterns via grep/find
35
+ 2. Read key files to understand current architecture
36
+ 3. Trace data flow through relevant code paths
37
+ 4. Identify types, interfaces, and contracts involved
38
+ 5. Note dependencies between components
39
+
40
+ Spawn `explore` agents for independent search areas. Synthesize findings.
41
+
42
+ ## Phase 3: Design
43
+
44
+ Create implementation approach:
45
+
46
+ 1. List concrete changes required (files, functions, types)
47
+ 2. Define the sequence — what depends on what
48
+ 3. Identify edge cases and error conditions
49
+ 4. Consider alternatives; justify your choice
50
+ 5. Note potential pitfalls or tricky parts
51
+
52
+ ## Phase 4: Produce Plan
53
+
54
+ Write a plan another engineer can execute without re-exploring the codebase.
55
+ </process>
56
+
57
+ <example>
58
+ ## Summary
59
+ What we're building and why (one paragraph).
60
+
61
+ ## Changes
62
+
63
+ 1. **`path/to/file.ts`** — What to change
64
+ - Specific modifications
65
+ 2. **`path/to/other.ts`** — ...
66
+
67
+ ## Sequence
68
+
69
+ 1. X (no dependencies)
70
+ 2. Y (depends on X)
71
+ 3. Z (integration)
72
+
73
+ ## Edge Cases
74
+
75
+ - Case: How to handle
76
+
77
+ ## Verification
78
+
79
+ - [ ] Test command or check
80
+ - [ ] Expected behavior
81
+
82
+ ## Critical Files
83
+
84
+ - `path/to/file.ts` (lines 50-120) — Why to read
85
+ </example>
86
+
87
+ <example>
88
+ ## Summary
89
+ Add rate limiting to the API gateway to prevent abuse. Requires middleware insertion and Redis integration for distributed counter storage.
17
90
 
18
- Your role is EXCLUSIVELY to explore and plan. You do NOT have access to file editing tools.
91
+ ## Changes
19
92
 
20
- ## Process
93
+ 1. **`src/middleware/rate-limit.ts`** — New file
94
+ - Create `RateLimitMiddleware` class using sliding window algorithm
95
+ - Accept `maxRequests`, `windowMs`, `keyGenerator` options
96
+ 2. **`src/gateway/index.ts`** — Wire middleware
97
+ - Import and register before auth middleware (line 45)
98
+ 3. **`src/config/redis.ts`** — Add rate limit key prefix
99
+ - Add `RATE_LIMIT_PREFIX` constant
21
100
 
22
- 1. **Understand Requirements**: Focus on the requirements provided.
101
+ ## Sequence
23
102
 
24
- 2. **Explore Thoroughly**:
25
- - Read any files provided in the initial prompt
26
- - Find existing patterns and conventions using find, grep, read
27
- - Understand the current architecture
28
- - Identify similar features as reference
29
- - Trace through relevant code paths
30
- - Use bash ONLY for git status/log/diff; use read/grep/find/ls tools for file and search operations
103
+ 1. `rate-limit.ts` (standalone, no deps)
104
+ 2. `redis.ts` (config only)
105
+ 3. `gateway/index.ts` (integration)
31
106
 
32
- 3. **Design Solution**:
33
- - Create implementation approach
34
- - Consider trade-offs and architectural decisions
35
- - Follow existing patterns where appropriate
107
+ ## Edge Cases
36
108
 
37
- 4. **Detail the Plan**:
38
- - Provide step-by-step implementation strategy
39
- - Identify dependencies and sequencing
40
- - Anticipate potential challenges
109
+ - Redis unavailable: fail open with warning log
110
+ - IPv6 addresses: normalize before using as key
41
111
 
42
- ## Required Output
112
+ ## Verification
43
113
 
44
- End your response with:
114
+ - [ ] `curl -X GET localhost:3000/api/test` 100x rapidly → 429 after limit
115
+ - [ ] Redis CLI: `KEYS rate:*` shows entries
45
116
 
46
- ### Critical Files for Implementation
117
+ ## Critical Files
47
118
 
48
- List 3-5 files most critical for implementing this plan:
119
+ - `src/middleware/auth.ts` (lines 20-50) Pattern to follow
120
+ - `src/types/middleware.ts` — Interface to implement
121
+ </example>
49
122
 
50
- - `path/to/file1.ts` - Brief reason (e.g., "Core logic to modify")
51
- - `path/to/file2.ts` - Brief reason (e.g., "Interfaces to implement")
52
- - `path/to/file3.ts` - Brief reason (e.g., "Pattern to follow")
123
+ <requirements>
124
+ - Plan must be specific enough to implement without additional exploration
125
+ - Include exact file paths and line ranges where relevant
126
+ - Sequence must respect dependencies
127
+ - Verification must be concrete and testable
128
+ </requirements>
53
129
 
130
+ Keep going until complete. This matters — get it right.
54
131
  REMEMBER: You can ONLY explore and plan. You CANNOT write, edit, or modify any files.
@@ -92,12 +92,12 @@ function buildConfig(shell: string): ShellConfig {
92
92
  * 3. On Unix: $SHELL if bash/zsh, then fallback paths
93
93
  * 4. Fallback: sh
94
94
  */
95
- export function getShellConfig(): ShellConfig {
95
+ export async function getShellConfig(): Promise<ShellConfig> {
96
96
  if (cachedShellConfig) {
97
97
  return cachedShellConfig;
98
98
  }
99
99
 
100
- const settings = SettingsManager.create();
100
+ const settings = await SettingsManager.create();
101
101
  const customShellPath = settings.getShellPath();
102
102
 
103
103
  // 1. Check user-specified shell path
@@ -1,112 +0,0 @@
1
- ---
2
- name: planner
3
- description: Software architect that explores codebase and produces detailed implementation plans
4
- tools: read, grep, find, ls, bash
5
- spawns: explore
6
- model: pi/slow, gpt-5.2-codex, gpt-5.2, codex, gpt
7
- ---
8
-
9
- <role>Senior software architect producing implementation plans. READ-ONLY — no file modifications, no state changes.</role>
10
-
11
- <context>
12
- Another engineer will execute your plan without re-exploring the codebase. Your plan must be specific enough to implement directly.
13
- </context>
14
-
15
- <process>
16
- ## Phase 1: Understand
17
-
18
- 1. Parse the task requirements precisely
19
- 2. Identify ambiguities — list assumptions you're making
20
- 3. Spawn parallel `explore` agents if the task spans multiple areas
21
-
22
- ## Phase 2: Explore
23
-
24
- Investigate thoroughly before designing:
25
-
26
- 1. Find existing patterns via grep/find
27
- 2. Read key files to understand current architecture
28
- 3. Trace data flow through relevant code paths
29
- 4. Identify types, interfaces, and contracts involved
30
- 5. Note dependencies between components
31
-
32
- Spawn `explore` agents for independent search areas. Synthesize findings.
33
-
34
- ## Phase 3: Design
35
-
36
- Create implementation approach:
37
-
38
- 1. List concrete changes required (files, functions, types)
39
- 2. Define the sequence — what depends on what
40
- 3. Identify edge cases and error conditions
41
- 4. Consider alternatives; justify your choice
42
- 5. Note potential pitfalls or tricky parts
43
-
44
- ## Phase 4: Produce Plan
45
-
46
- Write a plan another engineer can execute without re-exploring the codebase.
47
- </process>
48
-
49
- <example>
50
- ## Summary
51
- What we're building and why (one paragraph).
52
-
53
- ## Changes
54
- 1. **`path/to/file.ts`** — What to change
55
- - Specific modifications
56
- 2. **`path/to/other.ts`** — ...
57
-
58
- ## Sequence
59
- 1. X (no dependencies)
60
- 2. Y (depends on X)
61
- 3. Z (integration)
62
-
63
- ## Edge Cases
64
- - Case: How to handle
65
-
66
- ## Verification
67
- - [ ] Test command or check
68
- - [ ] Expected behavior
69
-
70
- ## Critical Files
71
- - `path/to/file.ts` (lines 50-120) — Why to read
72
- </example>
73
-
74
- <example>
75
- ## Summary
76
- Add rate limiting to the API gateway to prevent abuse. Requires middleware insertion and Redis integration for distributed counter storage.
77
-
78
- ## Changes
79
- 1. **`src/middleware/rate-limit.ts`** — New file
80
- - Create `RateLimitMiddleware` class using sliding window algorithm
81
- - Accept `maxRequests`, `windowMs`, `keyGenerator` options
82
- 2. **`src/gateway/index.ts`** — Wire middleware
83
- - Import and register before auth middleware (line 45)
84
- 3. **`src/config/redis.ts`** — Add rate limit key prefix
85
- - Add `RATE_LIMIT_PREFIX` constant
86
-
87
- ## Sequence
88
- 1. `rate-limit.ts` (standalone, no deps)
89
- 2. `redis.ts` (config only)
90
- 3. `gateway/index.ts` (integration)
91
-
92
- ## Edge Cases
93
- - Redis unavailable: fail open with warning log
94
- - IPv6 addresses: normalize before using as key
95
-
96
- ## Verification
97
- - [ ] `curl -X GET localhost:3000/api/test` 100x rapidly → 429 after limit
98
- - [ ] Redis CLI: `KEYS rate:*` shows entries
99
-
100
- ## Critical Files
101
- - `src/middleware/auth.ts` (lines 20-50) — Pattern to follow
102
- - `src/types/middleware.ts` — Interface to implement
103
- </example>
104
-
105
- <requirements>
106
- - Plan must be specific enough to implement without additional exploration
107
- - Include exact file paths and line ranges where relevant
108
- - Sequence must respect dependencies
109
- - Verification must be concrete and testable
110
- </requirements>
111
-
112
- Keep going until complete. This matters — get it right.