aicm 0.5.0 → 0.6.0
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 -48
- package/dist/utils/rule-status.js +3 -7
- package/dist/utils/rule-writer.js +14 -15
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,23 +6,13 @@ A CLI tool for syncing and managing Agentic IDE rules across projects
|
|
|
6
6
|
|
|
7
7
|
## Why
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
With the rise of Agentic IDEs like cursor and windsurf, we have an opportunity to enforce best practices through rules. However, these rules are typically isolated within individual developers or projects.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
- **Knowledge Silos**: Best practices remain trapped in individual projects
|
|
13
|
-
- **Change Management**: No efficient way to update and distribute new standards
|
|
14
|
-
|
|
15
|
-
As developers increasingly adopt AI-powered IDEs like Cursor and Windsurf, we have an opportunity to enforce best practices through rules. However, these rules are typically isolated within individual developers or projects.
|
|
16
|
-
|
|
17
|
-
**aicm** is a CLI tool that helps with distribution of agentic IDE configurations, rules and mcps:
|
|
18
|
-
|
|
19
|
-
- 🏛️ **Single Source of Truth**: Define, maintain and version-control all AI IDE rules in one central repository
|
|
20
|
-
- 📦 **Seamless Distribution**: Automatically synchronize the latest rules to developers' local projects using npm packages
|
|
21
|
-
- 🌐 **Cross-IDE Support**: Supports multiple AI-powered IDEs (Cursor, Windsurf)
|
|
11
|
+
**aicm** is a CLI tool for distributing Agentic IDE configurations, rules, and MCPs across projects. It leverages package managers to copy configurations from node_modules to the correct locations in your file system.
|
|
22
12
|
|
|
23
13
|
## Getting Started
|
|
24
14
|
|
|
25
|
-
|
|
15
|
+
Since aicm is not a package manager, begin by creating an npm package that contains your rules and MCP configurations.
|
|
26
16
|
|
|
27
17
|
Consider the following npm package structure:
|
|
28
18
|
|
|
@@ -31,8 +21,7 @@ Consider the following npm package structure:
|
|
|
31
21
|
├── package.json
|
|
32
22
|
└── rules/
|
|
33
23
|
├── typescript.mdc
|
|
34
|
-
|
|
35
|
-
└── general.mdc
|
|
24
|
+
└── react.mdc
|
|
36
25
|
```
|
|
37
26
|
|
|
38
27
|
1. **Point to the path within the npm package**
|
|
@@ -44,8 +33,7 @@ In your project's `aicm.json`, reference the package and the specific rule:
|
|
|
44
33
|
"ides": ["cursor"],
|
|
45
34
|
"rules": {
|
|
46
35
|
"typescript": "@myteam/ai-tools/rules/typescript.mdc",
|
|
47
|
-
"react": "@myteam/ai-tools/rules/react.mdc"
|
|
48
|
-
"general": "@myteam/ai-tools/rules/general.mdc"
|
|
36
|
+
"react": "@myteam/ai-tools/rules/react.mdc"
|
|
49
37
|
}
|
|
50
38
|
}
|
|
51
39
|
```
|
|
@@ -60,21 +48,28 @@ In your project's `aicm.json`, reference the package and the specific rule:
|
|
|
60
48
|
}
|
|
61
49
|
```
|
|
62
50
|
|
|
63
|
-
Now the rules will be linked to `.cursor/rules/` when you run `npm install`.
|
|
51
|
+
Now the rules will be linked to `.cursor/rules/aicm/` when you run `npm install`.
|
|
64
52
|
|
|
65
53
|
### Using Presets
|
|
66
54
|
|
|
67
|
-
Presets allow you to bundle multiple rules into a single configuration that can be shared across projects.
|
|
55
|
+
Presets allow you to bundle multiple rules & mcps into a single configuration that can be shared across projects.
|
|
68
56
|
|
|
69
57
|
1. **Create a preset package or directory**
|
|
70
58
|
|
|
71
59
|
Create an npm package with your rule definitions in an `aicm.json` file:
|
|
72
60
|
|
|
61
|
+
> `@myteam/ai-tools/aicm.json`
|
|
62
|
+
|
|
73
63
|
```json
|
|
74
64
|
{
|
|
75
65
|
"rules": {
|
|
76
66
|
"typescript": "./rules/typescript.mdc",
|
|
77
67
|
"react": "./rules/react.mdc"
|
|
68
|
+
},
|
|
69
|
+
"mcpServers": {
|
|
70
|
+
"my-mcp": {
|
|
71
|
+
"url": "https://example.com/sse"
|
|
72
|
+
}
|
|
78
73
|
}
|
|
79
74
|
}
|
|
80
75
|
```
|
|
@@ -90,14 +85,19 @@ In your project's `aicm.json`, reference the preset by its npm package or direct
|
|
|
90
85
|
}
|
|
91
86
|
```
|
|
92
87
|
|
|
93
|
-
When you run `npx aicm install`, all rules from the preset will be installed to `.cursor/rules
|
|
88
|
+
When you run `npx aicm install`, all rules from the preset will be installed to `.cursor/rules/aicm/` and all mcps from the preset will be installed to `.cursor/mcp.json`.
|
|
89
|
+
|
|
90
|
+
### Notes
|
|
94
91
|
|
|
95
|
-
|
|
92
|
+
- Generated rules are always placed in a subdirectory for deterministic cleanup and easy gitignore.
|
|
93
|
+
- Users may add `.cursor/rules/aicm/` and `.aicm/` (for Windsurf) to their `.gitignore` if they do not want to track generated rules.
|
|
96
94
|
|
|
97
|
-
|
|
95
|
+
### Overriding and Disabling Rules and MCP Servers from Presets
|
|
96
|
+
|
|
97
|
+
When you use a preset, you can override or disable any rule or mcpServer from the preset in your own `aicm.json` configuration:
|
|
98
98
|
|
|
99
99
|
- **Override**: To override a rule or mcpServer, specify the same key in your config with a new value. The value in your config will take precedence over the preset.
|
|
100
|
-
- **
|
|
100
|
+
- **Disable**: To disable a rule or mcpServer from a preset, set its value to `false` in your config.
|
|
101
101
|
|
|
102
102
|
**Example:**
|
|
103
103
|
|
|
@@ -119,16 +119,11 @@ When you use a preset, you can override or cancel any rule or mcpServer from the
|
|
|
119
119
|
}
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
-
- In this example, `npm-rule` is overridden with a local rule, and `preset-rule` is canceled.
|
|
123
|
-
- The `preset-mcp` server is overridden, and `another-mcp` is canceled.
|
|
124
|
-
|
|
125
|
-
This allows you to fully customize or selectively disable rules and servers from any preset you use.
|
|
126
|
-
|
|
127
122
|
### Demo
|
|
128
123
|
|
|
129
|
-
|
|
124
|
+
We'll install an npm package containing a simple rule to demonstrate how aicm works.
|
|
130
125
|
|
|
131
|
-
1. Install
|
|
126
|
+
1. Install an npm package containing a rule
|
|
132
127
|
|
|
133
128
|
```bash
|
|
134
129
|
npm install --save-dev pirate-coding-rule
|
|
@@ -179,11 +174,11 @@ Example `aicm.json`:
|
|
|
179
174
|
"ides": ["cursor"],
|
|
180
175
|
"presets": ["@my-team/ai-tools/my-aicm.json"],
|
|
181
176
|
"rules": {
|
|
182
|
-
"team-standards": "@my-team/ai-tools/rules/team-standards.mdc"
|
|
177
|
+
"team-rules/team-standards": "@my-team/ai-tools/rules/team-standards.mdc"
|
|
183
178
|
},
|
|
184
179
|
"mcpServers": {
|
|
185
180
|
"remote-mcp": {
|
|
186
|
-
"url": "https://example.com/
|
|
181
|
+
"url": "https://example.com/sse"
|
|
187
182
|
}
|
|
188
183
|
}
|
|
189
184
|
}
|
|
@@ -195,7 +190,7 @@ Example `aicm.json`:
|
|
|
195
190
|
|
|
196
191
|
- **rules**: Object containing rule configurations
|
|
197
192
|
|
|
198
|
-
- **rule-name**: A unique identifier for the rule
|
|
193
|
+
- **rule-name**: A unique identifier for the rule. Can include a directory path to install the rule to a specific directory.
|
|
199
194
|
- **source-location**: Location of the rule file (path within an npm package or local path)
|
|
200
195
|
|
|
201
196
|
- **mcpServers**: Object containing MCP server configurations. Each key is a unique server name, and the value is an object with either:
|
|
@@ -234,8 +229,8 @@ Rules stored locally in your project or filesystem. Any path containing slashes
|
|
|
234
229
|
|
|
235
230
|
## Supported IDEs
|
|
236
231
|
|
|
237
|
-
- **Cursor**: Rules are installed as individual `.mdc` files in the Cursor rules directory (`.cursor/rules/`), mcp servers are installed to `.cursor/mcp.json`
|
|
238
|
-
- **Windsurf**: Rules are installed in the `.
|
|
232
|
+
- **Cursor**: Rules are installed as individual `.mdc` files in the Cursor rules directory (`.cursor/rules/aicm/`), mcp servers are installed to `.cursor/mcp.json`
|
|
233
|
+
- **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.
|
|
239
234
|
|
|
240
235
|
## Commands
|
|
241
236
|
|
|
@@ -262,17 +257,6 @@ Installs rules from your configuration to the appropriate IDE locations.
|
|
|
262
257
|
npx aicm install
|
|
263
258
|
```
|
|
264
259
|
|
|
265
|
-
**Options:**
|
|
266
|
-
|
|
267
|
-
- No arguments are supported. All rules are installed from your configuration and any referenced presets.
|
|
268
|
-
|
|
269
|
-
**Examples:**
|
|
270
|
-
|
|
271
|
-
```bash
|
|
272
|
-
# Install all configured rules
|
|
273
|
-
npx -y aicm install
|
|
274
|
-
```
|
|
275
|
-
|
|
276
260
|
## Contributing
|
|
277
261
|
|
|
278
262
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
@@ -281,8 +265,6 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
|
281
265
|
|
|
282
266
|
### Testing
|
|
283
267
|
|
|
284
|
-
The project includes both unit tests and end-to-end (E2E) tests:
|
|
285
|
-
|
|
286
268
|
```bash
|
|
287
269
|
# Run all tests
|
|
288
270
|
npm test
|
|
@@ -13,8 +13,8 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
13
13
|
function getIdePaths() {
|
|
14
14
|
const projectDir = process.cwd(); // Get current working directory (project root)
|
|
15
15
|
return {
|
|
16
|
-
cursor: node_path_1.default.join(projectDir, ".cursor", "rules"),
|
|
17
|
-
windsurf: node_path_1.default.join(projectDir, ".
|
|
16
|
+
cursor: node_path_1.default.join(projectDir, ".cursor", "rules", "aicm"),
|
|
17
|
+
windsurf: node_path_1.default.join(projectDir, ".aicm"),
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
@@ -22,7 +22,6 @@ function getIdePaths() {
|
|
|
22
22
|
*/
|
|
23
23
|
function checkRuleStatus(ruleName, ruleType, ides) {
|
|
24
24
|
const idePaths = getIdePaths();
|
|
25
|
-
// Check if rule is installed in all specified IDEs
|
|
26
25
|
return ides.every((ide) => {
|
|
27
26
|
if (!idePaths[ide]) {
|
|
28
27
|
return false;
|
|
@@ -31,14 +30,11 @@ function checkRuleStatus(ruleName, ruleType, ides) {
|
|
|
31
30
|
return fs_extra_1.default.existsSync(node_path_1.default.join(idePaths[ide], `${ruleName}.mdc`));
|
|
32
31
|
}
|
|
33
32
|
if (ide === "windsurf") {
|
|
34
|
-
// For Windsurf, check if the rule exists in .rules directory
|
|
35
|
-
// and if it's referenced in .windsurfrules
|
|
36
33
|
const ruleExists = fs_extra_1.default.existsSync(node_path_1.default.join(idePaths[ide], `${ruleName}.md`));
|
|
37
|
-
// Check if .windsurfrules exists and contains a reference to this rule
|
|
38
34
|
const windsurfRulesPath = node_path_1.default.join(process.cwd(), ".windsurfrules");
|
|
39
35
|
if (fs_extra_1.default.existsSync(windsurfRulesPath)) {
|
|
40
36
|
const windsurfRulesContent = fs_extra_1.default.readFileSync(windsurfRulesPath, "utf8");
|
|
41
|
-
return (ruleExists && windsurfRulesContent.includes(`.
|
|
37
|
+
return (ruleExists && windsurfRulesContent.includes(`.aicm/${ruleName}.md`));
|
|
42
38
|
}
|
|
43
39
|
return false;
|
|
44
40
|
}
|
|
@@ -20,7 +20,7 @@ function writeRulesToTargets(collection) {
|
|
|
20
20
|
}
|
|
21
21
|
// Write Windsurf rules
|
|
22
22
|
if (collection.windsurf.length > 0) {
|
|
23
|
-
writeWindsurfRulesFromCollection(collection.windsurf);
|
|
23
|
+
writeWindsurfRulesFromCollection(collection.windsurf, idePaths.windsurf);
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
@@ -29,16 +29,14 @@ function writeRulesToTargets(collection) {
|
|
|
29
29
|
* @param cursorRulesDir The path to Cursor's rules directory
|
|
30
30
|
*/
|
|
31
31
|
function writeCursorRules(rules, cursorRulesDir) {
|
|
32
|
-
fs_extra_1.default.
|
|
32
|
+
fs_extra_1.default.emptyDirSync(cursorRulesDir);
|
|
33
33
|
for (const rule of rules) {
|
|
34
|
-
const ruleFile = node_path_1.default.join(cursorRulesDir,
|
|
35
|
-
|
|
34
|
+
const ruleFile = node_path_1.default.join(cursorRulesDir, ...rule.name.split("/")) + ".mdc";
|
|
35
|
+
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(ruleFile));
|
|
36
36
|
if (fs_extra_1.default.existsSync(rule.sourcePath)) {
|
|
37
|
-
// Copy the file (safer than symlink)
|
|
38
37
|
fs_extra_1.default.copyFileSync(rule.sourcePath, ruleFile);
|
|
39
38
|
}
|
|
40
39
|
else {
|
|
41
|
-
// If source path doesn't exist (shouldn't happen), write content directly
|
|
42
40
|
const mdcContent = `---\n${JSON.stringify(rule.metadata, null, 2)}\n---\n\n${rule.content}`;
|
|
43
41
|
fs_extra_1.default.writeFileSync(ruleFile, mdcContent);
|
|
44
42
|
}
|
|
@@ -48,21 +46,22 @@ function writeCursorRules(rules, cursorRulesDir) {
|
|
|
48
46
|
* Write rules to Windsurf's rules directory and update .windsurfrules file
|
|
49
47
|
* @param rules The rules to write
|
|
50
48
|
*/
|
|
51
|
-
function writeWindsurfRulesFromCollection(rules) {
|
|
52
|
-
|
|
53
|
-
const ruleDir = idePaths.windsurf;
|
|
54
|
-
fs_extra_1.default.ensureDirSync(ruleDir);
|
|
55
|
-
// First write individual rule files
|
|
49
|
+
function writeWindsurfRulesFromCollection(rules, ruleDir) {
|
|
50
|
+
fs_extra_1.default.emptyDirSync(ruleDir);
|
|
56
51
|
const ruleFiles = rules.map((rule) => {
|
|
57
|
-
const
|
|
58
|
-
fs_extra_1.default.
|
|
52
|
+
const physicalRulePath = node_path_1.default.join(ruleDir, ...rule.name.split("/")) + ".md";
|
|
53
|
+
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(physicalRulePath));
|
|
54
|
+
fs_extra_1.default.writeFileSync(physicalRulePath, rule.content);
|
|
55
|
+
const relativeRuleDir = node_path_1.default.basename(ruleDir); // Gets '.rules'
|
|
56
|
+
const windsurfPath = node_path_1.default.join(relativeRuleDir, ...rule.name.split("/")) + ".md";
|
|
57
|
+
// Normalize to POSIX style for cross-platform compatibility in .windsurfrules
|
|
58
|
+
const windsurfPathPosix = windsurfPath.replace(/\\/g, "/");
|
|
59
59
|
return {
|
|
60
60
|
name: rule.name,
|
|
61
|
-
path:
|
|
61
|
+
path: windsurfPathPosix,
|
|
62
62
|
metadata: rule.metadata,
|
|
63
63
|
};
|
|
64
64
|
});
|
|
65
|
-
// Then generate and write the .windsurfrules file
|
|
66
65
|
const windsurfRulesContent = (0, windsurf_writer_1.generateWindsurfRulesContent)(ruleFiles);
|
|
67
66
|
(0, windsurf_writer_1.writeWindsurfRules)(windsurfRulesContent);
|
|
68
67
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aicm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "A TypeScript CLI tool for managing AI IDE rules across different projects and teams",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"format": "prettier --write .",
|
|
25
25
|
"format:check": "prettier --check .",
|
|
26
26
|
"lint": "eslint",
|
|
27
|
-
"prepare": "husky && npx ts-node src/index.ts install",
|
|
27
|
+
"prepare": "husky install && npx ts-node src/index.ts install",
|
|
28
28
|
"version": "auto-changelog -p && git add CHANGELOG.md"
|
|
29
29
|
},
|
|
30
30
|
"keywords": [
|