aicm 0.1.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/LICENSE +21 -0
- package/README.md +232 -0
- package/dist/commands/check-updates.d.ts +6 -0
- package/dist/commands/check-updates.js +84 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +31 -0
- package/dist/commands/install.d.ts +1 -0
- package/dist/commands/install.js +152 -0
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.js +37 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +69 -0
- package/dist/types/index.d.ts +26 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/config.d.ts +21 -0
- package/dist/utils/config.js +155 -0
- package/dist/utils/mdc-parser.d.ts +9 -0
- package/dist/utils/mdc-parser.js +59 -0
- package/dist/utils/rule-collector.d.ts +33 -0
- package/dist/utils/rule-collector.js +160 -0
- package/dist/utils/rule-detector.d.ts +4 -0
- package/dist/utils/rule-detector.js +42 -0
- package/dist/utils/rule-status.d.ts +8 -0
- package/dist/utils/rule-status.js +47 -0
- package/dist/utils/rule-writer.d.ts +6 -0
- package/dist/utils/rule-writer.js +68 -0
- package/dist/utils/windsurf-writer.d.ts +15 -0
- package/dist/utils/windsurf-writer.js +137 -0
- package/package.json +68 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Config, Rules } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Get the full path to a preset file
|
|
4
|
+
*/
|
|
5
|
+
export declare function getFullPresetPath(presetPath: string): string | null;
|
|
6
|
+
/**
|
|
7
|
+
* Load a preset file and return its rules
|
|
8
|
+
*/
|
|
9
|
+
export declare function loadPreset(presetPath: string): Rules | null;
|
|
10
|
+
/**
|
|
11
|
+
* Get the configuration from the rules.json file and merge with any presets
|
|
12
|
+
*/
|
|
13
|
+
export declare function getConfig(): Config | null;
|
|
14
|
+
/**
|
|
15
|
+
* Get the source preset path for a rule if it came from a preset
|
|
16
|
+
*/
|
|
17
|
+
export declare function getRuleSource(config: Config, ruleName: string): string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Save the configuration to the rules.json file
|
|
20
|
+
*/
|
|
21
|
+
export declare function saveConfig(config: Config): boolean;
|
|
@@ -0,0 +1,155 @@
|
|
|
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.getFullPresetPath = getFullPresetPath;
|
|
7
|
+
exports.loadPreset = loadPreset;
|
|
8
|
+
exports.getConfig = getConfig;
|
|
9
|
+
exports.getRuleSource = getRuleSource;
|
|
10
|
+
exports.saveConfig = saveConfig;
|
|
11
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const rule_detector_1 = require("./rule-detector");
|
|
14
|
+
const CONFIG_FILE = "rules.json";
|
|
15
|
+
/**
|
|
16
|
+
* Get the full path to a preset file
|
|
17
|
+
*/
|
|
18
|
+
function getFullPresetPath(presetPath) {
|
|
19
|
+
try {
|
|
20
|
+
const ruleType = (0, rule_detector_1.detectRuleType)(presetPath);
|
|
21
|
+
let fullPresetPath = presetPath;
|
|
22
|
+
if (ruleType === "npm") {
|
|
23
|
+
try {
|
|
24
|
+
fullPresetPath = require.resolve(presetPath, {
|
|
25
|
+
paths: [process.cwd()],
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
catch (_a) {
|
|
29
|
+
const directPath = node_path_1.default.join(process.cwd(), "node_modules", presetPath);
|
|
30
|
+
if (fs_extra_1.default.existsSync(directPath)) {
|
|
31
|
+
fullPresetPath = directPath;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// For local files, resolve from current directory
|
|
40
|
+
fullPresetPath = node_path_1.default.resolve(process.cwd(), presetPath);
|
|
41
|
+
}
|
|
42
|
+
return fs_extra_1.default.existsSync(fullPresetPath) ? fullPresetPath : null;
|
|
43
|
+
}
|
|
44
|
+
catch (_b) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Load a preset file and return its rules
|
|
50
|
+
*/
|
|
51
|
+
function loadPreset(presetPath) {
|
|
52
|
+
const fullPresetPath = getFullPresetPath(presetPath);
|
|
53
|
+
if (!fullPresetPath) {
|
|
54
|
+
throw new Error(`Error loading preset: File not found: ${presetPath}. Make sure the package is installed in your project.`);
|
|
55
|
+
}
|
|
56
|
+
const presetContent = fs_extra_1.default.readFileSync(fullPresetPath, "utf8");
|
|
57
|
+
let preset;
|
|
58
|
+
try {
|
|
59
|
+
preset = JSON.parse(presetContent);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
const parseError = error;
|
|
63
|
+
throw new Error(`Error loading preset: Invalid JSON in ${presetPath}: ${parseError.message}`);
|
|
64
|
+
}
|
|
65
|
+
if (!preset.rules || typeof preset.rules !== "object") {
|
|
66
|
+
throw new Error(`Error loading preset: Invalid format in ${presetPath} - missing or invalid 'rules' object`);
|
|
67
|
+
}
|
|
68
|
+
return preset.rules;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Read the raw configuration file without processing presets
|
|
72
|
+
*/
|
|
73
|
+
function readConfigFile() {
|
|
74
|
+
const configPath = node_path_1.default.join(process.cwd(), CONFIG_FILE);
|
|
75
|
+
if (!fs_extra_1.default.existsSync(configPath)) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const configContent = fs_extra_1.default.readFileSync(configPath, "utf8");
|
|
80
|
+
const config = JSON.parse(configContent);
|
|
81
|
+
// Initialize rules object if it doesn't exist
|
|
82
|
+
if (!config.rules) {
|
|
83
|
+
config.rules = {};
|
|
84
|
+
}
|
|
85
|
+
return config;
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
console.error("Error reading configuration file:", error);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Process presets and merge their rules into the config
|
|
94
|
+
*/
|
|
95
|
+
function processPresets(config) {
|
|
96
|
+
if (!config.presets || !Array.isArray(config.presets)) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
for (const presetPath of config.presets) {
|
|
100
|
+
const presetRules = loadPreset(presetPath);
|
|
101
|
+
if (!presetRules)
|
|
102
|
+
continue;
|
|
103
|
+
const fullPresetPath = getFullPresetPath(presetPath);
|
|
104
|
+
if (!fullPresetPath)
|
|
105
|
+
continue;
|
|
106
|
+
mergePresetRules(config, presetRules, fullPresetPath);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Merge preset rules into the config
|
|
111
|
+
*/
|
|
112
|
+
function mergePresetRules(config, presetRules, presetPath) {
|
|
113
|
+
// Add preset rules, but don't override existing rules
|
|
114
|
+
for (const [ruleName, rulePath] of Object.entries(presetRules)) {
|
|
115
|
+
// Only add if not already defined in config
|
|
116
|
+
if (!config.rules[ruleName]) {
|
|
117
|
+
config.rules[ruleName] = rulePath;
|
|
118
|
+
// Store the source preset path in metadata
|
|
119
|
+
config.__ruleSources = config.__ruleSources || {};
|
|
120
|
+
config.__ruleSources[ruleName] = presetPath;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get the configuration from the rules.json file and merge with any presets
|
|
126
|
+
*/
|
|
127
|
+
function getConfig() {
|
|
128
|
+
const config = readConfigFile();
|
|
129
|
+
if (!config) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
processPresets(config);
|
|
133
|
+
return config;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get the source preset path for a rule if it came from a preset
|
|
137
|
+
*/
|
|
138
|
+
function getRuleSource(config, ruleName) {
|
|
139
|
+
var _a;
|
|
140
|
+
return (_a = config.__ruleSources) === null || _a === void 0 ? void 0 : _a[ruleName];
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Save the configuration to the rules.json file
|
|
144
|
+
*/
|
|
145
|
+
function saveConfig(config) {
|
|
146
|
+
const configPath = node_path_1.default.join(process.cwd(), CONFIG_FILE);
|
|
147
|
+
try {
|
|
148
|
+
fs_extra_1.default.writeJsonSync(configPath, config, { spaces: 2 });
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
console.error("Error writing configuration file:", error);
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse an MDC file and extract its content
|
|
3
|
+
* MDC files are Markdown files with metadata
|
|
4
|
+
* https://docs.cursor.com/context/rules
|
|
5
|
+
*/
|
|
6
|
+
export declare function parseMdcFile(filePath: string): {
|
|
7
|
+
metadata: Record<string, boolean | string | string[]>;
|
|
8
|
+
content: string;
|
|
9
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
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.parseMdcFile = parseMdcFile;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
/**
|
|
9
|
+
* Parse an MDC file and extract its content
|
|
10
|
+
* MDC files are Markdown files with metadata
|
|
11
|
+
* https://docs.cursor.com/context/rules
|
|
12
|
+
*/
|
|
13
|
+
function parseMdcFile(filePath) {
|
|
14
|
+
const fileContent = fs_extra_1.default.readFileSync(filePath, "utf8");
|
|
15
|
+
if (!fileContent.startsWith("---")) {
|
|
16
|
+
return {
|
|
17
|
+
metadata: {},
|
|
18
|
+
content: fileContent,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const endOfMetadata = fileContent.indexOf("---", 3);
|
|
22
|
+
if (endOfMetadata === -1) {
|
|
23
|
+
return {
|
|
24
|
+
metadata: {},
|
|
25
|
+
content: fileContent,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const metadataStr = fileContent.substring(3, endOfMetadata).trim();
|
|
29
|
+
const content = fileContent.substring(endOfMetadata + 3).trim();
|
|
30
|
+
const metadata = {};
|
|
31
|
+
metadataStr.split("\n").forEach((line) => {
|
|
32
|
+
const colonIndex = line.indexOf(":");
|
|
33
|
+
if (colonIndex !== -1) {
|
|
34
|
+
const key = line.substring(0, colonIndex).trim();
|
|
35
|
+
const valueStr = line.substring(colonIndex + 1).trim();
|
|
36
|
+
// Handle different value types
|
|
37
|
+
let value = "";
|
|
38
|
+
if (valueStr === "true") {
|
|
39
|
+
value = true;
|
|
40
|
+
}
|
|
41
|
+
else if (valueStr === "false") {
|
|
42
|
+
value = false;
|
|
43
|
+
}
|
|
44
|
+
else if (valueStr.startsWith('"') && valueStr.endsWith('"')) {
|
|
45
|
+
// Remove quotes from string values
|
|
46
|
+
value = valueStr.substring(1, valueStr.length - 1);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Default to using the string value as is
|
|
50
|
+
value = valueStr;
|
|
51
|
+
}
|
|
52
|
+
metadata[key] = value;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
return {
|
|
56
|
+
metadata,
|
|
57
|
+
content,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { RuleCollection, RuleContent } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Find the root directory of a package by name
|
|
4
|
+
* @param packageName The name of the package
|
|
5
|
+
* @returns The absolute path to the package's root directory
|
|
6
|
+
*/
|
|
7
|
+
export declare function findPackageRoot(packageName: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Initialize an empty rule collection
|
|
10
|
+
*/
|
|
11
|
+
export declare function initRuleCollection(): RuleCollection;
|
|
12
|
+
/**
|
|
13
|
+
* Add a rule to the collection
|
|
14
|
+
* @param collection The rule collection to add to
|
|
15
|
+
* @param rule The rule content to add
|
|
16
|
+
* @param ides The IDEs to add the rule for
|
|
17
|
+
*/
|
|
18
|
+
export declare function addRuleToCollection(collection: RuleCollection, rule: RuleContent, ides: string[]): void;
|
|
19
|
+
/**
|
|
20
|
+
* Collect a rule from a local file source
|
|
21
|
+
* @param ruleName The name of the rule
|
|
22
|
+
* @param source The source path (relative or absolute)
|
|
23
|
+
* @param ruleBasePath Optional base path for resolving relative paths
|
|
24
|
+
* @returns The rule content
|
|
25
|
+
*/
|
|
26
|
+
export declare function collectLocalRule(ruleName: string, source: string, ruleBasePath?: string): RuleContent;
|
|
27
|
+
/**
|
|
28
|
+
* Collect a rule from an npm package
|
|
29
|
+
* @param ruleName The name of the rule
|
|
30
|
+
* @param source The npm package source (can include path)
|
|
31
|
+
* @returns The rule content
|
|
32
|
+
*/
|
|
33
|
+
export declare function collectNpmRule(ruleName: string, source: string): RuleContent;
|
|
@@ -0,0 +1,160 @@
|
|
|
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.findPackageRoot = findPackageRoot;
|
|
7
|
+
exports.initRuleCollection = initRuleCollection;
|
|
8
|
+
exports.addRuleToCollection = addRuleToCollection;
|
|
9
|
+
exports.collectLocalRule = collectLocalRule;
|
|
10
|
+
exports.collectNpmRule = collectNpmRule;
|
|
11
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const mdc_parser_1 = require("./mdc-parser");
|
|
14
|
+
/**
|
|
15
|
+
* Find the root directory of a package by name
|
|
16
|
+
* @param packageName The name of the package
|
|
17
|
+
* @returns The absolute path to the package's root directory
|
|
18
|
+
*/
|
|
19
|
+
function findPackageRoot(packageName) {
|
|
20
|
+
try {
|
|
21
|
+
// Method 1: Try to use require.resolve
|
|
22
|
+
const packageEntry = require.resolve(packageName, {
|
|
23
|
+
paths: [process.cwd()],
|
|
24
|
+
});
|
|
25
|
+
// Navigate up until we find a package.json or reach a reasonable limit
|
|
26
|
+
let currentDir = node_path_1.default.dirname(packageEntry);
|
|
27
|
+
let iterations = 0;
|
|
28
|
+
const maxIterations = 3; // Prevent infinite loops
|
|
29
|
+
while (iterations < maxIterations) {
|
|
30
|
+
// If we found the package.json, this is likely the package root
|
|
31
|
+
if (fs_extra_1.default.existsSync(node_path_1.default.join(currentDir, "package.json"))) {
|
|
32
|
+
return currentDir;
|
|
33
|
+
}
|
|
34
|
+
// Move up one directory
|
|
35
|
+
const parentDir = node_path_1.default.dirname(currentDir);
|
|
36
|
+
// If we've reached the root or haven't changed directory, stop
|
|
37
|
+
if (parentDir === currentDir) {
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
currentDir = parentDir;
|
|
41
|
+
iterations++;
|
|
42
|
+
}
|
|
43
|
+
// If we couldn't find package.json, use the directory of the entry point
|
|
44
|
+
return node_path_1.default.dirname(packageEntry);
|
|
45
|
+
}
|
|
46
|
+
catch (_a) {
|
|
47
|
+
// Method 2: Fall back to direct node_modules resolution
|
|
48
|
+
const nodeModulesPath = node_path_1.default.resolve(process.cwd(), "node_modules", packageName);
|
|
49
|
+
if (!fs_extra_1.default.existsSync(nodeModulesPath)) {
|
|
50
|
+
throw new Error(`Package '${packageName}' not found in node_modules.`);
|
|
51
|
+
}
|
|
52
|
+
return nodeModulesPath;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Initialize an empty rule collection
|
|
57
|
+
*/
|
|
58
|
+
function initRuleCollection() {
|
|
59
|
+
return {
|
|
60
|
+
cursor: [],
|
|
61
|
+
windsurf: [],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Add a rule to the collection
|
|
66
|
+
* @param collection The rule collection to add to
|
|
67
|
+
* @param rule The rule content to add
|
|
68
|
+
* @param ides The IDEs to add the rule for
|
|
69
|
+
*/
|
|
70
|
+
function addRuleToCollection(collection, rule, ides) {
|
|
71
|
+
for (const ide of ides) {
|
|
72
|
+
if (ide === "cursor" &&
|
|
73
|
+
!collection.cursor.some((r) => r.name === rule.name)) {
|
|
74
|
+
collection.cursor.push(rule);
|
|
75
|
+
}
|
|
76
|
+
else if (ide === "windsurf" &&
|
|
77
|
+
!collection.windsurf.some((r) => r.name === rule.name)) {
|
|
78
|
+
collection.windsurf.push(rule);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Collect a rule from a local file source
|
|
84
|
+
* @param ruleName The name of the rule
|
|
85
|
+
* @param source The source path (relative or absolute)
|
|
86
|
+
* @param ruleBasePath Optional base path for resolving relative paths
|
|
87
|
+
* @returns The rule content
|
|
88
|
+
*/
|
|
89
|
+
function collectLocalRule(ruleName, source, ruleBasePath) {
|
|
90
|
+
// Resolve path relative to base path or current directory
|
|
91
|
+
let sourcePath = source;
|
|
92
|
+
if (!node_path_1.default.isAbsolute(source)) {
|
|
93
|
+
if (ruleBasePath) {
|
|
94
|
+
// If a base path is provided (e.g., for presets), resolve relative to that
|
|
95
|
+
sourcePath = node_path_1.default.resolve(node_path_1.default.dirname(ruleBasePath), source);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Otherwise resolve relative to current directory
|
|
99
|
+
sourcePath = node_path_1.default.resolve(process.cwd(), source);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (!fs_extra_1.default.existsSync(sourcePath)) {
|
|
103
|
+
throw new Error(`Source file ${sourcePath} not found.`);
|
|
104
|
+
}
|
|
105
|
+
// Parse the MDC file to extract metadata and content
|
|
106
|
+
const { metadata, content } = (0, mdc_parser_1.parseMdcFile)(sourcePath);
|
|
107
|
+
return {
|
|
108
|
+
name: ruleName,
|
|
109
|
+
content,
|
|
110
|
+
metadata,
|
|
111
|
+
sourcePath,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Collect a rule from an npm package
|
|
116
|
+
* @param ruleName The name of the rule
|
|
117
|
+
* @param source The npm package source (can include path)
|
|
118
|
+
* @returns The rule content
|
|
119
|
+
*/
|
|
120
|
+
function collectNpmRule(ruleName, source) {
|
|
121
|
+
// Parse source into package and file path
|
|
122
|
+
let packageName;
|
|
123
|
+
let packagePath;
|
|
124
|
+
if (source.includes("/") && !source.startsWith("@")) {
|
|
125
|
+
// Format: "package-name/path/to/file.mdc"
|
|
126
|
+
const firstSlash = source.indexOf("/");
|
|
127
|
+
packageName = source.substring(0, firstSlash);
|
|
128
|
+
packagePath = source.substring(firstSlash + 1);
|
|
129
|
+
}
|
|
130
|
+
else if (source.startsWith("@")) {
|
|
131
|
+
// Format: "@scope/package/path/to/file.mdc"
|
|
132
|
+
const parts = source.split("/");
|
|
133
|
+
// @scope/package
|
|
134
|
+
packageName = `${parts[0]}/${parts[1]}`;
|
|
135
|
+
// Remaining path, if any
|
|
136
|
+
packagePath = parts.slice(2).join("/");
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// Format: "package-name" (whole package)
|
|
140
|
+
packageName = source;
|
|
141
|
+
packagePath = "";
|
|
142
|
+
}
|
|
143
|
+
// Try to find the package and get its root directory
|
|
144
|
+
const packageRoot = findPackageRoot(packageName);
|
|
145
|
+
// Resolve the full path to the rule file
|
|
146
|
+
const ruleFile = packagePath
|
|
147
|
+
? node_path_1.default.join(packageRoot, packagePath)
|
|
148
|
+
: packageRoot;
|
|
149
|
+
if (!fs_extra_1.default.existsSync(ruleFile)) {
|
|
150
|
+
throw new Error(`Rule file not found in package: ${ruleFile}`);
|
|
151
|
+
}
|
|
152
|
+
// Parse the MDC file to extract metadata and content
|
|
153
|
+
const { metadata, content } = (0, mdc_parser_1.parseMdcFile)(ruleFile);
|
|
154
|
+
return {
|
|
155
|
+
name: ruleName,
|
|
156
|
+
content,
|
|
157
|
+
metadata,
|
|
158
|
+
sourcePath: ruleFile,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
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.detectRuleType = detectRuleType;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
/**
|
|
10
|
+
* Detects the rule type from the source string
|
|
11
|
+
*/
|
|
12
|
+
function detectRuleType(source) {
|
|
13
|
+
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
14
|
+
throw new Error("URL-based rules are not supported due to security concerns. Please use npm packages or local files instead.");
|
|
15
|
+
}
|
|
16
|
+
// Check if it's a local file path
|
|
17
|
+
if (source.startsWith("/") ||
|
|
18
|
+
source.startsWith("./") ||
|
|
19
|
+
source.startsWith("../") ||
|
|
20
|
+
source.startsWith("\\") ||
|
|
21
|
+
source.startsWith(".\\") ||
|
|
22
|
+
(source.includes(":\\") && source.includes("\\")) || // Windows absolute path with backslash
|
|
23
|
+
(source.includes(":") &&
|
|
24
|
+
(source.startsWith("file:") || source.includes(":\\"))) // Path with protocol or Windows drive letter
|
|
25
|
+
) {
|
|
26
|
+
return "local";
|
|
27
|
+
}
|
|
28
|
+
// Check if it's an npm package with a direct node_modules reference
|
|
29
|
+
const packageName = source.split(/[/\\]/)[0]; // Support both slash types
|
|
30
|
+
if (fs_extra_1.default.existsSync(node_path_1.default.resolve(process.cwd(), "node_modules", packageName))) {
|
|
31
|
+
return "npm";
|
|
32
|
+
}
|
|
33
|
+
// Try to interpret as npm package
|
|
34
|
+
try {
|
|
35
|
+
require.resolve(packageName, { paths: [process.cwd()] });
|
|
36
|
+
return "npm";
|
|
37
|
+
}
|
|
38
|
+
catch (_a) {
|
|
39
|
+
// If we couldn't resolve it as an npm package, assume it's a local path
|
|
40
|
+
return "local";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the IDE-specific rule installation paths
|
|
3
|
+
*/
|
|
4
|
+
export declare function getIdePaths(): Record<string, string>;
|
|
5
|
+
/**
|
|
6
|
+
* Check if a rule is installed in the specified IDEs
|
|
7
|
+
*/
|
|
8
|
+
export declare function checkRuleStatus(ruleName: string, ruleType: string, ides: string[]): boolean;
|
|
@@ -0,0 +1,47 @@
|
|
|
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.getIdePaths = getIdePaths;
|
|
7
|
+
exports.checkRuleStatus = checkRuleStatus;
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
/**
|
|
11
|
+
* Get the IDE-specific rule installation paths
|
|
12
|
+
*/
|
|
13
|
+
function getIdePaths() {
|
|
14
|
+
const projectDir = process.cwd(); // Get current working directory (project root)
|
|
15
|
+
return {
|
|
16
|
+
cursor: node_path_1.default.join(projectDir, ".cursor", "rules"),
|
|
17
|
+
windsurf: node_path_1.default.join(projectDir, ".rules"),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Check if a rule is installed in the specified IDEs
|
|
22
|
+
*/
|
|
23
|
+
function checkRuleStatus(ruleName, ruleType, ides) {
|
|
24
|
+
const idePaths = getIdePaths();
|
|
25
|
+
// Check if rule is installed in all specified IDEs
|
|
26
|
+
return ides.every((ide) => {
|
|
27
|
+
if (!idePaths[ide]) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
if (ide === "cursor") {
|
|
31
|
+
return fs_extra_1.default.existsSync(node_path_1.default.join(idePaths[ide], `${ruleName}.mdc`));
|
|
32
|
+
}
|
|
33
|
+
if (ide === "windsurf") {
|
|
34
|
+
// For Windsurf, check if the rule exists in .rules directory
|
|
35
|
+
// and if it's referenced in .windsurfrules
|
|
36
|
+
const ruleExists = fs_extra_1.default.existsSync(node_path_1.default.join(idePaths[ide], `${ruleName}.md`));
|
|
37
|
+
// Check if .windsurfrules exists and contains a reference to this rule
|
|
38
|
+
const windsurfRulesPath = node_path_1.default.join(process.cwd(), ".windsurfrules");
|
|
39
|
+
if (fs_extra_1.default.existsSync(windsurfRulesPath)) {
|
|
40
|
+
const windsurfRulesContent = fs_extra_1.default.readFileSync(windsurfRulesPath, "utf8");
|
|
41
|
+
return (ruleExists && windsurfRulesContent.includes(`.rules/${ruleName}.md`));
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
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.writeRulesToTargets = writeRulesToTargets;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const rule_status_1 = require("./rule-status");
|
|
10
|
+
const windsurf_writer_1 = require("./windsurf-writer");
|
|
11
|
+
/**
|
|
12
|
+
* Write all collected rules to their respective IDE targets
|
|
13
|
+
* @param collection The collection of rules to write
|
|
14
|
+
*/
|
|
15
|
+
function writeRulesToTargets(collection) {
|
|
16
|
+
const idePaths = (0, rule_status_1.getIdePaths)();
|
|
17
|
+
// Write Cursor rules
|
|
18
|
+
if (collection.cursor.length > 0) {
|
|
19
|
+
writeCursorRules(collection.cursor, idePaths.cursor);
|
|
20
|
+
}
|
|
21
|
+
// Write Windsurf rules
|
|
22
|
+
if (collection.windsurf.length > 0) {
|
|
23
|
+
writeWindsurfRulesFromCollection(collection.windsurf);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Write rules to Cursor's rules directory
|
|
28
|
+
* @param rules The rules to write
|
|
29
|
+
* @param cursorRulesDir The path to Cursor's rules directory
|
|
30
|
+
*/
|
|
31
|
+
function writeCursorRules(rules, cursorRulesDir) {
|
|
32
|
+
fs_extra_1.default.ensureDirSync(cursorRulesDir);
|
|
33
|
+
for (const rule of rules) {
|
|
34
|
+
const ruleFile = node_path_1.default.join(cursorRulesDir, `${rule.name}.mdc`);
|
|
35
|
+
// For Cursor, we either copy the file or create a symlink to the original
|
|
36
|
+
if (fs_extra_1.default.existsSync(rule.sourcePath)) {
|
|
37
|
+
// Copy the file (safer than symlink)
|
|
38
|
+
fs_extra_1.default.copyFileSync(rule.sourcePath, ruleFile);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// If source path doesn't exist (shouldn't happen), write content directly
|
|
42
|
+
const mdcContent = `---\n${JSON.stringify(rule.metadata, null, 2)}\n---\n\n${rule.content}`;
|
|
43
|
+
fs_extra_1.default.writeFileSync(ruleFile, mdcContent);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Write rules to Windsurf's rules directory and update .windsurfrules file
|
|
49
|
+
* @param rules The rules to write
|
|
50
|
+
*/
|
|
51
|
+
function writeWindsurfRulesFromCollection(rules) {
|
|
52
|
+
const idePaths = (0, rule_status_1.getIdePaths)();
|
|
53
|
+
const ruleDir = idePaths.windsurf;
|
|
54
|
+
fs_extra_1.default.ensureDirSync(ruleDir);
|
|
55
|
+
// First write individual rule files
|
|
56
|
+
const ruleFiles = rules.map((rule) => {
|
|
57
|
+
const ruleFile = node_path_1.default.join(ruleDir, `${rule.name}.md`);
|
|
58
|
+
fs_extra_1.default.writeFileSync(ruleFile, rule.content);
|
|
59
|
+
return {
|
|
60
|
+
name: rule.name,
|
|
61
|
+
path: `.rules/${rule.name}.md`,
|
|
62
|
+
metadata: rule.metadata,
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
// Then generate and write the .windsurfrules file
|
|
66
|
+
const windsurfRulesContent = (0, windsurf_writer_1.generateWindsurfRulesContent)(ruleFiles);
|
|
67
|
+
(0, windsurf_writer_1.writeWindsurfRules)(windsurfRulesContent);
|
|
68
|
+
}
|
|
@@ -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 writeWindsurfRules(rulesContent: string, rulesFilePath?: string): void;
|
|
8
|
+
/**
|
|
9
|
+
* Generate the Windsurf rules content based on rule files
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateWindsurfRulesContent(ruleFiles: {
|
|
12
|
+
name: string;
|
|
13
|
+
path: string;
|
|
14
|
+
metadata: Record<string, string | boolean | string[]>;
|
|
15
|
+
}[]): string;
|