aicm 0.9.0 → 0.10.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 +42 -0
- package/dist/commands/install.d.ts +13 -1
- package/dist/commands/install.js +101 -39
- package/dist/commands/monorepo/discovery.d.ts +7 -0
- package/dist/commands/monorepo/discovery.js +50 -0
- package/dist/commands/monorepo/installer.d.ts +9 -0
- package/dist/commands/monorepo/installer.js +70 -0
- package/dist/commands/monorepo/monorepo-install.d.ts +9 -0
- package/dist/commands/monorepo/monorepo-install.js +46 -0
- package/dist/commands/monorepo/types.d.ts +1 -0
- package/dist/commands/monorepo/types.js +17 -0
- package/dist/commands/workspaces/discovery.d.ts +7 -0
- package/dist/commands/workspaces/discovery.js +50 -0
- package/dist/commands/workspaces/workspaces-install.d.ts +9 -0
- package/dist/commands/workspaces/workspaces-install.js +46 -0
- package/dist/index.js +6 -1
- package/dist/types/index.d.ts +15 -0
- package/dist/utils/config.d.ts +14 -10
- package/dist/utils/config.js +134 -53
- package/dist/utils/package-detector.d.ts +6 -0
- package/dist/utils/package-detector.js +25 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -147,6 +147,44 @@ After installation, open Cursor and ask it to do something. Your AI assistant wi
|
|
|
147
147
|
|
|
148
148
|
To prevent [prompt-injection](https://en.wikipedia.org/wiki/Prompt_injection), use only packages from trusted sources.
|
|
149
149
|
|
|
150
|
+
## Workspaces Support
|
|
151
|
+
|
|
152
|
+
aicm supports workspaces by automatically discovering and installing configurations across multiple packages in your repository.
|
|
153
|
+
|
|
154
|
+
To enable workspaces mode, use the `--workspaces` flag:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
npx aicm install --workspaces
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
This will:
|
|
161
|
+
|
|
162
|
+
1. **Discover packages**: Automatically find all directories containing `aicm.json` files in your repository
|
|
163
|
+
2. **Install per package**: Install rules and MCPs for each package individually in their respective directories
|
|
164
|
+
|
|
165
|
+
### How It Works
|
|
166
|
+
|
|
167
|
+
Each directory containing an `aicm.json` file is treated as a separate package with its own configuration.
|
|
168
|
+
|
|
169
|
+
For example, in a workspace structure like:
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
├── packages/
|
|
173
|
+
│ ├── frontend/
|
|
174
|
+
│ │ └── aicm.json
|
|
175
|
+
│ └── backend/
|
|
176
|
+
│ └── aicm.json
|
|
177
|
+
└── services/
|
|
178
|
+
└── api/
|
|
179
|
+
└── aicm.json
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Running `npx aicm install --workspaces` will install rules for each package in their respective directories:
|
|
183
|
+
|
|
184
|
+
- `packages/frontend/.cursor/rules/aicm/`
|
|
185
|
+
- `packages/backend/.cursor/rules/aicm/`
|
|
186
|
+
- `services/api/.cursor/rules/aicm/`
|
|
187
|
+
|
|
150
188
|
## Configuration
|
|
151
189
|
|
|
152
190
|
To configure aicm, use either:
|
|
@@ -252,6 +290,8 @@ npx aicm install
|
|
|
252
290
|
Options:
|
|
253
291
|
|
|
254
292
|
- `--ci`: run in CI environments (default: `false`)
|
|
293
|
+
- `--workspaces`: enable workspaces mode to discover and install configurations across multiple packages
|
|
294
|
+
- `--verbose`: show detailed output during installation
|
|
255
295
|
|
|
256
296
|
## Node.js API
|
|
257
297
|
|
|
@@ -296,6 +336,8 @@ Installs rules and MCP servers based on configuration.
|
|
|
296
336
|
- `cwd`: Base directory to use instead of `process.cwd()`
|
|
297
337
|
- `config`: Custom config object to use instead of loading from file
|
|
298
338
|
- `installOnCI`: Run installation on CI environments (default: `false`)
|
|
339
|
+
- `workspaces`: Enable workspaces mode (default: `false`)
|
|
340
|
+
- `verbose`: Show verbose output during installation (default: `false`)
|
|
299
341
|
|
|
300
342
|
**Returns:**
|
|
301
343
|
|
|
@@ -15,6 +15,14 @@ export interface InstallOptions {
|
|
|
15
15
|
* allow installation on CI environments
|
|
16
16
|
*/
|
|
17
17
|
installOnCI?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Enable workspaces mode
|
|
20
|
+
*/
|
|
21
|
+
workspaces?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Show verbose output during installation
|
|
24
|
+
*/
|
|
25
|
+
verbose?: boolean;
|
|
18
26
|
}
|
|
19
27
|
/**
|
|
20
28
|
* Result of the install operation
|
|
@@ -32,6 +40,10 @@ export interface InstallResult {
|
|
|
32
40
|
* Number of rules installed
|
|
33
41
|
*/
|
|
34
42
|
installedRuleCount: number;
|
|
43
|
+
/**
|
|
44
|
+
* Number of packages installed
|
|
45
|
+
*/
|
|
46
|
+
packagesCount: number;
|
|
35
47
|
}
|
|
36
48
|
/**
|
|
37
49
|
* Core implementation of the rule installation logic
|
|
@@ -39,4 +51,4 @@ export interface InstallResult {
|
|
|
39
51
|
* @returns Result of the install operation
|
|
40
52
|
*/
|
|
41
53
|
export declare function install(options?: InstallOptions): Promise<InstallResult>;
|
|
42
|
-
export declare function installCommand(installOnCI?: boolean): Promise<void>;
|
|
54
|
+
export declare function installCommand(installOnCI?: boolean, workspaces?: boolean, verbose?: boolean): Promise<void>;
|
package/dist/commands/install.js
CHANGED
|
@@ -13,6 +13,26 @@ const rule_writer_1 = require("../utils/rule-writer");
|
|
|
13
13
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
14
14
|
const node_path_1 = __importDefault(require("node:path"));
|
|
15
15
|
const ci_info_1 = require("ci-info");
|
|
16
|
+
const discovery_1 = require("./workspaces/discovery");
|
|
17
|
+
const workspaces_install_1 = require("./workspaces/workspaces-install");
|
|
18
|
+
/**
|
|
19
|
+
* Helper function to execute a function within a specific working directory
|
|
20
|
+
* and ensure the original directory is always restored
|
|
21
|
+
*/
|
|
22
|
+
async function withWorkingDirectory(targetDir, fn) {
|
|
23
|
+
const originalCwd = process.cwd();
|
|
24
|
+
if (targetDir !== originalCwd) {
|
|
25
|
+
process.chdir(targetDir);
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return await fn();
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
if (targetDir !== originalCwd) {
|
|
32
|
+
process.chdir(originalCwd);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
16
36
|
/**
|
|
17
37
|
* Write MCP servers configuration to IDE targets
|
|
18
38
|
* @param mcpServers The MCP servers configuration
|
|
@@ -47,6 +67,64 @@ function isInCIEnvironment() {
|
|
|
47
67
|
// Fall back to ci-info's detection
|
|
48
68
|
return ci_info_1.isCI;
|
|
49
69
|
}
|
|
70
|
+
async function handleWorkspacesInstallation(cwd, installOnCI, verbose = false) {
|
|
71
|
+
return withWorkingDirectory(cwd, async () => {
|
|
72
|
+
if (verbose) {
|
|
73
|
+
console.log(chalk_1.default.blue("🔍 Discovering packages..."));
|
|
74
|
+
}
|
|
75
|
+
const packages = await (0, discovery_1.discoverPackagesWithAicm)(cwd);
|
|
76
|
+
if (packages.length === 0) {
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
error: "No packages with aicm configurations found",
|
|
80
|
+
installedRuleCount: 0,
|
|
81
|
+
packagesCount: 0,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (verbose) {
|
|
85
|
+
console.log(chalk_1.default.blue(`Found ${packages.length} packages with aicm configurations:`));
|
|
86
|
+
packages.forEach((pkg) => {
|
|
87
|
+
console.log(chalk_1.default.gray(` - ${pkg.relativePath}`));
|
|
88
|
+
});
|
|
89
|
+
console.log(chalk_1.default.blue(`📦 Installing configurations...`));
|
|
90
|
+
}
|
|
91
|
+
const result = await (0, workspaces_install_1.installWorkspacesPackages)(packages, {
|
|
92
|
+
installOnCI,
|
|
93
|
+
});
|
|
94
|
+
if (verbose) {
|
|
95
|
+
result.packages.forEach((pkg) => {
|
|
96
|
+
if (pkg.success) {
|
|
97
|
+
console.log(chalk_1.default.green(`✅ ${pkg.path} (${pkg.installedRuleCount} rules)`));
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
console.log(chalk_1.default.red(`❌ ${pkg.path}: ${pkg.error}`));
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const failedPackages = result.packages.filter((r) => !r.success);
|
|
105
|
+
if (failedPackages.length > 0) {
|
|
106
|
+
console.log(chalk_1.default.yellow(`Installation completed with errors`));
|
|
107
|
+
if (verbose) {
|
|
108
|
+
console.log(chalk_1.default.green(`Successfully installed: ${result.packages.length - failedPackages.length}/${result.packages.length} packages (${result.totalRuleCount} rules total)`));
|
|
109
|
+
console.log(chalk_1.default.red(`Failed packages: ${failedPackages.map((p) => p.path).join(", ")}`));
|
|
110
|
+
}
|
|
111
|
+
const errorDetails = failedPackages
|
|
112
|
+
.map((p) => `${p.path}: ${p.error}`)
|
|
113
|
+
.join("; ");
|
|
114
|
+
return {
|
|
115
|
+
success: false,
|
|
116
|
+
error: `Package installation failed for ${failedPackages.length} package(s): ${errorDetails}`,
|
|
117
|
+
installedRuleCount: result.totalRuleCount,
|
|
118
|
+
packagesCount: result.packages.length,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
success: true,
|
|
123
|
+
installedRuleCount: result.totalRuleCount,
|
|
124
|
+
packagesCount: result.packages.length,
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
}
|
|
50
128
|
/**
|
|
51
129
|
* Core implementation of the rule installation logic
|
|
52
130
|
* @param options Install options
|
|
@@ -55,45 +133,38 @@ function isInCIEnvironment() {
|
|
|
55
133
|
async function install(options = {}) {
|
|
56
134
|
const cwd = options.cwd || process.cwd();
|
|
57
135
|
const installOnCI = options.installOnCI === true; // Default to false if not specified
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
process.chdir(cwd);
|
|
136
|
+
return withWorkingDirectory(cwd, async () => {
|
|
137
|
+
if (options.workspaces) {
|
|
138
|
+
return await handleWorkspacesInstallation(cwd, installOnCI, options.verbose);
|
|
62
139
|
}
|
|
63
|
-
const ruleCollection = (0, rule_collector_1.initRuleCollection)();
|
|
64
140
|
const config = options.config || (0, config_1.getConfig)();
|
|
141
|
+
const ruleCollection = (0, rule_collector_1.initRuleCollection)();
|
|
65
142
|
if (!config) {
|
|
66
|
-
if (cwd !== originalCwd) {
|
|
67
|
-
process.chdir(originalCwd);
|
|
68
|
-
}
|
|
69
143
|
return {
|
|
70
144
|
success: false,
|
|
71
|
-
error: "Configuration file not found
|
|
145
|
+
error: "Configuration file not found",
|
|
72
146
|
installedRuleCount: 0,
|
|
147
|
+
packagesCount: 0,
|
|
73
148
|
};
|
|
74
149
|
}
|
|
75
150
|
const inCI = isInCIEnvironment();
|
|
76
151
|
if (inCI && !installOnCI && !config.installOnCI) {
|
|
77
|
-
if (cwd !== originalCwd) {
|
|
78
|
-
process.chdir(originalCwd);
|
|
79
|
-
}
|
|
80
152
|
console.log(chalk_1.default.yellow("Detected CI environment, skipping install."));
|
|
81
153
|
return {
|
|
82
154
|
success: true,
|
|
83
155
|
installedRuleCount: 0,
|
|
156
|
+
packagesCount: 0,
|
|
84
157
|
};
|
|
85
158
|
}
|
|
86
159
|
// Check if rules are defined (either directly or through presets)
|
|
87
160
|
if (!config.rules || Object.keys(config.rules).length === 0) {
|
|
88
161
|
// If there are no presets defined either, show a message
|
|
89
162
|
if (!config.presets || config.presets.length === 0) {
|
|
90
|
-
if (cwd !== originalCwd) {
|
|
91
|
-
process.chdir(originalCwd);
|
|
92
|
-
}
|
|
93
163
|
return {
|
|
94
164
|
success: false,
|
|
95
|
-
error: "No rules defined in configuration
|
|
165
|
+
error: "No rules defined in configuration",
|
|
96
166
|
installedRuleCount: 0,
|
|
167
|
+
packagesCount: 0,
|
|
97
168
|
};
|
|
98
169
|
}
|
|
99
170
|
}
|
|
@@ -139,13 +210,11 @@ async function install(options = {}) {
|
|
|
139
210
|
}
|
|
140
211
|
// If there were errors, exit with error
|
|
141
212
|
if (hasErrors) {
|
|
142
|
-
if (cwd !== originalCwd) {
|
|
143
|
-
process.chdir(originalCwd);
|
|
144
|
-
}
|
|
145
213
|
return {
|
|
146
214
|
success: false,
|
|
147
215
|
error: errorMessages.join("; "),
|
|
148
216
|
installedRuleCount,
|
|
217
|
+
packagesCount: 0,
|
|
149
218
|
};
|
|
150
219
|
}
|
|
151
220
|
// Write all collected rules to their targets
|
|
@@ -156,37 +225,30 @@ async function install(options = {}) {
|
|
|
156
225
|
const filteredMcpServers = Object.fromEntries(Object.entries(config.mcpServers).filter(([, v]) => v !== false));
|
|
157
226
|
writeMcpServersToTargets(filteredMcpServers, config.ides, cwd);
|
|
158
227
|
}
|
|
159
|
-
// Restore original cwd
|
|
160
|
-
if (cwd !== originalCwd) {
|
|
161
|
-
process.chdir(originalCwd);
|
|
162
|
-
}
|
|
163
228
|
return {
|
|
164
229
|
success: true,
|
|
165
230
|
installedRuleCount,
|
|
231
|
+
packagesCount: 1,
|
|
166
232
|
};
|
|
167
|
-
}
|
|
168
|
-
catch (e) {
|
|
169
|
-
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
170
|
-
// If cwd was changed, restore it
|
|
171
|
-
if (cwd !== process.cwd()) {
|
|
172
|
-
process.chdir(cwd);
|
|
173
|
-
}
|
|
174
|
-
return {
|
|
175
|
-
success: false,
|
|
176
|
-
error: errorMessage,
|
|
177
|
-
installedRuleCount: 0,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
233
|
+
});
|
|
180
234
|
}
|
|
181
|
-
async function installCommand(installOnCI) {
|
|
235
|
+
async function installCommand(installOnCI, workspaces, verbose) {
|
|
182
236
|
try {
|
|
183
|
-
const result = await install({ installOnCI });
|
|
237
|
+
const result = await install({ installOnCI, workspaces, verbose });
|
|
184
238
|
if (!result.success) {
|
|
185
239
|
console.error(chalk_1.default.red(result.error));
|
|
186
240
|
process.exit(1);
|
|
187
241
|
}
|
|
188
242
|
else {
|
|
189
|
-
|
|
243
|
+
if (result.packagesCount > 1) {
|
|
244
|
+
console.log(`Successfully installed ${result.installedRuleCount} rules across ${result.packagesCount} packages`);
|
|
245
|
+
}
|
|
246
|
+
else if (workspaces) {
|
|
247
|
+
console.log(`Successfully installed ${result.installedRuleCount} rules across ${result.packagesCount} packages`);
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
console.log("Rules installation completed");
|
|
251
|
+
}
|
|
190
252
|
}
|
|
191
253
|
}
|
|
192
254
|
catch (error) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { PackageInfo } from "../../types";
|
|
2
|
+
/**
|
|
3
|
+
* Discover all packages with aicm configurations in a monorepo
|
|
4
|
+
* @param rootDir The root directory to search from
|
|
5
|
+
* @returns Array of discovered packages
|
|
6
|
+
*/
|
|
7
|
+
export declare function discoverPackagesWithAicm(rootDir: string): Promise<PackageInfo[]>;
|
|
@@ -0,0 +1,50 @@
|
|
|
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.discoverPackagesWithAicm = discoverPackagesWithAicm;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const node_child_process_1 = require("node:child_process");
|
|
9
|
+
const config_1 = require("../../utils/config");
|
|
10
|
+
/**
|
|
11
|
+
* Discover aicm.json files using git ls-files
|
|
12
|
+
* @param rootDir The root directory to search from
|
|
13
|
+
* @returns Array of aicm.json file paths
|
|
14
|
+
*/
|
|
15
|
+
function findAicmFiles(rootDir) {
|
|
16
|
+
const output = (0, node_child_process_1.execSync)("git ls-files --cached --others --exclude-standard aicm.json **/aicm.json", {
|
|
17
|
+
cwd: rootDir,
|
|
18
|
+
encoding: "utf8",
|
|
19
|
+
});
|
|
20
|
+
return output
|
|
21
|
+
.trim()
|
|
22
|
+
.split("\n")
|
|
23
|
+
.filter(Boolean)
|
|
24
|
+
.map((file) => node_path_1.default.resolve(rootDir, file));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Discover all packages with aicm configurations in a monorepo
|
|
28
|
+
* @param rootDir The root directory to search from
|
|
29
|
+
* @returns Array of discovered packages
|
|
30
|
+
*/
|
|
31
|
+
async function discoverPackagesWithAicm(rootDir) {
|
|
32
|
+
const aicmFiles = findAicmFiles(rootDir);
|
|
33
|
+
const packages = [];
|
|
34
|
+
for (const aicmFile of aicmFiles) {
|
|
35
|
+
const packageDir = node_path_1.default.dirname(aicmFile);
|
|
36
|
+
const relativePath = node_path_1.default.relative(rootDir, packageDir);
|
|
37
|
+
// Normalize to forward slashes for cross-platform compatibility
|
|
38
|
+
const normalizedRelativePath = relativePath.replace(/\\/g, "/");
|
|
39
|
+
const config = (0, config_1.getConfig)(packageDir);
|
|
40
|
+
if (config) {
|
|
41
|
+
packages.push({
|
|
42
|
+
relativePath: normalizedRelativePath || ".",
|
|
43
|
+
absolutePath: packageDir,
|
|
44
|
+
config,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Sort packages by relativePath for deterministic order
|
|
49
|
+
return packages.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
50
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PackageInfo, MonorepoInstallResult } from "./types";
|
|
2
|
+
import { InstallOptions } from "../install";
|
|
3
|
+
/**
|
|
4
|
+
* Install aicm configurations for all packages in a monorepo
|
|
5
|
+
* @param packages The packages to install configurations for
|
|
6
|
+
* @param options Install options
|
|
7
|
+
* @returns Result of the monorepo installation
|
|
8
|
+
*/
|
|
9
|
+
export declare function installMonorepoPackages(packages: PackageInfo[], options?: InstallOptions): Promise<MonorepoInstallResult>;
|
|
@@ -0,0 +1,70 @@
|
|
|
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.installMonorepoPackages = installMonorepoPackages;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const install_1 = require("../install");
|
|
10
|
+
/**
|
|
11
|
+
* Install aicm configurations for all packages in a monorepo
|
|
12
|
+
* @param packages The packages to install configurations for
|
|
13
|
+
* @param options Install options
|
|
14
|
+
* @returns Result of the monorepo installation
|
|
15
|
+
*/
|
|
16
|
+
async function installMonorepoPackages(packages, options = {}) {
|
|
17
|
+
console.log(chalk_1.default.blue(`📦 Installing configurations...`));
|
|
18
|
+
const results = [];
|
|
19
|
+
let totalRuleCount = 0;
|
|
20
|
+
// Install packages sequentially for now (can be parallelized later)
|
|
21
|
+
for (const pkg of packages) {
|
|
22
|
+
const packagePath = node_path_1.default.resolve(process.cwd(), pkg.path);
|
|
23
|
+
try {
|
|
24
|
+
console.log(chalk_1.default.gray(` Installing ${pkg.path}...`));
|
|
25
|
+
const result = await (0, install_1.install)({
|
|
26
|
+
...options,
|
|
27
|
+
cwd: packagePath,
|
|
28
|
+
});
|
|
29
|
+
if (result.success) {
|
|
30
|
+
console.log(chalk_1.default.green(`✅ ${pkg.path} (${result.installedRuleCount} rules)`));
|
|
31
|
+
totalRuleCount += result.installedRuleCount;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
console.log(chalk_1.default.red(`❌ ${pkg.path}: ${result.error}`));
|
|
35
|
+
}
|
|
36
|
+
results.push({
|
|
37
|
+
path: pkg.path,
|
|
38
|
+
success: result.success,
|
|
39
|
+
error: result.error,
|
|
40
|
+
installedRuleCount: result.installedRuleCount,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
45
|
+
console.log(chalk_1.default.red(`❌ ${pkg.path}: ${errorMessage}`));
|
|
46
|
+
results.push({
|
|
47
|
+
path: pkg.path,
|
|
48
|
+
success: false,
|
|
49
|
+
error: errorMessage,
|
|
50
|
+
installedRuleCount: 0,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const successfulPackages = results.filter((r) => r.success);
|
|
55
|
+
const failedPackages = results.filter((r) => !r.success);
|
|
56
|
+
// Print summary
|
|
57
|
+
if (failedPackages.length > 0) {
|
|
58
|
+
console.log(chalk_1.default.yellow(`\n⚠️ Installation completed with errors`));
|
|
59
|
+
console.log(chalk_1.default.green(`Successfully installed: ${successfulPackages.length}/${packages.length} packages (${totalRuleCount} rules total)`));
|
|
60
|
+
console.log(chalk_1.default.red(`Failed packages: ${failedPackages.map((p) => p.path).join(", ")}`));
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
console.log(chalk_1.default.green(`\n🎉 Successfully installed ${totalRuleCount} rules across ${packages.length} packages`));
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
success: failedPackages.length === 0,
|
|
67
|
+
packages: results,
|
|
68
|
+
totalRuleCount,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PackageInfo, MonorepoInstallResult } from "../../types";
|
|
2
|
+
import { InstallOptions } from "../install";
|
|
3
|
+
/**
|
|
4
|
+
* Install aicm configurations for all packages in a monorepo
|
|
5
|
+
* @param packages The packages to install configurations for
|
|
6
|
+
* @param options Install options
|
|
7
|
+
* @returns Result of the monorepo installation
|
|
8
|
+
*/
|
|
9
|
+
export declare function installMonorepoPackages(packages: PackageInfo[], options?: InstallOptions): Promise<MonorepoInstallResult>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.installMonorepoPackages = installMonorepoPackages;
|
|
4
|
+
const install_1 = require("../install");
|
|
5
|
+
/**
|
|
6
|
+
* Install aicm configurations for all packages in a monorepo
|
|
7
|
+
* @param packages The packages to install configurations for
|
|
8
|
+
* @param options Install options
|
|
9
|
+
* @returns Result of the monorepo installation
|
|
10
|
+
*/
|
|
11
|
+
async function installMonorepoPackages(packages, options = {}) {
|
|
12
|
+
const results = [];
|
|
13
|
+
let totalRuleCount = 0;
|
|
14
|
+
// Install packages sequentially for now (can be parallelized later)
|
|
15
|
+
for (const pkg of packages) {
|
|
16
|
+
const packagePath = pkg.absolutePath;
|
|
17
|
+
try {
|
|
18
|
+
const result = await (0, install_1.install)({
|
|
19
|
+
...options,
|
|
20
|
+
cwd: packagePath,
|
|
21
|
+
});
|
|
22
|
+
totalRuleCount += result.installedRuleCount;
|
|
23
|
+
results.push({
|
|
24
|
+
path: pkg.relativePath,
|
|
25
|
+
success: result.success,
|
|
26
|
+
error: result.error,
|
|
27
|
+
installedRuleCount: result.installedRuleCount,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
32
|
+
results.push({
|
|
33
|
+
path: pkg.relativePath,
|
|
34
|
+
success: false,
|
|
35
|
+
error: errorMessage,
|
|
36
|
+
installedRuleCount: 0,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const failedPackages = results.filter((r) => !r.success);
|
|
41
|
+
return {
|
|
42
|
+
success: failedPackages.length === 0,
|
|
43
|
+
packages: results,
|
|
44
|
+
totalRuleCount,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../../types";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("../../types"), exports);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { PackageInfo } from "../../types";
|
|
2
|
+
/**
|
|
3
|
+
* Discover all packages with aicm configurations
|
|
4
|
+
* @param rootDir The root directory to search from
|
|
5
|
+
* @returns Array of discovered packages
|
|
6
|
+
*/
|
|
7
|
+
export declare function discoverPackagesWithAicm(rootDir: string): Promise<PackageInfo[]>;
|
|
@@ -0,0 +1,50 @@
|
|
|
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.discoverPackagesWithAicm = discoverPackagesWithAicm;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const node_child_process_1 = require("node:child_process");
|
|
9
|
+
const config_1 = require("../../utils/config");
|
|
10
|
+
/**
|
|
11
|
+
* Discover aicm.json files using git ls-files
|
|
12
|
+
* @param rootDir The root directory to search from
|
|
13
|
+
* @returns Array of aicm.json file paths
|
|
14
|
+
*/
|
|
15
|
+
function findAicmFiles(rootDir) {
|
|
16
|
+
const output = (0, node_child_process_1.execSync)("git ls-files --cached --others --exclude-standard aicm.json **/aicm.json", {
|
|
17
|
+
cwd: rootDir,
|
|
18
|
+
encoding: "utf8",
|
|
19
|
+
});
|
|
20
|
+
return output
|
|
21
|
+
.trim()
|
|
22
|
+
.split("\n")
|
|
23
|
+
.filter(Boolean)
|
|
24
|
+
.map((file) => node_path_1.default.resolve(rootDir, file));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Discover all packages with aicm configurations
|
|
28
|
+
* @param rootDir The root directory to search from
|
|
29
|
+
* @returns Array of discovered packages
|
|
30
|
+
*/
|
|
31
|
+
async function discoverPackagesWithAicm(rootDir) {
|
|
32
|
+
const aicmFiles = findAicmFiles(rootDir);
|
|
33
|
+
const packages = [];
|
|
34
|
+
for (const aicmFile of aicmFiles) {
|
|
35
|
+
const packageDir = node_path_1.default.dirname(aicmFile);
|
|
36
|
+
const relativePath = node_path_1.default.relative(rootDir, packageDir);
|
|
37
|
+
// Normalize to forward slashes for cross-platform compatibility
|
|
38
|
+
const normalizedRelativePath = relativePath.replace(/\\/g, "/");
|
|
39
|
+
const config = (0, config_1.getConfig)(packageDir);
|
|
40
|
+
if (config) {
|
|
41
|
+
packages.push({
|
|
42
|
+
relativePath: normalizedRelativePath || ".",
|
|
43
|
+
absolutePath: packageDir,
|
|
44
|
+
config,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Sort packages by relativePath for deterministic order
|
|
49
|
+
return packages.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
50
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PackageInfo, WorkspacesInstallResult } from "../../types";
|
|
2
|
+
import { InstallOptions } from "../install";
|
|
3
|
+
/**
|
|
4
|
+
* Install aicm configurations for all packages in a workspace
|
|
5
|
+
* @param packages The packages to install configurations for
|
|
6
|
+
* @param options Install options
|
|
7
|
+
* @returns Result of the workspace installation
|
|
8
|
+
*/
|
|
9
|
+
export declare function installWorkspacesPackages(packages: PackageInfo[], options?: InstallOptions): Promise<WorkspacesInstallResult>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.installWorkspacesPackages = installWorkspacesPackages;
|
|
4
|
+
const install_1 = require("../install");
|
|
5
|
+
/**
|
|
6
|
+
* Install aicm configurations for all packages in a workspace
|
|
7
|
+
* @param packages The packages to install configurations for
|
|
8
|
+
* @param options Install options
|
|
9
|
+
* @returns Result of the workspace installation
|
|
10
|
+
*/
|
|
11
|
+
async function installWorkspacesPackages(packages, options = {}) {
|
|
12
|
+
const results = [];
|
|
13
|
+
let totalRuleCount = 0;
|
|
14
|
+
// Install packages sequentially for now (can be parallelized later)
|
|
15
|
+
for (const pkg of packages) {
|
|
16
|
+
const packagePath = pkg.absolutePath;
|
|
17
|
+
try {
|
|
18
|
+
const result = await (0, install_1.install)({
|
|
19
|
+
...options,
|
|
20
|
+
cwd: packagePath,
|
|
21
|
+
});
|
|
22
|
+
totalRuleCount += result.installedRuleCount;
|
|
23
|
+
results.push({
|
|
24
|
+
path: pkg.relativePath,
|
|
25
|
+
success: result.success,
|
|
26
|
+
error: result.error,
|
|
27
|
+
installedRuleCount: result.installedRuleCount,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
32
|
+
results.push({
|
|
33
|
+
path: pkg.relativePath,
|
|
34
|
+
success: false,
|
|
35
|
+
error: errorMessage,
|
|
36
|
+
installedRuleCount: 0,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const failedPackages = results.filter((r) => !r.success);
|
|
41
|
+
return {
|
|
42
|
+
success: failedPackages.length === 0,
|
|
43
|
+
packages: results,
|
|
44
|
+
totalRuleCount,
|
|
45
|
+
};
|
|
46
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,8 @@ const args = (0, arg_1.default)({
|
|
|
17
17
|
"--help": Boolean,
|
|
18
18
|
"--version": Boolean,
|
|
19
19
|
"--ci": Boolean,
|
|
20
|
+
"--workspaces": Boolean,
|
|
21
|
+
"--verbose": Boolean,
|
|
20
22
|
"-h": "--help",
|
|
21
23
|
"-v": "--version",
|
|
22
24
|
}, {
|
|
@@ -36,7 +38,7 @@ switch (command) {
|
|
|
36
38
|
(0, init_1.initCommand)();
|
|
37
39
|
break;
|
|
38
40
|
case "install":
|
|
39
|
-
(0, install_1.installCommand)(args["--ci"]);
|
|
41
|
+
(0, install_1.installCommand)(args["--ci"], args["--workspaces"], args["--verbose"]);
|
|
40
42
|
break;
|
|
41
43
|
case "list":
|
|
42
44
|
(0, list_1.listCommand)();
|
|
@@ -62,10 +64,13 @@ ${chalk_1.default.bold("OPTIONS")}
|
|
|
62
64
|
-h, --help Show this help message
|
|
63
65
|
-v, --version Show version number
|
|
64
66
|
--ci Run in CI environments (default: \`false\`)
|
|
67
|
+
--workspaces Install rules across all workspaces
|
|
68
|
+
--verbose Show detailed output during installation
|
|
65
69
|
|
|
66
70
|
${chalk_1.default.bold("EXAMPLES")}
|
|
67
71
|
$ aicm init
|
|
68
72
|
$ aicm install
|
|
73
|
+
$ aicm install --workspaces
|
|
69
74
|
$ aicm list
|
|
70
75
|
`);
|
|
71
76
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -41,3 +41,18 @@ export interface RuleCollection {
|
|
|
41
41
|
cursor: RuleContent[];
|
|
42
42
|
windsurf: RuleContent[];
|
|
43
43
|
}
|
|
44
|
+
export interface PackageInfo {
|
|
45
|
+
relativePath: string;
|
|
46
|
+
absolutePath: string;
|
|
47
|
+
config: Config;
|
|
48
|
+
}
|
|
49
|
+
export interface WorkspacesInstallResult {
|
|
50
|
+
success: boolean;
|
|
51
|
+
packages: Array<{
|
|
52
|
+
path: string;
|
|
53
|
+
success: boolean;
|
|
54
|
+
error?: string;
|
|
55
|
+
installedRuleCount: number;
|
|
56
|
+
}>;
|
|
57
|
+
totalRuleCount: number;
|
|
58
|
+
}
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -1,29 +1,34 @@
|
|
|
1
1
|
import { Config, Rules } from "../types";
|
|
2
|
-
interface
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
export interface RuleMetadata {
|
|
3
|
+
ruleSources: Record<string, string>;
|
|
4
|
+
originalPresetPaths: Record<string, string>;
|
|
5
|
+
}
|
|
6
|
+
export interface ConfigResult {
|
|
7
|
+
config: Config;
|
|
8
|
+
metadata: RuleMetadata;
|
|
5
9
|
}
|
|
6
10
|
export interface PresetPathInfo {
|
|
7
11
|
fullPath: string;
|
|
8
12
|
originalPath: string;
|
|
9
13
|
}
|
|
10
|
-
export declare function getFullPresetPath(presetPath: string): PresetPathInfo | null;
|
|
14
|
+
export declare function getFullPresetPath(presetPath: string, cwd?: string): PresetPathInfo | null;
|
|
11
15
|
/**
|
|
12
|
-
* Load a preset file and return its
|
|
16
|
+
* Load a preset file and return its contents
|
|
13
17
|
*/
|
|
14
|
-
export declare function loadPreset(presetPath: string): {
|
|
18
|
+
export declare function loadPreset(presetPath: string, cwd?: string): {
|
|
15
19
|
rules: Rules;
|
|
16
20
|
mcpServers?: import("../types").MCPServers;
|
|
21
|
+
presets?: string[];
|
|
17
22
|
} | null;
|
|
18
23
|
/**
|
|
19
24
|
* Load the aicm config using cosmiconfigSync, supporting both aicm.json and package.json.
|
|
20
25
|
* Returns the config object or null if not found.
|
|
21
26
|
*/
|
|
22
|
-
export declare function loadAicmConfigCosmiconfig():
|
|
27
|
+
export declare function loadAicmConfigCosmiconfig(searchFrom?: string): Config | null;
|
|
23
28
|
/**
|
|
24
29
|
* Get the configuration from aicm.json or package.json (using cosmiconfigSync) and merge with any presets
|
|
25
30
|
*/
|
|
26
|
-
export declare function getConfig(): Config | null;
|
|
31
|
+
export declare function getConfig(cwd?: string): Config | null;
|
|
27
32
|
/**
|
|
28
33
|
* Get the source preset path for a rule if it came from a preset
|
|
29
34
|
*/
|
|
@@ -35,5 +40,4 @@ export declare function getOriginalPresetPath(config: Config, ruleName: string):
|
|
|
35
40
|
/**
|
|
36
41
|
* Save the configuration to the aicm.json file
|
|
37
42
|
*/
|
|
38
|
-
export declare function saveConfig(config: Config): boolean;
|
|
39
|
-
export {};
|
|
43
|
+
export declare function saveConfig(config: Config, cwd?: string): boolean;
|
package/dist/utils/config.js
CHANGED
|
@@ -14,36 +14,62 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
|
14
14
|
const node_path_1 = __importDefault(require("node:path"));
|
|
15
15
|
const cosmiconfig_1 = require("cosmiconfig");
|
|
16
16
|
const CONFIG_FILE = "aicm.json";
|
|
17
|
-
function getFullPresetPath(presetPath) {
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
function getFullPresetPath(presetPath, cwd) {
|
|
18
|
+
const workingDir = cwd || process.cwd();
|
|
19
|
+
// If it's a local file with .json extension, check relative to the working directory
|
|
20
|
+
if (presetPath.endsWith(".json")) {
|
|
21
|
+
const absolutePath = node_path_1.default.isAbsolute(presetPath)
|
|
22
|
+
? presetPath
|
|
23
|
+
: node_path_1.default.resolve(workingDir, presetPath);
|
|
24
|
+
if (fs_extra_1.default.pathExistsSync(absolutePath)) {
|
|
25
|
+
return { fullPath: absolutePath, originalPath: presetPath };
|
|
26
|
+
}
|
|
20
27
|
}
|
|
21
28
|
try {
|
|
22
29
|
let absolutePresetPath;
|
|
30
|
+
// Handle npm package with explicit JSON path
|
|
23
31
|
if (presetPath.endsWith(".json")) {
|
|
24
32
|
absolutePresetPath = require.resolve(presetPath, {
|
|
25
|
-
paths: [__dirname,
|
|
33
|
+
paths: [__dirname, workingDir],
|
|
26
34
|
});
|
|
27
35
|
}
|
|
36
|
+
// Handle npm package without explicit JSON path (add aicm.json)
|
|
28
37
|
else {
|
|
38
|
+
// For npm packages, ensure we properly handle scoped packages (@org/pkg)
|
|
29
39
|
const presetPathWithConfig = node_path_1.default.join(presetPath, "aicm.json");
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
try {
|
|
41
|
+
absolutePresetPath = require.resolve(presetPathWithConfig, {
|
|
42
|
+
paths: [__dirname, workingDir],
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
catch (_a) {
|
|
46
|
+
// If direct resolution fails, try as a package name
|
|
47
|
+
absolutePresetPath = require.resolve(presetPath, {
|
|
48
|
+
paths: [__dirname, workingDir],
|
|
49
|
+
});
|
|
50
|
+
// If we found the package but not the config file, look for aicm.json
|
|
51
|
+
if (fs_extra_1.default.existsSync(absolutePresetPath)) {
|
|
52
|
+
const packageDir = node_path_1.default.dirname(absolutePresetPath);
|
|
53
|
+
const configPath = node_path_1.default.join(packageDir, "aicm.json");
|
|
54
|
+
if (fs_extra_1.default.existsSync(configPath)) {
|
|
55
|
+
absolutePresetPath = configPath;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
33
59
|
}
|
|
34
60
|
return fs_extra_1.default.existsSync(absolutePresetPath)
|
|
35
61
|
? { fullPath: absolutePresetPath, originalPath: presetPath }
|
|
36
62
|
: null;
|
|
37
63
|
}
|
|
38
|
-
catch (
|
|
64
|
+
catch (_b) {
|
|
39
65
|
return null;
|
|
40
66
|
}
|
|
41
67
|
}
|
|
42
68
|
/**
|
|
43
|
-
* Load a preset file and return its
|
|
69
|
+
* Load a preset file and return its contents
|
|
44
70
|
*/
|
|
45
|
-
function loadPreset(presetPath) {
|
|
46
|
-
const pathInfo = getFullPresetPath(presetPath);
|
|
71
|
+
function loadPreset(presetPath, cwd) {
|
|
72
|
+
const pathInfo = getFullPresetPath(presetPath, cwd);
|
|
47
73
|
if (!pathInfo) {
|
|
48
74
|
throw new Error(`Error loading preset: "${presetPath}". Make sure the package is installed in your project.`);
|
|
49
75
|
}
|
|
@@ -59,82 +85,133 @@ function loadPreset(presetPath) {
|
|
|
59
85
|
if (!preset.rules || typeof preset.rules !== "object") {
|
|
60
86
|
throw new Error(`Error loading preset: Invalid format in ${presetPath} - missing or invalid 'rules' object`);
|
|
61
87
|
}
|
|
62
|
-
return {
|
|
88
|
+
return {
|
|
89
|
+
rules: preset.rules,
|
|
90
|
+
mcpServers: preset.mcpServers,
|
|
91
|
+
presets: preset.presets,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// Global metadata storage
|
|
95
|
+
let currentMetadata = null;
|
|
96
|
+
// Track processed presets to avoid circular references
|
|
97
|
+
const processedPresets = new Set();
|
|
98
|
+
/**
|
|
99
|
+
* Process presets and return a new config with merged rules and metadata
|
|
100
|
+
*/
|
|
101
|
+
function processPresets(config, cwd) {
|
|
102
|
+
// Create a deep copy of the config to avoid mutations
|
|
103
|
+
const newConfig = JSON.parse(JSON.stringify(config));
|
|
104
|
+
const metadata = {
|
|
105
|
+
ruleSources: {},
|
|
106
|
+
originalPresetPaths: {},
|
|
107
|
+
};
|
|
108
|
+
// Clear processed presets tracking set when starting from the top level
|
|
109
|
+
processedPresets.clear();
|
|
110
|
+
return processPresetsInternal(newConfig, metadata, cwd);
|
|
63
111
|
}
|
|
64
112
|
/**
|
|
65
|
-
*
|
|
113
|
+
* Internal function to process presets recursively
|
|
66
114
|
*/
|
|
67
|
-
function
|
|
115
|
+
function processPresetsInternal(config, metadata, cwd) {
|
|
68
116
|
if (!config.presets || !Array.isArray(config.presets)) {
|
|
69
|
-
return;
|
|
117
|
+
return { config, metadata };
|
|
70
118
|
}
|
|
71
119
|
for (const presetPath of config.presets) {
|
|
72
|
-
const
|
|
73
|
-
if (!
|
|
120
|
+
const pathInfo = getFullPresetPath(presetPath, cwd);
|
|
121
|
+
if (!pathInfo) {
|
|
122
|
+
throw new Error(`Error loading preset: "${presetPath}". Make sure the package is installed in your project.`);
|
|
123
|
+
}
|
|
124
|
+
// Skip if we've already processed this preset (prevents circular references)
|
|
125
|
+
if (processedPresets.has(pathInfo.fullPath)) {
|
|
126
|
+
console.warn(`Skipping already processed preset: ${presetPath}`);
|
|
74
127
|
continue;
|
|
75
|
-
|
|
76
|
-
|
|
128
|
+
}
|
|
129
|
+
// Mark this preset as processed
|
|
130
|
+
processedPresets.add(pathInfo.fullPath);
|
|
131
|
+
const preset = loadPreset(presetPath, cwd);
|
|
132
|
+
if (!preset)
|
|
77
133
|
continue;
|
|
78
|
-
|
|
134
|
+
// Process nested presets first (depth-first)
|
|
135
|
+
if (preset.presets && preset.presets.length > 0) {
|
|
136
|
+
// Create a temporary config with just the presets from this preset
|
|
137
|
+
const presetConfig = {
|
|
138
|
+
rules: {},
|
|
139
|
+
presets: preset.presets,
|
|
140
|
+
ides: [],
|
|
141
|
+
};
|
|
142
|
+
// Recursively process the nested presets
|
|
143
|
+
const { config: nestedConfig } = processPresetsInternal(presetConfig, metadata, cwd);
|
|
144
|
+
Object.assign(preset.rules, nestedConfig.rules);
|
|
145
|
+
}
|
|
146
|
+
const { updatedConfig, updatedMetadata } = mergePresetRules(config, preset.rules, pathInfo, metadata);
|
|
147
|
+
Object.assign(config.rules, updatedConfig.rules);
|
|
148
|
+
Object.assign(metadata.ruleSources, updatedMetadata.ruleSources);
|
|
149
|
+
Object.assign(metadata.originalPresetPaths, updatedMetadata.originalPresetPaths);
|
|
79
150
|
if (preset.mcpServers) {
|
|
80
|
-
mergePresetMcpServers(config, preset.mcpServers);
|
|
151
|
+
config.mcpServers = mergePresetMcpServers(config.mcpServers || {}, preset.mcpServers);
|
|
81
152
|
}
|
|
82
153
|
}
|
|
154
|
+
return { config, metadata };
|
|
83
155
|
}
|
|
84
156
|
/**
|
|
85
|
-
* Merge preset rules into the config
|
|
157
|
+
* Merge preset rules into the config without mutation
|
|
86
158
|
*/
|
|
87
|
-
function mergePresetRules(config, presetRules, pathInfo) {
|
|
159
|
+
function mergePresetRules(config, presetRules, pathInfo, metadata) {
|
|
160
|
+
const updatedRules = { ...config.rules };
|
|
161
|
+
const updatedMetadata = {
|
|
162
|
+
ruleSources: { ...metadata.ruleSources },
|
|
163
|
+
originalPresetPaths: { ...metadata.originalPresetPaths },
|
|
164
|
+
};
|
|
88
165
|
for (const [ruleName, rulePath] of Object.entries(presetRules)) {
|
|
89
166
|
// Cancel if set to false in config
|
|
90
167
|
if (Object.prototype.hasOwnProperty.call(config.rules, ruleName) &&
|
|
91
168
|
config.rules[ruleName] === false) {
|
|
92
|
-
delete
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (config.__originalPresetPaths)
|
|
96
|
-
delete config.__originalPresetPaths[ruleName];
|
|
169
|
+
delete updatedRules[ruleName];
|
|
170
|
+
delete updatedMetadata.ruleSources[ruleName];
|
|
171
|
+
delete updatedMetadata.originalPresetPaths[ruleName];
|
|
97
172
|
continue;
|
|
98
173
|
}
|
|
99
174
|
// Only add if not already defined in config (override handled by config)
|
|
100
175
|
if (!Object.prototype.hasOwnProperty.call(config.rules, ruleName)) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
config.__originalPresetPaths = config.__originalPresetPaths || {};
|
|
105
|
-
config.__originalPresetPaths[ruleName] = pathInfo.originalPath;
|
|
176
|
+
updatedRules[ruleName] = rulePath;
|
|
177
|
+
updatedMetadata.ruleSources[ruleName] = pathInfo.fullPath;
|
|
178
|
+
updatedMetadata.originalPresetPaths[ruleName] = pathInfo.originalPath;
|
|
106
179
|
}
|
|
107
180
|
}
|
|
181
|
+
return {
|
|
182
|
+
updatedConfig: { ...config, rules: updatedRules },
|
|
183
|
+
updatedMetadata,
|
|
184
|
+
};
|
|
108
185
|
}
|
|
109
186
|
/**
|
|
110
|
-
* Merge preset mcpServers
|
|
187
|
+
* Merge preset mcpServers without mutation
|
|
111
188
|
*/
|
|
112
|
-
function mergePresetMcpServers(
|
|
113
|
-
|
|
114
|
-
config.mcpServers = {};
|
|
189
|
+
function mergePresetMcpServers(configMcpServers, presetMcpServers) {
|
|
190
|
+
const newMcpServers = { ...configMcpServers };
|
|
115
191
|
for (const [serverName, serverConfig] of Object.entries(presetMcpServers)) {
|
|
116
192
|
// Cancel if set to false in config
|
|
117
|
-
if (Object.prototype.hasOwnProperty.call(
|
|
118
|
-
|
|
119
|
-
delete
|
|
193
|
+
if (Object.prototype.hasOwnProperty.call(newMcpServers, serverName) &&
|
|
194
|
+
newMcpServers[serverName] === false) {
|
|
195
|
+
delete newMcpServers[serverName];
|
|
120
196
|
continue;
|
|
121
197
|
}
|
|
122
198
|
// Only add if not already defined in config (override handled by config)
|
|
123
|
-
if (!Object.prototype.hasOwnProperty.call(
|
|
124
|
-
|
|
199
|
+
if (!Object.prototype.hasOwnProperty.call(newMcpServers, serverName)) {
|
|
200
|
+
newMcpServers[serverName] = serverConfig;
|
|
125
201
|
}
|
|
126
202
|
}
|
|
203
|
+
return newMcpServers;
|
|
127
204
|
}
|
|
128
205
|
/**
|
|
129
206
|
* Load the aicm config using cosmiconfigSync, supporting both aicm.json and package.json.
|
|
130
207
|
* Returns the config object or null if not found.
|
|
131
208
|
*/
|
|
132
|
-
function loadAicmConfigCosmiconfig() {
|
|
209
|
+
function loadAicmConfigCosmiconfig(searchFrom) {
|
|
133
210
|
const explorer = (0, cosmiconfig_1.cosmiconfigSync)("aicm", {
|
|
134
211
|
searchPlaces: ["package.json", "aicm.json"],
|
|
135
212
|
});
|
|
136
213
|
try {
|
|
137
|
-
const result = explorer.search();
|
|
214
|
+
const result = explorer.search(searchFrom);
|
|
138
215
|
if (!result || !result.config)
|
|
139
216
|
return null;
|
|
140
217
|
const config = result.config;
|
|
@@ -151,33 +228,37 @@ function loadAicmConfigCosmiconfig() {
|
|
|
151
228
|
/**
|
|
152
229
|
* Get the configuration from aicm.json or package.json (using cosmiconfigSync) and merge with any presets
|
|
153
230
|
*/
|
|
154
|
-
function getConfig() {
|
|
155
|
-
const
|
|
231
|
+
function getConfig(cwd) {
|
|
232
|
+
const workingDir = cwd || process.cwd();
|
|
233
|
+
const config = loadAicmConfigCosmiconfig(workingDir);
|
|
156
234
|
if (!config) {
|
|
157
|
-
throw new Error(`No config found in ${
|
|
235
|
+
throw new Error(`No config found in ${workingDir}, create one using "aicm init"`);
|
|
158
236
|
}
|
|
159
|
-
processPresets(config);
|
|
160
|
-
|
|
237
|
+
const { config: processedConfig, metadata } = processPresets(config, workingDir);
|
|
238
|
+
// Store metadata for later access
|
|
239
|
+
currentMetadata = metadata;
|
|
240
|
+
return processedConfig;
|
|
161
241
|
}
|
|
162
242
|
/**
|
|
163
243
|
* Get the source preset path for a rule if it came from a preset
|
|
164
244
|
*/
|
|
165
245
|
function getRuleSource(config, ruleName) {
|
|
166
246
|
var _a;
|
|
167
|
-
return (_a =
|
|
247
|
+
return (_a = currentMetadata === null || currentMetadata === void 0 ? void 0 : currentMetadata.ruleSources) === null || _a === void 0 ? void 0 : _a[ruleName];
|
|
168
248
|
}
|
|
169
249
|
/**
|
|
170
250
|
* Get the original preset path for a rule if it came from a preset
|
|
171
251
|
*/
|
|
172
252
|
function getOriginalPresetPath(config, ruleName) {
|
|
173
253
|
var _a;
|
|
174
|
-
return (_a =
|
|
254
|
+
return (_a = currentMetadata === null || currentMetadata === void 0 ? void 0 : currentMetadata.originalPresetPaths) === null || _a === void 0 ? void 0 : _a[ruleName];
|
|
175
255
|
}
|
|
176
256
|
/**
|
|
177
257
|
* Save the configuration to the aicm.json file
|
|
178
258
|
*/
|
|
179
|
-
function saveConfig(config) {
|
|
180
|
-
const
|
|
259
|
+
function saveConfig(config, cwd) {
|
|
260
|
+
const workingDir = cwd || process.cwd();
|
|
261
|
+
const configPath = node_path_1.default.join(workingDir, CONFIG_FILE);
|
|
181
262
|
try {
|
|
182
263
|
fs_extra_1.default.writeJsonSync(configPath, config, { spaces: 2 });
|
|
183
264
|
return true;
|
|
@@ -0,0 +1,25 @@
|
|
|
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.detectPackageType = detectPackageType;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
/**
|
|
10
|
+
* Detect the type of package based on files present in the directory
|
|
11
|
+
* @param packageDir The directory to check
|
|
12
|
+
* @returns The detected package type
|
|
13
|
+
*/
|
|
14
|
+
function detectPackageType(packageDir) {
|
|
15
|
+
// Check for npm package
|
|
16
|
+
if (fs_extra_1.default.existsSync(node_path_1.default.join(packageDir, "package.json"))) {
|
|
17
|
+
return "npm";
|
|
18
|
+
}
|
|
19
|
+
// Check for Bazel package
|
|
20
|
+
if (fs_extra_1.default.existsSync(node_path_1.default.join(packageDir, "BUILD")) ||
|
|
21
|
+
fs_extra_1.default.existsSync(node_path_1.default.join(packageDir, "BUILD.bazel"))) {
|
|
22
|
+
return "bazel";
|
|
23
|
+
}
|
|
24
|
+
return "unknown";
|
|
25
|
+
}
|