aicm 0.16.0 → 0.17.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 +26 -0
- package/dist/commands/install.d.ts +4 -0
- package/dist/commands/install.js +119 -13
- package/dist/utils/config.d.ts +10 -0
- package/dist/utils/config.js +38 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -95,6 +95,32 @@ For project-specific rules, you can specify `rulesDir` in your `aicm.json` confi
|
|
|
95
95
|
}
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
+
**Referencing Auxiliary Files**
|
|
99
|
+
|
|
100
|
+
You can place any file (e.g., `example.ts`, `schema.json`, `guide.md`) in your `rulesDir` alongside your `.mdc` files. These assets are automatically copied to the target location. You can reference them in your rules using relative paths, and aicm will automatically rewrite the links to point to the correct location for each target IDE.
|
|
101
|
+
|
|
102
|
+
Example `rules/my-rule.mdc`:
|
|
103
|
+
|
|
104
|
+
```markdown
|
|
105
|
+
# My Rule
|
|
106
|
+
|
|
107
|
+
See [Example](./example.ts) for details.
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Commands referencing files**
|
|
111
|
+
|
|
112
|
+
You can also use this feature to create commands that reference auxiliary files in your `rulesDir`. Since assets in `rulesDir` are copied to the target directory, your commands can link to them.
|
|
113
|
+
|
|
114
|
+
For example, if you have a schema file at `rules/schema.json` and a command at `commands/generate-schema.md`:
|
|
115
|
+
|
|
116
|
+
```markdown
|
|
117
|
+
# Generate Schema
|
|
118
|
+
|
|
119
|
+
Use the schema defined in [Schema Template](../rules/schema.json) to generate the response.
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
When installed, `aicm` will automatically rewrite the link to point to the correct location of `schema.json` in the target environment (e.g., `../../rules/aicm/schema.json` for Cursor).
|
|
123
|
+
|
|
98
124
|
### Using Commands
|
|
99
125
|
|
|
100
126
|
Cursor supports custom commands that can be invoked directly in the chat interface. aicm can manage these command files
|
package/dist/commands/install.js
CHANGED
|
@@ -18,9 +18,7 @@ function getTargetPaths() {
|
|
|
18
18
|
const projectDir = process.cwd();
|
|
19
19
|
return {
|
|
20
20
|
cursor: node_path_1.default.join(projectDir, ".cursor", "rules", "aicm"),
|
|
21
|
-
|
|
22
|
-
codex: node_path_1.default.join(projectDir, ".aicm"),
|
|
23
|
-
claude: node_path_1.default.join(projectDir, ".aicm"),
|
|
21
|
+
aicm: node_path_1.default.join(projectDir, ".aicm"),
|
|
24
22
|
};
|
|
25
23
|
}
|
|
26
24
|
function writeCursorRules(rules, cursorRulesDir) {
|
|
@@ -53,9 +51,29 @@ function writeCursorCommands(commands, cursorCommandsDir) {
|
|
|
53
51
|
const commandPath = node_path_1.default.join(cursorCommandsDir, ...commandNameParts);
|
|
54
52
|
const commandFile = commandPath + ".md";
|
|
55
53
|
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(commandFile));
|
|
56
|
-
|
|
54
|
+
// If the command file references assets in the rules directory, we need to rewrite the links.
|
|
55
|
+
// Commands are installed in .cursor/commands/aicm/
|
|
56
|
+
// Rules/assets are installed in .cursor/rules/aicm/
|
|
57
|
+
// So a link like "../rules/asset.json" in source (from commands/ to rules/)
|
|
58
|
+
// needs to become "../../rules/aicm/asset.json" in target (from .cursor/commands/aicm/ to .cursor/rules/aicm/)
|
|
59
|
+
const content = rewriteCommandRelativeLinks(command.content);
|
|
60
|
+
fs_extra_1.default.writeFileSync(commandFile, content);
|
|
57
61
|
}
|
|
58
62
|
}
|
|
63
|
+
function rewriteCommandRelativeLinks(content) {
|
|
64
|
+
return content.replace(/\[([^\]]*)\]\(([^)]+)\)/g, (match, text, url) => {
|
|
65
|
+
if (url.startsWith("http") || url.startsWith("#") || url.startsWith("/")) {
|
|
66
|
+
return match;
|
|
67
|
+
}
|
|
68
|
+
// Check if it's a link to the rules directory
|
|
69
|
+
if (url.includes("rules/")) {
|
|
70
|
+
const parts = url.split("/");
|
|
71
|
+
const filename = parts[parts.length - 1];
|
|
72
|
+
return `[${text}](../../rules/aicm/${filename})`;
|
|
73
|
+
}
|
|
74
|
+
return match;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
59
77
|
function extractNamespaceFromPresetPath(presetPath) {
|
|
60
78
|
// Special case: npm package names always use forward slashes, regardless of platform
|
|
61
79
|
if (presetPath.startsWith("@")) {
|
|
@@ -68,7 +86,7 @@ function extractNamespaceFromPresetPath(presetPath) {
|
|
|
68
86
|
/**
|
|
69
87
|
* Write rules to a shared directory and update the given rules file
|
|
70
88
|
*/
|
|
71
|
-
function writeRulesForFile(rules, ruleDir, rulesFile) {
|
|
89
|
+
function writeRulesForFile(rules, assets, ruleDir, rulesFile) {
|
|
72
90
|
fs_extra_1.default.emptyDirSync(ruleDir);
|
|
73
91
|
const ruleFiles = rules.map((rule) => {
|
|
74
92
|
let rulePath;
|
|
@@ -83,9 +101,10 @@ function writeRulesForFile(rules, ruleDir, rulesFile) {
|
|
|
83
101
|
// For local rules, maintain the original flat structure
|
|
84
102
|
rulePath = node_path_1.default.join(ruleDir, ...ruleNameParts);
|
|
85
103
|
}
|
|
104
|
+
const content = rule.content;
|
|
86
105
|
const physicalRulePath = rulePath + ".md";
|
|
87
106
|
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(physicalRulePath));
|
|
88
|
-
fs_extra_1.default.writeFileSync(physicalRulePath,
|
|
107
|
+
fs_extra_1.default.writeFileSync(physicalRulePath, content);
|
|
89
108
|
const relativeRuleDir = node_path_1.default.basename(ruleDir);
|
|
90
109
|
// For the rules file, maintain the same structure
|
|
91
110
|
let windsurfPath;
|
|
@@ -102,16 +121,46 @@ function writeRulesForFile(rules, ruleDir, rulesFile) {
|
|
|
102
121
|
return {
|
|
103
122
|
name: rule.name,
|
|
104
123
|
path: windsurfPathPosix,
|
|
105
|
-
metadata: (0, rules_file_writer_1.parseRuleFrontmatter)(
|
|
124
|
+
metadata: (0, rules_file_writer_1.parseRuleFrontmatter)(content),
|
|
106
125
|
};
|
|
107
126
|
});
|
|
108
127
|
const rulesContent = (0, rules_file_writer_1.generateRulesFileContent)(ruleFiles);
|
|
109
128
|
(0, rules_file_writer_1.writeRulesFile)(rulesContent, node_path_1.default.join(process.cwd(), rulesFile));
|
|
110
129
|
}
|
|
130
|
+
function writeAssetsToTargets(assets, targets) {
|
|
131
|
+
const targetPaths = getTargetPaths();
|
|
132
|
+
for (const target of targets) {
|
|
133
|
+
let targetDir;
|
|
134
|
+
switch (target) {
|
|
135
|
+
case "cursor":
|
|
136
|
+
targetDir = targetPaths.cursor;
|
|
137
|
+
break;
|
|
138
|
+
case "windsurf":
|
|
139
|
+
case "codex":
|
|
140
|
+
case "claude":
|
|
141
|
+
targetDir = targetPaths.aicm;
|
|
142
|
+
break;
|
|
143
|
+
default:
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
for (const asset of assets) {
|
|
147
|
+
let assetPath;
|
|
148
|
+
if (asset.presetName) {
|
|
149
|
+
const namespace = extractNamespaceFromPresetPath(asset.presetName);
|
|
150
|
+
assetPath = node_path_1.default.join(targetDir, ...namespace, asset.name);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
assetPath = node_path_1.default.join(targetDir, asset.name);
|
|
154
|
+
}
|
|
155
|
+
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(assetPath));
|
|
156
|
+
fs_extra_1.default.writeFileSync(assetPath, asset.content);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
111
160
|
/**
|
|
112
161
|
* Write all collected rules to their respective IDE targets
|
|
113
162
|
*/
|
|
114
|
-
function writeRulesToTargets(rules, targets) {
|
|
163
|
+
function writeRulesToTargets(rules, assets, targets) {
|
|
115
164
|
const targetPaths = getTargetPaths();
|
|
116
165
|
for (const target of targets) {
|
|
117
166
|
switch (target) {
|
|
@@ -122,21 +171,23 @@ function writeRulesToTargets(rules, targets) {
|
|
|
122
171
|
break;
|
|
123
172
|
case "windsurf":
|
|
124
173
|
if (rules.length > 0) {
|
|
125
|
-
writeRulesForFile(rules, targetPaths.
|
|
174
|
+
writeRulesForFile(rules, assets, targetPaths.aicm, ".windsurfrules");
|
|
126
175
|
}
|
|
127
176
|
break;
|
|
128
177
|
case "codex":
|
|
129
178
|
if (rules.length > 0) {
|
|
130
|
-
writeRulesForFile(rules, targetPaths.
|
|
179
|
+
writeRulesForFile(rules, assets, targetPaths.aicm, "AGENTS.md");
|
|
131
180
|
}
|
|
132
181
|
break;
|
|
133
182
|
case "claude":
|
|
134
183
|
if (rules.length > 0) {
|
|
135
|
-
writeRulesForFile(rules, targetPaths.
|
|
184
|
+
writeRulesForFile(rules, assets, targetPaths.aicm, "CLAUDE.md");
|
|
136
185
|
}
|
|
137
186
|
break;
|
|
138
187
|
}
|
|
139
188
|
}
|
|
189
|
+
// Write assets after rules so they don't get wiped by emptyDirSync
|
|
190
|
+
writeAssetsToTargets(assets, targets);
|
|
140
191
|
}
|
|
141
192
|
function writeCommandsToTargets(commands, targets) {
|
|
142
193
|
const projectDir = process.cwd();
|
|
@@ -180,6 +231,37 @@ function dedupeCommandsForInstall(commands) {
|
|
|
180
231
|
}
|
|
181
232
|
return Array.from(unique.values());
|
|
182
233
|
}
|
|
234
|
+
function mergeWorkspaceCommands(packages) {
|
|
235
|
+
var _a;
|
|
236
|
+
const commands = [];
|
|
237
|
+
const seenPresetCommands = new Set();
|
|
238
|
+
for (const pkg of packages) {
|
|
239
|
+
const hasCursorTarget = pkg.config.config.targets.includes("cursor");
|
|
240
|
+
if (!hasCursorTarget) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
for (const command of (_a = pkg.config.commands) !== null && _a !== void 0 ? _a : []) {
|
|
244
|
+
if (command.presetName) {
|
|
245
|
+
const presetKey = `${command.presetName}::${command.name}`;
|
|
246
|
+
if (seenPresetCommands.has(presetKey)) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
seenPresetCommands.add(presetKey);
|
|
250
|
+
}
|
|
251
|
+
commands.push(command);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return commands;
|
|
255
|
+
}
|
|
256
|
+
function collectWorkspaceCommandTargets(packages) {
|
|
257
|
+
const targets = new Set();
|
|
258
|
+
for (const pkg of packages) {
|
|
259
|
+
if (pkg.config.config.targets.includes("cursor")) {
|
|
260
|
+
targets.add("cursor");
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return Array.from(targets);
|
|
264
|
+
}
|
|
183
265
|
/**
|
|
184
266
|
* Write MCP servers configuration to IDE targets
|
|
185
267
|
*/
|
|
@@ -326,15 +408,17 @@ async function installPackage(options = {}) {
|
|
|
326
408
|
error: new Error("Configuration file not found"),
|
|
327
409
|
installedRuleCount: 0,
|
|
328
410
|
installedCommandCount: 0,
|
|
411
|
+
installedAssetCount: 0,
|
|
329
412
|
packagesCount: 0,
|
|
330
413
|
};
|
|
331
414
|
}
|
|
332
|
-
const { config, rules, commands, mcpServers } = resolvedConfig;
|
|
415
|
+
const { config, rules, commands, assets, mcpServers } = resolvedConfig;
|
|
333
416
|
if (config.skipInstall === true) {
|
|
334
417
|
return {
|
|
335
418
|
success: true,
|
|
336
419
|
installedRuleCount: 0,
|
|
337
420
|
installedCommandCount: 0,
|
|
421
|
+
installedAssetCount: 0,
|
|
338
422
|
packagesCount: 0,
|
|
339
423
|
};
|
|
340
424
|
}
|
|
@@ -342,7 +426,7 @@ async function installPackage(options = {}) {
|
|
|
342
426
|
const commandsToInstall = dedupeCommandsForInstall(commands);
|
|
343
427
|
try {
|
|
344
428
|
if (!options.dryRun) {
|
|
345
|
-
writeRulesToTargets(rules, config.targets);
|
|
429
|
+
writeRulesToTargets(rules, assets, config.targets);
|
|
346
430
|
writeCommandsToTargets(commandsToInstall, config.targets);
|
|
347
431
|
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
348
432
|
writeMcpServersToTargets(mcpServers, config.targets, cwd);
|
|
@@ -354,6 +438,7 @@ async function installPackage(options = {}) {
|
|
|
354
438
|
success: true,
|
|
355
439
|
installedRuleCount: uniqueRuleCount,
|
|
356
440
|
installedCommandCount: uniqueCommandCount,
|
|
441
|
+
installedAssetCount: assets.length,
|
|
357
442
|
packagesCount: 1,
|
|
358
443
|
};
|
|
359
444
|
}
|
|
@@ -363,6 +448,7 @@ async function installPackage(options = {}) {
|
|
|
363
448
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
364
449
|
installedRuleCount: 0,
|
|
365
450
|
installedCommandCount: 0,
|
|
451
|
+
installedAssetCount: 0,
|
|
366
452
|
packagesCount: 0,
|
|
367
453
|
};
|
|
368
454
|
}
|
|
@@ -375,6 +461,7 @@ async function installWorkspacesPackages(packages, options = {}) {
|
|
|
375
461
|
const results = [];
|
|
376
462
|
let totalRuleCount = 0;
|
|
377
463
|
let totalCommandCount = 0;
|
|
464
|
+
let totalAssetCount = 0;
|
|
378
465
|
// Install packages sequentially for now (can be parallelized later)
|
|
379
466
|
for (const pkg of packages) {
|
|
380
467
|
const packagePath = pkg.absolutePath;
|
|
@@ -386,12 +473,14 @@ async function installWorkspacesPackages(packages, options = {}) {
|
|
|
386
473
|
});
|
|
387
474
|
totalRuleCount += result.installedRuleCount;
|
|
388
475
|
totalCommandCount += result.installedCommandCount;
|
|
476
|
+
totalAssetCount += result.installedAssetCount;
|
|
389
477
|
results.push({
|
|
390
478
|
path: pkg.relativePath,
|
|
391
479
|
success: result.success,
|
|
392
480
|
error: result.error,
|
|
393
481
|
installedRuleCount: result.installedRuleCount,
|
|
394
482
|
installedCommandCount: result.installedCommandCount,
|
|
483
|
+
installedAssetCount: result.installedAssetCount,
|
|
395
484
|
});
|
|
396
485
|
}
|
|
397
486
|
catch (error) {
|
|
@@ -401,6 +490,7 @@ async function installWorkspacesPackages(packages, options = {}) {
|
|
|
401
490
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
402
491
|
installedRuleCount: 0,
|
|
403
492
|
installedCommandCount: 0,
|
|
493
|
+
installedAssetCount: 0,
|
|
404
494
|
});
|
|
405
495
|
}
|
|
406
496
|
}
|
|
@@ -410,6 +500,7 @@ async function installWorkspacesPackages(packages, options = {}) {
|
|
|
410
500
|
packages: results,
|
|
411
501
|
totalRuleCount,
|
|
412
502
|
totalCommandCount,
|
|
503
|
+
totalAssetCount,
|
|
413
504
|
};
|
|
414
505
|
}
|
|
415
506
|
/**
|
|
@@ -440,6 +531,7 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
440
531
|
error: new Error("No packages with aicm configurations found"),
|
|
441
532
|
installedRuleCount: 0,
|
|
442
533
|
installedCommandCount: 0,
|
|
534
|
+
installedAssetCount: 0,
|
|
443
535
|
packagesCount: 0,
|
|
444
536
|
};
|
|
445
537
|
}
|
|
@@ -455,6 +547,17 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
455
547
|
verbose,
|
|
456
548
|
dryRun,
|
|
457
549
|
});
|
|
550
|
+
const workspaceCommands = mergeWorkspaceCommands(packages);
|
|
551
|
+
const workspaceCommandTargets = collectWorkspaceCommandTargets(packages);
|
|
552
|
+
if (workspaceCommands.length > 0) {
|
|
553
|
+
warnPresetCommandCollisions(workspaceCommands);
|
|
554
|
+
}
|
|
555
|
+
if (!dryRun &&
|
|
556
|
+
workspaceCommands.length > 0 &&
|
|
557
|
+
workspaceCommandTargets.length > 0) {
|
|
558
|
+
const dedupedWorkspaceCommands = dedupeCommandsForInstall(workspaceCommands);
|
|
559
|
+
writeCommandsToTargets(dedupedWorkspaceCommands, workspaceCommandTargets);
|
|
560
|
+
}
|
|
458
561
|
const { merged: rootMcp, conflicts } = mergeWorkspaceMcpServers(packages);
|
|
459
562
|
const hasCursorTarget = packages.some((p) => p.config.config.targets.includes("cursor"));
|
|
460
563
|
if (!dryRun && hasCursorTarget && Object.keys(rootMcp).length > 0) {
|
|
@@ -496,6 +599,7 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
496
599
|
error: new Error(`Package installation failed for ${failedPackages.length} package(s): ${errorDetails}`),
|
|
497
600
|
installedRuleCount: result.totalRuleCount,
|
|
498
601
|
installedCommandCount: result.totalCommandCount,
|
|
602
|
+
installedAssetCount: result.totalAssetCount,
|
|
499
603
|
packagesCount: result.packages.length,
|
|
500
604
|
};
|
|
501
605
|
}
|
|
@@ -503,6 +607,7 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
503
607
|
success: true,
|
|
504
608
|
installedRuleCount: result.totalRuleCount,
|
|
505
609
|
installedCommandCount: result.totalCommandCount,
|
|
610
|
+
installedAssetCount: result.totalAssetCount,
|
|
506
611
|
packagesCount: result.packages.length,
|
|
507
612
|
};
|
|
508
613
|
});
|
|
@@ -520,6 +625,7 @@ async function install(options = {}) {
|
|
|
520
625
|
success: true,
|
|
521
626
|
installedRuleCount: 0,
|
|
522
627
|
installedCommandCount: 0,
|
|
628
|
+
installedAssetCount: 0,
|
|
523
629
|
packagesCount: 0,
|
|
524
630
|
};
|
|
525
631
|
}
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -40,6 +40,13 @@ export interface ManagedFile {
|
|
|
40
40
|
source: "local" | "preset";
|
|
41
41
|
presetName?: string;
|
|
42
42
|
}
|
|
43
|
+
export interface AssetFile {
|
|
44
|
+
name: string;
|
|
45
|
+
content: Buffer;
|
|
46
|
+
sourcePath: string;
|
|
47
|
+
source: "local" | "preset";
|
|
48
|
+
presetName?: string;
|
|
49
|
+
}
|
|
43
50
|
export type RuleFile = ManagedFile;
|
|
44
51
|
export type CommandFile = ManagedFile;
|
|
45
52
|
export interface RuleCollection {
|
|
@@ -49,6 +56,7 @@ export interface ResolvedConfig {
|
|
|
49
56
|
config: Config;
|
|
50
57
|
rules: RuleFile[];
|
|
51
58
|
commands: CommandFile[];
|
|
59
|
+
assets: AssetFile[];
|
|
52
60
|
mcpServers: MCPServers;
|
|
53
61
|
}
|
|
54
62
|
export declare const ALLOWED_CONFIG_KEYS: readonly ["rulesDir", "commandsDir", "targets", "presets", "overrides", "mcpServers", "workspaces", "skipInstall"];
|
|
@@ -60,6 +68,7 @@ export declare function applyDefaults(config: RawConfig, workspaces: boolean): C
|
|
|
60
68
|
export declare function validateConfig(config: unknown, configFilePath: string, cwd: string, isWorkspaceMode?: boolean): asserts config is Config;
|
|
61
69
|
export declare function loadRulesFromDirectory(rulesDir: string, source: "local" | "preset", presetName?: string): Promise<RuleFile[]>;
|
|
62
70
|
export declare function loadCommandsFromDirectory(commandsDir: string, source: "local" | "preset", presetName?: string): Promise<CommandFile[]>;
|
|
71
|
+
export declare function loadAssetsFromDirectory(rulesDir: string, source: "local" | "preset", presetName?: string): Promise<AssetFile[]>;
|
|
63
72
|
export declare function resolvePresetPath(presetPath: string, cwd: string): string | null;
|
|
64
73
|
export declare function loadPreset(presetPath: string, cwd: string): Promise<{
|
|
65
74
|
config: Config;
|
|
@@ -69,6 +78,7 @@ export declare function loadPreset(presetPath: string, cwd: string): Promise<{
|
|
|
69
78
|
export declare function loadAllRules(config: Config, cwd: string): Promise<{
|
|
70
79
|
rules: RuleFile[];
|
|
71
80
|
commands: CommandFile[];
|
|
81
|
+
assets: AssetFile[];
|
|
72
82
|
mcpServers: MCPServers;
|
|
73
83
|
}>;
|
|
74
84
|
export declare function applyOverrides<T extends ManagedFile>(files: T[], overrides: Record<string, string | false>, cwd: string): T[];
|
package/dist/utils/config.js
CHANGED
|
@@ -10,6 +10,7 @@ exports.applyDefaults = applyDefaults;
|
|
|
10
10
|
exports.validateConfig = validateConfig;
|
|
11
11
|
exports.loadRulesFromDirectory = loadRulesFromDirectory;
|
|
12
12
|
exports.loadCommandsFromDirectory = loadCommandsFromDirectory;
|
|
13
|
+
exports.loadAssetsFromDirectory = loadAssetsFromDirectory;
|
|
13
14
|
exports.resolvePresetPath = resolvePresetPath;
|
|
14
15
|
exports.loadPreset = loadPreset;
|
|
15
16
|
exports.loadAllRules = loadAllRules;
|
|
@@ -175,6 +176,34 @@ async function loadCommandsFromDirectory(commandsDir, source, presetName) {
|
|
|
175
176
|
}
|
|
176
177
|
return commands;
|
|
177
178
|
}
|
|
179
|
+
async function loadAssetsFromDirectory(rulesDir, source, presetName) {
|
|
180
|
+
const assets = [];
|
|
181
|
+
if (!fs_extra_1.default.existsSync(rulesDir)) {
|
|
182
|
+
return assets;
|
|
183
|
+
}
|
|
184
|
+
// Find all files except .mdc files and hidden files
|
|
185
|
+
const pattern = node_path_1.default.join(rulesDir, "**/*").replace(/\\/g, "/");
|
|
186
|
+
const filePaths = await (0, fast_glob_1.default)(pattern, {
|
|
187
|
+
onlyFiles: true,
|
|
188
|
+
absolute: true,
|
|
189
|
+
ignore: ["**/*.mdc", "**/.*"],
|
|
190
|
+
});
|
|
191
|
+
for (const filePath of filePaths) {
|
|
192
|
+
const content = await fs_extra_1.default.readFile(filePath);
|
|
193
|
+
// Preserve directory structure by using relative path from rulesDir
|
|
194
|
+
const relativePath = node_path_1.default.relative(rulesDir, filePath);
|
|
195
|
+
// Keep extension for assets
|
|
196
|
+
const assetName = relativePath.replace(/\\/g, "/");
|
|
197
|
+
assets.push({
|
|
198
|
+
name: assetName,
|
|
199
|
+
content,
|
|
200
|
+
sourcePath: filePath,
|
|
201
|
+
source,
|
|
202
|
+
presetName,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return assets;
|
|
206
|
+
}
|
|
178
207
|
function resolvePresetPath(presetPath, cwd) {
|
|
179
208
|
// Support specifying aicm.json directory and load the config from it
|
|
180
209
|
if (!presetPath.endsWith(".json")) {
|
|
@@ -230,12 +259,15 @@ async function loadPreset(presetPath, cwd) {
|
|
|
230
259
|
async function loadAllRules(config, cwd) {
|
|
231
260
|
const allRules = [];
|
|
232
261
|
const allCommands = [];
|
|
262
|
+
const allAssets = [];
|
|
233
263
|
let mergedMcpServers = { ...config.mcpServers };
|
|
234
264
|
// Load local rules only if rulesDir is provided
|
|
235
265
|
if (config.rulesDir) {
|
|
236
266
|
const localRulesPath = node_path_1.default.resolve(cwd, config.rulesDir);
|
|
237
267
|
const localRules = await loadRulesFromDirectory(localRulesPath, "local");
|
|
268
|
+
const localAssets = await loadAssetsFromDirectory(localRulesPath, "local");
|
|
238
269
|
allRules.push(...localRules);
|
|
270
|
+
allAssets.push(...localAssets);
|
|
239
271
|
}
|
|
240
272
|
if (config.commandsDir) {
|
|
241
273
|
const localCommandsPath = node_path_1.default.resolve(cwd, config.commandsDir);
|
|
@@ -247,7 +279,9 @@ async function loadAllRules(config, cwd) {
|
|
|
247
279
|
const preset = await loadPreset(presetPath, cwd);
|
|
248
280
|
if (preset.rulesDir) {
|
|
249
281
|
const presetRules = await loadRulesFromDirectory(preset.rulesDir, "preset", presetPath);
|
|
282
|
+
const presetAssets = await loadAssetsFromDirectory(preset.rulesDir, "preset", presetPath);
|
|
250
283
|
allRules.push(...presetRules);
|
|
284
|
+
allAssets.push(...presetAssets);
|
|
251
285
|
}
|
|
252
286
|
if (preset.commandsDir) {
|
|
253
287
|
const presetCommands = await loadCommandsFromDirectory(preset.commandsDir, "preset", presetPath);
|
|
@@ -262,6 +296,7 @@ async function loadAllRules(config, cwd) {
|
|
|
262
296
|
return {
|
|
263
297
|
rules: allRules,
|
|
264
298
|
commands: allCommands,
|
|
299
|
+
assets: allAssets,
|
|
265
300
|
mcpServers: mergedMcpServers,
|
|
266
301
|
};
|
|
267
302
|
}
|
|
@@ -341,9 +376,10 @@ async function loadConfig(cwd) {
|
|
|
341
376
|
const isWorkspaces = resolveWorkspaces(config, configResult.filepath, workingDir);
|
|
342
377
|
validateConfig(config, configResult.filepath, workingDir, isWorkspaces);
|
|
343
378
|
const configWithDefaults = applyDefaults(config, isWorkspaces);
|
|
344
|
-
const { rules, commands, mcpServers } = await loadAllRules(configWithDefaults, workingDir);
|
|
379
|
+
const { rules, commands, assets, mcpServers } = await loadAllRules(configWithDefaults, workingDir);
|
|
345
380
|
let rulesWithOverrides = rules;
|
|
346
381
|
let commandsWithOverrides = commands;
|
|
382
|
+
// Note: Assets are not currently supported in overrides as they are binary/varied files
|
|
347
383
|
if (configWithDefaults.overrides) {
|
|
348
384
|
const overrides = configWithDefaults.overrides;
|
|
349
385
|
const ruleNames = new Set(rules.map((rule) => rule.name));
|
|
@@ -366,6 +402,7 @@ async function loadConfig(cwd) {
|
|
|
366
402
|
config: configWithDefaults,
|
|
367
403
|
rules: rulesWithOverrides,
|
|
368
404
|
commands: commandsWithOverrides,
|
|
405
|
+
assets,
|
|
369
406
|
mcpServers,
|
|
370
407
|
};
|
|
371
408
|
}
|