1
>
/*---------------------------------------------------------------------------------------------
promptFileLocations.ts
2
>
* Copyright (c) Microsoft Corporation. All rights reserved.
3
>
* Licensed under the MIT License. See License.txt in the project root for license information.
4
>
*--------------------------------------------------------------------------------------------*/
5
>
6
>
import { URI } from '../../../../../../base/common/uri.js';
7
>
import { basename, dirname } from '../../../../../../base/common/resources.js';
8
>
import { PromptFileSource, PromptsType } from '../promptTypes.js';
9
>
import { PromptsStorage } from '../service/promptsService.js';
10
>
import { compareIgnoreCase } from '../../../../../../base/common/strings.js';
11
>
12
>
/**
13
>
* File extension for the reusable prompt files.
14
>
*/
15
>
export const PROMPT_FILE_EXTENSION = '.prompt.md';
16
>
17
>
/**
18
>
* File extension for the reusable instruction files.
19
>
*/
20
>
export const INSTRUCTION_FILE_EXTENSION = '.instructions.md';
21
>
22
>
/**
23
>
* File extension for the modes files.
24
>
*/
25
>
export const LEGACY_MODE_FILE_EXTENSION = '.chatmode.md';
26
>
27
>
/**
28
>
* File extension for the agent files.
29
>
*/
30
>
export const AGENT_FILE_EXTENSION = '.agent.md';
31
>
32
>
/**
33
>
* Skill file name (case insensitive).
34
>
*/
35
>
export const SKILL_FILENAME = 'SKILL.md';
36
>
37
>
/**
38
>
* Check if a filename is a skill file (case insensitive).
39
>
*/
40
>
export function isSkillFilename(filename: string): boolean {
41
return compareIgnoreCase(filename, SKILL_FILENAME) === 0;
42
}
44
>
/**
45
>
* Regex for valid skill names: lowercase alphanumeric and hyphens only.
46
>
*/
47
>
export const VALID_SKILL_NAME_REGEX = /^[a-z0-9-]+$/;
48
>
49
>
/**
50
>
* AGENT file name
51
>
*/
52
>
export const AGENT_MD_FILENAME = 'AGENTS.md';
53
>
54
>
/**
55
>
* Claude file name.
56
>
*/
57
>
export const CLAUDE_MD_FILENAME = 'CLAUDE.md';
58
>
59
>
/**
60
>
* Claude local file name.
61
>
*/
62
>
export const CLAUDE_LOCAL_MD_FILENAME = 'CLAUDE.local.md';
63
>
64
>
/**
65
>
* Claude configuration folder name.
66
>
*/
67
>
export const CLAUDE_CONFIG_FOLDER = '.claude';
68
>
69
>
/**
70
>
* Copilot configuration folder name.
71
>
*/
72
>
export const COPILOT_CONFIG_FOLDER = '.copilot';
73
>
74
>
/**
75
>
* Copilot custom instructions file name.
76
>
*/
77
>
export const COPILOT_CUSTOM_INSTRUCTIONS_FILENAME = 'copilot-instructions.md';
78
>
79
>
/**
80
>
* GitHub configuration folder name.
81
>
*/
82
>
export const GITHUB_CONFIG_FOLDER = '.github';
83
>
84
>
/**
85
>
* Default reusable prompt files source folder.
86
>
*/
87
>
export const PROMPT_DEFAULT_SOURCE_FOLDER = '.github/prompts';
88
>
89
>
/**
90
>
* Default reusable instructions files source folder.
91
>
*/
92
>
export const INSTRUCTIONS_DEFAULT_SOURCE_FOLDER = '.github/instructions';
93
>
94
>
/**
95
>
* Default modes source folder.
96
>
*/
97
>
export const LEGACY_MODE_DEFAULT_SOURCE_FOLDER = '.github/chatmodes';
98
>
99
>
/**
100
>
* Agents folder.
101
>
*/
102
>
export const AGENTS_SOURCE_FOLDER = '.github/agents';
103
>
104
>
/**
105
>
* Claude agents folder.
106
>
*/
107
>
export const CLAUDE_AGENTS_SOURCE_FOLDER = '.claude/agents';
108
>
109
>
/**
110
>
* Copilot user agents folder.
111
>
*/
112
>
export const COPILOT_USER_AGENTS_SOURCE_FOLDER = '~/.copilot/agents';
113
>
114
>
/**
115
>
* Claude rules folder.
116
>
*/
117
>
export const CLAUDE_RULES_SOURCE_FOLDER = '.claude/rules';
118
>
119
>
/**
120
>
* Hooks folder.
121
>
*/
122
>
export const HOOKS_SOURCE_FOLDER = '.github/hooks';
123
>
124
>
/**
125
>
* Subset of {@link PromptFileSource} values that can appear on folder-based
126
>
* prompt source configurations (excludes extension/plugin-only sources).
127
>
*/
128
>
export type PromptFolderSource = Exclude<PromptFileSource, PromptFileSource.ExtensionContribution | PromptFileSource.ExtensionAPI | PromptFileSource.Plugin>;
129
>
130
>
/**
131
>
* Prompt source folder path with source and storage type.
132
>
*/
133
>
export interface IPromptSourceFolder {
134
>
readonly path: string;
135
>
readonly source: PromptFolderSource;
136
>
readonly storage: PromptsStorage.local | PromptsStorage.user;
137
>
}
138
>
139
>
/**
140
>
* Resolved prompt folder with source and storage type.
141
>
*/
142
>
export interface IResolvedPromptSourceFolder {
143
>
readonly uri: URI;
144
>
readonly searchRoot: URI; // matches the URI when no glob pattern is used
145
>
readonly filePattern: string | undefined; // the part of the path with the glob pattern, or undefined if no glob pattern is used
146
>
readonly source: PromptFolderSource;
147
>
readonly storage: PromptsStorage.local | PromptsStorage.user;
148
>
/**
149
>
* The original path string before resolution (e.g., '~/.copilot/agents' or '.github/agents').
150
>
* Used for display purposes.
151
>
*/
152
>
readonly displayPath?: string;
153
>
/**
154
>
* Whether this is a default location (vs user-configured).
155
>
*/
156
>
readonly isDefault?: boolean;
157
>
}
158
>
159
>
/**
160
>
* All default skill source folders (both workspace and user home).
161
>
*/
162
>
export const DEFAULT_SKILL_SOURCE_FOLDERS: readonly IPromptSourceFolder[] = [
163
>
{ path: '.agents/skills', source: PromptFileSource.AgentsWorkspace, storage: PromptsStorage.local },
164
>
{ path: '.github/skills', source: PromptFileSource.GitHubWorkspace, storage: PromptsStorage.local },
165
>
{ path: '.claude/skills', source: PromptFileSource.ClaudeWorkspace, storage: PromptsStorage.local },
166
>
{ path: '~/.agents/skills', source: PromptFileSource.AgentsPersonal, storage: PromptsStorage.user },
167
>
{ path: '~/.copilot/skills', source: PromptFileSource.CopilotPersonal, storage: PromptsStorage.user },
168
>
{ path: '~/.claude/skills', source: PromptFileSource.ClaudePersonal, storage: PromptsStorage.user },
169
>
];
170
>
171
>
/**
172
>
* Default instructions source folders.
173
>
*/
174
>
export const DEFAULT_INSTRUCTIONS_SOURCE_FOLDERS: readonly IPromptSourceFolder[] = [
175
>
{ path: INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, source: PromptFileSource.GitHubWorkspace, storage: PromptsStorage.local },
176
>
{ path: CLAUDE_RULES_SOURCE_FOLDER, source: PromptFileSource.ClaudeWorkspace, storage: PromptsStorage.local },
177
>
{ path: '~/.copilot/instructions', source: PromptFileSource.CopilotPersonal, storage: PromptsStorage.user },
178
>
{ path: '~/' + CLAUDE_RULES_SOURCE_FOLDER, source: PromptFileSource.ClaudePersonal, storage: PromptsStorage.user },
179
>
];
180
>
181
>
/**
182
>
* Default prompt source folders.
183
>
*/
184
>
export const DEFAULT_PROMPT_SOURCE_FOLDERS: readonly IPromptSourceFolder[] = [
185
>
{ path: PROMPT_DEFAULT_SOURCE_FOLDER, source: PromptFileSource.GitHubWorkspace, storage: PromptsStorage.local },
186
>
];
187
>
188
>
/**
189
>
* Default agent source folders.
190
>
*/
191
>
export const DEFAULT_AGENT_SOURCE_FOLDERS: readonly IPromptSourceFolder[] = [
192
>
{ path: AGENTS_SOURCE_FOLDER, source: PromptFileSource.GitHubWorkspace, storage: PromptsStorage.local },
193
>
{ path: CLAUDE_AGENTS_SOURCE_FOLDER, source: PromptFileSource.ClaudeWorkspace, storage: PromptsStorage.local },
194
>
{ path: COPILOT_USER_AGENTS_SOURCE_FOLDER, source: PromptFileSource.CopilotPersonal, storage: PromptsStorage.user },
195
>
{ path: '~/' + CLAUDE_AGENTS_SOURCE_FOLDER, source: PromptFileSource.ClaudePersonal, storage: PromptsStorage.user },
196
>
];
197
>
198
>
/**
199
>
* Default hook file paths.
200
>
* Entries can be either a directory or a specific file path (.json)
201
>
*/
202
>
export const DEFAULT_HOOK_FILE_PATHS: readonly IPromptSourceFolder[] = [
203
>
{ path: '.github/hooks', source: PromptFileSource.GitHubWorkspace, storage: PromptsStorage.local },
204
>
{ path: '.claude/settings.local.json', source: PromptFileSource.ClaudeWorkspaceLocal, storage: PromptsStorage.local },
205
>
{ path: '.claude/settings.json', source: PromptFileSource.ClaudeWorkspace, storage: PromptsStorage.local },
206
>
{ path: '~/.copilot/hooks', source: PromptFileSource.CopilotPersonal, storage: PromptsStorage.user },
207
>
{ path: '~/.claude/settings.json', source: PromptFileSource.ClaudePersonal, storage: PromptsStorage.user },
208
>
];
209
>
210
>
/**
211
>
* Helper function to check if a file is directly in the .github/agents/ folder (not in subfolders).
212
>
*/
213
function isInAgentsFolder(fileUri: URI): boolean {
214
const dir = dirname(fileUri).path;
215
return dir.endsWith('/' + AGENTS_SOURCE_FOLDER) || dir.endsWith('/' + CLAUDE_AGENTS_SOURCE_FOLDER) || isInCopilotAgentsFolder(fileUri);
216
}
218
>
/**
219
>
* Helper function to check if a file is directly in the .claude/agents/ folder.
220
>
*/
221
>
export function isInClaudeAgentsFolder(fileUri: URI): boolean {
222
const dir = dirname(fileUri).path;
223
return dir.endsWith('/' + CLAUDE_AGENTS_SOURCE_FOLDER);
224
}
226
>
/**
227
>
* Helper function to check if a file is directly in the ~/.copilot/agents/ folder.
228
>
*/
229
>
export function isInCopilotAgentsFolder(fileUri: URI): boolean {
230
const dir = dirname(fileUri).path;
231
return dir.endsWith(COPILOT_USER_AGENTS_SOURCE_FOLDER.substring(1));
232
}
234
>
/**
235
>
* Helper function to check if a file is inside the .claude/rules/ folder (including subfolders).
236
>
* Claude rules files (.md) in this folder are treated as instruction files.
237
>
*/
238
>
export function isInClaudeRulesFolder(fileUri: URI): boolean {
239
const path = fileUri.path;
240
return path.includes('/' + CLAUDE_RULES_SOURCE_FOLDER + '/');
241
}
243
>
/**
244
>
* Gets the prompt file type from the provided path.
245
>
*
246
>
* Note: This function assumes the URI is already known to be a prompt file
247
>
* (e.g., from a configured prompt source folder). It does not validate that
248
>
* arbitrary URIs are prompt files - for example, any .json file will return
249
>
* PromptsType.hook regardless of its location.
250
>
*/
251
>
export function getPromptFileType(fileUri: URI): PromptsType | undefined {
252
const filename = basename(fileUri);
253