aicm 0.11.0 → 0.12.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
@@ -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/e80dedbc-89c4-4747-9acf-b7ecb7493fcc
7
+ ![aicm](https://github.com/user-attachments/assets/ca38f2d6-ece6-43ad-a127-6f4fce8b2a5a)
8
8
 
9
9
  ## Why
10
10
 
@@ -225,7 +225,32 @@ Example `aicm.json`:
225
225
  - **rules**: Object containing rule configurations
226
226
 
227
227
  - **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)
228
+ - **source-location**: Location of the rule file (path within an npm package or local path). Supports glob patterns for automatic file discovery.
229
+
230
+ #### Glob Pattern Support
231
+
232
+ Rules support glob patterns for automatic discovery of multiple `.mdc` files. This is particularly useful when you have multiple related rules organized in directories.
233
+
234
+ ```json
235
+ {
236
+ "rules": {
237
+ "typescript": "./rules/typescript/*.mdc",
238
+ "tests": "./rules/testing/**/*.mdc"
239
+ }
240
+ }
241
+ ```
242
+
243
+ The key becomes the base namespace for discovered files:
244
+
245
+ - `./rules/typescript/strict.mdc` → installed as `typescript/strict`
246
+ - `./rules/typescript/interfaces.mdc` → installed as `typescript/interfaces`
247
+ - `./rules/testing/unit/setup.mdc` → installed as `tests/unit/setup`
248
+
249
+ **Installation Behavior:**
250
+
251
+ - Glob patterns are expanded during installation
252
+ - Only `.mdc` files are included
253
+ - Files are sorted alphabetically for consistent behavior
229
254
 
230
255
  - **mcpServers**: Object containing MCP server configurations. Each key is a unique server name, and the value is an object with either:
231
256
 
@@ -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
- // Process each rule
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(config.rules)) {
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
  }
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aicm",
3
- "version": "0.11.0",
3
+ "version": "0.12.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",
@@ -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": {