aicm 0.11.0 → 0.12.1
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/README.md +30 -3
- package/dist/commands/install.js +20 -13
- package/dist/index.js +0 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/utils/glob-handler.d.ts +35 -0
- package/dist/utils/glob-handler.js +125 -0
- package/dist/utils/rule-collector.js +5 -0
- package/dist/utils/rule-status.js +10 -0
- package/dist/utils/rule-writer.js +10 -7
- package/dist/utils/rules-file-writer.d.ts +15 -0
- package/dist/utils/rules-file-writer.js +137 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
A CLI tool for managing Agentic IDE configurations across projects
|
|
6
6
|
|
|
7
|
-
https://github.com/user-attachments/assets/
|
|
7
|
+

|
|
8
8
|
|
|
9
9
|
## Why
|
|
10
10
|
|
|
@@ -94,7 +94,7 @@ When you run `npx aicm install`, all rules from the preset will be installed to
|
|
|
94
94
|
### Notes
|
|
95
95
|
|
|
96
96
|
- Generated rules are always placed in a subdirectory for deterministic cleanup and easy gitignore.
|
|
97
|
-
- Users may add `.cursor/rules/aicm/` and `.aicm/` (for Windsurf) to their `.gitignore` if they do not want to track generated rules.
|
|
97
|
+
- Users may add `.cursor/rules/aicm/` and `.aicm/` (for Windsurf/Codex) to their `.gitignore` if they do not want to track generated rules.
|
|
98
98
|
|
|
99
99
|
### Overriding and Disabling Rules and MCP Servers from Presets
|
|
100
100
|
|
|
@@ -219,13 +219,39 @@ Example `aicm.json`:
|
|
|
219
219
|
|
|
220
220
|
- `"cursor"`: For the Cursor IDE
|
|
221
221
|
- `"windsurf"`: For the Windsurf IDE
|
|
222
|
+
- `"codex"`: For the Codex Agent
|
|
222
223
|
|
|
223
224
|
> **Note:** The 'ides' field is default to `["cursor"]` if not specified.
|
|
224
225
|
|
|
225
226
|
- **rules**: Object containing rule configurations
|
|
226
227
|
|
|
227
228
|
- **rule-name**: A unique identifier for the rule. Can include a directory path to install the rule to a specific directory.
|
|
228
|
-
- **source-location**: Location of the rule file (path within an npm package or local path)
|
|
229
|
+
- **source-location**: Location of the rule file (path within an npm package or local path). Supports glob patterns for automatic file discovery.
|
|
230
|
+
|
|
231
|
+
#### Glob Pattern Support
|
|
232
|
+
|
|
233
|
+
Rules support glob patterns for automatic discovery of multiple `.mdc` files. This is particularly useful when you have multiple related rules organized in directories.
|
|
234
|
+
|
|
235
|
+
```json
|
|
236
|
+
{
|
|
237
|
+
"rules": {
|
|
238
|
+
"typescript": "./rules/typescript/*.mdc",
|
|
239
|
+
"tests": "./rules/testing/**/*.mdc"
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
The key becomes the base namespace for discovered files:
|
|
245
|
+
|
|
246
|
+
- `./rules/typescript/strict.mdc` → installed as `typescript/strict`
|
|
247
|
+
- `./rules/typescript/interfaces.mdc` → installed as `typescript/interfaces`
|
|
248
|
+
- `./rules/testing/unit/setup.mdc` → installed as `tests/unit/setup`
|
|
249
|
+
|
|
250
|
+
**Installation Behavior:**
|
|
251
|
+
|
|
252
|
+
- Glob patterns are expanded during installation
|
|
253
|
+
- Only `.mdc` files are included
|
|
254
|
+
- Files are sorted alphabetically for consistent behavior
|
|
229
255
|
|
|
230
256
|
- **mcpServers**: Object containing MCP server configurations. Each key is a unique server name, and the value is an object with either:
|
|
231
257
|
|
|
@@ -267,6 +293,7 @@ Rules stored locally in your project or filesystem. Any path containing slashes
|
|
|
267
293
|
|
|
268
294
|
- **Cursor**: Rules are installed as individual `.mdc` files in the Cursor rules directory (`.cursor/rules/aicm/`), mcp servers are installed to `.cursor/mcp.json`
|
|
269
295
|
- **Windsurf**: Rules are installed in the `.aicm` directory which should be added to your `.gitignore` file. Our approach for Windsurf is to create links from the `.windsurfrules` file to the respective rules in the `.aicm` directory. There is no support for local mcp servers at the moment.
|
|
296
|
+
- **Codex**: Rules are installed in the `.aicm` directory and referenced from `AGENTS.md` using the same markers as Windsurf.
|
|
270
297
|
|
|
271
298
|
## Commands
|
|
272
299
|
|
package/dist/commands/install.js
CHANGED
|
@@ -14,6 +14,7 @@ const ci_info_1 = require("ci-info");
|
|
|
14
14
|
const discovery_1 = require("./workspaces/discovery");
|
|
15
15
|
const workspaces_install_1 = require("./workspaces/workspaces-install");
|
|
16
16
|
const mcp_writer_1 = require("../utils/mcp-writer");
|
|
17
|
+
const glob_handler_1 = require("../utils/glob-handler");
|
|
17
18
|
/**
|
|
18
19
|
* Helper function to execute a function within a specific working directory
|
|
19
20
|
* and ensure the original directory is always restored
|
|
@@ -146,20 +147,31 @@ async function install(options = {}) {
|
|
|
146
147
|
};
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
|
-
|
|
150
|
+
let expandedRules;
|
|
151
|
+
try {
|
|
152
|
+
const expansion = await (0, glob_handler_1.expandRulesGlobPatterns)(config.rules, cwd);
|
|
153
|
+
expandedRules = expansion.expandedRules;
|
|
154
|
+
if (options.verbose) {
|
|
155
|
+
for (const [expandedKey, originalPattern] of Object.entries(expansion.globSources)) {
|
|
156
|
+
console.log(chalk_1.default.gray(` Pattern "${originalPattern}" → ${expandedKey}`));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
return {
|
|
162
|
+
success: false,
|
|
163
|
+
error: `Error expanding glob patterns: ${error instanceof Error ? error.message : String(error)}`,
|
|
164
|
+
installedRuleCount: 0,
|
|
165
|
+
packagesCount: 0,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
150
168
|
let hasErrors = false;
|
|
151
169
|
const errorMessages = [];
|
|
152
170
|
let installedRuleCount = 0;
|
|
153
|
-
for (const [name, source] of Object.entries(
|
|
154
|
-
if (source === false)
|
|
155
|
-
continue; // skip canceled rules
|
|
156
|
-
// Detect rule type from the source string
|
|
171
|
+
for (const [name, source] of Object.entries(expandedRules)) {
|
|
157
172
|
const ruleType = (0, rule_detector_1.detectRuleType)(source);
|
|
158
|
-
// Get the base path of the preset file if this rule came from a preset
|
|
159
173
|
const ruleBasePath = (0, config_1.getRuleSource)(config, name);
|
|
160
|
-
// Get the original preset path for namespacing
|
|
161
174
|
const originalPresetPath = (0, config_1.getOriginalPresetPath)(config, name);
|
|
162
|
-
// Collect the rule based on its type
|
|
163
175
|
try {
|
|
164
176
|
let ruleContent;
|
|
165
177
|
switch (ruleType) {
|
|
@@ -173,7 +185,6 @@ async function install(options = {}) {
|
|
|
173
185
|
errorMessages.push(`Unknown rule type: ${ruleType}`);
|
|
174
186
|
continue;
|
|
175
187
|
}
|
|
176
|
-
// Add the preset path to the rule content for namespacing
|
|
177
188
|
if (originalPresetPath) {
|
|
178
189
|
ruleContent.presetPath = originalPresetPath;
|
|
179
190
|
}
|
|
@@ -186,7 +197,6 @@ async function install(options = {}) {
|
|
|
186
197
|
errorMessages.push(errorMessage);
|
|
187
198
|
}
|
|
188
199
|
}
|
|
189
|
-
// If there were errors, exit with error
|
|
190
200
|
if (hasErrors) {
|
|
191
201
|
return {
|
|
192
202
|
success: false,
|
|
@@ -195,11 +205,8 @@ async function install(options = {}) {
|
|
|
195
205
|
packagesCount: 0,
|
|
196
206
|
};
|
|
197
207
|
}
|
|
198
|
-
// Write all collected rules to their targets
|
|
199
208
|
(0, rule_writer_1.writeRulesToTargets)(ruleCollection);
|
|
200
|
-
// Write mcpServers config to IDE targets
|
|
201
209
|
if (config.mcpServers) {
|
|
202
|
-
// Filter out canceled servers
|
|
203
210
|
const filteredMcpServers = Object.fromEntries(Object.entries(config.mcpServers).filter(([, v]) => v !== false));
|
|
204
211
|
(0, mcp_writer_1.writeMcpServersToTargets)(filteredMcpServers, config.ides, cwd);
|
|
205
212
|
}
|
package/dist/index.js
CHANGED
|
File without changes
|
package/dist/types/index.d.ts
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a rule source string contains glob patterns
|
|
3
|
+
*/
|
|
4
|
+
export declare function isGlobPattern(source: string): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Expand a glob pattern to matching .mdc files
|
|
7
|
+
* @param pattern The glob pattern to expand
|
|
8
|
+
* @param basePath The base path to resolve relative patterns from
|
|
9
|
+
* @returns Array of file paths that match the pattern
|
|
10
|
+
*/
|
|
11
|
+
export declare function expandGlobPattern(pattern: string, basePath?: string): Promise<string[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Generate a rule key from a file path and base key
|
|
14
|
+
* @param filePath The discovered file path
|
|
15
|
+
* @param baseKey The base key from the object notation
|
|
16
|
+
* @param patternBase The base directory of the glob pattern
|
|
17
|
+
* @returns Generated rule key with namespace
|
|
18
|
+
*/
|
|
19
|
+
export declare function generateGlobRuleKey(filePath: string, baseKey: string, patternBase: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Get the base directory from a glob pattern
|
|
22
|
+
* @param pattern The glob pattern
|
|
23
|
+
* @returns The base directory path without glob characters
|
|
24
|
+
*/
|
|
25
|
+
export declare function getGlobBase(pattern: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Expand glob patterns in rules object and return normalized rules
|
|
28
|
+
* @param rules The rules object that may contain glob patterns
|
|
29
|
+
* @param basePath The base path to resolve relative patterns from
|
|
30
|
+
* @returns Object with expanded rules and metadata about sources
|
|
31
|
+
*/
|
|
32
|
+
export declare function expandRulesGlobPatterns(rules: Record<string, string | false>, basePath?: string): Promise<{
|
|
33
|
+
expandedRules: Record<string, string>;
|
|
34
|
+
globSources: Record<string, string>;
|
|
35
|
+
}>;
|
|
@@ -0,0 +1,125 @@
|
|
|
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.isGlobPattern = isGlobPattern;
|
|
7
|
+
exports.expandGlobPattern = expandGlobPattern;
|
|
8
|
+
exports.generateGlobRuleKey = generateGlobRuleKey;
|
|
9
|
+
exports.getGlobBase = getGlobBase;
|
|
10
|
+
exports.expandRulesGlobPatterns = expandRulesGlobPatterns;
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
13
|
+
/**
|
|
14
|
+
* Check if a rule source string contains glob patterns
|
|
15
|
+
*/
|
|
16
|
+
function isGlobPattern(source) {
|
|
17
|
+
return /[*?{}[\]]/.test(source);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Expand a glob pattern to matching .mdc files
|
|
21
|
+
* @param pattern The glob pattern to expand
|
|
22
|
+
* @param basePath The base path to resolve relative patterns from
|
|
23
|
+
* @returns Array of file paths that match the pattern
|
|
24
|
+
*/
|
|
25
|
+
async function expandGlobPattern(pattern, basePath) {
|
|
26
|
+
// Normalize the pattern to use forward slashes for consistent behavior
|
|
27
|
+
const normalizedPattern = pattern.replace(/\\/g, "/");
|
|
28
|
+
try {
|
|
29
|
+
const matches = await (0, fast_glob_1.default)(normalizedPattern, {
|
|
30
|
+
ignore: ["**/.*"], // Ignore hidden files
|
|
31
|
+
absolute: false,
|
|
32
|
+
onlyFiles: true,
|
|
33
|
+
// Set the working directory if basePath is provided
|
|
34
|
+
cwd: basePath,
|
|
35
|
+
});
|
|
36
|
+
// Filter to only .mdc files, normalize paths, and sort for deterministic behavior
|
|
37
|
+
return matches
|
|
38
|
+
.filter((file) => file.endsWith(".mdc"))
|
|
39
|
+
.map((file) => file.replace(/\\/g, "/")) // Normalize Windows backslashes to forward slashes
|
|
40
|
+
.sort((a, b) => a.localeCompare(b));
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
throw new Error(`Error expanding glob pattern "${pattern}": ${error instanceof Error ? error.message : String(error)}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Generate a rule key from a file path and base key
|
|
48
|
+
* @param filePath The discovered file path
|
|
49
|
+
* @param baseKey The base key from the object notation
|
|
50
|
+
* @param patternBase The base directory of the glob pattern
|
|
51
|
+
* @returns Generated rule key with namespace
|
|
52
|
+
*/
|
|
53
|
+
function generateGlobRuleKey(filePath, baseKey, patternBase) {
|
|
54
|
+
// Normalize paths to use forward slashes for consistent behavior
|
|
55
|
+
const normalizedFilePath = filePath.replace(/\\/g, "/");
|
|
56
|
+
const normalizedPatternBase = patternBase.replace(/\\/g, "/");
|
|
57
|
+
// Get the relative path from the pattern base to the file
|
|
58
|
+
const relativePath = node_path_1.default.posix.relative(normalizedPatternBase, normalizedFilePath);
|
|
59
|
+
// Remove .mdc extension
|
|
60
|
+
const withoutExtension = relativePath.replace(/\.mdc$/, "");
|
|
61
|
+
// Return the combined key
|
|
62
|
+
return `${baseKey}/${withoutExtension}`;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get the base directory from a glob pattern
|
|
66
|
+
* @param pattern The glob pattern
|
|
67
|
+
* @returns The base directory path without glob characters
|
|
68
|
+
*/
|
|
69
|
+
function getGlobBase(pattern) {
|
|
70
|
+
// Normalize path separators to forward slashes for consistent behavior
|
|
71
|
+
const normalizedPattern = pattern.replace(/\\/g, "/");
|
|
72
|
+
// Find the first occurrence of glob characters
|
|
73
|
+
const globIndex = normalizedPattern.search(/[*?{}[\]]/);
|
|
74
|
+
if (globIndex === -1) {
|
|
75
|
+
// No glob characters, return the directory
|
|
76
|
+
return node_path_1.default.dirname(normalizedPattern);
|
|
77
|
+
}
|
|
78
|
+
// Get the path up to the first glob character
|
|
79
|
+
const basePath = normalizedPattern.substring(0, globIndex);
|
|
80
|
+
// Find the last path separator before the glob
|
|
81
|
+
const lastSeparator = basePath.lastIndexOf("/");
|
|
82
|
+
if (lastSeparator === -1) {
|
|
83
|
+
return ".";
|
|
84
|
+
}
|
|
85
|
+
return basePath.substring(0, lastSeparator);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Expand glob patterns in rules object and return normalized rules
|
|
89
|
+
* @param rules The rules object that may contain glob patterns
|
|
90
|
+
* @param basePath The base path to resolve relative patterns from
|
|
91
|
+
* @returns Object with expanded rules and metadata about sources
|
|
92
|
+
*/
|
|
93
|
+
async function expandRulesGlobPatterns(rules, basePath) {
|
|
94
|
+
const expandedRules = {};
|
|
95
|
+
const globSources = {};
|
|
96
|
+
for (const [key, source] of Object.entries(rules)) {
|
|
97
|
+
if (source === false) {
|
|
98
|
+
continue; // Skip canceled rules
|
|
99
|
+
}
|
|
100
|
+
if (isGlobPattern(source)) {
|
|
101
|
+
// Expand glob pattern
|
|
102
|
+
try {
|
|
103
|
+
const matchedFiles = await expandGlobPattern(source, basePath);
|
|
104
|
+
if (matchedFiles.length === 0) {
|
|
105
|
+
console.warn(`Warning: Glob pattern "${source}" matched no files`);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const patternBase = getGlobBase(source);
|
|
109
|
+
for (const filePath of matchedFiles) {
|
|
110
|
+
const generatedKey = generateGlobRuleKey(filePath, key, patternBase);
|
|
111
|
+
expandedRules[generatedKey] = filePath;
|
|
112
|
+
globSources[generatedKey] = source;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
throw new Error(`Error processing glob pattern for key "${key}": ${error instanceof Error ? error.message : String(error)}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// Regular file path
|
|
121
|
+
expandedRules[key] = source;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return { expandedRules, globSources };
|
|
125
|
+
}
|
|
@@ -59,6 +59,7 @@ function initRuleCollection() {
|
|
|
59
59
|
return {
|
|
60
60
|
cursor: [],
|
|
61
61
|
windsurf: [],
|
|
62
|
+
codex: [],
|
|
62
63
|
};
|
|
63
64
|
}
|
|
64
65
|
/**
|
|
@@ -77,6 +78,10 @@ function addRuleToCollection(collection, rule, ides) {
|
|
|
77
78
|
!collection.windsurf.some((r) => r.name === rule.name)) {
|
|
78
79
|
collection.windsurf.push(rule);
|
|
79
80
|
}
|
|
81
|
+
else if (ide === "codex" &&
|
|
82
|
+
!collection.codex.some((r) => r.name === rule.name)) {
|
|
83
|
+
collection.codex.push(rule);
|
|
84
|
+
}
|
|
80
85
|
}
|
|
81
86
|
}
|
|
82
87
|
/**
|
|
@@ -15,6 +15,7 @@ function getIdePaths() {
|
|
|
15
15
|
return {
|
|
16
16
|
cursor: node_path_1.default.join(projectDir, ".cursor", "rules", "aicm"),
|
|
17
17
|
windsurf: node_path_1.default.join(projectDir, ".aicm"),
|
|
18
|
+
codex: node_path_1.default.join(projectDir, ".aicm"),
|
|
18
19
|
};
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
@@ -38,6 +39,15 @@ function checkRuleStatus(ruleName, ides) {
|
|
|
38
39
|
}
|
|
39
40
|
return false;
|
|
40
41
|
}
|
|
42
|
+
if (ide === "codex") {
|
|
43
|
+
const ruleExists = fs_extra_1.default.existsSync(node_path_1.default.join(idePaths[ide], `${ruleName}.md`));
|
|
44
|
+
const codexFilePath = node_path_1.default.join(process.cwd(), "AGENTS.md");
|
|
45
|
+
if (fs_extra_1.default.existsSync(codexFilePath)) {
|
|
46
|
+
const codexContent = fs_extra_1.default.readFileSync(codexFilePath, "utf8");
|
|
47
|
+
return ruleExists && codexContent.includes(`.aicm/${ruleName}.md`);
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
41
51
|
return false;
|
|
42
52
|
});
|
|
43
53
|
}
|
|
@@ -7,7 +7,7 @@ exports.writeRulesToTargets = writeRulesToTargets;
|
|
|
7
7
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
9
|
const rule_status_1 = require("./rule-status");
|
|
10
|
-
const
|
|
10
|
+
const rules_file_writer_1 = require("./rules-file-writer");
|
|
11
11
|
/**
|
|
12
12
|
* Write all collected rules to their respective IDE targets
|
|
13
13
|
* @param collection The collection of rules to write
|
|
@@ -20,7 +20,10 @@ function writeRulesToTargets(collection) {
|
|
|
20
20
|
}
|
|
21
21
|
// Write Windsurf rules
|
|
22
22
|
if (collection.windsurf.length > 0) {
|
|
23
|
-
|
|
23
|
+
writeRulesForFile(collection.windsurf, idePaths.windsurf, ".windsurfrules");
|
|
24
|
+
}
|
|
25
|
+
if (collection.codex.length > 0) {
|
|
26
|
+
writeRulesForFile(collection.codex, idePaths.codex, "AGENTS.md");
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
29
|
/**
|
|
@@ -71,10 +74,10 @@ function writeCursorRules(rules, cursorRulesDir) {
|
|
|
71
74
|
}
|
|
72
75
|
}
|
|
73
76
|
/**
|
|
74
|
-
* Write rules to
|
|
77
|
+
* Write rules to a shared directory and update the given rules file
|
|
75
78
|
* @param rules The rules to write
|
|
76
79
|
*/
|
|
77
|
-
function
|
|
80
|
+
function writeRulesForFile(rules, ruleDir, rulesFile) {
|
|
78
81
|
fs_extra_1.default.emptyDirSync(ruleDir);
|
|
79
82
|
const ruleFiles = rules.map((rule) => {
|
|
80
83
|
let rulePath;
|
|
@@ -94,7 +97,7 @@ function writeWindsurfRulesFromCollection(rules, ruleDir) {
|
|
|
94
97
|
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(physicalRulePath));
|
|
95
98
|
fs_extra_1.default.writeFileSync(physicalRulePath, rule.content);
|
|
96
99
|
const relativeRuleDir = node_path_1.default.basename(ruleDir); // Gets '.rules'
|
|
97
|
-
// For the
|
|
100
|
+
// For the rules file, maintain the same structure
|
|
98
101
|
let windsurfPath;
|
|
99
102
|
if (rule.presetPath) {
|
|
100
103
|
const namespace = extractNamespaceFromPresetPath(rule.presetPath);
|
|
@@ -112,6 +115,6 @@ function writeWindsurfRulesFromCollection(rules, ruleDir) {
|
|
|
112
115
|
metadata: rule.metadata,
|
|
113
116
|
};
|
|
114
117
|
});
|
|
115
|
-
const windsurfRulesContent = (0,
|
|
116
|
-
(0,
|
|
118
|
+
const windsurfRulesContent = (0, rules_file_writer_1.generateRulesFileContent)(ruleFiles);
|
|
119
|
+
(0, rules_file_writer_1.writeRulesFile)(windsurfRulesContent, node_path_1.default.join(process.cwd(), rulesFile));
|
|
117
120
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write rules to the .windsurfrules file
|
|
3
|
+
* This will update the content between the RULES_BEGIN and RULES_END markers
|
|
4
|
+
* If the file doesn't exist, it will create it
|
|
5
|
+
* If the markers don't exist, it will append them to the existing content
|
|
6
|
+
*/
|
|
7
|
+
export declare function writeRulesFile(rulesContent: string, rulesFilePath?: string): void;
|
|
8
|
+
/**
|
|
9
|
+
* Generate the rules file content based on rule files
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateRulesFileContent(ruleFiles: {
|
|
12
|
+
name: string;
|
|
13
|
+
path: string;
|
|
14
|
+
metadata: Record<string, string | boolean | string[]>;
|
|
15
|
+
}[]): string;
|
|
@@ -0,0 +1,137 @@
|
|
|
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.writeRulesFile = writeRulesFile;
|
|
7
|
+
exports.generateRulesFileContent = generateRulesFileContent;
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const RULES_BEGIN = "<!-- AICM:BEGIN -->";
|
|
11
|
+
const RULES_END = "<!-- AICM:END -->";
|
|
12
|
+
const WARNING = "<!-- WARNING: Everything between these markers will be overwritten during installation -->";
|
|
13
|
+
/**
|
|
14
|
+
* Create a formatted block of content with rules markers
|
|
15
|
+
*/
|
|
16
|
+
function createRulesBlock(rulesContent) {
|
|
17
|
+
return `${RULES_BEGIN}
|
|
18
|
+
${WARNING}
|
|
19
|
+
|
|
20
|
+
${rulesContent}
|
|
21
|
+
|
|
22
|
+
${RULES_END}`;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Write rules to the .windsurfrules file
|
|
26
|
+
* This will update the content between the RULES_BEGIN and RULES_END markers
|
|
27
|
+
* If the file doesn't exist, it will create it
|
|
28
|
+
* If the markers don't exist, it will append them to the existing content
|
|
29
|
+
*/
|
|
30
|
+
function writeRulesFile(rulesContent, rulesFilePath = path_1.default.join(process.cwd(), ".windsurfrules")) {
|
|
31
|
+
let fileContent;
|
|
32
|
+
const formattedRulesBlock = createRulesBlock(rulesContent);
|
|
33
|
+
// Check if file exists
|
|
34
|
+
if (fs_extra_1.default.existsSync(rulesFilePath)) {
|
|
35
|
+
const existingContent = fs_extra_1.default.readFileSync(rulesFilePath, "utf8");
|
|
36
|
+
// Check if our markers exist
|
|
37
|
+
if (existingContent.includes(RULES_BEGIN) &&
|
|
38
|
+
existingContent.includes(RULES_END)) {
|
|
39
|
+
// Replace content between markers
|
|
40
|
+
const beforeMarker = existingContent.split(RULES_BEGIN)[0];
|
|
41
|
+
const afterMarker = existingContent.split(RULES_END)[1];
|
|
42
|
+
fileContent = beforeMarker + formattedRulesBlock + afterMarker;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// Preserve the existing content and append markers
|
|
46
|
+
// Ensure there's proper spacing between existing content and markers
|
|
47
|
+
let separator = "";
|
|
48
|
+
if (!existingContent.endsWith("\n")) {
|
|
49
|
+
separator += "\n";
|
|
50
|
+
}
|
|
51
|
+
// Add an extra line if the file doesn't already end with multiple newlines
|
|
52
|
+
if (!existingContent.endsWith("\n\n")) {
|
|
53
|
+
separator += "\n";
|
|
54
|
+
}
|
|
55
|
+
// Create the new file content with preserved original content
|
|
56
|
+
fileContent = existingContent + separator + formattedRulesBlock;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// Create new file with markers and content
|
|
61
|
+
fileContent = formattedRulesBlock;
|
|
62
|
+
}
|
|
63
|
+
fs_extra_1.default.writeFileSync(rulesFilePath, fileContent);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Generate the rules file content based on rule files
|
|
67
|
+
*/
|
|
68
|
+
function generateRulesFileContent(ruleFiles) {
|
|
69
|
+
const alwaysRules = [];
|
|
70
|
+
const autoAttachedRules = [];
|
|
71
|
+
const agentRequestedRules = [];
|
|
72
|
+
const manualRules = [];
|
|
73
|
+
ruleFiles.forEach(({ path, metadata }) => {
|
|
74
|
+
// Determine rule type based on metadata
|
|
75
|
+
if (metadata.type === "always" ||
|
|
76
|
+
metadata.alwaysApply === true ||
|
|
77
|
+
metadata.alwaysApply === "true") {
|
|
78
|
+
alwaysRules.push(path);
|
|
79
|
+
}
|
|
80
|
+
else if (metadata.type === "auto-attached" || metadata.globs) {
|
|
81
|
+
const globPattern = typeof metadata.globs === "string" || Array.isArray(metadata.globs)
|
|
82
|
+
? Array.isArray(metadata.globs)
|
|
83
|
+
? metadata.globs.join(", ")
|
|
84
|
+
: metadata.globs
|
|
85
|
+
: undefined;
|
|
86
|
+
if (globPattern !== undefined) {
|
|
87
|
+
autoAttachedRules.push({ path, glob: globPattern });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else if (metadata.type === "agent-requested" || metadata.description) {
|
|
91
|
+
agentRequestedRules.push(path);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// Default to manual inclusion
|
|
95
|
+
manualRules.push(path);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
// Generate the content
|
|
99
|
+
let content = "";
|
|
100
|
+
// Always rules
|
|
101
|
+
if (alwaysRules.length > 0) {
|
|
102
|
+
content +=
|
|
103
|
+
"The following rules always apply to all files in the project:\n";
|
|
104
|
+
alwaysRules.forEach((rule) => {
|
|
105
|
+
content += `- ${rule}\n`;
|
|
106
|
+
});
|
|
107
|
+
content += "\n";
|
|
108
|
+
}
|
|
109
|
+
// Auto Attached rules
|
|
110
|
+
if (autoAttachedRules.length > 0) {
|
|
111
|
+
content +=
|
|
112
|
+
"The following rules are automatically attached to matching glob patterns:\n";
|
|
113
|
+
autoAttachedRules.forEach((rule) => {
|
|
114
|
+
content += `- [${rule.glob}] ${rule.path}\n`;
|
|
115
|
+
});
|
|
116
|
+
content += "\n";
|
|
117
|
+
}
|
|
118
|
+
// Agent Requested rules
|
|
119
|
+
if (agentRequestedRules.length > 0) {
|
|
120
|
+
content +=
|
|
121
|
+
"The following rules are available for the AI to include when needed:\n";
|
|
122
|
+
agentRequestedRules.forEach((rule) => {
|
|
123
|
+
content += `- ${rule}\n`;
|
|
124
|
+
});
|
|
125
|
+
content += "\n";
|
|
126
|
+
}
|
|
127
|
+
// Manual rules
|
|
128
|
+
if (manualRules.length > 0) {
|
|
129
|
+
content +=
|
|
130
|
+
"The following rules are only included when explicitly referenced:\n";
|
|
131
|
+
manualRules.forEach((rule) => {
|
|
132
|
+
content += `- ${rule}\n`;
|
|
133
|
+
});
|
|
134
|
+
content += "\n";
|
|
135
|
+
}
|
|
136
|
+
return content.trim();
|
|
137
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aicm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "A TypeScript CLI tool for managing AI IDE rules across different projects and teams",
|
|
5
5
|
"main": "dist/api.js",
|
|
6
6
|
"types": "dist/api.d.ts",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"chalk": "^4.1.2",
|
|
44
44
|
"ci-info": "^4.2.0",
|
|
45
45
|
"cosmiconfig": "^9.0.0",
|
|
46
|
+
"fast-glob": "^3.3.3",
|
|
46
47
|
"fs-extra": "^11.1.1"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|