aicm 0.13.0 → 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.
@@ -1,44 +1,64 @@
1
- import { Config, NormalizedConfig, Rules } from "../types";
2
- export interface RuleMetadata {
3
- ruleSources: Record<string, string>;
4
- originalPresetPaths: Record<string, string>;
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;
5
9
  }
6
- export interface ConfigResult {
7
- config: NormalizedConfig;
8
- metadata: RuleMetadata;
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;
9
17
  }
10
- export declare function normalizeRules(rules: Rules | string | undefined): Rules;
11
- export interface PresetPathInfo {
12
- fullPath: string;
13
- originalPath: string;
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;
14
31
  }
15
- export declare function getFullPresetPath(presetPath: string, cwd?: string): PresetPathInfo | null;
16
- /**
17
- * Load a preset file and return its contents
18
- */
19
- export declare function loadPreset(presetPath: string, cwd?: string): {
20
- rules: Rules;
21
- mcpServers?: import("../types").MCPServers;
22
- presets?: string[];
23
- } | null;
24
- /**
25
- * Load the aicm config using cosmiconfigSync, supporting both aicm.json and package.json.
26
- * Returns the config object or null if not found.
27
- */
28
- export declare function loadAicmConfigCosmiconfig(searchFrom?: string): NormalizedConfig | null;
29
- /**
30
- * Get the configuration from aicm.json or package.json (using cosmiconfigSync) and merge with any presets
31
- */
32
- export declare function getConfig(cwd?: string): NormalizedConfig | null;
33
- /**
34
- * Get the source preset path for a rule if it came from a preset
35
- */
36
- export declare function getRuleSource(config: NormalizedConfig, ruleName: string): string | undefined;
37
- /**
38
- * Get the original preset path for a rule if it came from a preset
39
- */
40
- export declare function getOriginalPresetPath(config: NormalizedConfig, ruleName: string): string | undefined;
41
- /**
42
- * Save the configuration to the aicm.json file
43
- */
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, cwd?: string): 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>;
44
64
  export declare function saveConfig(config: Config, cwd?: string): boolean;
@@ -3,198 +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.normalizeRules = normalizeRules;
7
- exports.getFullPresetPath = getFullPresetPath;
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.loadAicmConfigCosmiconfig = loadAicmConfigCosmiconfig;
10
- exports.getConfig = getConfig;
11
- exports.getRuleSource = getRuleSource;
12
- exports.getOriginalPresetPath = getOriginalPresetPath;
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 CONFIG_FILE = "aicm.json";
18
- function normalizeRules(rules) {
19
- if (!rules)
20
- return {};
21
- if (typeof rules === "string") {
22
- return { "/": rules };
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;
23
33
  }
24
- return rules;
25
34
  }
26
- function getFullPresetPath(presetPath, cwd) {
35
+ function applyDefaults(config, cwd) {
27
36
  const workingDir = cwd || process.cwd();
28
- // If it's a local file with .json extension, check relative to the working directory
29
- if (presetPath.endsWith(".json")) {
30
- const absolutePath = node_path_1.default.isAbsolute(presetPath)
31
- ? presetPath
32
- : node_path_1.default.resolve(workingDir, presetPath);
33
- if (fs_extra_1.default.pathExistsSync(absolutePath)) {
34
- return { fullPath: absolutePath, originalPath: presetPath };
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}`);
35
70
  }
36
71
  }
37
- try {
38
- let absolutePresetPath;
39
- // Handle npm package with explicit JSON path
40
- if (presetPath.endsWith(".json")) {
41
- absolutePresetPath = require.resolve(presetPath, {
42
- paths: [__dirname, workingDir],
43
- });
72
+ if ("targets" in config) {
73
+ if (!Array.isArray(config.targets)) {
74
+ throw new Error(`targets must be an array in config at ${configFilePath}`);
44
75
  }
45
- // Handle npm package without explicit JSON path (add aicm.json)
46
- else {
47
- // For npm packages, ensure we properly handle scoped packages (@org/pkg)
48
- const presetPathWithConfig = node_path_1.default.join(presetPath, "aicm.json");
49
- try {
50
- absolutePresetPath = require.resolve(presetPathWithConfig, {
51
- paths: [__dirname, workingDir],
52
- });
53
- }
54
- catch (_a) {
55
- // If direct resolution fails, try as a package name
56
- absolutePresetPath = require.resolve(presetPath, {
57
- paths: [__dirname, workingDir],
58
- });
59
- // If we found the package but not the config file, look for aicm.json
60
- if (fs_extra_1.default.existsSync(absolutePresetPath)) {
61
- const packageDir = node_path_1.default.dirname(absolutePresetPath);
62
- const configPath = node_path_1.default.join(packageDir, "aicm.json");
63
- if (fs_extra_1.default.existsSync(configPath)) {
64
- absolutePresetPath = configPath;
65
- }
66
- }
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(", ")}`);
67
82
  }
68
83
  }
69
- return fs_extra_1.default.existsSync(absolutePresetPath)
70
- ? { fullPath: absolutePresetPath, originalPath: presetPath }
71
- : null;
72
84
  }
73
- catch (_b) {
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) {
74
132
  return null;
75
133
  }
76
134
  }
77
- /**
78
- * Load a preset file and return its contents
79
- */
80
- function loadPreset(presetPath, cwd) {
81
- const pathInfo = getFullPresetPath(presetPath, cwd);
82
- if (!pathInfo) {
83
- 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.`);
84
139
  }
85
- const presetContent = fs_extra_1.default.readFileSync(pathInfo.fullPath, "utf8");
86
- let preset;
140
+ let presetConfig;
87
141
  try {
88
- preset = JSON.parse(presetContent);
142
+ const content = await fs_extra_1.default.readFile(resolvedPresetPath, "utf8");
143
+ presetConfig = JSON.parse(content);
89
144
  }
90
145
  catch (error) {
91
- const parseError = error;
92
- 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"}`);
93
147
  }
94
- if (!preset.rules) {
95
- throw new Error(`Error loading preset: Invalid format in ${presetPath} - missing or invalid 'rules' object`);
148
+ // Validate that preset has rulesDir
149
+ if (!presetConfig.rulesDir) {
150
+ throw new Error(`Preset "${presetPath}" must have a rulesDir specified`);
96
151
  }
97
- const normalizedRules = normalizeRules(preset.rules);
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);
98
155
  return {
99
- rules: normalizedRules,
100
- mcpServers: preset.mcpServers,
101
- presets: preset.presets,
156
+ config: presetConfig,
157
+ rulesDir: presetRulesDir,
102
158
  };
103
159
  }
104
- // Global metadata storage
105
- let currentMetadata = null;
106
- // Track processed presets to avoid circular references
107
- const processedPresets = new Set();
108
- /**
109
- * Process presets and return a new config with merged rules and metadata
110
- */
111
- function processPresets(config, cwd) {
112
- // Create a deep copy of the config to avoid mutations
113
- const newConfig = JSON.parse(JSON.stringify(config));
114
- const metadata = {
115
- ruleSources: {},
116
- originalPresetPaths: {},
117
- };
118
- // Clear processed presets tracking set when starting from the top level
119
- processedPresets.clear();
120
- return processPresetsInternal(newConfig, metadata, cwd);
121
- }
122
- /**
123
- * Internal function to process presets recursively
124
- */
125
- function processPresetsInternal(config, metadata, cwd) {
126
- if (!config.presets || !Array.isArray(config.presets)) {
127
- 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);
128
168
  }
129
- for (const presetPath of config.presets) {
130
- const pathInfo = getFullPresetPath(presetPath, cwd);
131
- if (!pathInfo) {
132
- throw new Error(`Error loading preset: "${presetPath}". Make sure the package is installed in your project.`);
133
- }
134
- // Skip if we've already processed this preset (prevents circular references)
135
- if (processedPresets.has(pathInfo.fullPath)) {
136
- // Skip duplicates to prevent circular references
137
- continue;
138
- }
139
- // Mark this preset as processed
140
- processedPresets.add(pathInfo.fullPath);
141
- const preset = loadPreset(presetPath, cwd);
142
- if (!preset)
143
- continue;
144
- // Process nested presets first (depth-first)
145
- if (preset.presets && preset.presets.length > 0) {
146
- // Create a temporary config with just the presets from this preset
147
- const presetConfig = {
148
- rules: {},
149
- presets: preset.presets,
150
- ides: [],
151
- };
152
- // Recursively process the nested presets
153
- const { config: nestedConfig } = processPresetsInternal(presetConfig, metadata, cwd);
154
- Object.assign(preset.rules, nestedConfig.rules);
155
- }
156
- const { updatedConfig, updatedMetadata } = mergePresetRules(config, preset.rules, pathInfo, metadata);
157
- Object.assign(config.rules, updatedConfig.rules);
158
- Object.assign(metadata.ruleSources, updatedMetadata.ruleSources);
159
- Object.assign(metadata.originalPresetPaths, updatedMetadata.originalPresetPaths);
160
- if (preset.mcpServers) {
161
- 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
+ }
162
178
  }
163
179
  }
164
- return { config, metadata };
165
- }
166
- /**
167
- * Merge preset rules into the config without mutation
168
- */
169
- function mergePresetRules(config, presetRules, pathInfo, metadata) {
170
- const updatedRules = { ...config.rules };
171
- const updatedMetadata = {
172
- ruleSources: { ...metadata.ruleSources },
173
- originalPresetPaths: { ...metadata.originalPresetPaths },
180
+ return {
181
+ rules: allRules,
182
+ mcpServers: mergedMcpServers,
174
183
  };
175
- for (const [ruleName, rulePath] of Object.entries(presetRules)) {
176
- // Cancel if set to false in config
177
- if (Object.prototype.hasOwnProperty.call(config.rules, ruleName) &&
178
- config.rules[ruleName] === false) {
179
- delete updatedRules[ruleName];
180
- delete updatedMetadata.ruleSources[ruleName];
181
- delete updatedMetadata.originalPresetPaths[ruleName];
182
- continue;
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`);
183
192
  }
184
- // Only add if not already defined in config (override handled by config)
185
- if (!Object.prototype.hasOwnProperty.call(config.rules, ruleName)) {
186
- updatedRules[ruleName] = rulePath;
187
- updatedMetadata.ruleSources[ruleName] = pathInfo.fullPath;
188
- updatedMetadata.originalPresetPaths[ruleName] = pathInfo.originalPath;
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
+ });
189
214
  }
190
215
  }
191
- return {
192
- updatedConfig: { ...config, rules: updatedRules },
193
- updatedMetadata,
194
- };
216
+ return Array.from(ruleMap.values());
195
217
  }
196
218
  /**
197
- * Merge preset mcpServers without mutation
219
+ * Merge preset MCP servers with local config MCP servers
220
+ * Local config takes precedence over preset config
198
221
  */
199
222
  function mergePresetMcpServers(configMcpServers, presetMcpServers) {
200
223
  const newMcpServers = { ...configMcpServers };
@@ -212,71 +235,45 @@ function mergePresetMcpServers(configMcpServers, presetMcpServers) {
212
235
  }
213
236
  return newMcpServers;
214
237
  }
215
- /**
216
- * Load the aicm config using cosmiconfigSync, supporting both aicm.json and package.json.
217
- * Returns the config object or null if not found.
218
- */
219
- function loadAicmConfigCosmiconfig(searchFrom) {
220
- const explorer = (0, cosmiconfig_1.cosmiconfigSync)("aicm", {
221
- searchPlaces: ["package.json", "aicm.json"],
238
+ async function loadConfigFile(searchFrom) {
239
+ const explorer = (0, cosmiconfig_1.cosmiconfig)("aicm", {
240
+ searchPlaces: ["aicm.json", "package.json"],
222
241
  });
223
242
  try {
224
- const result = explorer.search(searchFrom);
225
- if (!result || !result.config)
226
- return null;
227
- const rawConfig = result.config;
228
- const normalizedRules = normalizeRules(rawConfig.rules);
229
- const config = {
230
- ...rawConfig,
231
- ides: rawConfig.ides || ["cursor"],
232
- rules: normalizedRules,
233
- };
234
- return config;
243
+ const result = await explorer.search(searchFrom);
244
+ return result;
235
245
  }
236
246
  catch (error) {
237
- throw new Error(`Error loading aicm config: ${error instanceof Error ? error.message : String(error)}`);
247
+ throw new Error(`Failed to load configuration: ${error instanceof Error ? error.message : "Unknown error"}`);
238
248
  }
239
249
  }
240
- /**
241
- * Get the configuration from aicm.json or package.json (using cosmiconfigSync) and merge with any presets
242
- */
243
- function getConfig(cwd) {
250
+ async function loadConfig(cwd) {
244
251
  const workingDir = cwd || process.cwd();
245
- const config = loadAicmConfigCosmiconfig(workingDir);
246
- if (!config) {
247
- throw new Error(`No config found in ${workingDir}, create one using "aicm init"`);
252
+ const configResult = await loadConfigFile(workingDir);
253
+ if (!(configResult === null || configResult === void 0 ? void 0 : configResult.config)) {
254
+ return null;
248
255
  }
249
- const { config: processedConfig, metadata } = processPresets(config, workingDir);
250
- // Store metadata for later access
251
- currentMetadata = metadata;
252
- return processedConfig;
253
- }
254
- /**
255
- * Get the source preset path for a rule if it came from a preset
256
- */
257
- function getRuleSource(config, ruleName) {
258
- var _a;
259
- return (_a = currentMetadata === null || currentMetadata === void 0 ? void 0 : currentMetadata.ruleSources) === null || _a === void 0 ? void 0 : _a[ruleName];
260
- }
261
- /**
262
- * Get the original preset path for a rule if it came from a preset
263
- */
264
- function getOriginalPresetPath(config, ruleName) {
265
- var _a;
266
- 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
+ };
267
268
  }
268
- /**
269
- * Save the configuration to the aicm.json file
270
- */
271
269
  function saveConfig(config, cwd) {
272
270
  const workingDir = cwd || process.cwd();
273
- const configPath = node_path_1.default.join(workingDir, CONFIG_FILE);
271
+ const configPath = node_path_1.default.join(workingDir, "aicm.json");
274
272
  try {
275
- fs_extra_1.default.writeJsonSync(configPath, config, { spaces: 2 });
273
+ fs_extra_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
276
274
  return true;
277
275
  }
278
- catch (error) {
279
- console.error("Error writing configuration file:", error);
276
+ catch (_a) {
280
277
  return false;
281
278
  }
282
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;