@oh-my-pi/pi-coding-agent 4.2.0 → 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.
- package/CHANGELOG.md +46 -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 +7 -7
- package/src/core/agent-storage.ts +50 -0
- package/src/core/auth-storage.ts +102 -3
- package/src/core/bash-executor.ts +1 -1
- package/src/core/custom-tools/loader.ts +2 -2
- package/src/core/export-html/index.ts +1 -33
- 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 +52 -10
- package/src/core/tools/complete.ts +5 -2
- package/src/core/tools/edit.ts +7 -4
- 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 -9
- 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 +368 -293
- package/src/discovery/claude.ts +183 -345
- package/src/discovery/cline.ts +30 -10
- package/src/discovery/codex.ts +188 -272
- 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 +114 -57
- 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 +9 -5
- package/src/modes/interactive/interactive-mode.ts +22 -15
- package/src/prompts/agents/plan.md +107 -30
- package/src/prompts/agents/task.md +5 -4
- package/src/prompts/system/system-prompt.md +5 -0
- package/src/prompts/tools/task.md +25 -19
- package/src/utils/shell.ts +2 -2
- package/src/prompts/agents/architect-plan.md +0 -10
- package/src/prompts/agents/implement-with-critic.md +0 -11
- package/src/prompts/agents/implement.md +0 -11
package/src/discovery/cursor.ts
CHANGED
|
@@ -6,15 +6,15 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Sources:
|
|
8
8
|
* - User: ~/.cursor
|
|
9
|
-
* - Project: .cursor/ (
|
|
9
|
+
* - Project: .cursor/ (cwd only)
|
|
10
10
|
*
|
|
11
11
|
* Capabilities:
|
|
12
12
|
* - mcps: From mcp.json with mcpServers key
|
|
13
13
|
* - rules: From rules/*.mdc files with MDC frontmatter (description, globs, alwaysApply)
|
|
14
14
|
* - settings: From settings.json if present
|
|
15
|
-
* - Legacy: .cursorrules file in project root as a single rule
|
|
16
15
|
*/
|
|
17
16
|
|
|
17
|
+
import { readFile } from "../capability/fs";
|
|
18
18
|
import { registerProvider } from "../capability/index";
|
|
19
19
|
import { type MCPServer, mcpCapability } from "../capability/mcp";
|
|
20
20
|
import type { Rule } from "../capability/rule";
|
|
@@ -40,66 +40,62 @@ const PRIORITY = 50;
|
|
|
40
40
|
// MCP Servers
|
|
41
41
|
// =============================================================================
|
|
42
42
|
|
|
43
|
-
function
|
|
43
|
+
function parseMCPServers(
|
|
44
|
+
content: string,
|
|
45
|
+
path: string,
|
|
46
|
+
level: "user" | "project",
|
|
47
|
+
): { items: MCPServer[]; warning?: string } {
|
|
48
|
+
const items: MCPServer[] = [];
|
|
49
|
+
|
|
50
|
+
const parsed = parseJSON<{ mcpServers?: Record<string, unknown> }>(content);
|
|
51
|
+
if (!parsed?.mcpServers) {
|
|
52
|
+
return { items, warning: `${path}: missing or invalid 'mcpServers' key` };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const servers = expandEnvVarsDeep(parsed.mcpServers);
|
|
56
|
+
for (const [name, config] of Object.entries(servers)) {
|
|
57
|
+
const serverConfig = config as Record<string, unknown>;
|
|
58
|
+
items.push({
|
|
59
|
+
name,
|
|
60
|
+
command: serverConfig.command as string | undefined,
|
|
61
|
+
args: serverConfig.args as string[] | undefined,
|
|
62
|
+
env: serverConfig.env as Record<string, string> | undefined,
|
|
63
|
+
url: serverConfig.url as string | undefined,
|
|
64
|
+
headers: serverConfig.headers as Record<string, string> | undefined,
|
|
65
|
+
transport: ["stdio", "sse", "http"].includes(serverConfig.type as string)
|
|
66
|
+
? (serverConfig.type as "stdio" | "sse" | "http")
|
|
67
|
+
: undefined,
|
|
68
|
+
_source: createSourceMeta(PROVIDER_ID, path, level),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { items };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>> {
|
|
44
76
|
const items: MCPServer[] = [];
|
|
45
77
|
const warnings: string[] = [];
|
|
46
78
|
|
|
47
|
-
// User-level: ~/.cursor/mcp.json
|
|
48
79
|
const userPath = getUserPath(ctx, "cursor", "mcp.json");
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
env: serverConfig.env as Record<string, string> | undefined,
|
|
62
|
-
url: serverConfig.url as string | undefined,
|
|
63
|
-
headers: serverConfig.headers as Record<string, string> | undefined,
|
|
64
|
-
transport: ["stdio", "sse", "http"].includes(serverConfig.type as string)
|
|
65
|
-
? (serverConfig.type as "stdio" | "sse" | "http")
|
|
66
|
-
: undefined,
|
|
67
|
-
_source: createSourceMeta(PROVIDER_ID, userPath, "user"),
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
} else {
|
|
71
|
-
warnings.push(`${userPath}: missing or invalid 'mcpServers' key`);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
80
|
+
|
|
81
|
+
const [userContent, projectPath] = await Promise.all([
|
|
82
|
+
userPath ? readFile(userPath) : Promise.resolve(null),
|
|
83
|
+
getProjectPath(ctx, "cursor", "mcp.json"),
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
const projectContentPromise = projectPath ? readFile(projectPath) : Promise.resolve(null);
|
|
87
|
+
|
|
88
|
+
if (userContent && userPath) {
|
|
89
|
+
const result = parseMCPServers(userContent, userPath, "user");
|
|
90
|
+
items.push(...result.items);
|
|
91
|
+
if (result.warning) warnings.push(result.warning);
|
|
74
92
|
}
|
|
75
93
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
const parsed = parseJSON<{ mcpServers?: Record<string, unknown> }>(content);
|
|
82
|
-
if (parsed?.mcpServers) {
|
|
83
|
-
const servers = expandEnvVarsDeep(parsed.mcpServers);
|
|
84
|
-
for (const [name, config] of Object.entries(servers)) {
|
|
85
|
-
const serverConfig = config as Record<string, unknown>;
|
|
86
|
-
items.push({
|
|
87
|
-
name,
|
|
88
|
-
command: serverConfig.command as string | undefined,
|
|
89
|
-
args: serverConfig.args as string[] | undefined,
|
|
90
|
-
env: serverConfig.env as Record<string, string> | undefined,
|
|
91
|
-
url: serverConfig.url as string | undefined,
|
|
92
|
-
headers: serverConfig.headers as Record<string, string> | undefined,
|
|
93
|
-
transport: ["stdio", "sse", "http"].includes(serverConfig.type as string)
|
|
94
|
-
? (serverConfig.type as "stdio" | "sse" | "http")
|
|
95
|
-
: undefined,
|
|
96
|
-
_source: createSourceMeta(PROVIDER_ID, projectPath, "project"),
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
} else {
|
|
100
|
-
warnings.push(`${projectPath}: missing or invalid 'mcpServers' key`);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
94
|
+
const projectContent = await projectContentPromise;
|
|
95
|
+
if (projectContent && projectPath) {
|
|
96
|
+
const result = parseMCPServers(projectContent, projectPath, "project");
|
|
97
|
+
items.push(...result.items);
|
|
98
|
+
if (result.warning) warnings.push(result.warning);
|
|
103
99
|
}
|
|
104
100
|
|
|
105
101
|
return { items, warnings };
|
|
@@ -109,45 +105,34 @@ function loadMCPServers(ctx: LoadContext): LoadResult<MCPServer> {
|
|
|
109
105
|
// Rules
|
|
110
106
|
// =============================================================================
|
|
111
107
|
|
|
112
|
-
function loadRules(ctx: LoadContext): LoadResult<Rule
|
|
108
|
+
async function loadRules(ctx: LoadContext): Promise<LoadResult<Rule>> {
|
|
113
109
|
const items: Rule[] = [];
|
|
114
110
|
const warnings: string[] = [];
|
|
115
111
|
|
|
116
|
-
// Legacy: .cursorrules file in project root
|
|
117
|
-
const legacyPath = ctx.fs.walkUp(".cursorrules", { file: true });
|
|
118
|
-
if (legacyPath) {
|
|
119
|
-
const content = ctx.fs.readFile(legacyPath);
|
|
120
|
-
if (content) {
|
|
121
|
-
items.push({
|
|
122
|
-
name: "cursorrules",
|
|
123
|
-
path: legacyPath,
|
|
124
|
-
content,
|
|
125
|
-
_source: createSourceMeta(PROVIDER_ID, legacyPath, "project"),
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// User-level: ~/.cursor/rules/*.mdc
|
|
131
112
|
const userRulesPath = getUserPath(ctx, "cursor", "rules");
|
|
132
|
-
if (userRulesPath && ctx.fs.isDir(userRulesPath)) {
|
|
133
|
-
const result = loadFilesFromDir<Rule>(ctx, userRulesPath, PROVIDER_ID, "user", {
|
|
134
|
-
extensions: ["mdc", "md"],
|
|
135
|
-
transform: transformMDCRule,
|
|
136
|
-
});
|
|
137
|
-
items.push(...result.items);
|
|
138
|
-
if (result.warnings) warnings.push(...result.warnings);
|
|
139
|
-
}
|
|
140
113
|
|
|
141
|
-
// Project-level: .cursor/rules/*.mdc
|
|
142
114
|
const projectRulesPath = getProjectPath(ctx, "cursor", "rules");
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
115
|
+
|
|
116
|
+
const [userResult, projectResult] = await Promise.all([
|
|
117
|
+
userRulesPath
|
|
118
|
+
? loadFilesFromDir<Rule>(ctx, userRulesPath, PROVIDER_ID, "user", {
|
|
119
|
+
extensions: ["mdc", "md"],
|
|
120
|
+
transform: transformMDCRule,
|
|
121
|
+
})
|
|
122
|
+
: Promise.resolve({ items: [] as Rule[], warnings: undefined }),
|
|
123
|
+
projectRulesPath
|
|
124
|
+
? loadFilesFromDir<Rule>(ctx, projectRulesPath, PROVIDER_ID, "project", {
|
|
125
|
+
extensions: ["mdc", "md"],
|
|
126
|
+
transform: transformMDCRule,
|
|
127
|
+
})
|
|
128
|
+
: Promise.resolve({ items: [] as Rule[], warnings: undefined }),
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
items.push(...userResult.items);
|
|
132
|
+
if (userResult.warnings) warnings.push(...userResult.warnings);
|
|
133
|
+
|
|
134
|
+
items.push(...projectResult.items);
|
|
135
|
+
if (projectResult.warnings) warnings.push(...projectResult.warnings);
|
|
151
136
|
|
|
152
137
|
return { items, warnings };
|
|
153
138
|
}
|
|
@@ -192,45 +177,45 @@ function transformMDCRule(
|
|
|
192
177
|
// Settings
|
|
193
178
|
// =============================================================================
|
|
194
179
|
|
|
195
|
-
function loadSettings(ctx: LoadContext): LoadResult<Settings
|
|
180
|
+
async function loadSettings(ctx: LoadContext): Promise<LoadResult<Settings>> {
|
|
196
181
|
const items: Settings[] = [];
|
|
197
182
|
const warnings: string[] = [];
|
|
198
183
|
|
|
199
|
-
// User-level: ~/.cursor/settings.json
|
|
200
184
|
const userPath = getUserPath(ctx, "cursor", "settings.json");
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
185
|
+
|
|
186
|
+
const [userContent, projectPath] = await Promise.all([
|
|
187
|
+
userPath ? readFile(userPath) : Promise.resolve(null),
|
|
188
|
+
getProjectPath(ctx, "cursor", "settings.json"),
|
|
189
|
+
]);
|
|
190
|
+
|
|
191
|
+
const projectContentPromise = projectPath ? readFile(projectPath) : Promise.resolve(null);
|
|
192
|
+
|
|
193
|
+
if (userContent && userPath) {
|
|
194
|
+
const parsed = parseJSON<Record<string, unknown>>(userContent);
|
|
195
|
+
if (parsed) {
|
|
196
|
+
items.push({
|
|
197
|
+
path: userPath,
|
|
198
|
+
data: parsed,
|
|
199
|
+
level: "user",
|
|
200
|
+
_source: createSourceMeta(PROVIDER_ID, userPath, "user"),
|
|
201
|
+
});
|
|
202
|
+
} else {
|
|
203
|
+
warnings.push(`${userPath}: invalid JSON`);
|
|
215
204
|
}
|
|
216
205
|
}
|
|
217
206
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
});
|
|
231
|
-
} else {
|
|
232
|
-
warnings.push(`${projectPath}: invalid JSON`);
|
|
233
|
-
}
|
|
207
|
+
const projectContent = await projectContentPromise;
|
|
208
|
+
if (projectContent && projectPath) {
|
|
209
|
+
const parsed = parseJSON<Record<string, unknown>>(projectContent);
|
|
210
|
+
if (parsed) {
|
|
211
|
+
items.push({
|
|
212
|
+
path: projectPath,
|
|
213
|
+
data: parsed,
|
|
214
|
+
level: "project",
|
|
215
|
+
_source: createSourceMeta(PROVIDER_ID, projectPath, "project"),
|
|
216
|
+
});
|
|
217
|
+
} else {
|
|
218
|
+
warnings.push(`${projectPath}: invalid JSON`);
|
|
234
219
|
}
|
|
235
220
|
}
|
|
236
221
|
|
package/src/discovery/gemini.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Sources:
|
|
8
8
|
* - User: ~/.gemini
|
|
9
|
-
* - Project: .gemini/ (
|
|
9
|
+
* - Project: .gemini/ (cwd only)
|
|
10
10
|
*
|
|
11
11
|
* Capabilities:
|
|
12
12
|
* - mcps: From settings.json with mcpServers key
|
|
@@ -16,10 +16,11 @@
|
|
|
16
16
|
* - settings: From settings.json
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import { join, sep } from "node:path";
|
|
20
20
|
import { type ContextFile, contextFileCapability } from "../capability/context-file";
|
|
21
21
|
import { type Extension, type ExtensionManifest, extensionCapability } from "../capability/extension";
|
|
22
22
|
import { type ExtensionModule, extensionModuleCapability } from "../capability/extension-module";
|
|
23
|
+
import { readDirEntries, readFile } from "../capability/fs";
|
|
23
24
|
import { registerProvider } from "../capability/index";
|
|
24
25
|
import { type MCPServer, mcpCapability } from "../capability/mcp";
|
|
25
26
|
import { type Settings, settingsCapability } from "../capability/settings";
|
|
@@ -44,22 +45,22 @@ const PRIORITY = 60;
|
|
|
44
45
|
// MCP Servers
|
|
45
46
|
// =============================================================================
|
|
46
47
|
|
|
47
|
-
function loadMCPServers(ctx: LoadContext): LoadResult<MCPServer
|
|
48
|
+
async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>> {
|
|
48
49
|
const items: MCPServer[] = [];
|
|
49
50
|
const warnings: string[] = [];
|
|
50
51
|
|
|
51
52
|
// User-level: ~/.gemini/settings.json → mcpServers
|
|
52
53
|
const userPath = getUserPath(ctx, "gemini", "settings.json");
|
|
53
|
-
if (userPath
|
|
54
|
-
const result = loadMCPFromSettings(ctx, userPath, "user");
|
|
54
|
+
if (userPath) {
|
|
55
|
+
const result = await loadMCPFromSettings(ctx, userPath, "user");
|
|
55
56
|
items.push(...result.items);
|
|
56
57
|
if (result.warnings) warnings.push(...result.warnings);
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
// Project-level: .gemini/settings.json → mcpServers
|
|
60
61
|
const projectPath = getProjectPath(ctx, "gemini", "settings.json");
|
|
61
|
-
if (projectPath
|
|
62
|
-
const result = loadMCPFromSettings(ctx, projectPath, "project");
|
|
62
|
+
if (projectPath) {
|
|
63
|
+
const result = await loadMCPFromSettings(ctx, projectPath, "project");
|
|
63
64
|
items.push(...result.items);
|
|
64
65
|
if (result.warnings) warnings.push(...result.warnings);
|
|
65
66
|
}
|
|
@@ -67,13 +68,16 @@ function loadMCPServers(ctx: LoadContext): LoadResult<MCPServer> {
|
|
|
67
68
|
return { items, warnings };
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
function loadMCPFromSettings(
|
|
71
|
+
async function loadMCPFromSettings(
|
|
72
|
+
_ctx: LoadContext,
|
|
73
|
+
path: string,
|
|
74
|
+
level: "user" | "project",
|
|
75
|
+
): Promise<LoadResult<MCPServer>> {
|
|
71
76
|
const items: MCPServer[] = [];
|
|
72
77
|
const warnings: string[] = [];
|
|
73
78
|
|
|
74
|
-
const content =
|
|
79
|
+
const content = await readFile(path);
|
|
75
80
|
if (!content) {
|
|
76
|
-
warnings.push(`Failed to read ${path}`);
|
|
77
81
|
return { items, warnings };
|
|
78
82
|
}
|
|
79
83
|
|
|
@@ -118,14 +122,14 @@ function loadMCPFromSettings(ctx: LoadContext, path: string, level: "user" | "pr
|
|
|
118
122
|
// Context Files
|
|
119
123
|
// =============================================================================
|
|
120
124
|
|
|
121
|
-
function loadContextFiles(ctx: LoadContext): LoadResult<ContextFile
|
|
125
|
+
async function loadContextFiles(ctx: LoadContext): Promise<LoadResult<ContextFile>> {
|
|
122
126
|
const items: ContextFile[] = [];
|
|
123
127
|
const warnings: string[] = [];
|
|
124
128
|
|
|
125
129
|
// User-level: ~/.gemini/GEMINI.md
|
|
126
130
|
const userGeminiMd = getUserPath(ctx, "gemini", "GEMINI.md");
|
|
127
|
-
if (userGeminiMd
|
|
128
|
-
const content =
|
|
131
|
+
if (userGeminiMd) {
|
|
132
|
+
const content = await readFile(userGeminiMd);
|
|
129
133
|
if (content) {
|
|
130
134
|
items.push({
|
|
131
135
|
path: userGeminiMd,
|
|
@@ -138,8 +142,8 @@ function loadContextFiles(ctx: LoadContext): LoadResult<ContextFile> {
|
|
|
138
142
|
|
|
139
143
|
// Project-level: .gemini/GEMINI.md
|
|
140
144
|
const projectGeminiMd = getProjectPath(ctx, "gemini", "GEMINI.md");
|
|
141
|
-
if (projectGeminiMd
|
|
142
|
-
const content =
|
|
145
|
+
if (projectGeminiMd) {
|
|
146
|
+
const content = await readFile(projectGeminiMd);
|
|
143
147
|
if (content) {
|
|
144
148
|
const projectBase = getProjectPath(ctx, "gemini", "");
|
|
145
149
|
const depth = projectBase ? calculateDepth(ctx.cwd, projectBase, sep) : 0;
|
|
@@ -154,28 +158,6 @@ function loadContextFiles(ctx: LoadContext): LoadResult<ContextFile> {
|
|
|
154
158
|
}
|
|
155
159
|
}
|
|
156
160
|
|
|
157
|
-
// Also check for GEMINI.md in project root (without .gemini directory)
|
|
158
|
-
const rootGeminiMd = ctx.fs.walkUp("GEMINI.md", { file: true });
|
|
159
|
-
if (rootGeminiMd) {
|
|
160
|
-
const content = ctx.fs.readFile(rootGeminiMd);
|
|
161
|
-
if (content) {
|
|
162
|
-
// Only add if not already added from .gemini/GEMINI.md
|
|
163
|
-
const alreadyAdded = items.some((item) => item.path === rootGeminiMd);
|
|
164
|
-
if (!alreadyAdded) {
|
|
165
|
-
const fileDir = dirname(rootGeminiMd);
|
|
166
|
-
const depth = calculateDepth(ctx.cwd, fileDir, sep);
|
|
167
|
-
|
|
168
|
-
items.push({
|
|
169
|
-
path: rootGeminiMd,
|
|
170
|
-
content,
|
|
171
|
-
level: "project",
|
|
172
|
-
depth,
|
|
173
|
-
_source: createSourceMeta(PROVIDER_ID, rootGeminiMd, "project"),
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
161
|
return { items, warnings };
|
|
180
162
|
}
|
|
181
163
|
|
|
@@ -183,22 +165,22 @@ function loadContextFiles(ctx: LoadContext): LoadResult<ContextFile> {
|
|
|
183
165
|
// Extensions
|
|
184
166
|
// =============================================================================
|
|
185
167
|
|
|
186
|
-
function loadExtensions(ctx: LoadContext): LoadResult<Extension
|
|
168
|
+
async function loadExtensions(ctx: LoadContext): Promise<LoadResult<Extension>> {
|
|
187
169
|
const items: Extension[] = [];
|
|
188
170
|
const warnings: string[] = [];
|
|
189
171
|
|
|
190
172
|
// User-level: ~/.gemini/extensions/*/gemini-extension.json
|
|
191
173
|
const userExtPath = getUserPath(ctx, "gemini", "extensions");
|
|
192
|
-
if (userExtPath
|
|
193
|
-
const result = loadExtensionsFromDir(
|
|
174
|
+
if (userExtPath) {
|
|
175
|
+
const result = await loadExtensionsFromDir(userExtPath, "user");
|
|
194
176
|
items.push(...result.items);
|
|
195
177
|
if (result.warnings) warnings.push(...result.warnings);
|
|
196
178
|
}
|
|
197
179
|
|
|
198
180
|
// Project-level: .gemini/extensions/*/gemini-extension.json
|
|
199
181
|
const projectExtPath = getProjectPath(ctx, "gemini", "extensions");
|
|
200
|
-
if (projectExtPath
|
|
201
|
-
const result = loadExtensionsFromDir(
|
|
182
|
+
if (projectExtPath) {
|
|
183
|
+
const result = await loadExtensionsFromDir(projectExtPath, "project");
|
|
202
184
|
items.push(...result.items);
|
|
203
185
|
if (result.warnings) warnings.push(...result.warnings);
|
|
204
186
|
}
|
|
@@ -206,27 +188,24 @@ function loadExtensions(ctx: LoadContext): LoadResult<Extension> {
|
|
|
206
188
|
return { items, warnings };
|
|
207
189
|
}
|
|
208
190
|
|
|
209
|
-
function loadExtensionsFromDir(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
level: "user" | "project",
|
|
213
|
-
): LoadResult<Extension> {
|
|
214
|
-
const items: Extension[] = [];
|
|
215
|
-
const warnings: string[] = [];
|
|
191
|
+
async function loadExtensionsFromDir(extensionsDir: string, level: "user" | "project"): Promise<LoadResult<Extension>> {
|
|
192
|
+
const entries = await readDirEntries(extensionsDir);
|
|
193
|
+
const dirEntries = entries.filter((entry) => entry.isDirectory());
|
|
216
194
|
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
195
|
+
const results = await Promise.all(
|
|
196
|
+
dirEntries.map(async (entry) => {
|
|
197
|
+
const extPath = join(extensionsDir, entry.name);
|
|
198
|
+
const manifestPath = join(extPath, "gemini-extension.json");
|
|
199
|
+
const content = await readFile(manifestPath);
|
|
200
|
+
return { entry, extPath, manifestPath, content };
|
|
201
|
+
}),
|
|
202
|
+
);
|
|
221
203
|
|
|
222
|
-
|
|
223
|
-
|
|
204
|
+
const items: Extension[] = [];
|
|
205
|
+
const warnings: string[] = [];
|
|
224
206
|
|
|
225
|
-
|
|
226
|
-
if (!content)
|
|
227
|
-
warnings.push(`Failed to read ${manifestPath}`);
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
207
|
+
for (const { entry, extPath, manifestPath, content } of results) {
|
|
208
|
+
if (!content) continue;
|
|
230
209
|
|
|
231
210
|
const manifest = parseJSON<ExtensionManifest>(content);
|
|
232
211
|
if (!manifest) {
|
|
@@ -235,7 +214,7 @@ function loadExtensionsFromDir(
|
|
|
235
214
|
}
|
|
236
215
|
|
|
237
216
|
items.push({
|
|
238
|
-
name: manifest.name ??
|
|
217
|
+
name: manifest.name ?? entry.name,
|
|
239
218
|
path: extPath,
|
|
240
219
|
manifest,
|
|
241
220
|
level,
|
|
@@ -250,49 +229,45 @@ function loadExtensionsFromDir(
|
|
|
250
229
|
// Extension Modules
|
|
251
230
|
// =============================================================================
|
|
252
231
|
|
|
253
|
-
function loadExtensionModules(ctx: LoadContext): LoadResult<ExtensionModule
|
|
254
|
-
const items: ExtensionModule[] = [];
|
|
255
|
-
const warnings: string[] = [];
|
|
256
|
-
|
|
232
|
+
async function loadExtensionModules(ctx: LoadContext): Promise<LoadResult<ExtensionModule>> {
|
|
257
233
|
const userExtensionsDir = getUserPath(ctx, "gemini", "extensions");
|
|
258
|
-
if (userExtensionsDir) {
|
|
259
|
-
for (const extPath of discoverExtensionModulePaths(ctx, userExtensionsDir)) {
|
|
260
|
-
items.push({
|
|
261
|
-
name: getExtensionNameFromPath(extPath),
|
|
262
|
-
path: extPath,
|
|
263
|
-
level: "user",
|
|
264
|
-
_source: createSourceMeta(PROVIDER_ID, extPath, "user"),
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
234
|
const projectExtensionsDir = getProjectPath(ctx, "gemini", "extensions");
|
|
270
|
-
if (projectExtensionsDir) {
|
|
271
|
-
for (const extPath of discoverExtensionModulePaths(ctx, projectExtensionsDir)) {
|
|
272
|
-
items.push({
|
|
273
|
-
name: getExtensionNameFromPath(extPath),
|
|
274
|
-
path: extPath,
|
|
275
|
-
level: "project",
|
|
276
|
-
_source: createSourceMeta(PROVIDER_ID, extPath, "project"),
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
235
|
|
|
281
|
-
|
|
236
|
+
const [userPaths, projectPaths] = await Promise.all([
|
|
237
|
+
userExtensionsDir ? discoverExtensionModulePaths(ctx, userExtensionsDir) : Promise.resolve([]),
|
|
238
|
+
projectExtensionsDir ? discoverExtensionModulePaths(ctx, projectExtensionsDir) : Promise.resolve([]),
|
|
239
|
+
]);
|
|
240
|
+
|
|
241
|
+
const items: ExtensionModule[] = [
|
|
242
|
+
...userPaths.map((extPath) => ({
|
|
243
|
+
name: getExtensionNameFromPath(extPath),
|
|
244
|
+
path: extPath,
|
|
245
|
+
level: "user" as const,
|
|
246
|
+
_source: createSourceMeta(PROVIDER_ID, extPath, "user"),
|
|
247
|
+
})),
|
|
248
|
+
...projectPaths.map((extPath) => ({
|
|
249
|
+
name: getExtensionNameFromPath(extPath),
|
|
250
|
+
path: extPath,
|
|
251
|
+
level: "project" as const,
|
|
252
|
+
_source: createSourceMeta(PROVIDER_ID, extPath, "project"),
|
|
253
|
+
})),
|
|
254
|
+
];
|
|
255
|
+
|
|
256
|
+
return { items, warnings: [] };
|
|
282
257
|
}
|
|
283
258
|
|
|
284
259
|
// =============================================================================
|
|
285
260
|
// Settings
|
|
286
261
|
// =============================================================================
|
|
287
262
|
|
|
288
|
-
function loadSettings(ctx: LoadContext): LoadResult<Settings
|
|
263
|
+
async function loadSettings(ctx: LoadContext): Promise<LoadResult<Settings>> {
|
|
289
264
|
const items: Settings[] = [];
|
|
290
265
|
const warnings: string[] = [];
|
|
291
266
|
|
|
292
267
|
// User-level: ~/.gemini/settings.json
|
|
293
268
|
const userPath = getUserPath(ctx, "gemini", "settings.json");
|
|
294
|
-
if (userPath
|
|
295
|
-
const content =
|
|
269
|
+
if (userPath) {
|
|
270
|
+
const content = await readFile(userPath);
|
|
296
271
|
if (content) {
|
|
297
272
|
const parsed = parseJSON<Record<string, unknown>>(content);
|
|
298
273
|
if (parsed) {
|
|
@@ -310,8 +285,8 @@ function loadSettings(ctx: LoadContext): LoadResult<Settings> {
|
|
|
310
285
|
|
|
311
286
|
// Project-level: .gemini/settings.json
|
|
312
287
|
const projectPath = getProjectPath(ctx, "gemini", "settings.json");
|
|
313
|
-
if (projectPath
|
|
314
|
-
const content =
|
|
288
|
+
if (projectPath) {
|
|
289
|
+
const content = await readFile(projectPath);
|
|
315
290
|
if (content) {
|
|
316
291
|
const parsed = parseJSON<Record<string, unknown>>(content);
|
|
317
292
|
if (parsed) {
|
|
@@ -354,13 +329,13 @@ registerProvider(contextFileCapability.id, {
|
|
|
354
329
|
// System Prompt
|
|
355
330
|
// =============================================================================
|
|
356
331
|
|
|
357
|
-
function loadSystemPrompt(ctx: LoadContext): LoadResult<SystemPrompt
|
|
332
|
+
async function loadSystemPrompt(ctx: LoadContext): Promise<LoadResult<SystemPrompt>> {
|
|
358
333
|
const items: SystemPrompt[] = [];
|
|
359
334
|
|
|
360
335
|
// User-level: ~/.gemini/system.md
|
|
361
336
|
const userSystemMd = getUserPath(ctx, "gemini", "system.md");
|
|
362
|
-
if (userSystemMd
|
|
363
|
-
const content =
|
|
337
|
+
if (userSystemMd) {
|
|
338
|
+
const content = await readFile(userSystemMd);
|
|
364
339
|
if (content) {
|
|
365
340
|
items.push({
|
|
366
341
|
path: userSystemMd,
|
|
@@ -373,8 +348,8 @@ function loadSystemPrompt(ctx: LoadContext): LoadResult<SystemPrompt> {
|
|
|
373
348
|
|
|
374
349
|
// Project-level: .gemini/system.md
|
|
375
350
|
const projectSystemMd = getProjectPath(ctx, "gemini", "system.md");
|
|
376
|
-
if (projectSystemMd
|
|
377
|
-
const content =
|
|
351
|
+
if (projectSystemMd) {
|
|
352
|
+
const content = await readFile(projectSystemMd);
|
|
378
353
|
if (content) {
|
|
379
354
|
items.push({
|
|
380
355
|
path: projectSystemMd,
|
package/src/discovery/github.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import { basename, dirname, sep } from "node:path";
|
|
16
16
|
import { type ContextFile, contextFileCapability } from "../capability/context-file";
|
|
17
|
+
import { readFile } from "../capability/fs";
|
|
17
18
|
import { registerProvider } from "../capability/index";
|
|
18
19
|
import { type Instruction, instructionCapability } from "../capability/instruction";
|
|
19
20
|
import type { LoadContext, LoadResult, SourceMeta } from "../capability/types";
|
|
@@ -27,14 +28,13 @@ const PRIORITY = 30;
|
|
|
27
28
|
// Context Files
|
|
28
29
|
// =============================================================================
|
|
29
30
|
|
|
30
|
-
function loadContextFiles(ctx: LoadContext): LoadResult<ContextFile
|
|
31
|
+
async function loadContextFiles(ctx: LoadContext): Promise<LoadResult<ContextFile>> {
|
|
31
32
|
const items: ContextFile[] = [];
|
|
32
33
|
const warnings: string[] = [];
|
|
33
34
|
|
|
34
|
-
// Project-level: .github/copilot-instructions.md
|
|
35
35
|
const copilotInstructionsPath = getProjectPath(ctx, "github", "copilot-instructions.md");
|
|
36
|
-
if (copilotInstructionsPath
|
|
37
|
-
const content =
|
|
36
|
+
if (copilotInstructionsPath) {
|
|
37
|
+
const content = await readFile(copilotInstructionsPath);
|
|
38
38
|
if (content) {
|
|
39
39
|
const fileDir = dirname(copilotInstructionsPath);
|
|
40
40
|
const depth = calculateDepth(ctx.cwd, fileDir, sep);
|
|
@@ -46,8 +46,6 @@ function loadContextFiles(ctx: LoadContext): LoadResult<ContextFile> {
|
|
|
46
46
|
depth,
|
|
47
47
|
_source: createSourceMeta(PROVIDER_ID, copilotInstructionsPath, "project"),
|
|
48
48
|
});
|
|
49
|
-
} else {
|
|
50
|
-
warnings.push(`Failed to read ${copilotInstructionsPath}`);
|
|
51
49
|
}
|
|
52
50
|
}
|
|
53
51
|
|
|
@@ -58,14 +56,13 @@ function loadContextFiles(ctx: LoadContext): LoadResult<ContextFile> {
|
|
|
58
56
|
// Instructions
|
|
59
57
|
// =============================================================================
|
|
60
58
|
|
|
61
|
-
function loadInstructions(ctx: LoadContext): LoadResult<Instruction
|
|
59
|
+
async function loadInstructions(ctx: LoadContext): Promise<LoadResult<Instruction>> {
|
|
62
60
|
const items: Instruction[] = [];
|
|
63
61
|
const warnings: string[] = [];
|
|
64
62
|
|
|
65
|
-
// Project-level: .github/instructions/*.instructions.md
|
|
66
63
|
const instructionsDir = getProjectPath(ctx, "github", "instructions");
|
|
67
|
-
if (instructionsDir
|
|
68
|
-
const result = loadFilesFromDir<Instruction>(ctx, instructionsDir, PROVIDER_ID, "project", {
|
|
64
|
+
if (instructionsDir) {
|
|
65
|
+
const result = await loadFilesFromDir<Instruction>(ctx, instructionsDir, PROVIDER_ID, "project", {
|
|
69
66
|
extensions: ["md"],
|
|
70
67
|
transform: transformInstruction,
|
|
71
68
|
});
|