aicm 0.8.0 → 0.9.1
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 +2 -2
- package/dist/commands/install.js +6 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/utils/config.d.ts +19 -6
- package/dist/utils/config.js +125 -38
- package/dist/utils/rule-writer.js +53 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,12 +37,12 @@ In your project's `aicm.json`, reference the package and the specific rule:
|
|
|
37
37
|
}
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
2. **Add a
|
|
40
|
+
2. **Add a prepare script** to your `package.json`:
|
|
41
41
|
|
|
42
42
|
```json
|
|
43
43
|
{
|
|
44
44
|
"scripts": {
|
|
45
|
-
"
|
|
45
|
+
"prepare": "npx -y aicm install"
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
```
|
package/dist/commands/install.js
CHANGED
|
@@ -108,6 +108,8 @@ async function install(options = {}) {
|
|
|
108
108
|
const ruleType = (0, rule_detector_1.detectRuleType)(source);
|
|
109
109
|
// Get the base path of the preset file if this rule came from a preset
|
|
110
110
|
const ruleBasePath = (0, config_1.getRuleSource)(config, name);
|
|
111
|
+
// Get the original preset path for namespacing
|
|
112
|
+
const originalPresetPath = (0, config_1.getOriginalPresetPath)(config, name);
|
|
111
113
|
// Collect the rule based on its type
|
|
112
114
|
try {
|
|
113
115
|
let ruleContent;
|
|
@@ -122,6 +124,10 @@ async function install(options = {}) {
|
|
|
122
124
|
errorMessages.push(`Unknown rule type: ${ruleType}`);
|
|
123
125
|
continue;
|
|
124
126
|
}
|
|
127
|
+
// Add the preset path to the rule content for namespacing
|
|
128
|
+
if (originalPresetPath) {
|
|
129
|
+
ruleContent.presetPath = originalPresetPath;
|
|
130
|
+
}
|
|
125
131
|
(0, rule_collector_1.addRuleToCollection)(ruleCollection, ruleContent, config.ides);
|
|
126
132
|
installedRuleCount++;
|
|
127
133
|
}
|
package/dist/types/index.d.ts
CHANGED
package/dist/utils/config.d.ts
CHANGED
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
import { Config, Rules } from "../types";
|
|
2
|
-
interface
|
|
3
|
-
|
|
2
|
+
export interface RuleMetadata {
|
|
3
|
+
ruleSources: Record<string, string>;
|
|
4
|
+
originalPresetPaths: Record<string, string>;
|
|
4
5
|
}
|
|
5
|
-
export
|
|
6
|
+
export interface ConfigResult {
|
|
7
|
+
config: Config;
|
|
8
|
+
metadata: RuleMetadata;
|
|
9
|
+
}
|
|
10
|
+
export interface PresetPathInfo {
|
|
11
|
+
fullPath: string;
|
|
12
|
+
originalPath: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function getFullPresetPath(presetPath: string): PresetPathInfo | null;
|
|
6
15
|
/**
|
|
7
|
-
* Load a preset file and return its
|
|
16
|
+
* Load a preset file and return its contents
|
|
8
17
|
*/
|
|
9
18
|
export declare function loadPreset(presetPath: string): {
|
|
10
19
|
rules: Rules;
|
|
11
20
|
mcpServers?: import("../types").MCPServers;
|
|
21
|
+
presets?: string[];
|
|
12
22
|
} | null;
|
|
13
23
|
/**
|
|
14
24
|
* Load the aicm config using cosmiconfigSync, supporting both aicm.json and package.json.
|
|
15
25
|
* Returns the config object or null if not found.
|
|
16
26
|
*/
|
|
17
|
-
export declare function loadAicmConfigCosmiconfig():
|
|
27
|
+
export declare function loadAicmConfigCosmiconfig(): Config | null;
|
|
18
28
|
/**
|
|
19
29
|
* Get the configuration from aicm.json or package.json (using cosmiconfigSync) and merge with any presets
|
|
20
30
|
*/
|
|
@@ -23,8 +33,11 @@ export declare function getConfig(): Config | null;
|
|
|
23
33
|
* Get the source preset path for a rule if it came from a preset
|
|
24
34
|
*/
|
|
25
35
|
export declare function getRuleSource(config: Config, ruleName: string): string | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Get the original preset path for a rule if it came from a preset
|
|
38
|
+
*/
|
|
39
|
+
export declare function getOriginalPresetPath(config: Config, ruleName: string): string | undefined;
|
|
26
40
|
/**
|
|
27
41
|
* Save the configuration to the aicm.json file
|
|
28
42
|
*/
|
|
29
43
|
export declare function saveConfig(config: Config): boolean;
|
|
30
|
-
export {};
|
package/dist/utils/config.js
CHANGED
|
@@ -8,43 +8,66 @@ exports.loadPreset = loadPreset;
|
|
|
8
8
|
exports.loadAicmConfigCosmiconfig = loadAicmConfigCosmiconfig;
|
|
9
9
|
exports.getConfig = getConfig;
|
|
10
10
|
exports.getRuleSource = getRuleSource;
|
|
11
|
+
exports.getOriginalPresetPath = getOriginalPresetPath;
|
|
11
12
|
exports.saveConfig = saveConfig;
|
|
12
13
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
13
14
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
15
|
const cosmiconfig_1 = require("cosmiconfig");
|
|
15
16
|
const CONFIG_FILE = "aicm.json";
|
|
16
17
|
function getFullPresetPath(presetPath) {
|
|
18
|
+
// If it's a local file with .json extension and exists, return as is
|
|
17
19
|
if (presetPath.endsWith(".json") && fs_extra_1.default.pathExistsSync(presetPath)) {
|
|
18
|
-
return presetPath;
|
|
20
|
+
return { fullPath: presetPath, originalPath: presetPath };
|
|
19
21
|
}
|
|
20
22
|
try {
|
|
21
23
|
let absolutePresetPath;
|
|
24
|
+
// Handle npm package with explicit JSON path
|
|
22
25
|
if (presetPath.endsWith(".json")) {
|
|
23
26
|
absolutePresetPath = require.resolve(presetPath, {
|
|
24
27
|
paths: [__dirname, process.cwd()],
|
|
25
28
|
});
|
|
26
29
|
}
|
|
30
|
+
// Handle npm package without explicit JSON path (add aicm.json)
|
|
27
31
|
else {
|
|
32
|
+
// For npm packages, ensure we properly handle scoped packages (@org/pkg)
|
|
28
33
|
const presetPathWithConfig = node_path_1.default.join(presetPath, "aicm.json");
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
try {
|
|
35
|
+
absolutePresetPath = require.resolve(presetPathWithConfig, {
|
|
36
|
+
paths: [__dirname, process.cwd()],
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
catch (_a) {
|
|
40
|
+
// If direct resolution fails, try as a package name
|
|
41
|
+
absolutePresetPath = require.resolve(presetPath, {
|
|
42
|
+
paths: [__dirname, process.cwd()],
|
|
43
|
+
});
|
|
44
|
+
// If we found the package but not the config file, look for aicm.json
|
|
45
|
+
if (fs_extra_1.default.existsSync(absolutePresetPath)) {
|
|
46
|
+
const packageDir = node_path_1.default.dirname(absolutePresetPath);
|
|
47
|
+
const configPath = node_path_1.default.join(packageDir, "aicm.json");
|
|
48
|
+
if (fs_extra_1.default.existsSync(configPath)) {
|
|
49
|
+
absolutePresetPath = configPath;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
32
53
|
}
|
|
33
|
-
return fs_extra_1.default.existsSync(absolutePresetPath)
|
|
54
|
+
return fs_extra_1.default.existsSync(absolutePresetPath)
|
|
55
|
+
? { fullPath: absolutePresetPath, originalPath: presetPath }
|
|
56
|
+
: null;
|
|
34
57
|
}
|
|
35
|
-
catch (
|
|
58
|
+
catch (_b) {
|
|
36
59
|
return null;
|
|
37
60
|
}
|
|
38
61
|
}
|
|
39
62
|
/**
|
|
40
|
-
* Load a preset file and return its
|
|
63
|
+
* Load a preset file and return its contents
|
|
41
64
|
*/
|
|
42
65
|
function loadPreset(presetPath) {
|
|
43
|
-
const
|
|
44
|
-
if (!
|
|
66
|
+
const pathInfo = getFullPresetPath(presetPath);
|
|
67
|
+
if (!pathInfo) {
|
|
45
68
|
throw new Error(`Error loading preset: "${presetPath}". Make sure the package is installed in your project.`);
|
|
46
69
|
}
|
|
47
|
-
const presetContent = fs_extra_1.default.readFileSync(
|
|
70
|
+
const presetContent = fs_extra_1.default.readFileSync(pathInfo.fullPath, "utf8");
|
|
48
71
|
let preset;
|
|
49
72
|
try {
|
|
50
73
|
preset = JSON.parse(presetContent);
|
|
@@ -56,67 +79,122 @@ function loadPreset(presetPath) {
|
|
|
56
79
|
if (!preset.rules || typeof preset.rules !== "object") {
|
|
57
80
|
throw new Error(`Error loading preset: Invalid format in ${presetPath} - missing or invalid 'rules' object`);
|
|
58
81
|
}
|
|
59
|
-
return {
|
|
82
|
+
return {
|
|
83
|
+
rules: preset.rules,
|
|
84
|
+
mcpServers: preset.mcpServers,
|
|
85
|
+
presets: preset.presets,
|
|
86
|
+
};
|
|
60
87
|
}
|
|
88
|
+
// Global metadata storage
|
|
89
|
+
let currentMetadata = null;
|
|
90
|
+
// Track processed presets to avoid circular references
|
|
91
|
+
const processedPresets = new Set();
|
|
61
92
|
/**
|
|
62
|
-
* Process presets and
|
|
93
|
+
* Process presets and return a new config with merged rules and metadata
|
|
63
94
|
*/
|
|
64
95
|
function processPresets(config) {
|
|
96
|
+
// Create a deep copy of the config to avoid mutations
|
|
97
|
+
const newConfig = JSON.parse(JSON.stringify(config));
|
|
98
|
+
const metadata = {
|
|
99
|
+
ruleSources: {},
|
|
100
|
+
originalPresetPaths: {},
|
|
101
|
+
};
|
|
102
|
+
// Clear processed presets tracking set when starting from the top level
|
|
103
|
+
processedPresets.clear();
|
|
104
|
+
return processPresetsInternal(newConfig, metadata);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Internal function to process presets recursively
|
|
108
|
+
*/
|
|
109
|
+
function processPresetsInternal(config, metadata) {
|
|
65
110
|
if (!config.presets || !Array.isArray(config.presets)) {
|
|
66
|
-
return;
|
|
111
|
+
return { config, metadata };
|
|
67
112
|
}
|
|
68
113
|
for (const presetPath of config.presets) {
|
|
114
|
+
const pathInfo = getFullPresetPath(presetPath);
|
|
115
|
+
if (!pathInfo) {
|
|
116
|
+
throw new Error(`Error loading preset: "${presetPath}". Make sure the package is installed in your project.`);
|
|
117
|
+
}
|
|
118
|
+
// Skip if we've already processed this preset (prevents circular references)
|
|
119
|
+
if (processedPresets.has(pathInfo.fullPath)) {
|
|
120
|
+
console.warn(`Skipping already processed preset: ${presetPath}`);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
// Mark this preset as processed
|
|
124
|
+
processedPresets.add(pathInfo.fullPath);
|
|
69
125
|
const preset = loadPreset(presetPath);
|
|
70
126
|
if (!preset)
|
|
71
127
|
continue;
|
|
72
|
-
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
|
|
128
|
+
// Process nested presets first (depth-first)
|
|
129
|
+
if (preset.presets && preset.presets.length > 0) {
|
|
130
|
+
// Create a temporary config with just the presets from this preset
|
|
131
|
+
const presetConfig = {
|
|
132
|
+
rules: {},
|
|
133
|
+
presets: preset.presets,
|
|
134
|
+
ides: [],
|
|
135
|
+
};
|
|
136
|
+
// Recursively process the nested presets
|
|
137
|
+
const { config: nestedConfig } = processPresetsInternal(presetConfig, metadata);
|
|
138
|
+
Object.assign(preset.rules, nestedConfig.rules);
|
|
139
|
+
}
|
|
140
|
+
const { updatedConfig, updatedMetadata } = mergePresetRules(config, preset.rules, pathInfo, metadata);
|
|
141
|
+
Object.assign(config.rules, updatedConfig.rules);
|
|
142
|
+
Object.assign(metadata.ruleSources, updatedMetadata.ruleSources);
|
|
143
|
+
Object.assign(metadata.originalPresetPaths, updatedMetadata.originalPresetPaths);
|
|
76
144
|
if (preset.mcpServers) {
|
|
77
|
-
mergePresetMcpServers(config, preset.mcpServers);
|
|
145
|
+
config.mcpServers = mergePresetMcpServers(config.mcpServers || {}, preset.mcpServers);
|
|
78
146
|
}
|
|
79
147
|
}
|
|
148
|
+
return { config, metadata };
|
|
80
149
|
}
|
|
81
150
|
/**
|
|
82
|
-
* Merge preset rules into the config
|
|
151
|
+
* Merge preset rules into the config without mutation
|
|
83
152
|
*/
|
|
84
|
-
function mergePresetRules(config, presetRules,
|
|
153
|
+
function mergePresetRules(config, presetRules, pathInfo, metadata) {
|
|
154
|
+
const updatedRules = { ...config.rules };
|
|
155
|
+
const updatedMetadata = {
|
|
156
|
+
ruleSources: { ...metadata.ruleSources },
|
|
157
|
+
originalPresetPaths: { ...metadata.originalPresetPaths },
|
|
158
|
+
};
|
|
85
159
|
for (const [ruleName, rulePath] of Object.entries(presetRules)) {
|
|
86
160
|
// Cancel if set to false in config
|
|
87
161
|
if (Object.prototype.hasOwnProperty.call(config.rules, ruleName) &&
|
|
88
162
|
config.rules[ruleName] === false) {
|
|
89
|
-
delete
|
|
90
|
-
|
|
91
|
-
|
|
163
|
+
delete updatedRules[ruleName];
|
|
164
|
+
delete updatedMetadata.ruleSources[ruleName];
|
|
165
|
+
delete updatedMetadata.originalPresetPaths[ruleName];
|
|
92
166
|
continue;
|
|
93
167
|
}
|
|
94
168
|
// Only add if not already defined in config (override handled by config)
|
|
95
169
|
if (!Object.prototype.hasOwnProperty.call(config.rules, ruleName)) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
170
|
+
updatedRules[ruleName] = rulePath;
|
|
171
|
+
updatedMetadata.ruleSources[ruleName] = pathInfo.fullPath;
|
|
172
|
+
updatedMetadata.originalPresetPaths[ruleName] = pathInfo.originalPath;
|
|
99
173
|
}
|
|
100
174
|
}
|
|
175
|
+
return {
|
|
176
|
+
updatedConfig: { ...config, rules: updatedRules },
|
|
177
|
+
updatedMetadata,
|
|
178
|
+
};
|
|
101
179
|
}
|
|
102
180
|
/**
|
|
103
|
-
* Merge preset mcpServers
|
|
181
|
+
* Merge preset mcpServers without mutation
|
|
104
182
|
*/
|
|
105
|
-
function mergePresetMcpServers(
|
|
106
|
-
|
|
107
|
-
config.mcpServers = {};
|
|
183
|
+
function mergePresetMcpServers(configMcpServers, presetMcpServers) {
|
|
184
|
+
const newMcpServers = { ...configMcpServers };
|
|
108
185
|
for (const [serverName, serverConfig] of Object.entries(presetMcpServers)) {
|
|
109
186
|
// Cancel if set to false in config
|
|
110
|
-
if (Object.prototype.hasOwnProperty.call(
|
|
111
|
-
|
|
112
|
-
delete
|
|
187
|
+
if (Object.prototype.hasOwnProperty.call(newMcpServers, serverName) &&
|
|
188
|
+
newMcpServers[serverName] === false) {
|
|
189
|
+
delete newMcpServers[serverName];
|
|
113
190
|
continue;
|
|
114
191
|
}
|
|
115
192
|
// Only add if not already defined in config (override handled by config)
|
|
116
|
-
if (!Object.prototype.hasOwnProperty.call(
|
|
117
|
-
|
|
193
|
+
if (!Object.prototype.hasOwnProperty.call(newMcpServers, serverName)) {
|
|
194
|
+
newMcpServers[serverName] = serverConfig;
|
|
118
195
|
}
|
|
119
196
|
}
|
|
197
|
+
return newMcpServers;
|
|
120
198
|
}
|
|
121
199
|
/**
|
|
122
200
|
* Load the aicm config using cosmiconfigSync, supporting both aicm.json and package.json.
|
|
@@ -149,15 +227,24 @@ function getConfig() {
|
|
|
149
227
|
if (!config) {
|
|
150
228
|
throw new Error(`No config found in ${process.cwd()}, create one using "aicm init"`);
|
|
151
229
|
}
|
|
152
|
-
processPresets(config);
|
|
153
|
-
|
|
230
|
+
const { config: processedConfig, metadata } = processPresets(config);
|
|
231
|
+
// Store metadata for later access
|
|
232
|
+
currentMetadata = metadata;
|
|
233
|
+
return processedConfig;
|
|
154
234
|
}
|
|
155
235
|
/**
|
|
156
236
|
* Get the source preset path for a rule if it came from a preset
|
|
157
237
|
*/
|
|
158
238
|
function getRuleSource(config, ruleName) {
|
|
159
239
|
var _a;
|
|
160
|
-
return (_a =
|
|
240
|
+
return (_a = currentMetadata === null || currentMetadata === void 0 ? void 0 : currentMetadata.ruleSources) === null || _a === void 0 ? void 0 : _a[ruleName];
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Get the original preset path for a rule if it came from a preset
|
|
244
|
+
*/
|
|
245
|
+
function getOriginalPresetPath(config, ruleName) {
|
|
246
|
+
var _a;
|
|
247
|
+
return (_a = currentMetadata === null || currentMetadata === void 0 ? void 0 : currentMetadata.originalPresetPaths) === null || _a === void 0 ? void 0 : _a[ruleName];
|
|
161
248
|
}
|
|
162
249
|
/**
|
|
163
250
|
* Save the configuration to the aicm.json file
|
|
@@ -23,6 +23,21 @@ function writeRulesToTargets(collection) {
|
|
|
23
23
|
writeWindsurfRulesFromCollection(collection.windsurf, idePaths.windsurf);
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Extract a normalized namespace from a preset path
|
|
28
|
+
* @param presetPath The original preset path
|
|
29
|
+
* @returns An array of path segments to use for namespacing
|
|
30
|
+
*/
|
|
31
|
+
function extractNamespaceFromPresetPath(presetPath) {
|
|
32
|
+
// Special case: npm package names always use forward slashes, regardless of platform
|
|
33
|
+
if (presetPath.startsWith("@")) {
|
|
34
|
+
// For scoped packages like @scope/package/subdir, create nested directories
|
|
35
|
+
return presetPath.split("/");
|
|
36
|
+
}
|
|
37
|
+
// Handle both Unix and Windows style path separators
|
|
38
|
+
const parts = presetPath.split(/[/\\]/);
|
|
39
|
+
return parts.filter((part) => part.length > 0); // Filter out empty segments
|
|
40
|
+
}
|
|
26
41
|
/**
|
|
27
42
|
* Write rules to Cursor's rules directory
|
|
28
43
|
* @param rules The rules to write
|
|
@@ -31,7 +46,20 @@ function writeRulesToTargets(collection) {
|
|
|
31
46
|
function writeCursorRules(rules, cursorRulesDir) {
|
|
32
47
|
fs_extra_1.default.emptyDirSync(cursorRulesDir);
|
|
33
48
|
for (const rule of rules) {
|
|
34
|
-
|
|
49
|
+
let rulePath;
|
|
50
|
+
// Parse rule name into path segments using platform-specific path separator
|
|
51
|
+
const ruleNameParts = rule.name.split(/[/\\]/).filter(Boolean);
|
|
52
|
+
if (rule.presetPath) {
|
|
53
|
+
// For rules from presets, create a namespaced directory structure
|
|
54
|
+
const namespace = extractNamespaceFromPresetPath(rule.presetPath);
|
|
55
|
+
// Path will be: cursorRulesDir/namespace/rule-name.mdc
|
|
56
|
+
rulePath = node_path_1.default.join(cursorRulesDir, ...namespace, ...ruleNameParts);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
// For local rules, maintain the original flat structure
|
|
60
|
+
rulePath = node_path_1.default.join(cursorRulesDir, ...ruleNameParts);
|
|
61
|
+
}
|
|
62
|
+
const ruleFile = rulePath + ".mdc";
|
|
35
63
|
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(ruleFile));
|
|
36
64
|
if (fs_extra_1.default.existsSync(rule.sourcePath)) {
|
|
37
65
|
fs_extra_1.default.copyFileSync(rule.sourcePath, ruleFile);
|
|
@@ -49,11 +77,33 @@ function writeCursorRules(rules, cursorRulesDir) {
|
|
|
49
77
|
function writeWindsurfRulesFromCollection(rules, ruleDir) {
|
|
50
78
|
fs_extra_1.default.emptyDirSync(ruleDir);
|
|
51
79
|
const ruleFiles = rules.map((rule) => {
|
|
52
|
-
|
|
80
|
+
let rulePath;
|
|
81
|
+
// Parse rule name into path segments using platform-specific path separator
|
|
82
|
+
const ruleNameParts = rule.name.split(/[/\\]/).filter(Boolean);
|
|
83
|
+
if (rule.presetPath) {
|
|
84
|
+
// For rules from presets, create a namespaced directory structure
|
|
85
|
+
const namespace = extractNamespaceFromPresetPath(rule.presetPath);
|
|
86
|
+
// Path will be: ruleDir/namespace/rule-name.md
|
|
87
|
+
rulePath = node_path_1.default.join(ruleDir, ...namespace, ...ruleNameParts);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
// For local rules, maintain the original flat structure
|
|
91
|
+
rulePath = node_path_1.default.join(ruleDir, ...ruleNameParts);
|
|
92
|
+
}
|
|
93
|
+
const physicalRulePath = rulePath + ".md";
|
|
53
94
|
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(physicalRulePath));
|
|
54
95
|
fs_extra_1.default.writeFileSync(physicalRulePath, rule.content);
|
|
55
96
|
const relativeRuleDir = node_path_1.default.basename(ruleDir); // Gets '.rules'
|
|
56
|
-
|
|
97
|
+
// For the Windsurf rules file, we need to maintain the same structure
|
|
98
|
+
let windsurfPath;
|
|
99
|
+
if (rule.presetPath) {
|
|
100
|
+
const namespace = extractNamespaceFromPresetPath(rule.presetPath);
|
|
101
|
+
windsurfPath =
|
|
102
|
+
node_path_1.default.join(relativeRuleDir, ...namespace, ...ruleNameParts) + ".md";
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
windsurfPath = node_path_1.default.join(relativeRuleDir, ...ruleNameParts) + ".md";
|
|
106
|
+
}
|
|
57
107
|
// Normalize to POSIX style for cross-platform compatibility in .windsurfrules
|
|
58
108
|
const windsurfPathPosix = windsurfPath.replace(/\\/g, "/");
|
|
59
109
|
return {
|