aicm 0.17.3 → 0.18.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
@@ -215,6 +215,17 @@ When installed, `aicm` will automatically rewrite the link to point to the corre
215
215
 
216
216
  > **Note:** Path rewriting works for any relative path format in your commands - markdown links, inline code references, or bare paths - as long as they point to actual files in your `rulesDir`.
217
217
 
218
+ #### usage in workspaces mode
219
+
220
+ When using workspaces, commands installed at the monorepo root need to access auxiliary files located in nested packages (e.g., `packages/frontend/rules/helper.js`).
221
+
222
+ `aicm` handles this automatically by:
223
+
224
+ 1. Copying referenced auxiliary files from nested packages to the root `.cursor/rules/aicm/` directory
225
+ 2. Rewriting paths in the root command to point to these copied files
226
+
227
+ **Warning:** If your command references a `.mdc` file (Cursor rule), `aicm` will check if it's a "manual" rule or an "automatic" rule (one that is always applied or auto-attached via globs). If it's an automatic rule, `aicm` will warn you that copying it to the root might cause the rule to be included twice in the context (once from the nested package and once from the root copy). For best results, only reference manual `.mdc` files or other file types (like `.js`, `.json`, `.md`) from commands.
228
+
218
229
  ### Overrides
219
230
 
220
231
  You can disable or replace specific rules or commands provided by presets using the `overrides` field:
@@ -271,6 +282,12 @@ Running `npx aicm install` will install rules for each package in their respecti
271
282
  - `packages/backend/.cursor/rules/aicm/`
272
283
  - `services/api/.cursor/rules/aicm/`
273
284
 
285
+ **Why install in both places?**
286
+ `aicm` installs configurations at both the package level AND the root level to support different workflows:
287
+
288
+ - **Package-level context:** When a developer opens a specific package folder (e.g., `packages/frontend`) in their IDE, they get the specific rules, commands, and MCP servers for that package.
289
+ - **Root-level context:** When a developer opens the monorepo root, `aicm` ensures they have access to all commands and MCP servers from all packages via the merged root configuration. While rules are typically read from nested directories by Cursor, commands and MCP servers must be configured at the root to be accessible.
290
+
274
291
  ### Preset Packages in Workspaces
275
292
 
276
293
  When you have a preset package within your workspace (a package that provides rules to be consumed by others), you can prevent aicm from installing rules into it by setting `skipInstall: true`:
@@ -0,0 +1,5 @@
1
+ import { InstallResult } from "./install";
2
+ /**
3
+ * Install rules across multiple packages in a workspace
4
+ */
5
+ export declare function installWorkspaces(cwd: string, installOnCI: boolean, verbose?: boolean, dryRun?: boolean): Promise<InstallResult>;
@@ -0,0 +1,367 @@
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.installWorkspaces = installWorkspaces;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const working_directory_1 = require("../utils/working-directory");
11
+ const rules_file_writer_1 = require("../utils/rules-file-writer");
12
+ const workspace_discovery_1 = require("../utils/workspace-discovery");
13
+ const install_1 = require("./install");
14
+ /**
15
+ * Extract .mdc file references from a command for warning purposes
16
+ * Returns absolute paths to .mdc files that are referenced
17
+ */
18
+ function extractMdcReferences(content, commandSourcePath) {
19
+ const commandDir = node_path_1.default.dirname(commandSourcePath);
20
+ const mdcFiles = [];
21
+ const seenPaths = new Set();
22
+ // Same regex pattern as rewriteCommandRelativeLinks
23
+ const matches = content.matchAll(/\.\.[/\\][\w\-/\\.]+/g);
24
+ for (const match of matches) {
25
+ const relativePath = match[0];
26
+ const resolved = node_path_1.default.normalize(node_path_1.default.resolve(commandDir, relativePath));
27
+ // Only process .mdc files that exist
28
+ if (resolved.endsWith(".mdc") &&
29
+ !seenPaths.has(resolved) &&
30
+ fs_extra_1.default.existsSync(resolved) &&
31
+ fs_extra_1.default.statSync(resolved).isFile()) {
32
+ seenPaths.add(resolved);
33
+ mdcFiles.push(resolved);
34
+ }
35
+ }
36
+ return mdcFiles;
37
+ }
38
+ /**
39
+ * Check if a .mdc file is a manual rule (not automatic/auto-attached/agent-requested)
40
+ */
41
+ function isManualRule(content) {
42
+ const metadata = (0, rules_file_writer_1.parseRuleFrontmatter)(content);
43
+ // Check for always rules
44
+ if (metadata.type === "always" ||
45
+ metadata.alwaysApply === true ||
46
+ metadata.alwaysApply === "true") {
47
+ return false;
48
+ }
49
+ // Check for auto-attached rules
50
+ if (metadata.type === "auto-attached" || metadata.globs) {
51
+ return false;
52
+ }
53
+ // Check for agent-requested rules
54
+ if (metadata.type === "agent-requested" || metadata.description) {
55
+ return false;
56
+ }
57
+ // Default to manual rule
58
+ return true;
59
+ }
60
+ /**
61
+ * Process .mdc files referenced by commands in workspace mode
62
+ * Warns about non-manual rules and copies them to root for command access
63
+ */
64
+ function processMdcFilesForWorkspace(mdcFilePaths, commands, packages, rootDir) {
65
+ const mdcAssets = [];
66
+ // Build a map of command source paths to their preset names
67
+ const commandPresetMap = new Map();
68
+ for (const command of commands) {
69
+ const commandDir = node_path_1.default.dirname(command.sourcePath);
70
+ commandPresetMap.set(commandDir, command.presetName);
71
+ }
72
+ for (const mdcPath of mdcFilePaths) {
73
+ const content = fs_extra_1.default.readFileSync(mdcPath, "utf8");
74
+ if (!isManualRule(content)) {
75
+ const relativePath = node_path_1.default.basename(mdcPath);
76
+ console.warn(chalk_1.default.yellow(`Warning: Command references non-manual rule file "${relativePath}". ` +
77
+ `This may cause the rule to be included twice in the context. ` +
78
+ `Consider using manual rules (without alwaysApply, globs, or description metadata) ` +
79
+ `when referencing from commands.`));
80
+ }
81
+ // Find which command references this .mdc file and get its preset name
82
+ let mdcAssetName = "";
83
+ let presetName;
84
+ let found = false;
85
+ // First check packages for local files
86
+ for (const pkg of packages) {
87
+ const rulesDir = pkg.config.config.rulesDir;
88
+ if (rulesDir) {
89
+ const pkgRulesPath = node_path_1.default.join(pkg.absolutePath, rulesDir);
90
+ if (mdcPath.startsWith(pkgRulesPath)) {
91
+ mdcAssetName = node_path_1.default.relative(pkgRulesPath, mdcPath);
92
+ found = true;
93
+ break;
94
+ }
95
+ }
96
+ }
97
+ // If not found in local packages, it might be from a preset
98
+ if (!found) {
99
+ // Find the command that references this .mdc file
100
+ for (const command of commands) {
101
+ if (command.presetName) {
102
+ const commandDir = node_path_1.default.dirname(command.sourcePath);
103
+ const rulesDir = node_path_1.default.join(node_path_1.default.dirname(commandDir), "rules");
104
+ if (mdcPath.startsWith(rulesDir)) {
105
+ mdcAssetName = node_path_1.default.relative(rulesDir, mdcPath);
106
+ presetName = command.presetName;
107
+ found = true;
108
+ break;
109
+ }
110
+ }
111
+ }
112
+ }
113
+ if (found) {
114
+ // Build the final asset name with preset namespace if applicable
115
+ const finalAssetName = presetName
116
+ ? node_path_1.default.posix.join(...(0, install_1.extractNamespaceFromPresetPath)(presetName), mdcAssetName.replace(/\\/g, "/"))
117
+ : mdcAssetName.replace(/\\/g, "/");
118
+ // Create an AssetFile entry for the .mdc file
119
+ mdcAssets.push({
120
+ name: finalAssetName,
121
+ content: Buffer.from(content),
122
+ sourcePath: mdcPath,
123
+ source: presetName ? "preset" : "local",
124
+ presetName,
125
+ });
126
+ // Copy to root .cursor/rules/aicm/
127
+ const cursorRulesDir = node_path_1.default.join(rootDir, ".cursor", "rules", "aicm");
128
+ const targetPath = node_path_1.default.join(cursorRulesDir, finalAssetName);
129
+ fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(targetPath));
130
+ fs_extra_1.default.writeFileSync(targetPath, content);
131
+ }
132
+ }
133
+ return mdcAssets;
134
+ }
135
+ function mergeWorkspaceCommands(packages) {
136
+ var _a;
137
+ const commands = [];
138
+ const seenPresetCommands = new Set();
139
+ for (const pkg of packages) {
140
+ const hasCursorTarget = pkg.config.config.targets.includes("cursor");
141
+ if (!hasCursorTarget) {
142
+ continue;
143
+ }
144
+ for (const command of (_a = pkg.config.commands) !== null && _a !== void 0 ? _a : []) {
145
+ if (command.presetName) {
146
+ const presetKey = `${command.presetName}::${command.name}`;
147
+ if (seenPresetCommands.has(presetKey)) {
148
+ continue;
149
+ }
150
+ seenPresetCommands.add(presetKey);
151
+ }
152
+ commands.push(command);
153
+ }
154
+ }
155
+ return commands;
156
+ }
157
+ function collectWorkspaceCommandTargets(packages) {
158
+ const targets = new Set();
159
+ for (const pkg of packages) {
160
+ if (pkg.config.config.targets.includes("cursor")) {
161
+ targets.add("cursor");
162
+ }
163
+ }
164
+ return Array.from(targets);
165
+ }
166
+ function mergeWorkspaceMcpServers(packages) {
167
+ const merged = {};
168
+ const info = {};
169
+ for (const pkg of packages) {
170
+ for (const [key, value] of Object.entries(pkg.config.mcpServers)) {
171
+ if (value === false)
172
+ continue;
173
+ const json = JSON.stringify(value);
174
+ if (!info[key]) {
175
+ info[key] = {
176
+ configs: new Set([json]),
177
+ packages: [pkg.relativePath],
178
+ chosen: pkg.relativePath,
179
+ };
180
+ }
181
+ else {
182
+ info[key].packages.push(pkg.relativePath);
183
+ info[key].configs.add(json);
184
+ info[key].chosen = pkg.relativePath;
185
+ }
186
+ merged[key] = value;
187
+ }
188
+ }
189
+ const conflicts = [];
190
+ for (const [key, data] of Object.entries(info)) {
191
+ if (data.configs.size > 1) {
192
+ conflicts.push({ key, packages: data.packages, chosen: data.chosen });
193
+ }
194
+ }
195
+ return { merged, conflicts };
196
+ }
197
+ /**
198
+ * Install aicm configurations for all packages in a workspace
199
+ */
200
+ async function installWorkspacesPackages(packages, options = {}) {
201
+ const results = [];
202
+ let totalRuleCount = 0;
203
+ let totalCommandCount = 0;
204
+ let totalAssetCount = 0;
205
+ // Install packages sequentially for now (can be parallelized later)
206
+ for (const pkg of packages) {
207
+ const packagePath = pkg.absolutePath;
208
+ try {
209
+ const result = await (0, install_1.installPackage)({
210
+ ...options,
211
+ cwd: packagePath,
212
+ config: pkg.config,
213
+ });
214
+ totalRuleCount += result.installedRuleCount;
215
+ totalCommandCount += result.installedCommandCount;
216
+ totalAssetCount += result.installedAssetCount;
217
+ results.push({
218
+ path: pkg.relativePath,
219
+ success: result.success,
220
+ error: result.error,
221
+ installedRuleCount: result.installedRuleCount,
222
+ installedCommandCount: result.installedCommandCount,
223
+ installedAssetCount: result.installedAssetCount,
224
+ });
225
+ }
226
+ catch (error) {
227
+ results.push({
228
+ path: pkg.relativePath,
229
+ success: false,
230
+ error: error instanceof Error ? error : new Error(String(error)),
231
+ installedRuleCount: 0,
232
+ installedCommandCount: 0,
233
+ installedAssetCount: 0,
234
+ });
235
+ }
236
+ }
237
+ const failedPackages = results.filter((r) => !r.success);
238
+ return {
239
+ success: failedPackages.length === 0,
240
+ packages: results,
241
+ totalRuleCount,
242
+ totalCommandCount,
243
+ totalAssetCount,
244
+ };
245
+ }
246
+ /**
247
+ * Install rules across multiple packages in a workspace
248
+ */
249
+ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = false) {
250
+ return (0, working_directory_1.withWorkingDirectory)(cwd, async () => {
251
+ if (verbose) {
252
+ console.log(chalk_1.default.blue("🔍 Discovering packages..."));
253
+ }
254
+ const allPackages = await (0, workspace_discovery_1.discoverPackagesWithAicm)(cwd);
255
+ const packages = allPackages.filter((pkg) => {
256
+ if (pkg.config.config.skipInstall === true) {
257
+ return false;
258
+ }
259
+ const isRoot = pkg.relativePath === ".";
260
+ if (!isRoot)
261
+ return true;
262
+ // For root directories, only keep if it has rules, commands, or presets
263
+ const hasRules = pkg.config.rules && pkg.config.rules.length > 0;
264
+ const hasCommands = pkg.config.commands && pkg.config.commands.length > 0;
265
+ const hasPresets = pkg.config.config.presets && pkg.config.config.presets.length > 0;
266
+ return hasRules || hasCommands || hasPresets;
267
+ });
268
+ if (packages.length === 0) {
269
+ return {
270
+ success: false,
271
+ error: new Error("No packages with aicm configurations found"),
272
+ installedRuleCount: 0,
273
+ installedCommandCount: 0,
274
+ installedAssetCount: 0,
275
+ packagesCount: 0,
276
+ };
277
+ }
278
+ if (verbose) {
279
+ console.log(chalk_1.default.blue(`Found ${packages.length} packages with aicm configurations:`));
280
+ packages.forEach((pkg) => {
281
+ console.log(chalk_1.default.gray(` - ${pkg.relativePath}`));
282
+ });
283
+ console.log(chalk_1.default.blue(`📦 Installing configurations...`));
284
+ }
285
+ const result = await installWorkspacesPackages(packages, {
286
+ installOnCI,
287
+ verbose,
288
+ dryRun,
289
+ });
290
+ const workspaceCommands = mergeWorkspaceCommands(packages);
291
+ const workspaceCommandTargets = collectWorkspaceCommandTargets(packages);
292
+ if (workspaceCommands.length > 0) {
293
+ (0, install_1.warnPresetCommandCollisions)(workspaceCommands);
294
+ }
295
+ if (!dryRun &&
296
+ workspaceCommands.length > 0 &&
297
+ workspaceCommandTargets.length > 0) {
298
+ const dedupedWorkspaceCommands = (0, install_1.dedupeCommandsForInstall)(workspaceCommands);
299
+ // Collect all assets from packages for command path rewriting
300
+ const allAssets = packages.flatMap((pkg) => { var _a; return (_a = pkg.config.assets) !== null && _a !== void 0 ? _a : []; });
301
+ // Copy assets to root so root commands can reference them
302
+ (0, install_1.writeAssetsToTargets)(allAssets, workspaceCommandTargets);
303
+ // Extract and process .mdc file references from commands
304
+ const mdcFilesSet = new Set();
305
+ for (const command of dedupedWorkspaceCommands) {
306
+ const mdcRefs = extractMdcReferences(command.content, command.sourcePath);
307
+ mdcRefs.forEach((ref) => mdcFilesSet.add(ref));
308
+ }
309
+ const mdcAssets = processMdcFilesForWorkspace(mdcFilesSet, dedupedWorkspaceCommands, packages, cwd);
310
+ // Merge .mdc assets with regular assets for link rewriting
311
+ const allAssetsWithMdc = [...allAssets, ...mdcAssets];
312
+ (0, install_1.writeCommandsToTargets)(dedupedWorkspaceCommands, allAssetsWithMdc, workspaceCommandTargets);
313
+ }
314
+ const { merged: rootMcp, conflicts } = mergeWorkspaceMcpServers(packages);
315
+ const hasCursorTarget = packages.some((p) => p.config.config.targets.includes("cursor"));
316
+ if (!dryRun && hasCursorTarget && Object.keys(rootMcp).length > 0) {
317
+ const mcpPath = node_path_1.default.join(cwd, ".cursor", "mcp.json");
318
+ (0, install_1.writeMcpServersToFile)(rootMcp, mcpPath);
319
+ }
320
+ for (const conflict of conflicts) {
321
+ console.warn(`Warning: MCP configuration conflict detected\n Key: "${conflict.key}"\n Packages: ${conflict.packages.join(", ")}\n Using configuration from: ${conflict.chosen}`);
322
+ }
323
+ if (verbose) {
324
+ result.packages.forEach((pkg) => {
325
+ if (pkg.success) {
326
+ const summaryParts = [`${pkg.installedRuleCount} rules`];
327
+ if (pkg.installedCommandCount > 0) {
328
+ summaryParts.push(`${pkg.installedCommandCount} command${pkg.installedCommandCount === 1 ? "" : "s"}`);
329
+ }
330
+ console.log(chalk_1.default.green(`✅ ${pkg.path} (${summaryParts.join(", ")})`));
331
+ }
332
+ else {
333
+ console.log(chalk_1.default.red(`❌ ${pkg.path}: ${pkg.error}`));
334
+ }
335
+ });
336
+ }
337
+ const failedPackages = result.packages.filter((r) => !r.success);
338
+ if (failedPackages.length > 0) {
339
+ console.log(chalk_1.default.yellow(`Installation completed with errors`));
340
+ if (verbose) {
341
+ const commandSummary = result.totalCommandCount > 0
342
+ ? `, ${result.totalCommandCount} command${result.totalCommandCount === 1 ? "" : "s"} total`
343
+ : "";
344
+ console.log(chalk_1.default.green(`Successfully installed: ${result.packages.length - failedPackages.length}/${result.packages.length} packages (${result.totalRuleCount} rule${result.totalRuleCount === 1 ? "" : "s"} total${commandSummary})`));
345
+ console.log(chalk_1.default.red(`Failed packages: ${failedPackages.map((p) => p.path).join(", ")}`));
346
+ }
347
+ const errorDetails = failedPackages
348
+ .map((p) => `${p.path}: ${p.error}`)
349
+ .join("; ");
350
+ return {
351
+ success: false,
352
+ error: new Error(`Package installation failed for ${failedPackages.length} package(s): ${errorDetails}`),
353
+ installedRuleCount: result.totalRuleCount,
354
+ installedCommandCount: result.totalCommandCount,
355
+ installedAssetCount: result.totalAssetCount,
356
+ packagesCount: result.packages.length,
357
+ };
358
+ }
359
+ return {
360
+ success: true,
361
+ installedRuleCount: result.totalRuleCount,
362
+ installedCommandCount: result.totalCommandCount,
363
+ installedAssetCount: result.totalAssetCount,
364
+ packagesCount: result.packages.length,
365
+ };
366
+ });
367
+ }
@@ -1,4 +1,4 @@
1
- import { ResolvedConfig } from "../utils/config";
1
+ import { ResolvedConfig, CommandFile, AssetFile, MCPServers, SupportedTarget } from "../utils/config";
2
2
  export interface InstallOptions {
3
3
  /**
4
4
  * Base directory to use instead of process.cwd()
@@ -50,6 +50,15 @@ export interface InstallResult {
50
50
  */
51
51
  packagesCount: number;
52
52
  }
53
+ export declare function extractNamespaceFromPresetPath(presetPath: string): string[];
54
+ export declare function writeAssetsToTargets(assets: AssetFile[], targets: SupportedTarget[]): void;
55
+ export declare function writeCommandsToTargets(commands: CommandFile[], assets: AssetFile[], targets: SupportedTarget[]): void;
56
+ export declare function warnPresetCommandCollisions(commands: CommandFile[]): void;
57
+ export declare function dedupeCommandsForInstall(commands: CommandFile[]): CommandFile[];
58
+ /**
59
+ * Write MCP servers configuration to a specific file
60
+ */
61
+ export declare function writeMcpServersToFile(mcpServers: MCPServers, mcpPath: string): void;
53
62
  /**
54
63
  * Install rules for a single package (used within workspaces and standalone installs)
55
64
  */
@@ -3,6 +3,12 @@ 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.extractNamespaceFromPresetPath = extractNamespaceFromPresetPath;
7
+ exports.writeAssetsToTargets = writeAssetsToTargets;
8
+ exports.writeCommandsToTargets = writeCommandsToTargets;
9
+ exports.warnPresetCommandCollisions = warnPresetCommandCollisions;
10
+ exports.dedupeCommandsForInstall = dedupeCommandsForInstall;
11
+ exports.writeMcpServersToFile = writeMcpServersToFile;
6
12
  exports.installPackage = installPackage;
7
13
  exports.install = install;
8
14
  exports.installCommand = installCommand;
@@ -13,7 +19,7 @@ const config_1 = require("../utils/config");
13
19
  const working_directory_1 = require("../utils/working-directory");
14
20
  const is_ci_1 = require("../utils/is-ci");
15
21
  const rules_file_writer_1 = require("../utils/rules-file-writer");
16
- const workspace_discovery_1 = require("../utils/workspace-discovery");
22
+ const install_workspaces_1 = require("./install-workspaces");
17
23
  function getTargetPaths() {
18
24
  const projectDir = process.cwd();
19
25
  return {
@@ -62,7 +68,19 @@ function writeCursorCommands(commands, cursorCommandsDir, assets) {
62
68
  }
63
69
  function rewriteCommandRelativeLinks(content, commandSourcePath, assets) {
64
70
  const commandDir = node_path_1.default.dirname(commandSourcePath);
65
- const assetMap = new Map(assets.map((a) => [node_path_1.default.normalize(a.sourcePath), a.name]));
71
+ const assetMap = new Map(assets.map((a) => {
72
+ let targetPath;
73
+ if (a.presetName) {
74
+ const namespace = extractNamespaceFromPresetPath(a.presetName);
75
+ // Use posix paths for URLs/links (always forward slashes)
76
+ targetPath = node_path_1.default.posix.join(...namespace, a.name);
77
+ }
78
+ else {
79
+ // Normalize to posix for consistent forward slashes in links
80
+ targetPath = a.name.split(node_path_1.default.sep).join(node_path_1.default.posix.sep);
81
+ }
82
+ return [node_path_1.default.normalize(a.sourcePath), targetPath];
83
+ }));
66
84
  return content.replace(/\.\.[/\\][\w\-/\\.]+/g, (match) => {
67
85
  const resolved = node_path_1.default.normalize(node_path_1.default.resolve(commandDir, match));
68
86
  return assetMap.has(resolved)
@@ -227,37 +245,6 @@ function dedupeCommandsForInstall(commands) {
227
245
  }
228
246
  return Array.from(unique.values());
229
247
  }
230
- function mergeWorkspaceCommands(packages) {
231
- var _a;
232
- const commands = [];
233
- const seenPresetCommands = new Set();
234
- for (const pkg of packages) {
235
- const hasCursorTarget = pkg.config.config.targets.includes("cursor");
236
- if (!hasCursorTarget) {
237
- continue;
238
- }
239
- for (const command of (_a = pkg.config.commands) !== null && _a !== void 0 ? _a : []) {
240
- if (command.presetName) {
241
- const presetKey = `${command.presetName}::${command.name}`;
242
- if (seenPresetCommands.has(presetKey)) {
243
- continue;
244
- }
245
- seenPresetCommands.add(presetKey);
246
- }
247
- commands.push(command);
248
- }
249
- }
250
- return commands;
251
- }
252
- function collectWorkspaceCommandTargets(packages) {
253
- const targets = new Set();
254
- for (const pkg of packages) {
255
- if (pkg.config.config.targets.includes("cursor")) {
256
- targets.add("cursor");
257
- }
258
- }
259
- return Array.from(targets);
260
- }
261
248
  /**
262
249
  * Write MCP servers configuration to IDE targets
263
250
  */
@@ -311,37 +298,6 @@ function writeMcpServersToFile(mcpServers, mcpPath) {
311
298
  };
312
299
  fs_extra_1.default.writeJsonSync(mcpPath, mergedConfig, { spaces: 2 });
313
300
  }
314
- function mergeWorkspaceMcpServers(packages) {
315
- const merged = {};
316
- const info = {};
317
- for (const pkg of packages) {
318
- for (const [key, value] of Object.entries(pkg.config.mcpServers)) {
319
- if (value === false)
320
- continue;
321
- const json = JSON.stringify(value);
322
- if (!info[key]) {
323
- info[key] = {
324
- configs: new Set([json]),
325
- packages: [pkg.relativePath],
326
- chosen: pkg.relativePath,
327
- };
328
- }
329
- else {
330
- info[key].packages.push(pkg.relativePath);
331
- info[key].configs.add(json);
332
- info[key].chosen = pkg.relativePath;
333
- }
334
- merged[key] = value;
335
- }
336
- }
337
- const conflicts = [];
338
- for (const [key, data] of Object.entries(info)) {
339
- if (data.configs.size > 1) {
340
- conflicts.push({ key, packages: data.packages, chosen: data.chosen });
341
- }
342
- }
343
- return { merged, conflicts };
344
- }
345
301
  /**
346
302
  * Install rules for a single package (used within workspaces and standalone installs)
347
303
  */
@@ -407,166 +363,6 @@ async function installPackage(options = {}) {
407
363
  }
408
364
  });
409
365
  }
410
- /**
411
- * Install aicm configurations for all packages in a workspace
412
- */
413
- async function installWorkspacesPackages(packages, options = {}) {
414
- const results = [];
415
- let totalRuleCount = 0;
416
- let totalCommandCount = 0;
417
- let totalAssetCount = 0;
418
- // Install packages sequentially for now (can be parallelized later)
419
- for (const pkg of packages) {
420
- const packagePath = pkg.absolutePath;
421
- try {
422
- const result = await installPackage({
423
- ...options,
424
- cwd: packagePath,
425
- config: pkg.config,
426
- });
427
- totalRuleCount += result.installedRuleCount;
428
- totalCommandCount += result.installedCommandCount;
429
- totalAssetCount += result.installedAssetCount;
430
- results.push({
431
- path: pkg.relativePath,
432
- success: result.success,
433
- error: result.error,
434
- installedRuleCount: result.installedRuleCount,
435
- installedCommandCount: result.installedCommandCount,
436
- installedAssetCount: result.installedAssetCount,
437
- });
438
- }
439
- catch (error) {
440
- results.push({
441
- path: pkg.relativePath,
442
- success: false,
443
- error: error instanceof Error ? error : new Error(String(error)),
444
- installedRuleCount: 0,
445
- installedCommandCount: 0,
446
- installedAssetCount: 0,
447
- });
448
- }
449
- }
450
- const failedPackages = results.filter((r) => !r.success);
451
- return {
452
- success: failedPackages.length === 0,
453
- packages: results,
454
- totalRuleCount,
455
- totalCommandCount,
456
- totalAssetCount,
457
- };
458
- }
459
- /**
460
- * Install rules across multiple packages in a workspace
461
- */
462
- async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = false) {
463
- return (0, working_directory_1.withWorkingDirectory)(cwd, async () => {
464
- if (verbose) {
465
- console.log(chalk_1.default.blue("🔍 Discovering packages..."));
466
- }
467
- const allPackages = await (0, workspace_discovery_1.discoverPackagesWithAicm)(cwd);
468
- const packages = allPackages.filter((pkg) => {
469
- if (pkg.config.config.skipInstall === true) {
470
- return false;
471
- }
472
- const isRoot = pkg.relativePath === ".";
473
- if (!isRoot)
474
- return true;
475
- // For root directories, only keep if it has rules, commands, or presets
476
- const hasRules = pkg.config.rules && pkg.config.rules.length > 0;
477
- const hasCommands = pkg.config.commands && pkg.config.commands.length > 0;
478
- const hasPresets = pkg.config.config.presets && pkg.config.config.presets.length > 0;
479
- return hasRules || hasCommands || hasPresets;
480
- });
481
- if (packages.length === 0) {
482
- return {
483
- success: false,
484
- error: new Error("No packages with aicm configurations found"),
485
- installedRuleCount: 0,
486
- installedCommandCount: 0,
487
- installedAssetCount: 0,
488
- packagesCount: 0,
489
- };
490
- }
491
- if (verbose) {
492
- console.log(chalk_1.default.blue(`Found ${packages.length} packages with aicm configurations:`));
493
- packages.forEach((pkg) => {
494
- console.log(chalk_1.default.gray(` - ${pkg.relativePath}`));
495
- });
496
- console.log(chalk_1.default.blue(`📦 Installing configurations...`));
497
- }
498
- const result = await installWorkspacesPackages(packages, {
499
- installOnCI,
500
- verbose,
501
- dryRun,
502
- });
503
- const workspaceCommands = mergeWorkspaceCommands(packages);
504
- const workspaceCommandTargets = collectWorkspaceCommandTargets(packages);
505
- if (workspaceCommands.length > 0) {
506
- warnPresetCommandCollisions(workspaceCommands);
507
- }
508
- if (!dryRun &&
509
- workspaceCommands.length > 0 &&
510
- workspaceCommandTargets.length > 0) {
511
- const dedupedWorkspaceCommands = dedupeCommandsForInstall(workspaceCommands);
512
- // Collect all assets from packages for command path rewriting
513
- const allAssets = packages.flatMap((pkg) => { var _a; return (_a = pkg.config.assets) !== null && _a !== void 0 ? _a : []; });
514
- writeCommandsToTargets(dedupedWorkspaceCommands, allAssets, workspaceCommandTargets);
515
- }
516
- const { merged: rootMcp, conflicts } = mergeWorkspaceMcpServers(packages);
517
- const hasCursorTarget = packages.some((p) => p.config.config.targets.includes("cursor"));
518
- if (!dryRun && hasCursorTarget && Object.keys(rootMcp).length > 0) {
519
- const mcpPath = node_path_1.default.join(cwd, ".cursor", "mcp.json");
520
- writeMcpServersToFile(rootMcp, mcpPath);
521
- }
522
- for (const conflict of conflicts) {
523
- console.warn(`Warning: MCP configuration conflict detected\n Key: "${conflict.key}"\n Packages: ${conflict.packages.join(", ")}\n Using configuration from: ${conflict.chosen}`);
524
- }
525
- if (verbose) {
526
- result.packages.forEach((pkg) => {
527
- if (pkg.success) {
528
- const summaryParts = [`${pkg.installedRuleCount} rules`];
529
- if (pkg.installedCommandCount > 0) {
530
- summaryParts.push(`${pkg.installedCommandCount} command${pkg.installedCommandCount === 1 ? "" : "s"}`);
531
- }
532
- console.log(chalk_1.default.green(`✅ ${pkg.path} (${summaryParts.join(", ")})`));
533
- }
534
- else {
535
- console.log(chalk_1.default.red(`❌ ${pkg.path}: ${pkg.error}`));
536
- }
537
- });
538
- }
539
- const failedPackages = result.packages.filter((r) => !r.success);
540
- if (failedPackages.length > 0) {
541
- console.log(chalk_1.default.yellow(`Installation completed with errors`));
542
- if (verbose) {
543
- const commandSummary = result.totalCommandCount > 0
544
- ? `, ${result.totalCommandCount} command${result.totalCommandCount === 1 ? "" : "s"} total`
545
- : "";
546
- console.log(chalk_1.default.green(`Successfully installed: ${result.packages.length - failedPackages.length}/${result.packages.length} packages (${result.totalRuleCount} rule${result.totalRuleCount === 1 ? "" : "s"} total${commandSummary})`));
547
- console.log(chalk_1.default.red(`Failed packages: ${failedPackages.map((p) => p.path).join(", ")}`));
548
- }
549
- const errorDetails = failedPackages
550
- .map((p) => `${p.path}: ${p.error}`)
551
- .join("; ");
552
- return {
553
- success: false,
554
- error: new Error(`Package installation failed for ${failedPackages.length} package(s): ${errorDetails}`),
555
- installedRuleCount: result.totalRuleCount,
556
- installedCommandCount: result.totalCommandCount,
557
- installedAssetCount: result.totalAssetCount,
558
- packagesCount: result.packages.length,
559
- };
560
- }
561
- return {
562
- success: true,
563
- installedRuleCount: result.totalRuleCount,
564
- installedCommandCount: result.totalCommandCount,
565
- installedAssetCount: result.totalAssetCount,
566
- packagesCount: result.packages.length,
567
- };
568
- });
569
- }
570
366
  /**
571
367
  * Core implementation of the rule installation logic
572
368
  */
@@ -595,7 +391,7 @@ async function install(options = {}) {
595
391
  const shouldUseWorkspaces = (resolvedConfig === null || resolvedConfig === void 0 ? void 0 : resolvedConfig.config.workspaces) ||
596
392
  (!resolvedConfig && (0, config_1.detectWorkspacesFromPackageJson)(cwd));
597
393
  if (shouldUseWorkspaces) {
598
- return await installWorkspaces(cwd, installOnCI, options.verbose, options.dryRun);
394
+ return await (0, install_workspaces_1.installWorkspaces)(cwd, installOnCI, options.verbose, options.dryRun);
599
395
  }
600
396
  return installPackage(options);
601
397
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aicm",
3
- "version": "0.17.3",
3
+ "version": "0.18.0",
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",
@@ -63,6 +63,6 @@
63
63
  "format:check": "prettier --check .",
64
64
  "lint": "eslint",
65
65
  "version": "auto-changelog -p && git add CHANGELOG.md",
66
- "release": "np"
66
+ "release": "np --no-tests"
67
67
  }
68
68
  }