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 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
- Development teams struggle with:
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
- - **Inconsistent Practices**: Developers apply varying standards across projects
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
- To get automatic rule updates from NPM Packages, you can create, publish, and use dedicated npm packages to distribute AI rules across multiple projects.
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
- ├── react.mdc
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
- ### Overriding and Canceling Rules and MCP Servers from Presets
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
- When you use a preset, you can override or cancel any rule or mcpServer from the preset in your own `aicm.json` configuration:
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
- - **Cancel**: To cancel (remove) a rule or mcpServer from a preset, set its value to `false` in your config.
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
- Here is a package to demonstrate how aicm works:
124
+ We'll install an npm package containing a simple rule to demonstrate how aicm works.
130
125
 
131
- 1. Install a package containing a rule
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/mcp-config.json"
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 `.rules` 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 `.rules` directory. There is no support for local mcp servers at the moment.
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, ".rules"),
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(`.rules/${ruleName}.md`));
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.ensureDirSync(cursorRulesDir);
32
+ fs_extra_1.default.emptyDirSync(cursorRulesDir);
33
33
  for (const rule of rules) {
34
- const ruleFile = node_path_1.default.join(cursorRulesDir, `${rule.name}.mdc`);
35
- // For Cursor, we either copy the file or create a symlink to the original
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
- const idePaths = (0, rule_status_1.getIdePaths)();
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 ruleFile = node_path_1.default.join(ruleDir, `${rule.name}.md`);
58
- fs_extra_1.default.writeFileSync(ruleFile, rule.content);
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: `.rules/${rule.name}.md`,
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.5.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": [