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.
- 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 +8 -5
- package/dist/cli_v2.d.ts +2 -0
- package/dist/cli_v2.js +94 -0
- package/dist/commands/install/install-package.js +1 -1
- package/dist/commands/install/install-workspaces.js +1 -1
- 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.d.ts +1 -1
- package/dist/commands/list.js +5 -18
- package/dist/utils/config.d.ts +60 -40
- package/dist/utils/config.js +205 -208
- 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 +6 -3
package/dist/utils/config.d.ts
CHANGED
|
@@ -1,44 +1,64 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export interface
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
export declare function
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
export declare function
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
export declare function
|
|
41
|
-
|
|
42
|
-
|
|
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;
|
package/dist/utils/config.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
35
|
+
function applyDefaults(config, cwd) {
|
|
27
36
|
const workingDir = cwd || process.cwd();
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
86
|
-
let preset;
|
|
140
|
+
let presetConfig;
|
|
87
141
|
try {
|
|
88
|
-
|
|
142
|
+
const content = await fs_extra_1.default.readFile(resolvedPresetPath, "utf8");
|
|
143
|
+
presetConfig = JSON.parse(content);
|
|
89
144
|
}
|
|
90
145
|
catch (error) {
|
|
91
|
-
|
|
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
|
-
|
|
95
|
-
|
|
148
|
+
// Validate that preset has rulesDir
|
|
149
|
+
if (!presetConfig.rulesDir) {
|
|
150
|
+
throw new Error(`Preset "${presetPath}" must have a rulesDir specified`);
|
|
96
151
|
}
|
|
97
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
presets: preset.presets,
|
|
156
|
+
config: presetConfig,
|
|
157
|
+
rulesDir: presetRulesDir,
|
|
102
158
|
};
|
|
103
159
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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 {
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
|
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
|
-
|
|
217
|
-
|
|
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
|
-
|
|
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(`
|
|
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
|
|
246
|
-
if (!config) {
|
|
247
|
-
|
|
252
|
+
const configResult = await loadConfigFile(workingDir);
|
|
253
|
+
if (!(configResult === null || configResult === void 0 ? void 0 : configResult.config)) {
|
|
254
|
+
return null;
|
|
248
255
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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,
|
|
271
|
+
const configPath = node_path_1.default.join(workingDir, "aicm.json");
|
|
274
272
|
try {
|
|
275
|
-
fs_extra_1.default.
|
|
273
|
+
fs_extra_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
276
274
|
return true;
|
|
277
275
|
}
|
|
278
|
-
catch (
|
|
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;
|