aicm 0.12.0 → 0.12.2
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 +18 -2
- package/dist/api.d.ts +1 -1
- package/dist/commands/install.d.ts +2 -2
- package/dist/index.js +0 -0
- package/dist/types/index.d.ts +8 -3
- package/dist/utils/config.d.ts +7 -6
- package/dist/utils/config.js +19 -7
- package/dist/utils/mcp-writer.d.ts +3 -3
- package/dist/utils/rule-collector.js +5 -0
- package/dist/utils/rule-status.js +10 -0
- package/dist/utils/rule-writer.js +10 -7
- package/dist/utils/rules-file-writer.d.ts +15 -0
- package/dist/utils/rules-file-writer.js +137 -0
- package/package.json +19 -18
package/README.md
CHANGED
|
@@ -94,7 +94,7 @@ When you run `npx aicm install`, all rules from the preset will be installed to
|
|
|
94
94
|
### Notes
|
|
95
95
|
|
|
96
96
|
- Generated rules are always placed in a subdirectory for deterministic cleanup and easy gitignore.
|
|
97
|
-
- Users may add `.cursor/rules/aicm/` and `.aicm/` (for Windsurf) to their `.gitignore` if they do not want to track generated rules.
|
|
97
|
+
- Users may add `.cursor/rules/aicm/` and `.aicm/` (for Windsurf/Codex) to their `.gitignore` if they do not want to track generated rules.
|
|
98
98
|
|
|
99
99
|
### Overriding and Disabling Rules and MCP Servers from Presets
|
|
100
100
|
|
|
@@ -219,10 +219,19 @@ Example `aicm.json`:
|
|
|
219
219
|
|
|
220
220
|
- `"cursor"`: For the Cursor IDE
|
|
221
221
|
- `"windsurf"`: For the Windsurf IDE
|
|
222
|
+
- `"codex"`: For the Codex Agent
|
|
222
223
|
|
|
223
224
|
> **Note:** The 'ides' field is default to `["cursor"]` if not specified.
|
|
224
225
|
|
|
225
|
-
- **rules**: Object containing rule configurations
|
|
226
|
+
- **rules**: Object containing rule configurations.
|
|
227
|
+
If you want to install every rule from a single directory, you can also use a
|
|
228
|
+
string as a shortcut:
|
|
229
|
+
|
|
230
|
+
```json
|
|
231
|
+
{
|
|
232
|
+
"rules": "./rules/*"
|
|
233
|
+
}
|
|
234
|
+
```
|
|
226
235
|
|
|
227
236
|
- **rule-name**: A unique identifier for the rule. Can include a directory path to install the rule to a specific directory.
|
|
228
237
|
- **source-location**: Location of the rule file (path within an npm package or local path). Supports glob patterns for automatic file discovery.
|
|
@@ -292,6 +301,7 @@ Rules stored locally in your project or filesystem. Any path containing slashes
|
|
|
292
301
|
|
|
293
302
|
- **Cursor**: Rules are installed as individual `.mdc` files in the Cursor rules directory (`.cursor/rules/aicm/`), mcp servers are installed to `.cursor/mcp.json`
|
|
294
303
|
- **Windsurf**: Rules are installed in the `.aicm` directory which should be added to your `.gitignore` file. Our approach for Windsurf is to create links from the `.windsurfrules` file to the respective rules in the `.aicm` directory. There is no support for local mcp servers at the moment.
|
|
304
|
+
- **Codex**: Rules are installed in the `.aicm` directory and referenced from `AGENTS.md` using the same markers as Windsurf.
|
|
295
305
|
|
|
296
306
|
## Commands
|
|
297
307
|
|
|
@@ -396,3 +406,9 @@ npm run test:unit
|
|
|
396
406
|
# Run only E2E tests
|
|
397
407
|
npm run test:e2e
|
|
398
408
|
```
|
|
409
|
+
|
|
410
|
+
### Publishing
|
|
411
|
+
|
|
412
|
+
```bash
|
|
413
|
+
npm run release
|
|
414
|
+
```
|
package/dist/api.d.ts
CHANGED
|
@@ -5,4 +5,4 @@ import { InstallOptions, InstallResult } from "./commands/install";
|
|
|
5
5
|
* @returns Result of the install operation
|
|
6
6
|
*/
|
|
7
7
|
export declare function install(options?: InstallOptions): Promise<InstallResult>;
|
|
8
|
-
export { Config, Rule, Rules, RuleMetadata, RuleContent, RuleCollection, } from "./types";
|
|
8
|
+
export { Config, NormalizedConfig, Rule, Rules, RuleMetadata, RuleContent, RuleCollection, } from "./types";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NormalizedConfig } from "../types";
|
|
2
2
|
/**
|
|
3
3
|
* Options for the installCore function
|
|
4
4
|
*/
|
|
@@ -10,7 +10,7 @@ export interface InstallOptions {
|
|
|
10
10
|
/**
|
|
11
11
|
* Custom config object to use instead of loading from file
|
|
12
12
|
*/
|
|
13
|
-
config?:
|
|
13
|
+
config?: NormalizedConfig;
|
|
14
14
|
/**
|
|
15
15
|
* allow installation on CI environments
|
|
16
16
|
*/
|
package/dist/index.js
CHANGED
|
File without changes
|
package/dist/types/index.d.ts
CHANGED
|
@@ -17,12 +17,16 @@ export interface MCPServers {
|
|
|
17
17
|
[serverName: string]: MCPServer;
|
|
18
18
|
}
|
|
19
19
|
export interface Config {
|
|
20
|
-
ides
|
|
21
|
-
rules
|
|
20
|
+
ides?: string[];
|
|
21
|
+
rules?: Rules | string;
|
|
22
22
|
presets?: string[];
|
|
23
23
|
mcpServers?: MCPServers;
|
|
24
24
|
installOnCI?: boolean;
|
|
25
25
|
}
|
|
26
|
+
export interface NormalizedConfig extends Omit<Config, "rules"> {
|
|
27
|
+
ides: string[];
|
|
28
|
+
rules: Rules;
|
|
29
|
+
}
|
|
26
30
|
export interface RuleMetadata {
|
|
27
31
|
type?: string;
|
|
28
32
|
alwaysApply?: boolean | string;
|
|
@@ -40,11 +44,12 @@ export interface RuleContent {
|
|
|
40
44
|
export interface RuleCollection {
|
|
41
45
|
cursor: RuleContent[];
|
|
42
46
|
windsurf: RuleContent[];
|
|
47
|
+
codex: RuleContent[];
|
|
43
48
|
}
|
|
44
49
|
export interface PackageInfo {
|
|
45
50
|
relativePath: string;
|
|
46
51
|
absolutePath: string;
|
|
47
|
-
config:
|
|
52
|
+
config: NormalizedConfig;
|
|
48
53
|
}
|
|
49
54
|
export interface WorkspacesInstallResult {
|
|
50
55
|
success: boolean;
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { Config, Rules } from "../types";
|
|
1
|
+
import { Config, NormalizedConfig, Rules } from "../types";
|
|
2
2
|
export interface RuleMetadata {
|
|
3
3
|
ruleSources: Record<string, string>;
|
|
4
4
|
originalPresetPaths: Record<string, string>;
|
|
5
5
|
}
|
|
6
6
|
export interface ConfigResult {
|
|
7
|
-
config:
|
|
7
|
+
config: NormalizedConfig;
|
|
8
8
|
metadata: RuleMetadata;
|
|
9
9
|
}
|
|
10
|
+
export declare function normalizeRules(rules: Rules | string | undefined): Rules;
|
|
10
11
|
export interface PresetPathInfo {
|
|
11
12
|
fullPath: string;
|
|
12
13
|
originalPath: string;
|
|
@@ -24,19 +25,19 @@ export declare function loadPreset(presetPath: string, cwd?: string): {
|
|
|
24
25
|
* Load the aicm config using cosmiconfigSync, supporting both aicm.json and package.json.
|
|
25
26
|
* Returns the config object or null if not found.
|
|
26
27
|
*/
|
|
27
|
-
export declare function loadAicmConfigCosmiconfig(searchFrom?: string):
|
|
28
|
+
export declare function loadAicmConfigCosmiconfig(searchFrom?: string): NormalizedConfig | null;
|
|
28
29
|
/**
|
|
29
30
|
* Get the configuration from aicm.json or package.json (using cosmiconfigSync) and merge with any presets
|
|
30
31
|
*/
|
|
31
|
-
export declare function getConfig(cwd?: string):
|
|
32
|
+
export declare function getConfig(cwd?: string): NormalizedConfig | null;
|
|
32
33
|
/**
|
|
33
34
|
* Get the source preset path for a rule if it came from a preset
|
|
34
35
|
*/
|
|
35
|
-
export declare function getRuleSource(config:
|
|
36
|
+
export declare function getRuleSource(config: NormalizedConfig, ruleName: string): string | undefined;
|
|
36
37
|
/**
|
|
37
38
|
* Get the original preset path for a rule if it came from a preset
|
|
38
39
|
*/
|
|
39
|
-
export declare function getOriginalPresetPath(config:
|
|
40
|
+
export declare function getOriginalPresetPath(config: NormalizedConfig, ruleName: string): string | undefined;
|
|
40
41
|
/**
|
|
41
42
|
* Save the configuration to the aicm.json file
|
|
42
43
|
*/
|
package/dist/utils/config.js
CHANGED
|
@@ -3,6 +3,7 @@ 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;
|
|
6
7
|
exports.getFullPresetPath = getFullPresetPath;
|
|
7
8
|
exports.loadPreset = loadPreset;
|
|
8
9
|
exports.loadAicmConfigCosmiconfig = loadAicmConfigCosmiconfig;
|
|
@@ -14,6 +15,14 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
|
14
15
|
const node_path_1 = __importDefault(require("node:path"));
|
|
15
16
|
const cosmiconfig_1 = require("cosmiconfig");
|
|
16
17
|
const CONFIG_FILE = "aicm.json";
|
|
18
|
+
function normalizeRules(rules) {
|
|
19
|
+
if (!rules)
|
|
20
|
+
return {};
|
|
21
|
+
if (typeof rules === "string") {
|
|
22
|
+
return { "/": rules };
|
|
23
|
+
}
|
|
24
|
+
return rules;
|
|
25
|
+
}
|
|
17
26
|
function getFullPresetPath(presetPath, cwd) {
|
|
18
27
|
const workingDir = cwd || process.cwd();
|
|
19
28
|
// If it's a local file with .json extension, check relative to the working directory
|
|
@@ -82,11 +91,12 @@ function loadPreset(presetPath, cwd) {
|
|
|
82
91
|
const parseError = error;
|
|
83
92
|
throw new Error(`Error loading preset: Invalid JSON in ${presetPath}: ${parseError.message}`);
|
|
84
93
|
}
|
|
85
|
-
if (!preset.rules
|
|
94
|
+
if (!preset.rules) {
|
|
86
95
|
throw new Error(`Error loading preset: Invalid format in ${presetPath} - missing or invalid 'rules' object`);
|
|
87
96
|
}
|
|
97
|
+
const normalizedRules = normalizeRules(preset.rules);
|
|
88
98
|
return {
|
|
89
|
-
rules:
|
|
99
|
+
rules: normalizedRules,
|
|
90
100
|
mcpServers: preset.mcpServers,
|
|
91
101
|
presets: preset.presets,
|
|
92
102
|
};
|
|
@@ -214,11 +224,13 @@ function loadAicmConfigCosmiconfig(searchFrom) {
|
|
|
214
224
|
const result = explorer.search(searchFrom);
|
|
215
225
|
if (!result || !result.config)
|
|
216
226
|
return null;
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
+
};
|
|
222
234
|
return config;
|
|
223
235
|
}
|
|
224
236
|
catch (error) {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NormalizedConfig } from "../types";
|
|
2
2
|
/**
|
|
3
3
|
* Write MCP servers configuration to IDE targets
|
|
4
4
|
* @param mcpServers The MCP servers configuration
|
|
5
5
|
* @param ides The IDEs to write to
|
|
6
6
|
* @param cwd The current working directory
|
|
7
7
|
*/
|
|
8
|
-
export declare function writeMcpServersToTargets(mcpServers:
|
|
8
|
+
export declare function writeMcpServersToTargets(mcpServers: NormalizedConfig["mcpServers"], ides: string[], cwd: string): void;
|
|
9
9
|
/**
|
|
10
10
|
* Write MCP servers configuration to a specific file
|
|
11
11
|
* @param mcpServers The MCP servers configuration
|
|
12
12
|
* @param mcpPath The path to the mcp.json file
|
|
13
13
|
*/
|
|
14
|
-
export declare function writeMcpServersToFile(mcpServers:
|
|
14
|
+
export declare function writeMcpServersToFile(mcpServers: NormalizedConfig["mcpServers"], mcpPath: string): void;
|
|
@@ -59,6 +59,7 @@ function initRuleCollection() {
|
|
|
59
59
|
return {
|
|
60
60
|
cursor: [],
|
|
61
61
|
windsurf: [],
|
|
62
|
+
codex: [],
|
|
62
63
|
};
|
|
63
64
|
}
|
|
64
65
|
/**
|
|
@@ -77,6 +78,10 @@ function addRuleToCollection(collection, rule, ides) {
|
|
|
77
78
|
!collection.windsurf.some((r) => r.name === rule.name)) {
|
|
78
79
|
collection.windsurf.push(rule);
|
|
79
80
|
}
|
|
81
|
+
else if (ide === "codex" &&
|
|
82
|
+
!collection.codex.some((r) => r.name === rule.name)) {
|
|
83
|
+
collection.codex.push(rule);
|
|
84
|
+
}
|
|
80
85
|
}
|
|
81
86
|
}
|
|
82
87
|
/**
|
|
@@ -15,6 +15,7 @@ function getIdePaths() {
|
|
|
15
15
|
return {
|
|
16
16
|
cursor: node_path_1.default.join(projectDir, ".cursor", "rules", "aicm"),
|
|
17
17
|
windsurf: node_path_1.default.join(projectDir, ".aicm"),
|
|
18
|
+
codex: node_path_1.default.join(projectDir, ".aicm"),
|
|
18
19
|
};
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
@@ -38,6 +39,15 @@ function checkRuleStatus(ruleName, ides) {
|
|
|
38
39
|
}
|
|
39
40
|
return false;
|
|
40
41
|
}
|
|
42
|
+
if (ide === "codex") {
|
|
43
|
+
const ruleExists = fs_extra_1.default.existsSync(node_path_1.default.join(idePaths[ide], `${ruleName}.md`));
|
|
44
|
+
const codexFilePath = node_path_1.default.join(process.cwd(), "AGENTS.md");
|
|
45
|
+
if (fs_extra_1.default.existsSync(codexFilePath)) {
|
|
46
|
+
const codexContent = fs_extra_1.default.readFileSync(codexFilePath, "utf8");
|
|
47
|
+
return ruleExists && codexContent.includes(`.aicm/${ruleName}.md`);
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
41
51
|
return false;
|
|
42
52
|
});
|
|
43
53
|
}
|
|
@@ -7,7 +7,7 @@ exports.writeRulesToTargets = writeRulesToTargets;
|
|
|
7
7
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
9
|
const rule_status_1 = require("./rule-status");
|
|
10
|
-
const
|
|
10
|
+
const rules_file_writer_1 = require("./rules-file-writer");
|
|
11
11
|
/**
|
|
12
12
|
* Write all collected rules to their respective IDE targets
|
|
13
13
|
* @param collection The collection of rules to write
|
|
@@ -20,7 +20,10 @@ function writeRulesToTargets(collection) {
|
|
|
20
20
|
}
|
|
21
21
|
// Write Windsurf rules
|
|
22
22
|
if (collection.windsurf.length > 0) {
|
|
23
|
-
|
|
23
|
+
writeRulesForFile(collection.windsurf, idePaths.windsurf, ".windsurfrules");
|
|
24
|
+
}
|
|
25
|
+
if (collection.codex.length > 0) {
|
|
26
|
+
writeRulesForFile(collection.codex, idePaths.codex, "AGENTS.md");
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
29
|
/**
|
|
@@ -71,10 +74,10 @@ function writeCursorRules(rules, cursorRulesDir) {
|
|
|
71
74
|
}
|
|
72
75
|
}
|
|
73
76
|
/**
|
|
74
|
-
* Write rules to
|
|
77
|
+
* Write rules to a shared directory and update the given rules file
|
|
75
78
|
* @param rules The rules to write
|
|
76
79
|
*/
|
|
77
|
-
function
|
|
80
|
+
function writeRulesForFile(rules, ruleDir, rulesFile) {
|
|
78
81
|
fs_extra_1.default.emptyDirSync(ruleDir);
|
|
79
82
|
const ruleFiles = rules.map((rule) => {
|
|
80
83
|
let rulePath;
|
|
@@ -94,7 +97,7 @@ function writeWindsurfRulesFromCollection(rules, ruleDir) {
|
|
|
94
97
|
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(physicalRulePath));
|
|
95
98
|
fs_extra_1.default.writeFileSync(physicalRulePath, rule.content);
|
|
96
99
|
const relativeRuleDir = node_path_1.default.basename(ruleDir); // Gets '.rules'
|
|
97
|
-
// For the
|
|
100
|
+
// For the rules file, maintain the same structure
|
|
98
101
|
let windsurfPath;
|
|
99
102
|
if (rule.presetPath) {
|
|
100
103
|
const namespace = extractNamespaceFromPresetPath(rule.presetPath);
|
|
@@ -112,6 +115,6 @@ function writeWindsurfRulesFromCollection(rules, ruleDir) {
|
|
|
112
115
|
metadata: rule.metadata,
|
|
113
116
|
};
|
|
114
117
|
});
|
|
115
|
-
const windsurfRulesContent = (0,
|
|
116
|
-
(0,
|
|
118
|
+
const windsurfRulesContent = (0, rules_file_writer_1.generateRulesFileContent)(ruleFiles);
|
|
119
|
+
(0, rules_file_writer_1.writeRulesFile)(windsurfRulesContent, node_path_1.default.join(process.cwd(), rulesFile));
|
|
117
120
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write rules to the .windsurfrules file
|
|
3
|
+
* This will update the content between the RULES_BEGIN and RULES_END markers
|
|
4
|
+
* If the file doesn't exist, it will create it
|
|
5
|
+
* If the markers don't exist, it will append them to the existing content
|
|
6
|
+
*/
|
|
7
|
+
export declare function writeRulesFile(rulesContent: string, rulesFilePath?: string): void;
|
|
8
|
+
/**
|
|
9
|
+
* Generate the rules file content based on rule files
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateRulesFileContent(ruleFiles: {
|
|
12
|
+
name: string;
|
|
13
|
+
path: string;
|
|
14
|
+
metadata: Record<string, string | boolean | string[]>;
|
|
15
|
+
}[]): string;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.writeRulesFile = writeRulesFile;
|
|
7
|
+
exports.generateRulesFileContent = generateRulesFileContent;
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const RULES_BEGIN = "<!-- AICM:BEGIN -->";
|
|
11
|
+
const RULES_END = "<!-- AICM:END -->";
|
|
12
|
+
const WARNING = "<!-- WARNING: Everything between these markers will be overwritten during installation -->";
|
|
13
|
+
/**
|
|
14
|
+
* Create a formatted block of content with rules markers
|
|
15
|
+
*/
|
|
16
|
+
function createRulesBlock(rulesContent) {
|
|
17
|
+
return `${RULES_BEGIN}
|
|
18
|
+
${WARNING}
|
|
19
|
+
|
|
20
|
+
${rulesContent}
|
|
21
|
+
|
|
22
|
+
${RULES_END}`;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Write rules to the .windsurfrules file
|
|
26
|
+
* This will update the content between the RULES_BEGIN and RULES_END markers
|
|
27
|
+
* If the file doesn't exist, it will create it
|
|
28
|
+
* If the markers don't exist, it will append them to the existing content
|
|
29
|
+
*/
|
|
30
|
+
function writeRulesFile(rulesContent, rulesFilePath = path_1.default.join(process.cwd(), ".windsurfrules")) {
|
|
31
|
+
let fileContent;
|
|
32
|
+
const formattedRulesBlock = createRulesBlock(rulesContent);
|
|
33
|
+
// Check if file exists
|
|
34
|
+
if (fs_extra_1.default.existsSync(rulesFilePath)) {
|
|
35
|
+
const existingContent = fs_extra_1.default.readFileSync(rulesFilePath, "utf8");
|
|
36
|
+
// Check if our markers exist
|
|
37
|
+
if (existingContent.includes(RULES_BEGIN) &&
|
|
38
|
+
existingContent.includes(RULES_END)) {
|
|
39
|
+
// Replace content between markers
|
|
40
|
+
const beforeMarker = existingContent.split(RULES_BEGIN)[0];
|
|
41
|
+
const afterMarker = existingContent.split(RULES_END)[1];
|
|
42
|
+
fileContent = beforeMarker + formattedRulesBlock + afterMarker;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// Preserve the existing content and append markers
|
|
46
|
+
// Ensure there's proper spacing between existing content and markers
|
|
47
|
+
let separator = "";
|
|
48
|
+
if (!existingContent.endsWith("\n")) {
|
|
49
|
+
separator += "\n";
|
|
50
|
+
}
|
|
51
|
+
// Add an extra line if the file doesn't already end with multiple newlines
|
|
52
|
+
if (!existingContent.endsWith("\n\n")) {
|
|
53
|
+
separator += "\n";
|
|
54
|
+
}
|
|
55
|
+
// Create the new file content with preserved original content
|
|
56
|
+
fileContent = existingContent + separator + formattedRulesBlock;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// Create new file with markers and content
|
|
61
|
+
fileContent = formattedRulesBlock;
|
|
62
|
+
}
|
|
63
|
+
fs_extra_1.default.writeFileSync(rulesFilePath, fileContent);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Generate the rules file content based on rule files
|
|
67
|
+
*/
|
|
68
|
+
function generateRulesFileContent(ruleFiles) {
|
|
69
|
+
const alwaysRules = [];
|
|
70
|
+
const autoAttachedRules = [];
|
|
71
|
+
const agentRequestedRules = [];
|
|
72
|
+
const manualRules = [];
|
|
73
|
+
ruleFiles.forEach(({ path, metadata }) => {
|
|
74
|
+
// Determine rule type based on metadata
|
|
75
|
+
if (metadata.type === "always" ||
|
|
76
|
+
metadata.alwaysApply === true ||
|
|
77
|
+
metadata.alwaysApply === "true") {
|
|
78
|
+
alwaysRules.push(path);
|
|
79
|
+
}
|
|
80
|
+
else if (metadata.type === "auto-attached" || metadata.globs) {
|
|
81
|
+
const globPattern = typeof metadata.globs === "string" || Array.isArray(metadata.globs)
|
|
82
|
+
? Array.isArray(metadata.globs)
|
|
83
|
+
? metadata.globs.join(", ")
|
|
84
|
+
: metadata.globs
|
|
85
|
+
: undefined;
|
|
86
|
+
if (globPattern !== undefined) {
|
|
87
|
+
autoAttachedRules.push({ path, glob: globPattern });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else if (metadata.type === "agent-requested" || metadata.description) {
|
|
91
|
+
agentRequestedRules.push(path);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// Default to manual inclusion
|
|
95
|
+
manualRules.push(path);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
// Generate the content
|
|
99
|
+
let content = "";
|
|
100
|
+
// Always rules
|
|
101
|
+
if (alwaysRules.length > 0) {
|
|
102
|
+
content +=
|
|
103
|
+
"The following rules always apply to all files in the project:\n";
|
|
104
|
+
alwaysRules.forEach((rule) => {
|
|
105
|
+
content += `- ${rule}\n`;
|
|
106
|
+
});
|
|
107
|
+
content += "\n";
|
|
108
|
+
}
|
|
109
|
+
// Auto Attached rules
|
|
110
|
+
if (autoAttachedRules.length > 0) {
|
|
111
|
+
content +=
|
|
112
|
+
"The following rules are automatically attached to matching glob patterns:\n";
|
|
113
|
+
autoAttachedRules.forEach((rule) => {
|
|
114
|
+
content += `- [${rule.glob}] ${rule.path}\n`;
|
|
115
|
+
});
|
|
116
|
+
content += "\n";
|
|
117
|
+
}
|
|
118
|
+
// Agent Requested rules
|
|
119
|
+
if (agentRequestedRules.length > 0) {
|
|
120
|
+
content +=
|
|
121
|
+
"The following rules are available for the AI to include when needed:\n";
|
|
122
|
+
agentRequestedRules.forEach((rule) => {
|
|
123
|
+
content += `- ${rule}\n`;
|
|
124
|
+
});
|
|
125
|
+
content += "\n";
|
|
126
|
+
}
|
|
127
|
+
// Manual rules
|
|
128
|
+
if (manualRules.length > 0) {
|
|
129
|
+
content +=
|
|
130
|
+
"The following rules are only included when explicitly referenced:\n";
|
|
131
|
+
manualRules.forEach((rule) => {
|
|
132
|
+
content += `- ${rule}\n`;
|
|
133
|
+
});
|
|
134
|
+
content += "\n";
|
|
135
|
+
}
|
|
136
|
+
return content.trim();
|
|
137
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aicm",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.2",
|
|
4
4
|
"description": "A TypeScript CLI tool for managing AI IDE rules across different projects and teams",
|
|
5
5
|
"main": "dist/api.js",
|
|
6
6
|
"types": "dist/api.d.ts",
|
|
@@ -12,22 +12,6 @@
|
|
|
12
12
|
"README.md",
|
|
13
13
|
"LICENSE"
|
|
14
14
|
],
|
|
15
|
-
"scripts": {
|
|
16
|
-
"build": "tsc",
|
|
17
|
-
"watch": "tsc --watch",
|
|
18
|
-
"start": "node dist/index.js",
|
|
19
|
-
"dev": "ts-node src/index.ts",
|
|
20
|
-
"test": "jest",
|
|
21
|
-
"test:watch": "jest --watch",
|
|
22
|
-
"test:all": "npm run build && npm run test",
|
|
23
|
-
"test:unit": "jest --config jest.unit.config.js",
|
|
24
|
-
"test:e2e": "jest tests/e2e",
|
|
25
|
-
"format": "prettier --write .",
|
|
26
|
-
"format:check": "prettier --check .",
|
|
27
|
-
"lint": "eslint",
|
|
28
|
-
"prepare": "husky install && npx ts-node src/index.ts install",
|
|
29
|
-
"version": "auto-changelog -p && git add CHANGELOG.md"
|
|
30
|
-
},
|
|
31
15
|
"keywords": [
|
|
32
16
|
"ai",
|
|
33
17
|
"ide",
|
|
@@ -58,6 +42,7 @@
|
|
|
58
42
|
"lint-staged": "^15.2.0",
|
|
59
43
|
"mock-fs": "^5.2.0",
|
|
60
44
|
"nock": "^13.3.8",
|
|
45
|
+
"np": "^10.2.0",
|
|
61
46
|
"prettier": "^3.1.0",
|
|
62
47
|
"rimraf": "^5.0.5",
|
|
63
48
|
"ts-jest": "^29.1.1",
|
|
@@ -68,5 +53,21 @@
|
|
|
68
53
|
"lint-staged": {
|
|
69
54
|
"*.{js,ts,json,md,mjs}": "prettier --write",
|
|
70
55
|
"*.ts": "eslint"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsc",
|
|
59
|
+
"watch": "tsc --watch",
|
|
60
|
+
"start": "node dist/index.js",
|
|
61
|
+
"dev": "ts-node src/index.ts",
|
|
62
|
+
"test": "jest",
|
|
63
|
+
"test:watch": "jest --watch",
|
|
64
|
+
"test:all": "npm run build && npm run test",
|
|
65
|
+
"test:unit": "jest --config jest.unit.config.js",
|
|
66
|
+
"test:e2e": "jest tests/e2e",
|
|
67
|
+
"format": "prettier --write .",
|
|
68
|
+
"format:check": "prettier --check .",
|
|
69
|
+
"lint": "eslint",
|
|
70
|
+
"version": "auto-changelog -p && git add CHANGELOG.md",
|
|
71
|
+
"release": "np"
|
|
71
72
|
}
|
|
72
|
-
}
|
|
73
|
+
}
|