rulesync 0.40.0 → 0.41.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.ja.md CHANGED
@@ -164,14 +164,14 @@ rulesyncは2レベルのルールシステムを使用します:
164
164
 
165
165
  各AIツールはルールレベルを異なって処理します:
166
166
 
167
- | ツール | ルートルール | 非ルートルール | 特別な動作 |
168
- |------|------------|----------------|------------------|
169
- | **Claude Code** | `./CLAUDE.md` | `.claude/memories/*.md` | CLAUDE.mdが詳細ファイルへの`@filename`参照を含む |
170
- | **Cursor** | `ruletype: always` | `ruletype: autoattached` | globsのない詳細ルールは`ruletype: agentrequested`を使用 |
171
- | **GitHub Copilot** | 標準フォーマット | 標準フォーマット | すべてのルールがフロントマター付きの同じフォーマットを使用 |
172
- | **Cline** | 標準フォーマット | 標準フォーマット | すべてのルールがプレーンMarkdownフォーマットを使用 |
173
- | **Roo Code** | 標準フォーマット | 標準フォーマット | すべてのルールが説明ヘッダー付きのプレーンMarkdownフォーマットを使用 |
174
- | **Gemini CLI** | `GEMINI.md` | `.gemini/memories/*.md` | GEMINI.mdがメモリファイルへの`@filename`参照を含む |
167
+ | ツール | ルートルール | 非ルートルール | 特別な動作 |
168
+ | ------------------ | ------------------ | ------------------------ | -------------------------------------------------------------------- |
169
+ | **Claude Code** | `./CLAUDE.md` | `.claude/memories/*.md` | CLAUDE.mdが詳細ファイルへの`@filename`参照を含む |
170
+ | **Cursor** | `cursorRuleType: always` | `cursorRuleType: specificFiles` (globs指定時)<br>`cursorRuleType: intelligently` (description指定時)<br>`cursorRuleType: manual` (デフォルト) | コンテンツ解析に基づく高度なルールタイプシステム |
171
+ | **GitHub Copilot** | 標準フォーマット | 標準フォーマット | すべてのルールがフロントマター付きの同じフォーマットを使用 |
172
+ | **Cline** | 標準フォーマット | 標準フォーマット | すべてのルールがプレーンMarkdownフォーマットを使用 |
173
+ | **Roo Code** | 標準フォーマット | 標準フォーマット | すべてのルールが説明ヘッダー付きのプレーンMarkdownフォーマットを使用 |
174
+ | **Gemini CLI** | `GEMINI.md` | `.gemini/memories/*.md` | GEMINI.mdがメモリファイルへの`@filename`参照を含む |
175
175
 
176
176
  ### 3. 設定ファイルの生成
177
177
 
@@ -241,6 +241,34 @@ importコマンドの動作:
241
241
  - YAMLフロントマター付きのCursorのMDCファイルなど複雑なフォーマットをサポート
242
242
  - 複数ファイルのインポート(例:`.claude/memories/`ディレクトリのすべてのファイル)に対応
243
243
 
244
+ ### Cursorインポートの詳細
245
+
246
+ Cursorからのインポートでは、以下の4つのルールタイプが自動的に識別されます:
247
+
248
+ 1. **always** (`cursorRuleType: always`)
249
+ - 条件: `alwaysApply: true` が設定されている場合
250
+ - 変換: ルートルール(`root: false`)としてインポート、`globs: ["**/*"]`を設定
251
+
252
+ 2. **manual** (`cursorRuleType: manual`)
253
+ - 条件: description空 + globs空 + `alwaysApply: false`
254
+ - 変換: 空のglobsパターンでインポート(手動適用ルール)
255
+
256
+ 3. **specificFiles** (`cursorRuleType: specificFiles`)
257
+ - 条件: globs指定あり(description有無問わず)
258
+ - 変換: 指定されたglobsパターンを配列として保持、descriptionは空文字に設定
259
+
260
+ 4. **intelligently** (`cursorRuleType: intelligently`)
261
+ - 条件: description指定あり + globs空
262
+ - 変換: descriptionを保持、空のglobsパターンを設定
263
+
264
+ #### エッジケース処理
265
+ - **description非空 + globs非空の場合**: `specificFiles`として処理(globsパターンを優先)
266
+ - **判定条件に該当しない場合**: `manual`として処理(デフォルト)
267
+
268
+ #### Cursorのサポートファイル
269
+ - `.cursor/rules/*.mdc` (モダンな推奨形式)
270
+ - `.cursorrules` (レガシーな形式)
271
+
244
272
  ### 5. その他のコマンド
245
273
 
246
274
  ```bash
@@ -314,9 +342,19 @@ root: true | false # 必須: ルールレベル (概要の場合tr
314
342
  targets: ["*"] # 必須: ターゲットツール (* = すべて、または特定のツール)
315
343
  description: "簡潔な説明" # 必須: ルールの説明
316
344
  globs: "**/*.ts,**/*.js" # 必須: ファイルパターン (カンマ区切りまたは空文字列)
345
+ cursorRuleType: "always" # オプション: Cursor固有のルールタイプ (always, manual, specificFiles, intelligently)
317
346
  ---
318
347
  ```
319
348
 
349
+ #### cursorRuleTypeフィールド (オプション)
350
+
351
+ Cursorツール用の追加メタデータフィールド:
352
+
353
+ - **`always`**: プロジェクト全体に常に適用されるルール
354
+ - **`manual`**: 手動で適用するルール(デフォルト)
355
+ - **`specificFiles`**: 特定のファイルパターンに自動適用されるルール
356
+ - **`intelligently`**: AIが判断して適用するルール
357
+
320
358
  ### ファイル例
321
359
 
322
360
  **ルートファイル** (`.rulesync/overview.md`):
@@ -351,14 +389,14 @@ globs: "**/*.ts,**/*.tsx"
351
389
 
352
390
  ## 生成される設定ファイル
353
391
 
354
- | ツール | 出力パス | フォーマット | ルールレベル処理 |
355
- |------|------------|--------|-------------------|
356
- | **GitHub Copilot** | `.github/instructions/*.instructions.md` | フロントマター + Markdown | 両レベルとも同じフォーマットを使用 |
357
- | **Cursor** | `.cursor/rules/*.mdc` | MDC (YAMLヘッダー + Markdown) | ルート: `ruletype: always`<br>非ルート: `ruletype: autoattached`<br>globsなしの非ルート: `ruletype: agentrequested` |
358
- | **Cline** | `.clinerules/*.md` | プレーンMarkdown | 両レベルとも同じフォーマットを使用 |
359
- | **Claude Code** | `./CLAUDE.md` (ルート)<br>`.claude/memories/*.md` (非ルート) | プレーンMarkdown | ルートはCLAUDE.mdに移動<br>非ルートは別メモリファイルに移動<br>CLAUDE.mdは`@filename`参照を含む |
360
- | **Roo Code** | `.roo/rules/*.md` | プレーンMarkdown | 両レベルとも説明ヘッダー付きの同じフォーマットを使用 |
361
- | **Gemini CLI** | `GEMINI.md` (ルート)<br>`.gemini/memories/*.md` (非ルート) | プレーンMarkdown | ルートはGEMINI.mdに移動<br>非ルートは別メモリファイルに移動<br>GEMINI.mdは`@filename`参照を含む |
392
+ | ツール | 出力パス | フォーマット | ルールレベル処理 |
393
+ | ------------------ | ------------------------------------------------------------ | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
394
+ | **GitHub Copilot** | `.github/instructions/*.instructions.md` | フロントマター + Markdown | 両レベルとも同じフォーマットを使用 |
395
+ | **Cursor** | `.cursor/rules/*.mdc` | MDC (YAMLヘッダー + Markdown) | ルート: `cursorRuleType: always`<br>非ルート: `cursorRuleType: specificFiles` (globs指定時)<br>非ルート: `cursorRuleType: intelligently` (description指定時)<br>非ルート: `cursorRuleType: manual` (デフォルト) |
396
+ | **Cline** | `.clinerules/*.md` | プレーンMarkdown | 両レベルとも同じフォーマットを使用 |
397
+ | **Claude Code** | `./CLAUDE.md` (ルート)<br>`.claude/memories/*.md` (非ルート) | プレーンMarkdown | ルートはCLAUDE.mdに移動<br>非ルートは別メモリファイルに移動<br>CLAUDE.mdは`@filename`参照を含む |
398
+ | **Roo Code** | `.roo/rules/*.md` | プレーンMarkdown | 両レベルとも説明ヘッダー付きの同じフォーマットを使用 |
399
+ | **Gemini CLI** | `GEMINI.md` (ルート)<br>`.gemini/memories/*.md` (非ルート) | プレーンMarkdown | ルートはGEMINI.mdに移動<br>非ルートは別メモリファイルに移動<br>GEMINI.mdは`@filename`参照を含む |
362
400
 
363
401
  ## バリデーション
364
402
 
@@ -402,7 +440,7 @@ rulesyncは、対応するAIツール用のMCPサーバー設定も管理でき
402
440
  "ghcr.io/github/github-mcp-server"
403
441
  ],
404
442
  "env": {},
405
- "rulesyncTargets": ["*"]
443
+ "targets": ["*"]
406
444
  }
407
445
  }
408
446
  }
@@ -415,7 +453,7 @@ rulesyncは、対応するAIツール用のMCPサーバー設定も管理でき
415
453
  - **`args`**: コマンド引数
416
454
  - **`url`**: HTTP/SSEベースのサーバー用URL
417
455
  - **`env`**: サーバーに渡す環境変数
418
- - **`rulesyncTargets`**: このサーバーをデプロイするツール名の配列
456
+ - **`targets`**: このサーバーをデプロイするツール名の配列
419
457
  - 特定のツール名を使用: `["claude", "cursor", "copilot"]`
420
458
  - すべてのサポートツールにデプロイするには`["*"]`を使用
421
459
  - 省略した場合、デフォルトですべてのツールにデプロイ
@@ -445,4 +483,4 @@ MIT License
445
483
 
446
484
  Issues と Pull Requests を歓迎します!
447
485
 
448
- 開発環境の設定と貢献ガイドラインについては、[CONTRIBUTING.ja.md](./CONTRIBUTING.ja.md)を参照してください。
486
+ 開発環境の設定と貢献ガイドラインについては、[CONTRIBUTING.ja.md](./CONTRIBUTING.ja.md)を参照してください。
package/README.md CHANGED
@@ -167,7 +167,7 @@ Each AI tool handles rule levels differently:
167
167
  | Tool | Root Rules | Non-Root Rules | Special Behavior |
168
168
  |------|------------|----------------|------------------|
169
169
  | **Claude Code** | `./CLAUDE.md` | `.claude/memories/*.md` | CLAUDE.md includes `@filename` references to detail files |
170
- | **Cursor** | `ruletype: always` | `ruletype: autoattached` | Detail rules without globs use `ruletype: agentrequested` |
170
+ | **Cursor** | `cursorRuleType: always` | `cursorRuleType: specificFiles` (with globs)<br>`cursorRuleType: intelligently` (with description)<br>`cursorRuleType: manual` (default) | Advanced rule type system based on content analysis |
171
171
  | **GitHub Copilot** | Standard format | Standard format | All rules use same format with frontmatter |
172
172
  | **Cline** | Standard format | Standard format | All rules use plain Markdown format |
173
173
  | **Roo Code** | Standard format | Standard format | All rules use plain Markdown format with description header |
@@ -241,6 +241,36 @@ The import command will:
241
241
  - Support complex formats like Cursor's MDC files with YAML frontmatter
242
242
  - Handle multiple file imports (e.g., all files from `.claude/memories/` directory)
243
243
 
244
+ ### Cursor Import Details
245
+
246
+ When importing from Cursor, the following four rule types are automatically identified:
247
+
248
+ 1. **always** (`cursorRuleType: always`)
249
+ - Condition: `alwaysApply: true` is set
250
+ - Conversion: Imported as root rule (`root: false`), with `globs: ["**/*"]` set
251
+
252
+ 2. **manual** (`cursorRuleType: manual`)
253
+ - Condition: empty description + empty globs + `alwaysApply: false`
254
+ - Conversion: Imported with empty globs patterns (manual application rule)
255
+
256
+ 3. **specificFiles** (`cursorRuleType: specificFiles`)
257
+ - Condition: globs specified (regardless of description)
258
+ - Conversion: Specified globs patterns preserved as array, description set to empty string
259
+
260
+ 4. **intelligently** (`cursorRuleType: intelligently`)
261
+ - Condition: description specified + empty globs
262
+ - Conversion: Description preserved, empty globs patterns set
263
+
264
+ #### Edge Case Handling
265
+ - **Non-empty description + non-empty globs**: Processed as `specificFiles` (globs patterns take priority)
266
+ - **No matching conditions**: Processed as `manual` (default)
267
+
268
+ #### Supported Files
269
+ - `.cursorrules` (legacy format)
270
+ - `.cursor/rules/*.mdc` (modern MDC format)
271
+ - `.cursorignore` (ignore patterns)
272
+ - `.cursor/mcp.json` (MCP server configuration)
273
+
244
274
  ### 5. Other Commands
245
275
 
246
276
  ```bash
@@ -314,9 +344,19 @@ root: true | false # Required: Rule level (true for overview, fals
314
344
  targets: ["*"] # Required: Target tools (* = all, or specific tools)
315
345
  description: "Brief description" # Required: Rule description
316
346
  globs: "**/*.ts,**/*.js" # Required: File patterns (comma-separated or empty string)
347
+ cursorRuleType: "always" # Optional: Cursor-specific rule type (always, manual, specificFiles, intelligently)
317
348
  ---
318
349
  ```
319
350
 
351
+ #### cursorRuleType Field (Optional)
352
+
353
+ Additional metadata field for Cursor tool:
354
+
355
+ - **`always`**: Rules applied to the entire project constantly
356
+ - **`manual`**: Rules applied manually (default)
357
+ - **`specificFiles`**: Rules automatically applied to specific file patterns
358
+ - **`intelligently`**: Rules applied by AI judgment
359
+
320
360
  ### Example Files
321
361
 
322
362
  **Root file** (`.rulesync/overview.md`):
@@ -354,7 +394,7 @@ globs: "**/*.ts,**/*.tsx"
354
394
  | Tool | Output Path | Format | Rule Level Handling |
355
395
  |------|------------|--------|-------------------|
356
396
  | **GitHub Copilot** | `.github/instructions/*.instructions.md` | Front Matter + Markdown | Both levels use same format |
357
- | **Cursor** | `.cursor/rules/*.mdc` | MDC (YAML header + Markdown) | Root: `ruletype: always`<br>Non-root: `ruletype: autoattached`<br>Non-root without globs: `ruletype: agentrequested` |
397
+ | **Cursor** | `.cursor/rules/*.mdc` | MDC (YAML header + Markdown) | Root: `cursorRuleType: always`<br>Non-root: `cursorRuleType: specificFiles` (with globs)<br>Non-root: `cursorRuleType: intelligently` (with description)<br>Non-root: `cursorRuleType: manual` (default) |
358
398
  | **Cline** | `.clinerules/*.md` | Plain Markdown | Both levels use same format |
359
399
  | **Claude Code** | `./CLAUDE.md` (root)<br>`.claude/memories/*.md` (non-root) | Plain Markdown | Root goes to CLAUDE.md<br>Non-root go to separate memory files<br>CLAUDE.md includes `@filename` references |
360
400
  | **Roo Code** | `.roo/rules/*.md` | Plain Markdown | Both levels use same format with description header |
@@ -402,7 +442,7 @@ Create a `.rulesync/.mcp.json` file in your project:
402
442
  "ghcr.io/github/github-mcp-server"
403
443
  ],
404
444
  "env": {},
405
- "rulesyncTargets": ["*"]
445
+ "targets": ["*"]
406
446
  }
407
447
  }
408
448
  }
@@ -415,7 +455,7 @@ Create a `.rulesync/.mcp.json` file in your project:
415
455
  - **`args`**: Command arguments
416
456
  - **`url`**: URL for HTTP/SSE-based servers
417
457
  - **`env`**: Environment variables to pass to the server
418
- - **`rulesyncTargets`**: Array of tool names to deploy this server to
458
+ - **`targets`**: Array of tool names to deploy this server to
419
459
  - Use specific tool names: `["claude", "cursor", "copilot"]`
420
460
  - Use `["*"]` to deploy to all supported tools
421
461
  - If omitted, server is deployed to all tools by default
@@ -61,6 +61,10 @@ function generateRooMcpConfiguration(mcpServers, baseDir = "") {
61
61
  continue;
62
62
  }
63
63
  const { targets: _targets, ...serverConfig } = serverObj;
64
+ if (serverConfig.httpUrl && serverConfig.url) {
65
+ serverConfig.url = serverConfig.httpUrl;
66
+ delete serverConfig.httpUrl;
67
+ }
64
68
  config.mcpServers[serverName] = serverConfig;
65
69
  }
66
70
  return [
package/dist/index.cjs CHANGED
@@ -773,24 +773,56 @@ async function generateCursorConfig(rules, config, baseDir) {
773
773
  }
774
774
  function generateCursorMarkdown(rule) {
775
775
  const lines = [];
776
+ const ruleType = determineCursorRuleType(rule.frontmatter);
776
777
  lines.push("---");
777
- lines.push(`description: ${rule.frontmatter.description}`);
778
- if (rule.frontmatter.globs.length > 0) {
779
- lines.push(`globs: ${rule.frontmatter.globs.join(",")}`);
780
- }
781
- let ruletype;
782
- if (rule.frontmatter.root === true) {
783
- ruletype = "always";
784
- } else if (rule.frontmatter.root === false && rule.frontmatter.globs.length === 0) {
785
- ruletype = "agentrequested";
786
- } else {
787
- ruletype = "autoattached";
778
+ switch (ruleType) {
779
+ case "always":
780
+ lines.push("description:");
781
+ lines.push("globs:");
782
+ lines.push("alwaysApply: true");
783
+ break;
784
+ case "manual":
785
+ lines.push("description:");
786
+ lines.push("globs:");
787
+ lines.push("alwaysApply: false");
788
+ break;
789
+ case "specificFiles":
790
+ lines.push("description:");
791
+ lines.push(`globs: ${rule.frontmatter.globs.join(",")}`);
792
+ lines.push("alwaysApply: false");
793
+ break;
794
+ case "intelligently":
795
+ lines.push(`description: ${rule.frontmatter.description}`);
796
+ lines.push("globs:");
797
+ lines.push("alwaysApply: false");
798
+ break;
788
799
  }
789
- lines.push(`ruletype: ${ruletype}`);
790
800
  lines.push("---");
801
+ lines.push("");
791
802
  lines.push(rule.content);
792
803
  return lines.join("\n");
793
804
  }
805
+ function determineCursorRuleType(frontmatter) {
806
+ if (frontmatter.cursorRuleType) {
807
+ return frontmatter.cursorRuleType;
808
+ }
809
+ const isDescriptionEmpty = !frontmatter.description || frontmatter.description.trim() === "";
810
+ const isGlobsEmpty = frontmatter.globs.length === 0;
811
+ const isGlobsExactlyAllFiles = frontmatter.globs.length === 1 && frontmatter.globs[0] === "**/*";
812
+ if (isGlobsExactlyAllFiles) {
813
+ return "always";
814
+ }
815
+ if (isDescriptionEmpty && isGlobsEmpty) {
816
+ return "manual";
817
+ }
818
+ if (isDescriptionEmpty && !isGlobsEmpty) {
819
+ return "specificFiles";
820
+ }
821
+ if (!isDescriptionEmpty && isGlobsEmpty) {
822
+ return "intelligently";
823
+ }
824
+ return "intelligently";
825
+ }
794
826
  function generateCursorIgnore(patterns) {
795
827
  const lines = [
796
828
  "# Generated by rulesync from .rulesyncignore",
@@ -1053,9 +1085,9 @@ function validateFrontmatter(data, filepath) {
1053
1085
  `Missing required field "description" in ${filepath}: must be a descriptive string`
1054
1086
  );
1055
1087
  }
1056
- if (!obj.description || typeof obj.description !== "string") {
1088
+ if (typeof obj.description !== "string") {
1057
1089
  throw new Error(
1058
- `Invalid "description" field in ${filepath}: must be a non-empty string, got ${typeof obj.description}`
1090
+ `Invalid "description" field in ${filepath}: must be a string, got ${typeof obj.description}`
1059
1091
  );
1060
1092
  }
1061
1093
  if (obj.globs === void 0) {
@@ -1419,7 +1451,7 @@ var gitignoreCommand = async () => {
1419
1451
  }
1420
1452
  }
1421
1453
  if (linesToAdd.length === 0) {
1422
- console.log("\u2705 .gitignore\u306F\u65E2\u306B\u6700\u65B0\u3067\u3059");
1454
+ console.log("\u2705 .gitignore is already up to date");
1423
1455
  return;
1424
1456
  }
1425
1457
  const newContent = gitignoreContent ? `${gitignoreContent.trimEnd()}
@@ -1428,7 +1460,7 @@ ${linesToAdd.join("\n")}
1428
1460
  ` : `${linesToAdd.join("\n")}
1429
1461
  `;
1430
1462
  (0, import_node_fs2.writeFileSync)(gitignorePath, newContent);
1431
- console.log(`\u2705 .gitignore\u306B${linesToAdd.length}\u500B\u306E\u30EB\u30FC\u30EB\u3092\u8FFD\u52A0\u3057\u307E\u3057\u305F:`);
1463
+ console.log(`\u2705 Added ${linesToAdd.length} rules to .gitignore:`);
1432
1464
  for (const line of linesToAdd) {
1433
1465
  if (!line.startsWith("#")) {
1434
1466
  console.log(` ${line}`);
@@ -1732,7 +1764,7 @@ var customMatterOptions = {
1732
1764
  yaml: {
1733
1765
  parse: (str) => {
1734
1766
  try {
1735
- const preprocessed = str.replace(/^(\s*globs:\s*)\*\s*$/gm, '$1"*"');
1767
+ let preprocessed = str.replace(/^(\s*globs:\s*)\*\s*$/gm, '$1"*"').replace(/^(\s*globs:\s*)([^\s"'[\n][^"'[\n]*?)(\s*)$/gm, '$1"$2"$3');
1736
1768
  return (0, import_js_yaml.load)(preprocessed, { schema: import_js_yaml.DEFAULT_SCHEMA });
1737
1769
  } catch (error) {
1738
1770
  try {
@@ -1745,6 +1777,85 @@ var customMatterOptions = {
1745
1777
  }
1746
1778
  }
1747
1779
  };
1780
+ function convertCursorMdcFrontmatter(cursorFrontmatter, _filename) {
1781
+ const frontmatter = cursorFrontmatter;
1782
+ const description = normalizeValue(frontmatter?.description);
1783
+ const globs = normalizeGlobsValue(frontmatter?.globs);
1784
+ const alwaysApply = frontmatter?.alwaysApply === true || frontmatter?.alwaysApply === "true";
1785
+ if (alwaysApply) {
1786
+ return {
1787
+ root: false,
1788
+ targets: ["*"],
1789
+ description: description || "",
1790
+ globs: ["**/*"],
1791
+ cursorRuleType: "always"
1792
+ };
1793
+ }
1794
+ if (isEmpty(description) && isEmpty(globs)) {
1795
+ return {
1796
+ root: false,
1797
+ targets: ["*"],
1798
+ description: "",
1799
+ globs: [],
1800
+ cursorRuleType: "manual"
1801
+ };
1802
+ }
1803
+ if (!isEmpty(globs)) {
1804
+ return {
1805
+ root: false,
1806
+ targets: ["*"],
1807
+ description: "",
1808
+ globs: convertGlobsToArray(globs),
1809
+ cursorRuleType: "specificFiles"
1810
+ };
1811
+ }
1812
+ if (!isEmpty(description)) {
1813
+ return {
1814
+ root: false,
1815
+ targets: ["*"],
1816
+ description,
1817
+ globs: [],
1818
+ cursorRuleType: "intelligently"
1819
+ };
1820
+ }
1821
+ return {
1822
+ root: false,
1823
+ targets: ["*"],
1824
+ description: "",
1825
+ globs: [],
1826
+ cursorRuleType: "manual"
1827
+ };
1828
+ }
1829
+ function normalizeValue(value) {
1830
+ if (value === void 0 || value === null || value === "") {
1831
+ return void 0;
1832
+ }
1833
+ return String(value);
1834
+ }
1835
+ function normalizeGlobsValue(value) {
1836
+ if (value === void 0 || value === null || value === "") {
1837
+ return void 0;
1838
+ }
1839
+ if (Array.isArray(value)) {
1840
+ return value.length === 0 ? void 0 : value;
1841
+ }
1842
+ return String(value);
1843
+ }
1844
+ function isEmpty(value) {
1845
+ return value === void 0 || value === null || value === "";
1846
+ }
1847
+ function convertGlobsToArray(globs) {
1848
+ if (!globs) {
1849
+ return [];
1850
+ }
1851
+ if (Array.isArray(globs)) {
1852
+ return globs;
1853
+ }
1854
+ if (typeof globs === "string") {
1855
+ return globs.split(",").map((g) => g.trim()).filter((g) => g.length > 0);
1856
+ }
1857
+ return [];
1858
+ }
1748
1859
  async function parseCursorConfiguration(baseDir = process.cwd()) {
1749
1860
  const errors = [];
1750
1861
  const rules = [];
@@ -1757,12 +1868,8 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
1757
1868
  const parsed = (0, import_gray_matter3.default)(rawContent, customMatterOptions);
1758
1869
  const content = parsed.content.trim();
1759
1870
  if (content) {
1760
- const frontmatter = {
1761
- root: false,
1762
- targets: ["cursor"],
1763
- description: "Cursor IDE configuration rules",
1764
- globs: ["**/*"]
1765
- };
1871
+ const frontmatter = convertCursorMdcFrontmatter(parsed.data, "cursorrules");
1872
+ frontmatter.targets = ["cursor"];
1766
1873
  rules.push({
1767
1874
  frontmatter,
1768
1875
  content,
@@ -1789,12 +1896,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
1789
1896
  const content = parsed.content.trim();
1790
1897
  if (content) {
1791
1898
  const filename = (0, import_node_path18.basename)(file, ".mdc");
1792
- const frontmatter = {
1793
- root: false,
1794
- targets: ["cursor"],
1795
- description: `Cursor rule: ${filename}`,
1796
- globs: ["**/*"]
1797
- };
1899
+ const frontmatter = convertCursorMdcFrontmatter(parsed.data, filename);
1798
1900
  rules.push({
1799
1901
  frontmatter,
1800
1902
  content,
@@ -2535,7 +2637,7 @@ async function watchCommand() {
2535
2637
 
2536
2638
  // src/cli/index.ts
2537
2639
  var program = new import_commander.Command();
2538
- program.name("rulesync").description("Unified AI rules management CLI tool").version("0.40.0");
2640
+ program.name("rulesync").description("Unified AI rules management CLI tool").version("0.41.0");
2539
2641
  program.command("init").description("Initialize rulesync in current directory").action(initCommand);
2540
2642
  program.command("add <filename>").description("Add a new rule file").action(addCommand);
2541
2643
  program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  } from "./chunk-QR656A7R.js";
17
17
  import {
18
18
  generateRooMcp
19
- } from "./chunk-XSHEX7Q4.js";
19
+ } from "./chunk-PPX47BRK.js";
20
20
  import "./chunk-6YNGMPAL.js";
21
21
 
22
22
  // src/cli/index.ts
@@ -427,24 +427,56 @@ async function generateCursorConfig(rules, config, baseDir) {
427
427
  }
428
428
  function generateCursorMarkdown(rule) {
429
429
  const lines = [];
430
+ const ruleType = determineCursorRuleType(rule.frontmatter);
430
431
  lines.push("---");
431
- lines.push(`description: ${rule.frontmatter.description}`);
432
- if (rule.frontmatter.globs.length > 0) {
433
- lines.push(`globs: ${rule.frontmatter.globs.join(",")}`);
434
- }
435
- let ruletype;
436
- if (rule.frontmatter.root === true) {
437
- ruletype = "always";
438
- } else if (rule.frontmatter.root === false && rule.frontmatter.globs.length === 0) {
439
- ruletype = "agentrequested";
440
- } else {
441
- ruletype = "autoattached";
432
+ switch (ruleType) {
433
+ case "always":
434
+ lines.push("description:");
435
+ lines.push("globs:");
436
+ lines.push("alwaysApply: true");
437
+ break;
438
+ case "manual":
439
+ lines.push("description:");
440
+ lines.push("globs:");
441
+ lines.push("alwaysApply: false");
442
+ break;
443
+ case "specificFiles":
444
+ lines.push("description:");
445
+ lines.push(`globs: ${rule.frontmatter.globs.join(",")}`);
446
+ lines.push("alwaysApply: false");
447
+ break;
448
+ case "intelligently":
449
+ lines.push(`description: ${rule.frontmatter.description}`);
450
+ lines.push("globs:");
451
+ lines.push("alwaysApply: false");
452
+ break;
442
453
  }
443
- lines.push(`ruletype: ${ruletype}`);
444
454
  lines.push("---");
455
+ lines.push("");
445
456
  lines.push(rule.content);
446
457
  return lines.join("\n");
447
458
  }
459
+ function determineCursorRuleType(frontmatter) {
460
+ if (frontmatter.cursorRuleType) {
461
+ return frontmatter.cursorRuleType;
462
+ }
463
+ const isDescriptionEmpty = !frontmatter.description || frontmatter.description.trim() === "";
464
+ const isGlobsEmpty = frontmatter.globs.length === 0;
465
+ const isGlobsExactlyAllFiles = frontmatter.globs.length === 1 && frontmatter.globs[0] === "**/*";
466
+ if (isGlobsExactlyAllFiles) {
467
+ return "always";
468
+ }
469
+ if (isDescriptionEmpty && isGlobsEmpty) {
470
+ return "manual";
471
+ }
472
+ if (isDescriptionEmpty && !isGlobsEmpty) {
473
+ return "specificFiles";
474
+ }
475
+ if (!isDescriptionEmpty && isGlobsEmpty) {
476
+ return "intelligently";
477
+ }
478
+ return "intelligently";
479
+ }
448
480
  function generateCursorIgnore(patterns) {
449
481
  const lines = [
450
482
  "# Generated by rulesync from .rulesyncignore",
@@ -707,9 +739,9 @@ function validateFrontmatter(data, filepath) {
707
739
  `Missing required field "description" in ${filepath}: must be a descriptive string`
708
740
  );
709
741
  }
710
- if (!obj.description || typeof obj.description !== "string") {
742
+ if (typeof obj.description !== "string") {
711
743
  throw new Error(
712
- `Invalid "description" field in ${filepath}: must be a non-empty string, got ${typeof obj.description}`
744
+ `Invalid "description" field in ${filepath}: must be a string, got ${typeof obj.description}`
713
745
  );
714
746
  }
715
747
  if (obj.globs === void 0) {
@@ -1065,7 +1097,7 @@ var gitignoreCommand = async () => {
1065
1097
  }
1066
1098
  }
1067
1099
  if (linesToAdd.length === 0) {
1068
- console.log("\u2705 .gitignore\u306F\u65E2\u306B\u6700\u65B0\u3067\u3059");
1100
+ console.log("\u2705 .gitignore is already up to date");
1069
1101
  return;
1070
1102
  }
1071
1103
  const newContent = gitignoreContent ? `${gitignoreContent.trimEnd()}
@@ -1074,7 +1106,7 @@ ${linesToAdd.join("\n")}
1074
1106
  ` : `${linesToAdd.join("\n")}
1075
1107
  `;
1076
1108
  writeFileSync(gitignorePath, newContent);
1077
- console.log(`\u2705 .gitignore\u306B${linesToAdd.length}\u500B\u306E\u30EB\u30FC\u30EB\u3092\u8FFD\u52A0\u3057\u307E\u3057\u305F:`);
1109
+ console.log(`\u2705 Added ${linesToAdd.length} rules to .gitignore:`);
1078
1110
  for (const line of linesToAdd) {
1079
1111
  if (!line.startsWith("#")) {
1080
1112
  console.log(` ${line}`);
@@ -1378,7 +1410,7 @@ var customMatterOptions = {
1378
1410
  yaml: {
1379
1411
  parse: (str) => {
1380
1412
  try {
1381
- const preprocessed = str.replace(/^(\s*globs:\s*)\*\s*$/gm, '$1"*"');
1413
+ let preprocessed = str.replace(/^(\s*globs:\s*)\*\s*$/gm, '$1"*"').replace(/^(\s*globs:\s*)([^\s"'[\n][^"'[\n]*?)(\s*)$/gm, '$1"$2"$3');
1382
1414
  return load(preprocessed, { schema: DEFAULT_SCHEMA });
1383
1415
  } catch (error) {
1384
1416
  try {
@@ -1391,6 +1423,85 @@ var customMatterOptions = {
1391
1423
  }
1392
1424
  }
1393
1425
  };
1426
+ function convertCursorMdcFrontmatter(cursorFrontmatter, _filename) {
1427
+ const frontmatter = cursorFrontmatter;
1428
+ const description = normalizeValue(frontmatter?.description);
1429
+ const globs = normalizeGlobsValue(frontmatter?.globs);
1430
+ const alwaysApply = frontmatter?.alwaysApply === true || frontmatter?.alwaysApply === "true";
1431
+ if (alwaysApply) {
1432
+ return {
1433
+ root: false,
1434
+ targets: ["*"],
1435
+ description: description || "",
1436
+ globs: ["**/*"],
1437
+ cursorRuleType: "always"
1438
+ };
1439
+ }
1440
+ if (isEmpty(description) && isEmpty(globs)) {
1441
+ return {
1442
+ root: false,
1443
+ targets: ["*"],
1444
+ description: "",
1445
+ globs: [],
1446
+ cursorRuleType: "manual"
1447
+ };
1448
+ }
1449
+ if (!isEmpty(globs)) {
1450
+ return {
1451
+ root: false,
1452
+ targets: ["*"],
1453
+ description: "",
1454
+ globs: convertGlobsToArray(globs),
1455
+ cursorRuleType: "specificFiles"
1456
+ };
1457
+ }
1458
+ if (!isEmpty(description)) {
1459
+ return {
1460
+ root: false,
1461
+ targets: ["*"],
1462
+ description,
1463
+ globs: [],
1464
+ cursorRuleType: "intelligently"
1465
+ };
1466
+ }
1467
+ return {
1468
+ root: false,
1469
+ targets: ["*"],
1470
+ description: "",
1471
+ globs: [],
1472
+ cursorRuleType: "manual"
1473
+ };
1474
+ }
1475
+ function normalizeValue(value) {
1476
+ if (value === void 0 || value === null || value === "") {
1477
+ return void 0;
1478
+ }
1479
+ return String(value);
1480
+ }
1481
+ function normalizeGlobsValue(value) {
1482
+ if (value === void 0 || value === null || value === "") {
1483
+ return void 0;
1484
+ }
1485
+ if (Array.isArray(value)) {
1486
+ return value.length === 0 ? void 0 : value;
1487
+ }
1488
+ return String(value);
1489
+ }
1490
+ function isEmpty(value) {
1491
+ return value === void 0 || value === null || value === "";
1492
+ }
1493
+ function convertGlobsToArray(globs) {
1494
+ if (!globs) {
1495
+ return [];
1496
+ }
1497
+ if (Array.isArray(globs)) {
1498
+ return globs;
1499
+ }
1500
+ if (typeof globs === "string") {
1501
+ return globs.split(",").map((g) => g.trim()).filter((g) => g.length > 0);
1502
+ }
1503
+ return [];
1504
+ }
1394
1505
  async function parseCursorConfiguration(baseDir = process.cwd()) {
1395
1506
  const errors = [];
1396
1507
  const rules = [];
@@ -1403,12 +1514,8 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
1403
1514
  const parsed = matter3(rawContent, customMatterOptions);
1404
1515
  const content = parsed.content.trim();
1405
1516
  if (content) {
1406
- const frontmatter = {
1407
- root: false,
1408
- targets: ["cursor"],
1409
- description: "Cursor IDE configuration rules",
1410
- globs: ["**/*"]
1411
- };
1517
+ const frontmatter = convertCursorMdcFrontmatter(parsed.data, "cursorrules");
1518
+ frontmatter.targets = ["cursor"];
1412
1519
  rules.push({
1413
1520
  frontmatter,
1414
1521
  content,
@@ -1435,12 +1542,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
1435
1542
  const content = parsed.content.trim();
1436
1543
  if (content) {
1437
1544
  const filename = basename4(file, ".mdc");
1438
- const frontmatter = {
1439
- root: false,
1440
- targets: ["cursor"],
1441
- description: `Cursor rule: ${filename}`,
1442
- globs: ["**/*"]
1443
- };
1545
+ const frontmatter = convertCursorMdcFrontmatter(parsed.data, filename);
1444
1546
  rules.push({
1445
1547
  frontmatter,
1446
1548
  content,
@@ -2181,7 +2283,7 @@ async function watchCommand() {
2181
2283
 
2182
2284
  // src/cli/index.ts
2183
2285
  var program = new Command();
2184
- program.name("rulesync").description("Unified AI rules management CLI tool").version("0.40.0");
2286
+ program.name("rulesync").description("Unified AI rules management CLI tool").version("0.41.0");
2185
2287
  program.command("init").description("Initialize rulesync in current directory").action(initCommand);
2186
2288
  program.command("add <filename>").description("Add a new rule file").action(addCommand);
2187
2289
  program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  generateRooMcp,
3
3
  generateRooMcpConfiguration
4
- } from "./chunk-XSHEX7Q4.js";
4
+ } from "./chunk-PPX47BRK.js";
5
5
  import "./chunk-6YNGMPAL.js";
6
6
  export {
7
7
  generateRooMcp,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rulesync",
3
- "version": "0.40.0",
3
+ "version": "0.41.0",
4
4
  "description": "Unified AI rules management CLI tool that generates configuration files for various AI development tools",
5
5
  "keywords": [
6
6
  "ai",