aicm 0.20.1 → 0.20.5

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.
@@ -1,50 +0,0 @@
1
- export type HookType = "beforeShellExecution" | "beforeMCPExecution" | "afterShellExecution" | "afterMCPExecution" | "beforeReadFile" | "afterFileEdit" | "beforeSubmitPrompt" | "stop";
2
- export interface HookCommand {
3
- command: string;
4
- }
5
- export interface HooksJson {
6
- version: number;
7
- hooks: {
8
- [K in HookType]?: HookCommand[];
9
- };
10
- }
11
- export interface HookFile {
12
- name: string;
13
- basename: string;
14
- content: Buffer;
15
- sourcePath: string;
16
- source: "local" | "preset";
17
- presetName?: string;
18
- }
19
- /**
20
- * Load hooks configuration from a root directory
21
- * The directory should contain hooks.json (sibling to hooks/ directory)
22
- * All files in the hooks/ subdirectory are copied during installation
23
- */
24
- export declare function loadHooksFromDirectory(rootDir: string, source: "local" | "preset", presetName?: string): Promise<{
25
- config: HooksJson;
26
- files: HookFile[];
27
- }>;
28
- /**
29
- * Merge multiple hooks configurations into one
30
- * Later configurations override earlier ones for the same hook type
31
- */
32
- export declare function mergeHooksConfigs(configs: HooksJson[]): HooksJson;
33
- /**
34
- * Rewrite command paths to point to the managed hooks directory (hooks/aicm/)
35
- * At this point, paths are already namespaced filenames from loadHooksFromDirectory
36
- */
37
- export declare function rewriteHooksConfigToManagedDir(hooksConfig: HooksJson): HooksJson;
38
- /**
39
- * Count the number of hook entries in a hooks configuration
40
- */
41
- export declare function countHooks(hooksConfig: HooksJson): number;
42
- /**
43
- * Dedupe hook files by namespaced path, warn on content conflicts
44
- * Presets are namespaced with directories, so same basename from different presets won't collide
45
- */
46
- export declare function dedupeHookFiles(hookFiles: HookFile[]): HookFile[];
47
- /**
48
- * Write hooks configuration and files to Cursor target
49
- */
50
- export declare function writeHooksToCursor(hooksConfig: HooksJson, hookFiles: HookFile[], cwd: string): void;
@@ -1,346 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.loadHooksFromDirectory = loadHooksFromDirectory;
7
- exports.mergeHooksConfigs = mergeHooksConfigs;
8
- exports.rewriteHooksConfigToManagedDir = rewriteHooksConfigToManagedDir;
9
- exports.countHooks = countHooks;
10
- exports.dedupeHookFiles = dedupeHookFiles;
11
- exports.writeHooksToCursor = writeHooksToCursor;
12
- const node_crypto_1 = __importDefault(require("node:crypto"));
13
- const fs_extra_1 = __importDefault(require("fs-extra"));
14
- const node_path_1 = __importDefault(require("node:path"));
15
- const config_1 = require("./config");
16
- /**
17
- * Validate that a command path points to a file within the hooks directory
18
- * Commands should be relative paths starting with ./hooks/
19
- */
20
- function validateHookPath(commandPath, rootDir, hooksDir) {
21
- if (!commandPath.startsWith("./") && !commandPath.startsWith("../")) {
22
- return { valid: false };
23
- }
24
- // Resolve path relative to rootDir (where hooks.json is located)
25
- const resolvedPath = node_path_1.default.resolve(rootDir, commandPath);
26
- const relativePath = node_path_1.default.relative(hooksDir, resolvedPath);
27
- // Check if the file is within hooks directory (not using .. to escape)
28
- if (relativePath.startsWith("..") || node_path_1.default.isAbsolute(relativePath)) {
29
- return { valid: false };
30
- }
31
- return { valid: true, relativePath };
32
- }
33
- /**
34
- * Load all files from the hooks directory
35
- */
36
- async function loadAllFilesFromDirectory(dir, baseDir = dir) {
37
- const files = [];
38
- const entries = await fs_extra_1.default.readdir(dir, { withFileTypes: true });
39
- for (const entry of entries) {
40
- const fullPath = node_path_1.default.join(dir, entry.name);
41
- if (entry.isDirectory()) {
42
- // Recursively load files from subdirectories
43
- const subFiles = await loadAllFilesFromDirectory(fullPath, baseDir);
44
- files.push(...subFiles);
45
- }
46
- else if (entry.isFile() && entry.name !== "hooks.json") {
47
- // Skip hooks.json, collect all other files
48
- const relativePath = node_path_1.default.relative(baseDir, fullPath);
49
- files.push({ relativePath, absolutePath: fullPath });
50
- }
51
- }
52
- return files;
53
- }
54
- /**
55
- * Load hooks configuration from a root directory
56
- * The directory should contain hooks.json (sibling to hooks/ directory)
57
- * All files in the hooks/ subdirectory are copied during installation
58
- */
59
- async function loadHooksFromDirectory(rootDir, source, presetName) {
60
- const hooksFilePath = node_path_1.default.join(rootDir, "hooks.json");
61
- const hooksDir = node_path_1.default.join(rootDir, "hooks");
62
- if (!fs_extra_1.default.existsSync(hooksFilePath)) {
63
- return {
64
- config: { version: 1, hooks: {} },
65
- files: [],
66
- };
67
- }
68
- const content = await fs_extra_1.default.readFile(hooksFilePath, "utf8");
69
- const hooksConfig = JSON.parse(content);
70
- // Load all files from hooks/ subdirectory
71
- const hookFiles = [];
72
- if (fs_extra_1.default.existsSync(hooksDir)) {
73
- const allFiles = await loadAllFilesFromDirectory(hooksDir);
74
- // Create a map of all files for validation
75
- const filePathMap = new Map();
76
- for (const file of allFiles) {
77
- filePathMap.set(file.relativePath, file.absolutePath);
78
- }
79
- // Validate that all referenced commands point to files within hooks directory
80
- if (hooksConfig.hooks) {
81
- for (const hookType of Object.keys(hooksConfig.hooks)) {
82
- const hookCommands = hooksConfig.hooks[hookType];
83
- if (hookCommands && Array.isArray(hookCommands)) {
84
- for (const hookCommand of hookCommands) {
85
- const commandPath = hookCommand.command;
86
- if (commandPath && typeof commandPath === "string") {
87
- const validation = validateHookPath(commandPath, rootDir, hooksDir);
88
- if (!validation.valid || !validation.relativePath) {
89
- console.warn(`Warning: Hook command "${commandPath}" in hooks.json must reference a file within the hooks/ directory. Skipping.`);
90
- }
91
- }
92
- }
93
- }
94
- }
95
- }
96
- // Copy all files from the hooks/ directory
97
- for (const file of allFiles) {
98
- const fileContent = await fs_extra_1.default.readFile(file.absolutePath);
99
- const basename = node_path_1.default.basename(file.absolutePath);
100
- // Namespace preset files
101
- let namespacedPath;
102
- if (source === "preset" && presetName) {
103
- const namespace = (0, config_1.extractNamespaceFromPresetPath)(presetName);
104
- // Use posix paths for consistent cross-platform behavior
105
- namespacedPath = node_path_1.default.posix.join(...namespace, file.relativePath.split(node_path_1.default.sep).join(node_path_1.default.posix.sep));
106
- }
107
- else {
108
- // For local files, use the relative path as-is
109
- namespacedPath = file.relativePath.split(node_path_1.default.sep).join(node_path_1.default.posix.sep);
110
- }
111
- hookFiles.push({
112
- name: namespacedPath,
113
- basename,
114
- content: fileContent,
115
- sourcePath: file.absolutePath,
116
- source,
117
- presetName,
118
- });
119
- }
120
- }
121
- // Rewrite the config to use namespaced file names
122
- const rewrittenConfig = rewriteHooksConfigForNamespace(hooksConfig, hookFiles, rootDir);
123
- return { config: rewrittenConfig, files: hookFiles };
124
- }
125
- /**
126
- * Rewrite hooks config to use the namespaced names from the hook files
127
- */
128
- function rewriteHooksConfigForNamespace(hooksConfig, hookFiles, rootDir) {
129
- // Create a map from sourcePath to the hookFile
130
- const sourcePathToFile = new Map();
131
- for (const hookFile of hookFiles) {
132
- sourcePathToFile.set(hookFile.sourcePath, hookFile);
133
- }
134
- const rewritten = {
135
- version: hooksConfig.version,
136
- hooks: {},
137
- };
138
- if (hooksConfig.hooks) {
139
- for (const hookType of Object.keys(hooksConfig.hooks)) {
140
- const hookCommands = hooksConfig.hooks[hookType];
141
- if (hookCommands && Array.isArray(hookCommands)) {
142
- rewritten.hooks[hookType] = hookCommands
143
- .map((hookCommand) => {
144
- const commandPath = hookCommand.command;
145
- if (commandPath &&
146
- typeof commandPath === "string" &&
147
- (commandPath.startsWith("./") || commandPath.startsWith("../"))) {
148
- // Resolve path relative to rootDir (where hooks.json is)
149
- const resolvedPath = node_path_1.default.resolve(rootDir, commandPath);
150
- const hookFile = sourcePathToFile.get(resolvedPath);
151
- if (hookFile) {
152
- // Use the namespaced name
153
- return { command: hookFile.name };
154
- }
155
- // File was invalid or not found, filter it out
156
- return null;
157
- }
158
- return hookCommand;
159
- })
160
- .filter((cmd) => cmd !== null);
161
- }
162
- }
163
- }
164
- return rewritten;
165
- }
166
- /**
167
- * Merge multiple hooks configurations into one
168
- * Later configurations override earlier ones for the same hook type
169
- */
170
- function mergeHooksConfigs(configs) {
171
- const merged = {
172
- version: 1,
173
- hooks: {},
174
- };
175
- for (const config of configs) {
176
- // Use the latest version
177
- if (config.version) {
178
- merged.version = config.version;
179
- }
180
- // Merge hooks - concatenate arrays for each hook type
181
- if (config.hooks) {
182
- for (const hookType of Object.keys(config.hooks)) {
183
- const hookCommands = config.hooks[hookType];
184
- if (hookCommands && Array.isArray(hookCommands)) {
185
- if (!merged.hooks[hookType]) {
186
- merged.hooks[hookType] = [];
187
- }
188
- // Concatenate commands (later configs add to the list)
189
- merged.hooks[hookType] = [
190
- ...(merged.hooks[hookType] || []),
191
- ...hookCommands,
192
- ];
193
- }
194
- }
195
- }
196
- }
197
- return merged;
198
- }
199
- /**
200
- * Rewrite command paths to point to the managed hooks directory (hooks/aicm/)
201
- * At this point, paths are already namespaced filenames from loadHooksFromDirectory
202
- */
203
- function rewriteHooksConfigToManagedDir(hooksConfig) {
204
- const rewritten = {
205
- version: hooksConfig.version,
206
- hooks: {},
207
- };
208
- if (hooksConfig.hooks) {
209
- for (const hookType of Object.keys(hooksConfig.hooks)) {
210
- const hookCommands = hooksConfig.hooks[hookType];
211
- if (hookCommands && Array.isArray(hookCommands)) {
212
- rewritten.hooks[hookType] = hookCommands.map((hookCommand) => {
213
- const commandPath = hookCommand.command;
214
- if (commandPath && typeof commandPath === "string") {
215
- return { command: `./hooks/aicm/${commandPath}` };
216
- }
217
- return hookCommand;
218
- });
219
- }
220
- }
221
- }
222
- return rewritten;
223
- }
224
- /**
225
- * Count the number of hook entries in a hooks configuration
226
- */
227
- function countHooks(hooksConfig) {
228
- let count = 0;
229
- if (hooksConfig.hooks) {
230
- for (const hookType of Object.keys(hooksConfig.hooks)) {
231
- const hookCommands = hooksConfig.hooks[hookType];
232
- if (hookCommands && Array.isArray(hookCommands)) {
233
- count += hookCommands.length;
234
- }
235
- }
236
- }
237
- return count;
238
- }
239
- /**
240
- * Dedupe hook files by namespaced path, warn on content conflicts
241
- * Presets are namespaced with directories, so same basename from different presets won't collide
242
- */
243
- function dedupeHookFiles(hookFiles) {
244
- const fileMap = new Map();
245
- for (const hookFile of hookFiles) {
246
- const namespacedPath = hookFile.name;
247
- if (fileMap.has(namespacedPath)) {
248
- const existing = fileMap.get(namespacedPath);
249
- const existingHash = node_crypto_1.default
250
- .createHash("md5")
251
- .update(existing.content)
252
- .digest("hex");
253
- const currentHash = node_crypto_1.default
254
- .createHash("md5")
255
- .update(hookFile.content)
256
- .digest("hex");
257
- if (existingHash !== currentHash) {
258
- const sourceInfo = hookFile.presetName
259
- ? `preset "${hookFile.presetName}"`
260
- : hookFile.source;
261
- const existingSourceInfo = existing.presetName
262
- ? `preset "${existing.presetName}"`
263
- : existing.source;
264
- console.warn(`Warning: Hook file "${namespacedPath}" has different content from ${existingSourceInfo} and ${sourceInfo}. Using last occurrence.`);
265
- }
266
- // Last writer wins
267
- fileMap.set(namespacedPath, hookFile);
268
- }
269
- else {
270
- fileMap.set(namespacedPath, hookFile);
271
- }
272
- }
273
- return Array.from(fileMap.values());
274
- }
275
- /**
276
- * Write hooks configuration and files to Cursor target
277
- */
278
- function writeHooksToCursor(hooksConfig, hookFiles, cwd) {
279
- const cursorRoot = node_path_1.default.join(cwd, ".cursor");
280
- const hooksJsonPath = node_path_1.default.join(cursorRoot, "hooks.json");
281
- const hooksDir = node_path_1.default.join(cursorRoot, "hooks", "aicm");
282
- // Dedupe hook files
283
- const dedupedHookFiles = dedupeHookFiles(hookFiles);
284
- // Create hooks directory and clean it
285
- fs_extra_1.default.emptyDirSync(hooksDir);
286
- // Copy hook files to managed directory
287
- for (const hookFile of dedupedHookFiles) {
288
- const targetPath = node_path_1.default.join(hooksDir, hookFile.name);
289
- fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(targetPath));
290
- fs_extra_1.default.writeFileSync(targetPath, hookFile.content);
291
- }
292
- // Rewrite paths to point to managed directory
293
- const finalConfig = rewriteHooksConfigToManagedDir(hooksConfig);
294
- // Read existing hooks.json and preserve user hooks
295
- let existingConfig = null;
296
- if (fs_extra_1.default.existsSync(hooksJsonPath)) {
297
- try {
298
- existingConfig = fs_extra_1.default.readJsonSync(hooksJsonPath);
299
- }
300
- catch (_a) {
301
- existingConfig = null;
302
- }
303
- }
304
- // Extract user hooks (non-aicm managed)
305
- const userHooks = { version: 1, hooks: {} };
306
- if (existingConfig === null || existingConfig === void 0 ? void 0 : existingConfig.hooks) {
307
- for (const hookType of Object.keys(existingConfig.hooks)) {
308
- const commands = existingConfig.hooks[hookType];
309
- if (commands && Array.isArray(commands)) {
310
- const userCommands = commands.filter((cmd) => { var _a; return !((_a = cmd.command) === null || _a === void 0 ? void 0 : _a.includes("hooks/aicm/")); });
311
- if (userCommands.length > 0) {
312
- userHooks.hooks[hookType] = userCommands;
313
- }
314
- }
315
- }
316
- }
317
- // Merge user hooks with aicm hooks
318
- const mergedConfig = {
319
- version: finalConfig.version,
320
- hooks: {},
321
- };
322
- // Add user hooks first
323
- if (userHooks.hooks) {
324
- for (const hookType of Object.keys(userHooks.hooks)) {
325
- const commands = userHooks.hooks[hookType];
326
- if (commands) {
327
- mergedConfig.hooks[hookType] = [...commands];
328
- }
329
- }
330
- }
331
- // Then add aicm hooks
332
- if (finalConfig.hooks) {
333
- for (const hookType of Object.keys(finalConfig.hooks)) {
334
- const commands = finalConfig.hooks[hookType];
335
- if (commands) {
336
- if (!mergedConfig.hooks[hookType]) {
337
- mergedConfig.hooks[hookType] = [];
338
- }
339
- mergedConfig.hooks[hookType].push(...commands);
340
- }
341
- }
342
- }
343
- // Write hooks.json
344
- fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(hooksJsonPath));
345
- fs_extra_1.default.writeJsonSync(hooksJsonPath, mergedConfig, { spaces: 2 });
346
- }
@@ -1 +0,0 @@
1
- export declare const isCIEnvironment: () => boolean;
@@ -1,8 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isCIEnvironment = void 0;
4
- const node_process_1 = require("node:process");
5
- const isCIEnvironment = () => (node_process_1.env.CI !== "0" && node_process_1.env.CI !== "false" && "CI" in node_process_1.env) ||
6
- "CONTINUOUS_INTEGRATION" in node_process_1.env ||
7
- Object.keys(node_process_1.env).some((key) => key.startsWith("CI_"));
8
- exports.isCIEnvironment = isCIEnvironment;
@@ -1,24 +0,0 @@
1
- export type RuleMetadata = Record<string, string | boolean | string[]>;
2
- /**
3
- * Parse YAML frontmatter blocks from a rule file and return a flat metadata object
4
- */
5
- export declare function parseRuleFrontmatter(content: string): RuleMetadata;
6
- /**
7
- * Remove the rules block from the content
8
- */
9
- export declare function removeRulesBlock(content: string): string;
10
- /**
11
- * Write rules to the .windsurfrules file
12
- * This will update the content between the RULES_BEGIN and RULES_END markers
13
- * If the file doesn't exist, it will create it
14
- * If the markers don't exist, it will append them to the existing content
15
- */
16
- export declare function writeRulesFile(rulesContent: string, rulesFilePath?: string): void;
17
- /**
18
- * Generate the rules file content based on rule files
19
- */
20
- export declare function generateRulesFileContent(ruleFiles: {
21
- name: string;
22
- path: string;
23
- metadata: Record<string, string | boolean | string[]>;
24
- }[]): string;
@@ -1,197 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.parseRuleFrontmatter = parseRuleFrontmatter;
7
- exports.removeRulesBlock = removeRulesBlock;
8
- exports.writeRulesFile = writeRulesFile;
9
- exports.generateRulesFileContent = generateRulesFileContent;
10
- const fs_extra_1 = __importDefault(require("fs-extra"));
11
- const path_1 = __importDefault(require("path"));
12
- /**
13
- * Parse YAML frontmatter blocks from a rule file and return a flat metadata object
14
- */
15
- function parseRuleFrontmatter(content) {
16
- const metadata = {};
17
- // Support both LF and CRLF line endings
18
- const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---/gm;
19
- let match;
20
- while ((match = frontmatterRegex.exec(content)) !== null) {
21
- const lines = match[1].split("\n");
22
- for (const line of lines) {
23
- const trimmed = line.trim();
24
- if (!trimmed)
25
- continue;
26
- const [key, ...rest] = trimmed.split(":");
27
- if (!key)
28
- continue;
29
- const raw = rest.join(":").trim();
30
- if (raw === "") {
31
- metadata[key] = "";
32
- }
33
- else if (raw === "true" || raw === "false") {
34
- metadata[key] = raw === "true";
35
- }
36
- else if (raw.startsWith("[") && raw.endsWith("]")) {
37
- try {
38
- const parsed = JSON.parse(raw.replace(/'/g, '"'));
39
- metadata[key] = parsed;
40
- }
41
- catch (_a) {
42
- metadata[key] = raw;
43
- }
44
- }
45
- else if ((raw.startsWith('"') && raw.endsWith('"')) ||
46
- (raw.startsWith("'") && raw.endsWith("'"))) {
47
- metadata[key] = raw.slice(1, -1);
48
- }
49
- else {
50
- metadata[key] = raw;
51
- }
52
- }
53
- }
54
- return metadata;
55
- }
56
- const RULES_BEGIN = "<!-- AICM:BEGIN -->";
57
- const RULES_END = "<!-- AICM:END -->";
58
- const WARNING = "<!-- WARNING: Everything between these markers will be overwritten during installation -->";
59
- /**
60
- * Remove the rules block from the content
61
- */
62
- function removeRulesBlock(content) {
63
- // Check if our markers exist
64
- if (content.includes(RULES_BEGIN) && content.includes(RULES_END)) {
65
- const parts = content.split(RULES_BEGIN);
66
- const beforeMarker = parts[0];
67
- const afterParts = parts[1].split(RULES_END);
68
- const afterMarker = afterParts.slice(1).join(RULES_END); // In case RULES_END appears multiple times (unlikely but safe)
69
- return (beforeMarker + afterMarker).trim();
70
- }
71
- return content;
72
- }
73
- /**
74
- * Create a formatted block of content with rules markers
75
- */
76
- function createRulesBlock(rulesContent) {
77
- return `${RULES_BEGIN}
78
- ${WARNING}
79
-
80
- ${rulesContent}
81
-
82
- ${RULES_END}`;
83
- }
84
- /**
85
- * Write rules to the .windsurfrules file
86
- * This will update the content between the RULES_BEGIN and RULES_END markers
87
- * If the file doesn't exist, it will create it
88
- * If the markers don't exist, it will append them to the existing content
89
- */
90
- function writeRulesFile(rulesContent, rulesFilePath = path_1.default.join(process.cwd(), ".windsurfrules")) {
91
- let fileContent;
92
- const formattedRulesBlock = createRulesBlock(rulesContent);
93
- // Check if file exists
94
- if (fs_extra_1.default.existsSync(rulesFilePath)) {
95
- const existingContent = fs_extra_1.default.readFileSync(rulesFilePath, "utf8");
96
- // Check if our markers exist
97
- if (existingContent.includes(RULES_BEGIN) &&
98
- existingContent.includes(RULES_END)) {
99
- // Replace content between markers
100
- const beforeMarker = existingContent.split(RULES_BEGIN)[0];
101
- const afterMarker = existingContent.split(RULES_END)[1];
102
- fileContent = beforeMarker + formattedRulesBlock + afterMarker;
103
- }
104
- else {
105
- // Preserve the existing content and append markers
106
- // Ensure there's proper spacing between existing content and markers
107
- let separator = "";
108
- if (!existingContent.endsWith("\n")) {
109
- separator += "\n";
110
- }
111
- // Add an extra line if the file doesn't already end with multiple newlines
112
- if (!existingContent.endsWith("\n\n")) {
113
- separator += "\n";
114
- }
115
- // Create the new file content with preserved original content
116
- fileContent = existingContent + separator + formattedRulesBlock;
117
- }
118
- }
119
- else {
120
- // Create new file with markers and content
121
- fileContent = formattedRulesBlock;
122
- }
123
- fs_extra_1.default.writeFileSync(rulesFilePath, fileContent);
124
- }
125
- /**
126
- * Generate the rules file content based on rule files
127
- */
128
- function generateRulesFileContent(ruleFiles) {
129
- const alwaysRules = [];
130
- const autoAttachedRules = [];
131
- const agentRequestedRules = [];
132
- const manualRules = [];
133
- ruleFiles.forEach(({ path, metadata }) => {
134
- // Determine rule type based on metadata
135
- if (metadata.type === "always" ||
136
- metadata.alwaysApply === true ||
137
- metadata.alwaysApply === "true") {
138
- alwaysRules.push(path);
139
- }
140
- else if (metadata.type === "auto-attached" || metadata.globs) {
141
- const globPattern = typeof metadata.globs === "string" || Array.isArray(metadata.globs)
142
- ? Array.isArray(metadata.globs)
143
- ? metadata.globs.join(", ")
144
- : metadata.globs
145
- : undefined;
146
- if (globPattern !== undefined) {
147
- autoAttachedRules.push({ path, glob: globPattern });
148
- }
149
- }
150
- else if (metadata.type === "agent-requested" || metadata.description) {
151
- agentRequestedRules.push(path);
152
- }
153
- else {
154
- // Default to manual inclusion
155
- manualRules.push(path);
156
- }
157
- });
158
- // Generate the content
159
- let content = "";
160
- // Always rules
161
- if (alwaysRules.length > 0) {
162
- content +=
163
- "The following rules always apply to all files in the project:\n";
164
- alwaysRules.forEach((rule) => {
165
- content += `- ${rule}\n`;
166
- });
167
- content += "\n";
168
- }
169
- // Auto Attached rules
170
- if (autoAttachedRules.length > 0) {
171
- content +=
172
- "The following rules are automatically attached to matching glob patterns:\n";
173
- autoAttachedRules.forEach((rule) => {
174
- content += `- [${rule.glob}] ${rule.path}\n`;
175
- });
176
- content += "\n";
177
- }
178
- // Agent Requested rules
179
- if (agentRequestedRules.length > 0) {
180
- content +=
181
- "The following rules can be loaded when relevant. Check each file's description:\n";
182
- agentRequestedRules.forEach((rule) => {
183
- content += `- ${rule}\n`;
184
- });
185
- content += "\n";
186
- }
187
- // Manual rules
188
- if (manualRules.length > 0) {
189
- content +=
190
- "The following rules are only included when explicitly referenced:\n";
191
- manualRules.forEach((rule) => {
192
- content += `- ${rule}\n`;
193
- });
194
- content += "\n";
195
- }
196
- return content.trim();
197
- }
@@ -1,10 +0,0 @@
1
- /**
2
- * Check if a resolved path is safely within the specified base directory.
3
- * This prevents path traversal attacks where malicious paths like "../../../etc"
4
- * or absolute paths could escape the intended directory.
5
- *
6
- * @param baseDir - The directory that should contain the path
7
- * @param relativePath - The potentially untrusted relative path
8
- * @returns The safely resolved full path, or null if the path would escape baseDir
9
- */
10
- export declare function resolveSafePath(baseDir: string, relativePath: string): string | null;
@@ -1,28 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.resolveSafePath = resolveSafePath;
7
- const node_path_1 = __importDefault(require("node:path"));
8
- /**
9
- * Check if a resolved path is safely within the specified base directory.
10
- * This prevents path traversal attacks where malicious paths like "../../../etc"
11
- * or absolute paths could escape the intended directory.
12
- *
13
- * @param baseDir - The directory that should contain the path
14
- * @param relativePath - The potentially untrusted relative path
15
- * @returns The safely resolved full path, or null if the path would escape baseDir
16
- */
17
- function resolveSafePath(baseDir, relativePath) {
18
- // Resolve both to absolute paths
19
- const resolvedBase = node_path_1.default.resolve(baseDir);
20
- const resolvedTarget = node_path_1.default.resolve(baseDir, relativePath);
21
- // The resolved path must start with the base directory + separator
22
- // This ensures it's truly inside the directory, not a sibling with similar prefix
23
- // e.g., /foo/bar should not match /foo/bar-other
24
- if (!resolvedTarget.startsWith(resolvedBase + node_path_1.default.sep)) {
25
- return null;
26
- }
27
- return resolvedTarget;
28
- }
@@ -1,5 +0,0 @@
1
- /**
2
- * Helper function to execute a function within a specific working directory
3
- * and ensure the original directory is always restored
4
- */
5
- export declare function withWorkingDirectory<T>(targetDir: string, fn: () => Promise<T>): Promise<T>;