aicm 0.13.1 → 0.14.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 +75 -164
- package/dist/api.d.ts +3 -2
- package/dist/api_v2.d.ts +9 -0
- package/dist/api_v2.js +12 -0
- package/dist/bin/aicm_v2.d.ts +2 -0
- package/dist/bin/aicm_v2.js +8 -0
- package/dist/cli.js +7 -4
- package/dist/cli_v2.d.ts +2 -0
- package/dist/cli_v2.js +94 -0
- package/dist/commands/install.d.ts +47 -3
- package/dist/commands/install.js +461 -25
- package/dist/commands/install_new.d.ts +17 -0
- package/dist/commands/install_new.js +457 -0
- package/dist/commands/install_v2.d.ts +52 -0
- package/dist/commands/install_v2.js +505 -0
- package/dist/commands/install_/327/2242.d.ts +59 -0
- package/dist/commands/install_/327/2242.js +546 -0
- package/dist/commands/list.js +4 -17
- package/dist/utils/config.d.ts +60 -40
- package/dist/utils/config.js +203 -211
- package/dist/utils/config_new.d.ts +64 -0
- package/dist/utils/config_new.js +228 -0
- package/dist/utils/config_v2.d.ts +64 -0
- package/dist/utils/config_v2.js +250 -0
- package/dist/utils/is-ci.d.ts +1 -0
- package/dist/utils/is-ci.js +8 -0
- package/dist/utils/rule-writer.js +3 -5
- package/package.json +3 -3
package/dist/utils/config.js
CHANGED
|
@@ -3,203 +3,221 @@ 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.
|
|
7
|
-
exports.
|
|
6
|
+
exports.SUPPORTED_TARGETS = void 0;
|
|
7
|
+
exports.applyDefaults = applyDefaults;
|
|
8
|
+
exports.validateConfig = validateConfig;
|
|
9
|
+
exports.loadRulesFromDirectory = loadRulesFromDirectory;
|
|
10
|
+
exports.resolvePresetPath = resolvePresetPath;
|
|
8
11
|
exports.loadPreset = loadPreset;
|
|
9
|
-
exports.
|
|
10
|
-
exports.
|
|
11
|
-
exports.
|
|
12
|
-
exports.
|
|
12
|
+
exports.loadAllRules = loadAllRules;
|
|
13
|
+
exports.applyOverrides = applyOverrides;
|
|
14
|
+
exports.loadConfigFile = loadConfigFile;
|
|
15
|
+
exports.loadConfig = loadConfig;
|
|
13
16
|
exports.saveConfig = saveConfig;
|
|
14
17
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
15
18
|
const node_path_1 = __importDefault(require("node:path"));
|
|
16
19
|
const cosmiconfig_1 = require("cosmiconfig");
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
21
|
+
exports.SUPPORTED_TARGETS = ["cursor", "windsurf", "codex"];
|
|
22
|
+
function detectWorkspacesFromPackageJson(cwd) {
|
|
23
|
+
try {
|
|
24
|
+
const packageJsonPath = node_path_1.default.join(cwd, "package.json");
|
|
25
|
+
if (!fs_extra_1.default.existsSync(packageJsonPath)) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
const packageJson = JSON.parse(fs_extra_1.default.readFileSync(packageJsonPath, "utf8"));
|
|
29
|
+
return Boolean(packageJson.workspaces);
|
|
30
|
+
}
|
|
31
|
+
catch (_a) {
|
|
32
|
+
return false;
|
|
24
33
|
}
|
|
25
|
-
return rules;
|
|
26
34
|
}
|
|
27
|
-
function
|
|
35
|
+
function applyDefaults(config, cwd) {
|
|
28
36
|
const workingDir = cwd || process.cwd();
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
// Auto-detect workspaces if not explicitly set
|
|
38
|
+
const workspaces = config.workspaces !== undefined
|
|
39
|
+
? config.workspaces
|
|
40
|
+
: detectWorkspacesFromPackageJson(workingDir);
|
|
41
|
+
return {
|
|
42
|
+
rulesDir: config.rulesDir,
|
|
43
|
+
targets: config.targets || ["cursor"],
|
|
44
|
+
presets: config.presets || [],
|
|
45
|
+
overrides: config.overrides || {},
|
|
46
|
+
mcpServers: config.mcpServers || {},
|
|
47
|
+
workspaces,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function validateConfig(config, configFilePath, cwd) {
|
|
51
|
+
if (typeof config !== "object" || config === null) {
|
|
52
|
+
throw new Error(`Config is not an object at ${configFilePath}`);
|
|
53
|
+
}
|
|
54
|
+
// Validate that either rulesDir or presets is provided
|
|
55
|
+
const hasRulesDir = "rulesDir" in config && typeof config.rulesDir === "string";
|
|
56
|
+
const hasPresets = "presets" in config &&
|
|
57
|
+
Array.isArray(config.presets) &&
|
|
58
|
+
config.presets.length > 0;
|
|
59
|
+
if (!hasRulesDir && !hasPresets) {
|
|
60
|
+
throw new Error(`Either rulesDir or presets must be specified in config at ${configFilePath}`);
|
|
61
|
+
}
|
|
62
|
+
// Validate rulesDir if provided
|
|
63
|
+
if (hasRulesDir) {
|
|
64
|
+
const rulesPath = node_path_1.default.resolve(cwd, config.rulesDir);
|
|
65
|
+
if (!fs_extra_1.default.existsSync(rulesPath)) {
|
|
66
|
+
throw new Error(`Rules directory does not exist: ${rulesPath}`);
|
|
67
|
+
}
|
|
68
|
+
if (!fs_extra_1.default.statSync(rulesPath).isDirectory()) {
|
|
69
|
+
throw new Error(`Rules path is not a directory: ${rulesPath}`);
|
|
36
70
|
}
|
|
37
71
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (presetPath.endsWith(".json")) {
|
|
42
|
-
absolutePresetPath = require.resolve(presetPath, {
|
|
43
|
-
paths: [__dirname, workingDir],
|
|
44
|
-
});
|
|
72
|
+
if ("targets" in config) {
|
|
73
|
+
if (!Array.isArray(config.targets)) {
|
|
74
|
+
throw new Error(`targets must be an array in config at ${configFilePath}`);
|
|
45
75
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
paths: [__dirname, workingDir],
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
catch (_a) {
|
|
56
|
-
// If direct resolution fails, try as a package name
|
|
57
|
-
absolutePresetPath = require.resolve(presetPath, {
|
|
58
|
-
paths: [__dirname, workingDir],
|
|
59
|
-
});
|
|
60
|
-
// If we found the package but not the config file, look for aicm.json
|
|
61
|
-
if (fs_extra_1.default.existsSync(absolutePresetPath)) {
|
|
62
|
-
const packageDir = node_path_1.default.dirname(absolutePresetPath);
|
|
63
|
-
const configPath = node_path_1.default.join(packageDir, "aicm.json");
|
|
64
|
-
if (fs_extra_1.default.existsSync(configPath)) {
|
|
65
|
-
absolutePresetPath = configPath;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
76
|
+
if (config.targets.length === 0) {
|
|
77
|
+
throw new Error(`targets must not be empty in config at ${configFilePath}`);
|
|
78
|
+
}
|
|
79
|
+
for (const target of config.targets) {
|
|
80
|
+
if (!exports.SUPPORTED_TARGETS.includes(target)) {
|
|
81
|
+
throw new Error(`Unsupported target: ${target}. Supported targets: ${exports.SUPPORTED_TARGETS.join(", ")}`);
|
|
68
82
|
}
|
|
69
83
|
}
|
|
70
|
-
return fs_extra_1.default.existsSync(absolutePresetPath)
|
|
71
|
-
? { fullPath: absolutePresetPath, originalPath: presetPath }
|
|
72
|
-
: null;
|
|
73
84
|
}
|
|
74
|
-
|
|
85
|
+
// Validate override rule names will be checked after rule resolution
|
|
86
|
+
}
|
|
87
|
+
async function loadRulesFromDirectory(rulesDir, source, presetName) {
|
|
88
|
+
const rules = [];
|
|
89
|
+
if (!fs_extra_1.default.existsSync(rulesDir)) {
|
|
90
|
+
return rules;
|
|
91
|
+
}
|
|
92
|
+
const pattern = node_path_1.default.join(rulesDir, "**/*.mdc").replace(/\\/g, "/");
|
|
93
|
+
const filePaths = await (0, fast_glob_1.default)(pattern, {
|
|
94
|
+
onlyFiles: true,
|
|
95
|
+
absolute: true,
|
|
96
|
+
});
|
|
97
|
+
for (const filePath of filePaths) {
|
|
98
|
+
const content = await fs_extra_1.default.readFile(filePath, "utf8");
|
|
99
|
+
// Preserve directory structure by using relative path from rulesDir
|
|
100
|
+
const relativePath = node_path_1.default.relative(rulesDir, filePath);
|
|
101
|
+
const ruleName = relativePath.replace(/\.mdc$/, "").replace(/\\/g, "/");
|
|
102
|
+
rules.push({
|
|
103
|
+
name: ruleName,
|
|
104
|
+
content,
|
|
105
|
+
sourcePath: filePath,
|
|
106
|
+
source,
|
|
107
|
+
presetName,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return rules;
|
|
111
|
+
}
|
|
112
|
+
function resolvePresetPath(presetPath, cwd) {
|
|
113
|
+
// Support specifying aicm.json directory and load the config from it
|
|
114
|
+
if (!presetPath.endsWith(".json")) {
|
|
115
|
+
presetPath = node_path_1.default.join(presetPath, "aicm.json");
|
|
116
|
+
}
|
|
117
|
+
// Support local or absolute paths
|
|
118
|
+
const absolutePath = node_path_1.default.isAbsolute(presetPath)
|
|
119
|
+
? presetPath
|
|
120
|
+
: node_path_1.default.resolve(cwd, presetPath);
|
|
121
|
+
if (fs_extra_1.default.existsSync(absolutePath)) {
|
|
122
|
+
return absolutePath;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
// Support npm packages
|
|
126
|
+
const resolvedPath = require.resolve(presetPath, {
|
|
127
|
+
paths: [cwd, __dirname],
|
|
128
|
+
});
|
|
129
|
+
return fs_extra_1.default.existsSync(resolvedPath) ? resolvedPath : null;
|
|
130
|
+
}
|
|
131
|
+
catch (_a) {
|
|
75
132
|
return null;
|
|
76
133
|
}
|
|
77
134
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const pathInfo = getFullPresetPath(presetPath, cwd);
|
|
83
|
-
if (!pathInfo) {
|
|
84
|
-
throw new Error(`Error loading preset: "${presetPath}". Make sure the package is installed in your project.`);
|
|
135
|
+
async function loadPreset(presetPath, cwd) {
|
|
136
|
+
const resolvedPresetPath = resolvePresetPath(presetPath, cwd);
|
|
137
|
+
if (!resolvedPresetPath) {
|
|
138
|
+
throw new Error(`Preset not found: "${presetPath}". Make sure the package is installed or the path is correct.`);
|
|
85
139
|
}
|
|
86
|
-
|
|
87
|
-
let preset;
|
|
140
|
+
let presetConfig;
|
|
88
141
|
try {
|
|
89
|
-
|
|
142
|
+
const content = await fs_extra_1.default.readFile(resolvedPresetPath, "utf8");
|
|
143
|
+
presetConfig = JSON.parse(content);
|
|
90
144
|
}
|
|
91
145
|
catch (error) {
|
|
92
|
-
|
|
93
|
-
throw new Error(`Error loading preset: Invalid JSON in ${presetPath}: ${parseError.message}`);
|
|
146
|
+
throw new Error(`Failed to load preset "${presetPath}": ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
94
147
|
}
|
|
95
|
-
|
|
96
|
-
|
|
148
|
+
// Validate that preset has rulesDir
|
|
149
|
+
if (!presetConfig.rulesDir) {
|
|
150
|
+
throw new Error(`Preset "${presetPath}" must have a rulesDir specified`);
|
|
97
151
|
}
|
|
98
|
-
|
|
152
|
+
// Resolve preset's rules directory relative to the preset file
|
|
153
|
+
const presetDir = node_path_1.default.dirname(resolvedPresetPath);
|
|
154
|
+
const presetRulesDir = node_path_1.default.resolve(presetDir, presetConfig.rulesDir);
|
|
99
155
|
return {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
presets: preset.presets,
|
|
156
|
+
config: presetConfig,
|
|
157
|
+
rulesDir: presetRulesDir,
|
|
103
158
|
};
|
|
104
159
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
// Create a deep copy of the config to avoid mutations
|
|
114
|
-
const newConfig = JSON.parse(JSON.stringify(config));
|
|
115
|
-
const metadata = {
|
|
116
|
-
ruleSources: {},
|
|
117
|
-
originalPresetPaths: {},
|
|
118
|
-
};
|
|
119
|
-
// Clear processed presets tracking set when starting from the top level
|
|
120
|
-
processedPresets.clear();
|
|
121
|
-
return await processPresetsInternal(newConfig, metadata, cwd);
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Internal function to process presets recursively
|
|
125
|
-
*/
|
|
126
|
-
async function processPresetsInternal(config, metadata, cwd) {
|
|
127
|
-
if (!config.presets || !Array.isArray(config.presets)) {
|
|
128
|
-
return { config, metadata };
|
|
160
|
+
async function loadAllRules(config, cwd) {
|
|
161
|
+
const allRules = [];
|
|
162
|
+
let mergedMcpServers = { ...config.mcpServers };
|
|
163
|
+
// Load local rules only if rulesDir is provided
|
|
164
|
+
if (config.rulesDir) {
|
|
165
|
+
const localRulesPath = node_path_1.default.resolve(cwd, config.rulesDir);
|
|
166
|
+
const localRules = await loadRulesFromDirectory(localRulesPath, "local");
|
|
167
|
+
allRules.push(...localRules);
|
|
129
168
|
}
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
// Mark this preset as processed
|
|
141
|
-
processedPresets.add(pathInfo.fullPath);
|
|
142
|
-
const preset = loadPreset(presetPath, cwd);
|
|
143
|
-
if (!preset)
|
|
144
|
-
continue;
|
|
145
|
-
// Expand glob patterns within the preset using its base directory
|
|
146
|
-
const presetDir = node_path_1.default.dirname(pathInfo.fullPath);
|
|
147
|
-
const expansion = await (0, glob_handler_1.expandRulesGlobPatterns)(preset.rules, presetDir);
|
|
148
|
-
preset.rules = expansion.expandedRules;
|
|
149
|
-
// Process nested presets first (depth-first)
|
|
150
|
-
if (preset.presets && preset.presets.length > 0) {
|
|
151
|
-
// Create a temporary config with just the presets from this preset
|
|
152
|
-
const presetConfig = {
|
|
153
|
-
rules: {},
|
|
154
|
-
presets: preset.presets,
|
|
155
|
-
ides: [],
|
|
156
|
-
};
|
|
157
|
-
// Recursively process the nested presets
|
|
158
|
-
const { config: nestedConfig } = await processPresetsInternal(presetConfig, metadata, cwd);
|
|
159
|
-
Object.assign(preset.rules, nestedConfig.rules);
|
|
160
|
-
}
|
|
161
|
-
const { updatedConfig, updatedMetadata } = mergePresetRules(config, preset.rules, pathInfo, metadata);
|
|
162
|
-
Object.assign(config.rules, updatedConfig.rules);
|
|
163
|
-
Object.assign(metadata.ruleSources, updatedMetadata.ruleSources);
|
|
164
|
-
Object.assign(metadata.originalPresetPaths, updatedMetadata.originalPresetPaths);
|
|
165
|
-
if (preset.mcpServers) {
|
|
166
|
-
config.mcpServers = mergePresetMcpServers(config.mcpServers || {}, preset.mcpServers);
|
|
169
|
+
if (config.presets) {
|
|
170
|
+
for (const presetPath of config.presets) {
|
|
171
|
+
const preset = await loadPreset(presetPath, cwd);
|
|
172
|
+
const presetRules = await loadRulesFromDirectory(preset.rulesDir, "preset", presetPath);
|
|
173
|
+
allRules.push(...presetRules);
|
|
174
|
+
// Merge MCP servers from preset
|
|
175
|
+
if (preset.config.mcpServers) {
|
|
176
|
+
mergedMcpServers = mergePresetMcpServers(mergedMcpServers, preset.config.mcpServers);
|
|
177
|
+
}
|
|
167
178
|
}
|
|
168
179
|
}
|
|
169
|
-
return {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
* Merge preset rules into the config without mutation
|
|
173
|
-
*/
|
|
174
|
-
function mergePresetRules(config, presetRules, pathInfo, metadata) {
|
|
175
|
-
const updatedRules = { ...config.rules };
|
|
176
|
-
const updatedMetadata = {
|
|
177
|
-
ruleSources: { ...metadata.ruleSources },
|
|
178
|
-
originalPresetPaths: { ...metadata.originalPresetPaths },
|
|
180
|
+
return {
|
|
181
|
+
rules: allRules,
|
|
182
|
+
mcpServers: mergedMcpServers,
|
|
179
183
|
};
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
184
|
+
}
|
|
185
|
+
function applyOverrides(rules, overrides, cwd) {
|
|
186
|
+
// Validate that all override rule names exist in the resolved rules
|
|
187
|
+
for (const ruleName of Object.keys(overrides)) {
|
|
188
|
+
// TODO: support better error messages with edit distance, helping the user in case of a typo
|
|
189
|
+
// TODO: or shows a list of potential rules to override
|
|
190
|
+
if (!rules.some((rule) => rule.name === ruleName)) {
|
|
191
|
+
throw new Error(`Override rule "${ruleName}" does not exist in resolved rules`);
|
|
188
192
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
193
|
+
}
|
|
194
|
+
const ruleMap = new Map();
|
|
195
|
+
for (const rule of rules) {
|
|
196
|
+
ruleMap.set(rule.name, rule);
|
|
197
|
+
}
|
|
198
|
+
for (const [ruleName, override] of Object.entries(overrides)) {
|
|
199
|
+
if (override === false) {
|
|
200
|
+
ruleMap.delete(ruleName);
|
|
201
|
+
}
|
|
202
|
+
else if (typeof override === "string") {
|
|
203
|
+
const overridePath = node_path_1.default.resolve(cwd, override);
|
|
204
|
+
if (!fs_extra_1.default.existsSync(overridePath)) {
|
|
205
|
+
throw new Error(`Override rule file not found: ${override} in ${cwd}`);
|
|
206
|
+
}
|
|
207
|
+
const content = fs_extra_1.default.readFileSync(overridePath, "utf8");
|
|
208
|
+
ruleMap.set(ruleName, {
|
|
209
|
+
name: ruleName,
|
|
210
|
+
content,
|
|
211
|
+
sourcePath: overridePath,
|
|
212
|
+
source: "local",
|
|
213
|
+
});
|
|
194
214
|
}
|
|
195
215
|
}
|
|
196
|
-
return
|
|
197
|
-
updatedConfig: { ...config, rules: updatedRules },
|
|
198
|
-
updatedMetadata,
|
|
199
|
-
};
|
|
216
|
+
return Array.from(ruleMap.values());
|
|
200
217
|
}
|
|
201
218
|
/**
|
|
202
|
-
* Merge preset
|
|
219
|
+
* Merge preset MCP servers with local config MCP servers
|
|
220
|
+
* Local config takes precedence over preset config
|
|
203
221
|
*/
|
|
204
222
|
function mergePresetMcpServers(configMcpServers, presetMcpServers) {
|
|
205
223
|
const newMcpServers = { ...configMcpServers };
|
|
@@ -217,71 +235,45 @@ function mergePresetMcpServers(configMcpServers, presetMcpServers) {
|
|
|
217
235
|
}
|
|
218
236
|
return newMcpServers;
|
|
219
237
|
}
|
|
220
|
-
|
|
221
|
-
* Load the aicm config using cosmiconfig, supporting both aicm.json and package.json.
|
|
222
|
-
* Returns the config object or null if not found.
|
|
223
|
-
*/
|
|
224
|
-
async function loadAicmConfigCosmiconfig(searchFrom) {
|
|
238
|
+
async function loadConfigFile(searchFrom) {
|
|
225
239
|
const explorer = (0, cosmiconfig_1.cosmiconfig)("aicm", {
|
|
226
|
-
searchPlaces: ["
|
|
240
|
+
searchPlaces: ["aicm.json", "package.json"],
|
|
227
241
|
});
|
|
228
242
|
try {
|
|
229
243
|
const result = await explorer.search(searchFrom);
|
|
230
|
-
|
|
231
|
-
return null;
|
|
232
|
-
const rawConfig = result.config;
|
|
233
|
-
const normalizedRules = normalizeRules(rawConfig.rules);
|
|
234
|
-
const config = {
|
|
235
|
-
...rawConfig,
|
|
236
|
-
ides: rawConfig.ides || ["cursor"],
|
|
237
|
-
rules: normalizedRules,
|
|
238
|
-
};
|
|
239
|
-
return config;
|
|
244
|
+
return result;
|
|
240
245
|
}
|
|
241
246
|
catch (error) {
|
|
242
|
-
throw new Error(`
|
|
247
|
+
throw new Error(`Failed to load configuration: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
243
248
|
}
|
|
244
249
|
}
|
|
245
|
-
|
|
246
|
-
* Get the configuration from aicm.json or package.json (using cosmiconfigSync) and merge with any presets
|
|
247
|
-
*/
|
|
248
|
-
async function getConfig(cwd) {
|
|
250
|
+
async function loadConfig(cwd) {
|
|
249
251
|
const workingDir = cwd || process.cwd();
|
|
250
|
-
const
|
|
251
|
-
if (!config) {
|
|
252
|
-
|
|
252
|
+
const configResult = await loadConfigFile(workingDir);
|
|
253
|
+
if (!(configResult === null || configResult === void 0 ? void 0 : configResult.config)) {
|
|
254
|
+
return null;
|
|
253
255
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* Get the original preset path for a rule if it came from a preset
|
|
268
|
-
*/
|
|
269
|
-
function getOriginalPresetPath(config, ruleName) {
|
|
270
|
-
var _a;
|
|
271
|
-
return (_a = currentMetadata === null || currentMetadata === void 0 ? void 0 : currentMetadata.originalPresetPaths) === null || _a === void 0 ? void 0 : _a[ruleName];
|
|
256
|
+
validateConfig(configResult.config, configResult.filepath, workingDir);
|
|
257
|
+
const config = applyDefaults(configResult.config, workingDir);
|
|
258
|
+
const { rules, mcpServers } = await loadAllRules(config, workingDir);
|
|
259
|
+
let rulesWithOverrides = rules;
|
|
260
|
+
if (config.overrides) {
|
|
261
|
+
rulesWithOverrides = applyOverrides(rules, config.overrides, workingDir);
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
config,
|
|
265
|
+
rules: rulesWithOverrides,
|
|
266
|
+
mcpServers,
|
|
267
|
+
};
|
|
272
268
|
}
|
|
273
|
-
/**
|
|
274
|
-
* Save the configuration to the aicm.json file
|
|
275
|
-
*/
|
|
276
269
|
function saveConfig(config, cwd) {
|
|
277
270
|
const workingDir = cwd || process.cwd();
|
|
278
|
-
const configPath = node_path_1.default.join(workingDir,
|
|
271
|
+
const configPath = node_path_1.default.join(workingDir, "aicm.json");
|
|
279
272
|
try {
|
|
280
|
-
fs_extra_1.default.
|
|
273
|
+
fs_extra_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
281
274
|
return true;
|
|
282
275
|
}
|
|
283
|
-
catch (
|
|
284
|
-
console.error("Error writing configuration file:", error);
|
|
276
|
+
catch (_a) {
|
|
285
277
|
return false;
|
|
286
278
|
}
|
|
287
279
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { CosmiconfigResult } from "cosmiconfig";
|
|
2
|
+
export interface RawConfig {
|
|
3
|
+
rulesDir: string;
|
|
4
|
+
targets?: string[];
|
|
5
|
+
presets?: string[];
|
|
6
|
+
overrides?: Record<string, string | false>;
|
|
7
|
+
mcpServers?: MCPServers;
|
|
8
|
+
workspaces?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface Config {
|
|
11
|
+
rulesDir: string;
|
|
12
|
+
targets: string[];
|
|
13
|
+
presets?: string[];
|
|
14
|
+
overrides?: Record<string, string | false>;
|
|
15
|
+
mcpServers?: MCPServers;
|
|
16
|
+
workspaces?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export type MCPServer = {
|
|
19
|
+
command: string;
|
|
20
|
+
args?: string[];
|
|
21
|
+
env?: Record<string, string>;
|
|
22
|
+
url?: never;
|
|
23
|
+
} | {
|
|
24
|
+
url: string;
|
|
25
|
+
env?: Record<string, string>;
|
|
26
|
+
command?: never;
|
|
27
|
+
args?: never;
|
|
28
|
+
} | false;
|
|
29
|
+
export interface MCPServers {
|
|
30
|
+
[serverName: string]: MCPServer;
|
|
31
|
+
}
|
|
32
|
+
export interface RuleFile {
|
|
33
|
+
name: string;
|
|
34
|
+
content: string;
|
|
35
|
+
sourcePath: string;
|
|
36
|
+
source: "local" | "preset";
|
|
37
|
+
presetName?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface RuleCollection {
|
|
40
|
+
[target: string]: RuleFile[];
|
|
41
|
+
}
|
|
42
|
+
export interface ResolvedConfig {
|
|
43
|
+
config: Config;
|
|
44
|
+
rules: RuleFile[];
|
|
45
|
+
mcpServers: MCPServers;
|
|
46
|
+
}
|
|
47
|
+
export declare const SUPPORTED_TARGETS: readonly ["cursor", "windsurf", "codex"];
|
|
48
|
+
export type SupportedTarget = (typeof SUPPORTED_TARGETS)[number];
|
|
49
|
+
export declare function applyDefaults(config: RawConfig): Config;
|
|
50
|
+
export declare function validateConfig(config: unknown, configFilePath: string, cwd: string): asserts config is Config;
|
|
51
|
+
export declare function loadRulesFromDirectory(rulesDir: string, source: "local" | "preset", presetName?: string): Promise<RuleFile[]>;
|
|
52
|
+
export declare function resolvePresetPath(presetPath: string, cwd: string): string | null;
|
|
53
|
+
export declare function loadPreset(presetPath: string, cwd: string): Promise<{
|
|
54
|
+
config: Config;
|
|
55
|
+
rulesDir: string;
|
|
56
|
+
}>;
|
|
57
|
+
export declare function loadAllRules(config: Config, cwd: string): Promise<{
|
|
58
|
+
rules: RuleFile[];
|
|
59
|
+
mcpServers: MCPServers;
|
|
60
|
+
}>;
|
|
61
|
+
export declare function applyOverrides(rules: RuleFile[], overrides: Record<string, string | false>, cwd: string): RuleFile[];
|
|
62
|
+
export declare function loadConfigFile(searchFrom?: string): Promise<CosmiconfigResult>;
|
|
63
|
+
export declare function loadConfig(cwd?: string): Promise<ResolvedConfig | null>;
|
|
64
|
+
export declare function saveConfig(config: Config, cwd?: string): boolean;
|