aicm 0.5.0 → 0.6.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 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`.
94
89
 
95
- ### Overriding and Canceling Rules and MCP Servers from Presets
90
+ ### Notes
96
91
 
97
- When you use a preset, you can override or cancel any rule or mcpServer from the preset in your own `aicm.json` configuration:
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.
94
+
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,19 +119,14 @@ 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](https://github.com/ranyitz/pirate-coding) containing a simple preset to demonstrate how aicm works.
130
125
 
131
- 1. Install a package containing a rule
126
+ 1. Install an npm package containing a preset
132
127
 
133
128
  ```bash
134
- npm install --save-dev pirate-coding-rule
129
+ npm install --save-dev pirate-coding
135
130
  ```
136
131
 
137
132
  2. Initialize aicm config
@@ -145,13 +140,11 @@ npx -y aicm init
145
140
  ```json
146
141
  {
147
142
  "ides": ["cursor"],
148
- "rules": {
149
- "pirate-coding": "pirate-coding-rule/rule.mdc"
150
- }
143
+ "presets": ["pirate-coding"]
151
144
  }
152
145
  ```
153
146
 
154
- 4. Install all rules from your configuration
147
+ 4. Install all rules & mcps from your configuration
155
148
 
156
149
  ```bash
157
150
  npx -y aicm install
@@ -159,7 +152,7 @@ npx -y aicm install
159
152
 
160
153
  This command installs all configured rules and MCPs to their IDE-specific locations.
161
154
 
162
- After installation, open Cursor and ask it to do something. Your AI assistant will respond with pirate-themed coding advice.
155
+ After installation, open Cursor and ask it to do something. Your AI assistant will respond with pirate-themed coding advice. You can also ask it about the aicm library which uses https://gitmcp.io/ to give you advise based on the latest documentation.
163
156
 
164
157
  ## Security Note
165
158
 
@@ -179,11 +172,11 @@ Example `aicm.json`:
179
172
  "ides": ["cursor"],
180
173
  "presets": ["@my-team/ai-tools/my-aicm.json"],
181
174
  "rules": {
182
- "team-standards": "@my-team/ai-tools/rules/team-standards.mdc"
175
+ "team-rules/team-standards": "@my-team/ai-tools/rules/team-standards.mdc"
183
176
  },
184
177
  "mcpServers": {
185
178
  "remote-mcp": {
186
- "url": "https://example.com/mcp-config.json"
179
+ "url": "https://example.com/sse"
187
180
  }
188
181
  }
189
182
  }
@@ -195,7 +188,7 @@ Example `aicm.json`:
195
188
 
196
189
  - **rules**: Object containing rule configurations
197
190
 
198
- - **rule-name**: A unique identifier for the rule
191
+ - **rule-name**: A unique identifier for the rule. Can include a directory path to install the rule to a specific directory.
199
192
  - **source-location**: Location of the rule file (path within an npm package or local path)
200
193
 
201
194
  - **mcpServers**: Object containing MCP server configurations. Each key is a unique server name, and the value is an object with either:
@@ -234,8 +227,8 @@ Rules stored locally in your project or filesystem. Any path containing slashes
234
227
 
235
228
  ## Supported IDEs
236
229
 
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.
230
+ - **Cursor**: Rules are installed as individual `.mdc` files in the Cursor rules directory (`.cursor/rules/aicm/`), mcp servers are installed to `.cursor/mcp.json`
231
+ - **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
232
 
240
233
  ## Commands
241
234
 
@@ -256,23 +249,65 @@ npx aicm init
256
249
 
257
250
  ### `install`
258
251
 
259
- Installs rules from your configuration to the appropriate IDE locations.
252
+ Installs all rules and MCPs configured in your `aicm.json`.
260
253
 
261
254
  ```bash
262
255
  npx aicm install
263
256
  ```
264
257
 
265
- **Options:**
258
+ ## Node.js API
266
259
 
267
- - No arguments are supported. All rules are installed from your configuration and any referenced presets.
260
+ In addition to the CLI, aicm can be used programmatically in Node.js applications:
268
261
 
269
- **Examples:**
262
+ ```javascript
263
+ const { install, Config } = require("aicm");
270
264
 
271
- ```bash
272
- # Install all configured rules
273
- npx -y aicm install
265
+ install().then((result) => {
266
+ if (result.success) {
267
+ console.log(`Successfully installed ${result.installedRuleCount} rules`);
268
+ } else {
269
+ console.error(`Error: ${result.error}`);
270
+ }
271
+ });
272
+
273
+ // Install with custom options
274
+ const customConfig = {
275
+ ides: ["cursor"],
276
+ rules: {
277
+ typescript: "./rules/typescript.mdc",
278
+ react: "@org/rules/react.mdc",
279
+ },
280
+ };
281
+
282
+ install({
283
+ config: customConfig,
284
+ cwd: "/path/to/project",
285
+ silent: true,
286
+ }).then((result) => {
287
+ // Handle result
288
+ });
274
289
  ```
275
290
 
291
+ ### API Reference
292
+
293
+ #### `install(options?: InstallOptions): Promise<InstallResult>`
294
+
295
+ Installs rules and MCP servers based on configuration.
296
+
297
+ **Options:**
298
+
299
+ - `cwd`: Base directory to use instead of `process.cwd()`
300
+ - `config`: Custom config object to use instead of loading from file
301
+ - `silent`: Whether to suppress console output
302
+
303
+ **Returns:**
304
+
305
+ A Promise that resolves to an object with:
306
+
307
+ - `success`: Whether the operation was successful
308
+ - `error`: Error message if the operation failed
309
+ - `installedRuleCount`: Number of rules installed
310
+
276
311
  ## Contributing
277
312
 
278
313
  Contributions are welcome! Please feel free to submit a Pull Request.
@@ -281,8 +316,6 @@ Contributions are welcome! Please feel free to submit a Pull Request.
281
316
 
282
317
  ### Testing
283
318
 
284
- The project includes both unit tests and end-to-end (E2E) tests:
285
-
286
319
  ```bash
287
320
  # Run all tests
288
321
  npm test
package/dist/api.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { InstallOptions, InstallResult } from "./commands/install";
2
+ /**
3
+ * Install AICM rules based on configuration
4
+ * @param options Install options
5
+ * @returns Result of the install operation
6
+ */
7
+ export declare function install(options?: InstallOptions): Promise<InstallResult>;
8
+ export { Config, Rule, Rules, RuleMetadata, RuleContent, RuleCollection, } from "./types";
package/dist/api.js ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.install = install;
4
+ const install_1 = require("./commands/install");
5
+ /**
6
+ * Install AICM rules based on configuration
7
+ * @param options Install options
8
+ * @returns Result of the install operation
9
+ */
10
+ async function install(options = {}) {
11
+ return (0, install_1.install)(options);
12
+ }
@@ -20,11 +20,10 @@ function initCommand() {
20
20
  }
21
21
  try {
22
22
  fs_extra_1.default.writeJsonSync(configPath, defaultConfig, { spaces: 2 });
23
- console.log(chalk_1.default.green("Configuration file created successfully!"));
24
23
  console.log(`Configuration file location: ${chalk_1.default.blue(configPath)}`);
25
24
  console.log(`\nNext steps:`);
26
- console.log(` 1. Edit ${chalk_1.default.blue("aicm.json")} to configure your rules`);
27
- console.log(` 2. Run ${chalk_1.default.blue("npx aicm install")} to install rules`);
25
+ console.log(` 1. Edit ${chalk_1.default.blue("aicm.json")} to configure your rules & presets`);
26
+ console.log(` 2. Run ${chalk_1.default.blue("npx aicm install")} to install rules & mcps`);
28
27
  }
29
28
  catch (error) {
30
29
  console.error(chalk_1.default.red("Error creating configuration file:"), error);
@@ -1 +1,42 @@
1
+ import { Config } from "../types";
2
+ /**
3
+ * Options for the installCore function
4
+ */
5
+ export interface InstallOptions {
6
+ /**
7
+ * Base directory to use instead of process.cwd()
8
+ */
9
+ cwd?: string;
10
+ /**
11
+ * Custom config object to use instead of loading from file
12
+ */
13
+ config?: Config;
14
+ /**
15
+ * Whether to log progress to console
16
+ */
17
+ silent?: boolean;
18
+ }
19
+ /**
20
+ * Result of the install operation
21
+ */
22
+ export interface InstallResult {
23
+ /**
24
+ * Whether the operation was successful
25
+ */
26
+ success: boolean;
27
+ /**
28
+ * Error message if the operation failed
29
+ */
30
+ error?: string;
31
+ /**
32
+ * Number of rules installed
33
+ */
34
+ installedRuleCount: number;
35
+ }
36
+ /**
37
+ * Core implementation of the rule installation logic
38
+ * @param options Install options
39
+ * @returns Result of the install operation
40
+ */
41
+ export declare function install(options?: InstallOptions): Promise<InstallResult>;
1
42
  export declare function installCommand(): Promise<void>;
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.install = install;
6
7
  exports.installCommand = installCommand;
7
8
  const chalk_1 = __importDefault(require("chalk"));
8
9
  const config_1 = require("../utils/config");
@@ -11,13 +12,19 @@ const rule_collector_1 = require("../utils/rule-collector");
11
12
  const rule_writer_1 = require("../utils/rule-writer");
12
13
  const fs_extra_1 = __importDefault(require("fs-extra"));
13
14
  const node_path_1 = __importDefault(require("node:path"));
14
- function writeMcpServersToTargets(mcpServers, ides) {
15
+ /**
16
+ * Write MCP servers configuration to IDE targets
17
+ * @param mcpServers The MCP servers configuration
18
+ * @param ides The IDEs to write to
19
+ * @param cwd The current working directory
20
+ */
21
+ function writeMcpServersToTargets(mcpServers, ides, cwd) {
15
22
  if (!mcpServers)
16
23
  return;
17
24
  for (const ide of ides) {
18
25
  let mcpPath = null;
19
26
  if (ide === "cursor") {
20
- mcpPath = node_path_1.default.join(process.cwd(), ".cursor", "mcp.json");
27
+ mcpPath = node_path_1.default.join(cwd, ".cursor", "mcp.json");
21
28
  fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(mcpPath));
22
29
  }
23
30
  // Windsurf does not support project mcpServers, so skip
@@ -26,37 +33,59 @@ function writeMcpServersToTargets(mcpServers, ides) {
26
33
  }
27
34
  }
28
35
  }
29
- async function installCommand() {
36
+ /**
37
+ * Core implementation of the rule installation logic
38
+ * @param options Install options
39
+ * @returns Result of the install operation
40
+ */
41
+ async function install(options = {}) {
42
+ const cwd = options.cwd || process.cwd();
43
+ const silent = options.silent || false;
44
+ const log = silent ? () => { } : console.log;
45
+ const error = silent ? () => { } : console.error;
30
46
  try {
47
+ // Save original process.cwd() and change to the specified cwd
48
+ const originalCwd = process.cwd();
49
+ if (cwd !== originalCwd) {
50
+ process.chdir(cwd);
51
+ }
31
52
  // Initialize rule collection
32
53
  const ruleCollection = (0, rule_collector_1.initRuleCollection)();
33
- // Load configuration
34
- const config = (0, config_1.getConfig)();
35
- // If config doesn't exist, print error and exit
54
+ // Use provided config or load from file
55
+ const config = options.config || (0, config_1.getConfig)();
56
+ // If config doesn't exist, return error
36
57
  if (!config) {
37
- console.error(chalk_1.default.red("Configuration file not found! Please run 'npx aicm init' to create one."));
38
- process.exit(1);
58
+ error("Configuration file not found!");
59
+ // Restore original cwd
60
+ if (cwd !== originalCwd) {
61
+ process.chdir(originalCwd);
62
+ }
63
+ return {
64
+ success: false,
65
+ error: "Configuration file not found!",
66
+ installedRuleCount: 0,
67
+ };
39
68
  }
40
69
  // Check if rules are defined (either directly or through presets)
41
70
  if (!config.rules || Object.keys(config.rules).length === 0) {
42
71
  // If there are no presets defined either, show a message
43
72
  if (!config.presets || config.presets.length === 0) {
44
- console.log(chalk_1.default.yellow("No rules defined in configuration."));
45
- console.log(`Edit your ${chalk_1.default.blue("aicm.json")} file to add rules.`);
46
- return;
47
- }
48
- }
49
- if (config.presets && config.presets.length > 0) {
50
- const hasValidPresets = true;
51
- // If no valid presets and no direct rules, exit
52
- if (!hasValidPresets &&
53
- (!config.rules || Object.keys(config.rules).length === 0)) {
54
- console.log(chalk_1.default.yellow("\nNo valid rules found in configuration or presets."));
55
- return;
73
+ error("No rules defined in configuration.");
74
+ // Restore original cwd
75
+ if (cwd !== originalCwd) {
76
+ process.chdir(originalCwd);
77
+ }
78
+ return {
79
+ success: false,
80
+ error: "No rules defined in configuration.",
81
+ installedRuleCount: 0,
82
+ };
56
83
  }
57
84
  }
58
85
  // Process each rule
59
86
  let hasErrors = false;
87
+ const errorMessages = [];
88
+ let installedRuleCount = 0;
60
89
  for (const [name, source] of Object.entries(config.rules)) {
61
90
  if (source === false)
62
91
  continue; // skip canceled rules
@@ -75,20 +104,32 @@ async function installCommand() {
75
104
  ruleContent = (0, rule_collector_1.collectLocalRule)(name, source, ruleBasePath);
76
105
  break;
77
106
  default:
78
- console.log(chalk_1.default.yellow(`Unknown rule type: ${ruleType}`));
107
+ error(`Unknown rule type: ${ruleType}`);
108
+ errorMessages.push(`Unknown rule type: ${ruleType}`);
79
109
  continue;
80
110
  }
81
111
  // Add rule to collection
82
112
  (0, rule_collector_1.addRuleToCollection)(ruleCollection, ruleContent, config.ides);
113
+ installedRuleCount++;
83
114
  }
84
- catch (error) {
115
+ catch (e) {
85
116
  hasErrors = true;
86
- console.error(chalk_1.default.red(`Error processing rule ${name}: ${error instanceof Error ? error.message : String(error)}`));
117
+ const errorMessage = `Error processing rule ${name}: ${e instanceof Error ? e.message : String(e)}`;
118
+ error(errorMessage);
119
+ errorMessages.push(errorMessage);
87
120
  }
88
121
  }
89
122
  // If there were errors, exit with error
90
123
  if (hasErrors) {
91
- throw new Error("One or more rules failed to process");
124
+ // Restore original cwd
125
+ if (cwd !== originalCwd) {
126
+ process.chdir(originalCwd);
127
+ }
128
+ return {
129
+ success: false,
130
+ error: errorMessages.join("; "),
131
+ installedRuleCount,
132
+ };
92
133
  }
93
134
  // Write all collected rules to their targets
94
135
  (0, rule_writer_1.writeRulesToTargets)(ruleCollection);
@@ -96,9 +137,40 @@ async function installCommand() {
96
137
  if (config.mcpServers) {
97
138
  // Filter out canceled servers
98
139
  const filteredMcpServers = Object.fromEntries(Object.entries(config.mcpServers).filter(([, v]) => v !== false));
99
- writeMcpServersToTargets(filteredMcpServers, config.ides);
140
+ writeMcpServersToTargets(filteredMcpServers, config.ides, cwd);
141
+ }
142
+ log("Rules installation completed!");
143
+ // Restore original cwd
144
+ if (cwd !== originalCwd) {
145
+ process.chdir(originalCwd);
146
+ }
147
+ return {
148
+ success: true,
149
+ installedRuleCount,
150
+ };
151
+ }
152
+ catch (e) {
153
+ const errorMessage = `Error during rule installation: ${e instanceof Error ? e.message : String(e)}`;
154
+ error(errorMessage);
155
+ // If cwd was changed, restore it
156
+ if (cwd !== process.cwd()) {
157
+ process.chdir(cwd);
158
+ }
159
+ return {
160
+ success: false,
161
+ error: errorMessage,
162
+ installedRuleCount: 0,
163
+ };
164
+ }
165
+ }
166
+ async function installCommand() {
167
+ try {
168
+ const result = await install({ silent: false });
169
+ if (!result.success) {
170
+ console.error(chalk_1.default.red(result.error));
171
+ process.exit(1);
100
172
  }
101
- console.log(chalk_1.default.green("\nRules installation completed!"));
173
+ console.log(chalk_1.default.green("Rules installation completed!"));
102
174
  }
103
175
  catch (error) {
104
176
  console.error(chalk_1.default.red(`Error during rule installation: ${error instanceof Error ? error.message : String(error)}`));
@@ -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,8 +1,9 @@
1
1
  {
2
2
  "name": "aicm",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "A TypeScript CLI tool for managing AI IDE rules across different projects and teams",
5
- "main": "dist/index.js",
5
+ "main": "dist/api.js",
6
+ "types": "dist/api.d.ts",
6
7
  "bin": {
7
8
  "aicm": "./dist/index.js"
8
9
  },
@@ -24,7 +25,7 @@
24
25
  "format": "prettier --write .",
25
26
  "format:check": "prettier --check .",
26
27
  "lint": "eslint",
27
- "prepare": "husky && npx ts-node src/index.ts install",
28
+ "prepare": "husky install && npx ts-node src/index.ts install",
28
29
  "version": "auto-changelog -p && git add CHANGELOG.md"
29
30
  },
30
31
  "keywords": [