@oh-my-pi/pi-coding-agent 4.2.1 → 4.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/docs/sdk.md +5 -5
- package/examples/sdk/10-settings.ts +2 -2
- package/package.json +5 -5
- package/src/capability/fs.ts +90 -0
- package/src/capability/index.ts +41 -227
- package/src/capability/types.ts +1 -11
- package/src/cli/args.ts +4 -0
- package/src/core/agent-session.ts +4 -4
- package/src/core/agent-storage.ts +50 -0
- package/src/core/auth-storage.ts +112 -4
- package/src/core/bash-executor.ts +1 -1
- package/src/core/custom-tools/loader.ts +2 -2
- package/src/core/extensions/loader.ts +2 -2
- package/src/core/extensions/types.ts +1 -1
- package/src/core/hooks/loader.ts +2 -2
- package/src/core/mcp/config.ts +2 -2
- package/src/core/model-registry.ts +46 -0
- package/src/core/sdk.ts +37 -29
- package/src/core/settings-manager.ts +152 -135
- package/src/core/skills.ts +72 -51
- package/src/core/slash-commands.ts +3 -3
- package/src/core/system-prompt.ts +10 -10
- package/src/core/tools/edit.ts +7 -4
- package/src/core/tools/find.ts +2 -2
- package/src/core/tools/index.test.ts +16 -0
- package/src/core/tools/index.ts +21 -8
- package/src/core/tools/lsp/index.ts +4 -1
- package/src/core/tools/ssh.ts +6 -6
- package/src/core/tools/task/commands.ts +3 -5
- package/src/core/tools/task/executor.ts +88 -3
- package/src/core/tools/task/index.ts +4 -0
- package/src/core/tools/task/model-resolver.ts +10 -7
- package/src/core/tools/task/worker-protocol.ts +48 -2
- package/src/core/tools/task/worker.ts +152 -7
- package/src/core/tools/write.ts +7 -4
- package/src/discovery/agents-md.ts +13 -19
- package/src/discovery/builtin.ts +367 -247
- package/src/discovery/claude.ts +181 -290
- package/src/discovery/cline.ts +30 -10
- package/src/discovery/codex.ts +185 -244
- package/src/discovery/cursor.ts +106 -121
- package/src/discovery/gemini.ts +72 -97
- package/src/discovery/github.ts +7 -10
- package/src/discovery/helpers.ts +94 -88
- package/src/discovery/index.ts +1 -2
- package/src/discovery/mcp-json.ts +15 -18
- package/src/discovery/ssh.ts +9 -17
- package/src/discovery/vscode.ts +10 -5
- package/src/discovery/windsurf.ts +52 -86
- package/src/main.ts +5 -1
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +24 -11
- package/src/modes/interactive/components/extensions/state-manager.ts +19 -15
- package/src/modes/interactive/controllers/selector-controller.ts +6 -2
- package/src/modes/interactive/interactive-mode.ts +19 -15
- package/src/prompts/agents/plan.md +107 -30
- package/src/utils/shell.ts +2 -2
- 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
|
|
37
|
-
private mainList
|
|
38
|
-
private inspector
|
|
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
|
|
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
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
550
|
-
|
|
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
|
-
//
|
|
234
|
-
|
|
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
|
|
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
|
-
|
|
5
|
+
spawns: explore
|
|
6
|
+
model: pi/slow, gpt-5.2-codex, gpt-5.2, codex, gpt
|
|
6
7
|
---
|
|
7
8
|
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
|
|
91
|
+
## Changes
|
|
19
92
|
|
|
20
|
-
|
|
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
|
-
|
|
101
|
+
## Sequence
|
|
23
102
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
33
|
-
- Create implementation approach
|
|
34
|
-
- Consider trade-offs and architectural decisions
|
|
35
|
-
- Follow existing patterns where appropriate
|
|
107
|
+
## Edge Cases
|
|
36
108
|
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
##
|
|
112
|
+
## Verification
|
|
43
113
|
|
|
44
|
-
|
|
114
|
+
- [ ] `curl -X GET localhost:3000/api/test` 100x rapidly → 429 after limit
|
|
115
|
+
- [ ] Redis CLI: `KEYS rate:*` shows entries
|
|
45
116
|
|
|
46
|
-
|
|
117
|
+
## Critical Files
|
|
47
118
|
|
|
48
|
-
|
|
119
|
+
- `src/middleware/auth.ts` (lines 20-50) — Pattern to follow
|
|
120
|
+
- `src/types/middleware.ts` — Interface to implement
|
|
121
|
+
</example>
|
|
49
122
|
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
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.
|
package/src/utils/shell.ts
CHANGED
|
@@ -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.
|