rulesync 6.6.3 → 6.7.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 CHANGED
@@ -118,26 +118,29 @@ Get-FileHash rulesync.exe -Algorithm SHA256 | ForEach-Object {
118
118
  ## Getting Started
119
119
 
120
120
  ```bash
121
+ # Install rulesync globally
122
+ npm install -g rulesync
123
+
121
124
  # Create necessary directories, sample rule files, and configuration file
122
- npx rulesync init
125
+ rulesync init
123
126
 
124
127
  # Install official skills (recommended)
125
- npx rulesync fetch dyoshikawa/rulesync --features skills
128
+ rulesync fetch dyoshikawa/rulesync --features skills
126
129
  ```
127
130
 
128
131
  On the other hand, if you already have AI tool configurations:
129
132
 
130
133
  ```bash
131
134
  # Import existing files (to .rulesync/**/*)
132
- npx rulesync import --targets claudecode # From CLAUDE.md
133
- npx rulesync import --targets cursor # From .cursorrules
134
- npx rulesync import --targets copilot # From .github/copilot-instructions.md
135
- npx rulesync import --targets claudecode --features rules,mcp,commands,subagents
135
+ rulesync import --targets claudecode # From CLAUDE.md
136
+ rulesync import --targets cursor # From .cursorrules
137
+ rulesync import --targets copilot # From .github/copilot-instructions.md
138
+ rulesync import --targets claudecode --features rules,mcp,commands,subagents
136
139
 
137
140
  # And more tool supports
138
141
 
139
142
  # Generate unified configurations with all features
140
- npx rulesync generate --targets "*" --features "*"
143
+ rulesync generate --targets "*" --features "*"
141
144
  ```
142
145
 
143
146
  ## Supported Tools and Features
@@ -211,46 +214,46 @@ Rulesync is trusted by leading companies and recognized by the industry:
211
214
 
212
215
  ```bash
213
216
  # Initialize new project (recommended: organized rules structure)
214
- npx rulesync init
217
+ rulesync init
215
218
 
216
219
  # Import existing configurations (to .rulesync/rules/ by default)
217
- npx rulesync import --targets claudecode --features rules,ignore,mcp,commands,subagents,skills
220
+ rulesync import --targets claudecode --features rules,ignore,mcp,commands,subagents,skills
218
221
 
219
222
  # Fetch configurations from a Git repository
220
- npx rulesync fetch owner/repo
221
- npx rulesync fetch owner/repo@v1.0.0 --features rules,commands
222
- npx rulesync fetch https://github.com/owner/repo --conflict skip
223
+ rulesync fetch owner/repo
224
+ rulesync fetch owner/repo@v1.0.0 --features rules,commands
225
+ rulesync fetch https://github.com/owner/repo --conflict skip
223
226
 
224
227
  # Generate all features for all tools (new preferred syntax)
225
- npx rulesync generate --targets "*" --features "*"
228
+ rulesync generate --targets "*" --features "*"
226
229
 
227
230
  # Generate specific features for specific tools
228
- npx rulesync generate --targets copilot,cursor,cline --features rules,mcp
229
- npx rulesync generate --targets claudecode --features rules,subagents
231
+ rulesync generate --targets copilot,cursor,cline --features rules,mcp
232
+ rulesync generate --targets claudecode --features rules,subagents
230
233
 
231
234
  # Generate only rules (no MCP, ignore files, commands, or subagents)
232
- npx rulesync generate --targets "*" --features rules
235
+ rulesync generate --targets "*" --features rules
233
236
 
234
237
  # Generate simulated commands and subagents
235
- npx rulesync generate --targets copilot,cursor,codexcli --features commands,subagents --simulate-commands --simulate-subagents
238
+ rulesync generate --targets copilot,cursor,codexcli --features commands,subagents --simulate-commands --simulate-subagents
236
239
 
237
240
  # Dry run: show changes without writing files
238
- npx rulesync generate --dry-run --targets claudecode --features rules
241
+ rulesync generate --dry-run --targets claudecode --features rules
239
242
 
240
243
  # Check if files are up to date (for CI/CD pipelines)
241
- npx rulesync generate --check --targets "*" --features "*"
244
+ rulesync generate --check --targets "*" --features "*"
242
245
 
243
246
  # Add generated files to .gitignore
244
- npx rulesync gitignore
247
+ rulesync gitignore
245
248
 
246
249
  # Update rulesync to the latest version (single-binary installs)
247
- npx rulesync update
250
+ rulesync update
248
251
 
249
252
  # Check for updates without installing
250
- npx rulesync update --check
253
+ rulesync update --check
251
254
 
252
255
  # Force update even if already at latest version
253
- npx rulesync update --force
256
+ rulesync update --force
254
257
  ```
255
258
 
256
259
  ## Dry Run
@@ -262,7 +265,7 @@ Rulesync provides two dry run options for the `generate` command that allow you
262
265
  Show what would be written or deleted without actually writing any files. Changes are displayed with a `[DRY RUN]` prefix.
263
266
 
264
267
  ```bash
265
- npx rulesync generate --dry-run --targets claudecode --features rules
268
+ rulesync generate --dry-run --targets claudecode --features rules
266
269
  ```
267
270
 
268
271
  ### `--check`
@@ -271,7 +274,7 @@ Same as `--dry-run`, but exits with code 1 if files are not up to date. This is
271
274
 
272
275
  ```bash
273
276
  # In your CI pipeline
274
- npx rulesync generate --check --targets "*" --features "*"
277
+ rulesync generate --check --targets "*" --features "*"
275
278
  echo $? # 0 if up to date, 1 if changes needed
276
279
  ```
277
280
 
@@ -291,20 +294,20 @@ The `fetch` command allows you to fetch configuration files directly from a Git
291
294
 
292
295
  ```bash
293
296
  # Full URL format
294
- npx rulesync fetch https://github.com/owner/repo
295
- npx rulesync fetch https://github.com/owner/repo/tree/branch
296
- npx rulesync fetch https://github.com/owner/repo/tree/branch/path/to/subdir
297
- npx rulesync fetch https://gitlab.com/owner/repo # GitLab (planned)
297
+ rulesync fetch https://github.com/owner/repo
298
+ rulesync fetch https://github.com/owner/repo/tree/branch
299
+ rulesync fetch https://github.com/owner/repo/tree/branch/path/to/subdir
300
+ rulesync fetch https://gitlab.com/owner/repo # GitLab (planned)
298
301
 
299
302
  # Prefix format
300
- npx rulesync fetch github:owner/repo
301
- npx rulesync fetch gitlab:owner/repo # GitLab (planned)
303
+ rulesync fetch github:owner/repo
304
+ rulesync fetch gitlab:owner/repo # GitLab (planned)
302
305
 
303
306
  # Shorthand format (defaults to GitHub)
304
- npx rulesync fetch owner/repo
305
- npx rulesync fetch owner/repo@ref # Specify branch/tag/commit
306
- npx rulesync fetch owner/repo:path # Specify subdirectory
307
- npx rulesync fetch owner/repo@ref:path # Both ref and path
307
+ rulesync fetch owner/repo
308
+ rulesync fetch owner/repo@ref # Specify branch/tag/commit
309
+ rulesync fetch owner/repo:path # Specify subdirectory
310
+ rulesync fetch owner/repo@ref:path # Both ref and path
308
311
  ```
309
312
 
310
313
  ### Options
@@ -323,27 +326,27 @@ npx rulesync fetch owner/repo@ref:path # Both ref and path
323
326
 
324
327
  ```bash
325
328
  # Fetch skills from external repositories
326
- npx rulesync fetch vercel-labs/agent-skills --features skills
327
- npx rulesync fetch anthropics/skills --features skills
329
+ rulesync fetch vercel-labs/agent-skills --features skills
330
+ rulesync fetch anthropics/skills --features skills
328
331
 
329
332
  # Fetch all features from a public repository
330
- npx rulesync fetch dyoshikawa/rulesync --path .rulesync
333
+ rulesync fetch dyoshikawa/rulesync --path .rulesync
331
334
 
332
335
  # Fetch only rules and commands from a specific tag
333
- npx rulesync fetch owner/repo@v1.0.0 --features rules,commands
336
+ rulesync fetch owner/repo@v1.0.0 --features rules,commands
334
337
 
335
338
  # Fetch from a private repository (uses GITHUB_TOKEN env var)
336
339
  export GITHUB_TOKEN=ghp_xxxx
337
- npx rulesync fetch owner/private-repo
340
+ rulesync fetch owner/private-repo
338
341
 
339
342
  # Or use GitHub CLI to get the token
340
- GITHUB_TOKEN=$(gh auth token) npx rulesync fetch owner/private-repo
343
+ GITHUB_TOKEN=$(gh auth token) rulesync fetch owner/private-repo
341
344
 
342
345
  # Preserve existing files (skip conflicts)
343
- npx rulesync fetch owner/repo --conflict skip
346
+ rulesync fetch owner/repo --conflict skip
344
347
 
345
348
  # Fetch from a monorepo subdirectory
346
- npx rulesync fetch owner/repo:packages/my-package
349
+ rulesync fetch owner/repo:packages/my-package
347
350
  ```
348
351
 
349
352
  ## Configuration
@@ -695,7 +698,7 @@ Currently, supports rules and commands generation for Claude Code. Import for gl
695
698
  2. Initialize files for global files in the directory.
696
699
  ```bash
697
700
  cd ~/.aiglobal
698
- npx rulesync init
701
+ rulesync init
699
702
  ```
700
703
  3. Edit `~/.aiglobal/rulesync.jsonc` to enable global mode.
701
704
  ```jsonc
@@ -718,7 +721,7 @@ Currently, supports rules and commands generation for Claude Code. Import for gl
718
721
  5. Generate rules for global settings.
719
722
  ```bash
720
723
  # Run in the `~/.aiglobal` directory
721
- npx rulesync generate
724
+ rulesync generate
722
725
  ```
723
726
 
724
727
  > [!NOTE]
@@ -735,7 +738,7 @@ Simulated commands, subagents and skills allow you to generate simulated feature
735
738
  1. Prepare `.rulesync/commands/*.md`, `.rulesync/subagents/*.md` and `.rulesync/skills/*/SKILL.md` for your purposes.
736
739
  2. Generate simulated commands, subagents and skills for specific tools that are included in cursor, codexcli and etc.
737
740
  ```bash
738
- npx rulesync generate \
741
+ rulesync generate \
739
742
  --targets copilot,cursor,codexcli \
740
743
  --features commands,subagents,skills \
741
744
  --simulate-commands \
@@ -762,7 +765,7 @@ Rulesync supports compressing tokens consumed by MCP servers [d-kimuson/modular-
762
765
 
763
766
  ```bash
764
767
  # Enable modular-mcp via CLI
765
- npx rulesync generate --targets claudecode --features mcp --modular-mcp
768
+ rulesync generate --targets claudecode --features mcp --modular-mcp
766
769
 
767
770
  # Or via configuration file
768
771
  {
@@ -906,7 +909,7 @@ So, in this case, approximately 92% reduction in MCP tools consumption!
906
909
  Rulesync provides official skills that you can install using the fetch command:
907
910
 
908
911
  ```bash
909
- npx rulesync fetch dyoshikawa/rulesync --features skills
912
+ rulesync fetch dyoshikawa/rulesync --features skills
910
913
  ```
911
914
 
912
915
  This will install the Rulesync documentation skill to your project.
package/dist/index.cjs CHANGED
@@ -321,16 +321,22 @@ var FeatureProcessor = class {
321
321
  * Returns the number of files written.
322
322
  */
323
323
  async writeAiFiles(aiFiles) {
324
+ let changedCount = 0;
324
325
  for (const aiFile of aiFiles) {
325
326
  const filePath = aiFile.getFilePath();
327
+ const contentWithNewline = addTrailingNewline(aiFile.getFileContent());
328
+ const existingContent = await readFileContentOrNull(filePath);
329
+ if (existingContent === contentWithNewline) {
330
+ continue;
331
+ }
326
332
  if (this.dryRun) {
327
333
  logger.info(`[DRY RUN] Would write: ${filePath}`);
328
334
  } else {
329
- const contentWithNewline = addTrailingNewline(aiFile.getFileContent());
330
335
  await writeFileContent(filePath, contentWithNewline);
331
336
  }
337
+ changedCount++;
332
338
  }
333
- return aiFiles.length;
339
+ return changedCount;
334
340
  }
335
341
  async removeAiFiles(aiFiles) {
336
342
  for (const aiFile of aiFiles) {
@@ -352,6 +358,7 @@ var FeatureProcessor = class {
352
358
  await removeFile(filePath);
353
359
  }
354
360
  }
361
+ return orphanFiles.length;
355
362
  }
356
363
  };
357
364
 
@@ -5445,7 +5452,7 @@ var OpencodeMcp = class _OpencodeMcp extends ToolMcp {
5445
5452
  static getSettablePaths({ global } = {}) {
5446
5453
  if (global) {
5447
5454
  return {
5448
- relativeDirPath: ".",
5455
+ relativeDirPath: (0, import_node_path48.join)(".config", "opencode"),
5449
5456
  relativeFilePath: "opencode.json"
5450
5457
  };
5451
5458
  }
@@ -6422,37 +6429,71 @@ var DirFeatureProcessor = class {
6422
6429
  /**
6423
6430
  * Once converted to rulesync/tool dirs, write them to the filesystem.
6424
6431
  * Returns the number of directories written.
6432
+ *
6433
+ * Note: This method uses directory-level change detection. If any file within
6434
+ * a directory has changed, ALL files in that directory are rewritten. This is
6435
+ * an intentional design decision to ensure consistency within directory units.
6425
6436
  */
6426
6437
  async writeAiDirs(aiDirs) {
6438
+ let changedCount = 0;
6427
6439
  for (const aiDir of aiDirs) {
6428
6440
  const dirPath = aiDir.getDirPath();
6441
+ let dirHasChanges = false;
6442
+ const mainFile = aiDir.getMainFile();
6443
+ let mainFileContent;
6444
+ if (mainFile) {
6445
+ const mainFilePath = (0, import_node_path56.join)(dirPath, mainFile.name);
6446
+ const content = stringifyFrontmatter(mainFile.body, mainFile.frontmatter);
6447
+ mainFileContent = addTrailingNewline(content);
6448
+ const existingContent = await readFileContentOrNull(mainFilePath);
6449
+ if (existingContent !== mainFileContent) {
6450
+ dirHasChanges = true;
6451
+ }
6452
+ }
6453
+ const otherFiles = aiDir.getOtherFiles();
6454
+ const otherFileContents = [];
6455
+ for (const file of otherFiles) {
6456
+ const contentWithNewline = addTrailingNewline(file.fileBuffer.toString("utf-8"));
6457
+ otherFileContents.push(contentWithNewline);
6458
+ if (!dirHasChanges) {
6459
+ const filePath = (0, import_node_path56.join)(dirPath, file.relativeFilePathToDirPath);
6460
+ const existingContent = await readFileContentOrNull(filePath);
6461
+ if (existingContent !== contentWithNewline) {
6462
+ dirHasChanges = true;
6463
+ }
6464
+ }
6465
+ }
6466
+ if (!dirHasChanges) {
6467
+ continue;
6468
+ }
6429
6469
  if (this.dryRun) {
6430
6470
  logger.info(`[DRY RUN] Would create directory: ${dirPath}`);
6431
- const mainFile = aiDir.getMainFile();
6432
6471
  if (mainFile) {
6433
6472
  logger.info(`[DRY RUN] Would write: ${(0, import_node_path56.join)(dirPath, mainFile.name)}`);
6434
6473
  }
6435
- for (const file of aiDir.getOtherFiles()) {
6474
+ for (const file of otherFiles) {
6436
6475
  logger.info(`[DRY RUN] Would write: ${(0, import_node_path56.join)(dirPath, file.relativeFilePathToDirPath)}`);
6437
6476
  }
6438
6477
  } else {
6439
6478
  await ensureDir(dirPath);
6440
- const mainFile = aiDir.getMainFile();
6441
- if (mainFile) {
6479
+ if (mainFile && mainFileContent) {
6442
6480
  const mainFilePath = (0, import_node_path56.join)(dirPath, mainFile.name);
6443
- const content = stringifyFrontmatter(mainFile.body, mainFile.frontmatter);
6444
- const contentWithNewline = addTrailingNewline(content);
6445
- await writeFileContent(mainFilePath, contentWithNewline);
6481
+ await writeFileContent(mainFilePath, mainFileContent);
6446
6482
  }
6447
- const otherFiles = aiDir.getOtherFiles();
6448
- for (const file of otherFiles) {
6483
+ for (const [i, file] of otherFiles.entries()) {
6449
6484
  const filePath = (0, import_node_path56.join)(dirPath, file.relativeFilePathToDirPath);
6450
- const contentWithNewline = addTrailingNewline(file.fileBuffer.toString("utf-8"));
6451
- await writeFileContent(filePath, contentWithNewline);
6485
+ const content = otherFileContents[i];
6486
+ if (content === void 0) {
6487
+ throw new Error(
6488
+ `Internal error: content for file ${file.relativeFilePathToDirPath} is undefined. This indicates a synchronization issue between otherFiles and otherFileContents arrays.`
6489
+ );
6490
+ }
6491
+ await writeFileContent(filePath, content);
6452
6492
  }
6453
6493
  }
6494
+ changedCount++;
6454
6495
  }
6455
- return aiDirs.length;
6496
+ return changedCount;
6456
6497
  }
6457
6498
  async removeAiDirs(aiDirs) {
6458
6499
  for (const aiDir of aiDirs) {
@@ -6474,6 +6515,7 @@ var DirFeatureProcessor = class {
6474
6515
  await removeDirectory(dirPath);
6475
6516
  }
6476
6517
  }
6518
+ return orphanDirs.length;
6477
6519
  }
6478
6520
  };
6479
6521
 
@@ -14701,19 +14743,13 @@ async function generateRulesCore(params) {
14701
14743
  });
14702
14744
  const rulesyncFiles = await processor.loadRulesyncFiles();
14703
14745
  const toolFiles = await processor.convertRulesyncFilesToToolFiles(rulesyncFiles);
14704
- if (isPreviewMode) {
14705
- const fileDiff = await detectFileDiff(toolFiles);
14706
- if (fileDiff) hasDiff = true;
14707
- }
14708
14746
  const writtenCount = await processor.writeAiFiles(toolFiles);
14709
14747
  totalCount += writtenCount;
14748
+ if (writtenCount > 0) hasDiff = true;
14710
14749
  if (config.getDelete()) {
14711
14750
  const existingToolFiles = await processor.loadToolFiles({ forDeletion: true });
14712
- if (isPreviewMode) {
14713
- const orphanDiff = await detectOrphanFileDiff(existingToolFiles, toolFiles);
14714
- if (orphanDiff) hasDiff = true;
14715
- }
14716
- await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14751
+ const orphanCount = await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14752
+ if (orphanCount > 0) hasDiff = true;
14717
14753
  }
14718
14754
  }
14719
14755
  }
@@ -14741,26 +14777,18 @@ async function generateIgnoreCore(params) {
14741
14777
  const rulesyncFiles = await processor.loadRulesyncFiles();
14742
14778
  if (rulesyncFiles.length > 0) {
14743
14779
  const toolFiles = await processor.convertRulesyncFilesToToolFiles(rulesyncFiles);
14744
- if (isPreviewMode) {
14745
- const fileDiff = await detectFileDiff(toolFiles);
14746
- if (fileDiff) hasDiff = true;
14747
- }
14748
14780
  const writtenCount = await processor.writeAiFiles(toolFiles);
14749
14781
  totalCount += writtenCount;
14782
+ if (writtenCount > 0) hasDiff = true;
14750
14783
  if (config.getDelete()) {
14751
14784
  const existingToolFiles = await processor.loadToolFiles({ forDeletion: true });
14752
- if (isPreviewMode) {
14753
- const orphanDiff = await detectOrphanFileDiff(existingToolFiles, toolFiles);
14754
- if (orphanDiff) hasDiff = true;
14755
- }
14756
- await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14785
+ const orphanCount = await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14786
+ if (orphanCount > 0) hasDiff = true;
14757
14787
  }
14758
14788
  } else if (config.getDelete()) {
14759
14789
  const existingToolFiles = await processor.loadToolFiles({ forDeletion: true });
14760
- if (isPreviewMode && existingToolFiles.length > 0) {
14761
- hasDiff = true;
14762
- }
14763
- await processor.removeOrphanAiFiles(existingToolFiles, []);
14790
+ const orphanCount = await processor.removeOrphanAiFiles(existingToolFiles, []);
14791
+ if (orphanCount > 0) hasDiff = true;
14764
14792
  }
14765
14793
  } catch (error) {
14766
14794
  logger.warn(
@@ -14795,19 +14823,13 @@ async function generateMcpCore(params) {
14795
14823
  });
14796
14824
  const rulesyncFiles = await processor.loadRulesyncFiles();
14797
14825
  const toolFiles = await processor.convertRulesyncFilesToToolFiles(rulesyncFiles);
14798
- if (isPreviewMode) {
14799
- const fileDiff = await detectFileDiff(toolFiles);
14800
- if (fileDiff) hasDiff = true;
14801
- }
14802
14826
  const writtenCount = await processor.writeAiFiles(toolFiles);
14803
14827
  totalCount += writtenCount;
14828
+ if (writtenCount > 0) hasDiff = true;
14804
14829
  if (config.getDelete()) {
14805
14830
  const existingToolFiles = await processor.loadToolFiles({ forDeletion: true });
14806
- if (isPreviewMode) {
14807
- const orphanDiff = await detectOrphanFileDiff(existingToolFiles, toolFiles);
14808
- if (orphanDiff) hasDiff = true;
14809
- }
14810
- await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14831
+ const orphanCount = await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14832
+ if (orphanCount > 0) hasDiff = true;
14811
14833
  }
14812
14834
  }
14813
14835
  }
@@ -14838,19 +14860,13 @@ async function generateCommandsCore(params) {
14838
14860
  });
14839
14861
  const rulesyncFiles = await processor.loadRulesyncFiles();
14840
14862
  const toolFiles = await processor.convertRulesyncFilesToToolFiles(rulesyncFiles);
14841
- if (isPreviewMode) {
14842
- const fileDiff = await detectFileDiff(toolFiles);
14843
- if (fileDiff) hasDiff = true;
14844
- }
14845
14863
  const writtenCount = await processor.writeAiFiles(toolFiles);
14846
14864
  totalCount += writtenCount;
14865
+ if (writtenCount > 0) hasDiff = true;
14847
14866
  if (config.getDelete()) {
14848
14867
  const existingToolFiles = await processor.loadToolFiles({ forDeletion: true });
14849
- if (isPreviewMode) {
14850
- const orphanDiff = await detectOrphanFileDiff(existingToolFiles, toolFiles);
14851
- if (orphanDiff) hasDiff = true;
14852
- }
14853
- await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14868
+ const orphanCount = await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14869
+ if (orphanCount > 0) hasDiff = true;
14854
14870
  }
14855
14871
  }
14856
14872
  }
@@ -14881,19 +14897,13 @@ async function generateSubagentsCore(params) {
14881
14897
  });
14882
14898
  const rulesyncFiles = await processor.loadRulesyncFiles();
14883
14899
  const toolFiles = await processor.convertRulesyncFilesToToolFiles(rulesyncFiles);
14884
- if (isPreviewMode) {
14885
- const fileDiff = await detectFileDiff(toolFiles);
14886
- if (fileDiff) hasDiff = true;
14887
- }
14888
14900
  const writtenCount = await processor.writeAiFiles(toolFiles);
14889
14901
  totalCount += writtenCount;
14902
+ if (writtenCount > 0) hasDiff = true;
14890
14903
  if (config.getDelete()) {
14891
14904
  const existingToolFiles = await processor.loadToolFiles({ forDeletion: true });
14892
- if (isPreviewMode) {
14893
- const orphanDiff = await detectOrphanFileDiff(existingToolFiles, toolFiles);
14894
- if (orphanDiff) hasDiff = true;
14895
- }
14896
- await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14905
+ const orphanCount = await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14906
+ if (orphanCount > 0) hasDiff = true;
14897
14907
  }
14898
14908
  }
14899
14909
  }
@@ -14930,19 +14940,13 @@ async function generateSkillsCore(params) {
14930
14940
  }
14931
14941
  }
14932
14942
  const toolDirs = await processor.convertRulesyncDirsToToolDirs(rulesyncDirs);
14933
- if (isPreviewMode) {
14934
- const dirDiff = await detectDirDiff(toolDirs);
14935
- if (dirDiff) hasDiff = true;
14936
- }
14937
14943
  const writtenCount = await processor.writeAiDirs(toolDirs);
14938
14944
  totalCount += writtenCount;
14945
+ if (writtenCount > 0) hasDiff = true;
14939
14946
  if (config.getDelete()) {
14940
14947
  const existingToolDirs = await processor.loadToolDirsToDelete();
14941
- if (isPreviewMode) {
14942
- const orphanDiff = await detectOrphanDirDiff(existingToolDirs, toolDirs);
14943
- if (orphanDiff) hasDiff = true;
14944
- }
14945
- await processor.removeOrphanAiDirs(existingToolDirs, toolDirs);
14948
+ const orphanCount = await processor.removeOrphanAiDirs(existingToolDirs, toolDirs);
14949
+ if (orphanCount > 0) hasDiff = true;
14946
14950
  }
14947
14951
  }
14948
14952
  }
@@ -14972,77 +14976,24 @@ async function generateHooksCore(params) {
14972
14976
  if (rulesyncFiles.length === 0) {
14973
14977
  if (config.getDelete()) {
14974
14978
  const existingToolFiles = await processor.loadToolFiles({ forDeletion: true });
14975
- if (isPreviewMode && existingToolFiles.length > 0) {
14976
- hasDiff = true;
14977
- }
14978
- await processor.removeOrphanAiFiles(existingToolFiles, []);
14979
+ const orphanCount = await processor.removeOrphanAiFiles(existingToolFiles, []);
14980
+ if (orphanCount > 0) hasDiff = true;
14979
14981
  }
14980
14982
  continue;
14981
14983
  }
14982
14984
  const toolFiles = await processor.convertRulesyncFilesToToolFiles(rulesyncFiles);
14983
- if (isPreviewMode) {
14984
- const fileDiff = await detectFileDiff(toolFiles);
14985
- if (fileDiff) hasDiff = true;
14986
- }
14987
14985
  const writtenCount = await processor.writeAiFiles(toolFiles);
14988
14986
  totalCount += writtenCount;
14987
+ if (writtenCount > 0) hasDiff = true;
14989
14988
  if (config.getDelete()) {
14990
14989
  const existingToolFiles = await processor.loadToolFiles({ forDeletion: true });
14991
- if (isPreviewMode) {
14992
- const orphanDiff = await detectOrphanFileDiff(existingToolFiles, toolFiles);
14993
- if (orphanDiff) hasDiff = true;
14994
- }
14995
- await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14990
+ const orphanCount = await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14991
+ if (orphanCount > 0) hasDiff = true;
14996
14992
  }
14997
14993
  }
14998
14994
  }
14999
14995
  return { count: totalCount, hasDiff };
15000
14996
  }
15001
- async function detectFileDiff(aiFiles) {
15002
- for (const aiFile of aiFiles) {
15003
- const filePath = aiFile.getFilePath();
15004
- const newContent = addTrailingNewline(aiFile.getFileContent());
15005
- const existingContent = await readFileContentOrNull(filePath);
15006
- if (existingContent !== newContent) {
15007
- return true;
15008
- }
15009
- }
15010
- return false;
15011
- }
15012
- async function detectOrphanFileDiff(existingFiles, generatedFiles) {
15013
- const generatedPaths = new Set(generatedFiles.map((f) => f.getFilePath()));
15014
- const orphanFiles = existingFiles.filter((f) => !generatedPaths.has(f.getFilePath()));
15015
- return orphanFiles.length > 0;
15016
- }
15017
- async function detectDirDiff(aiDirs) {
15018
- for (const aiDir of aiDirs) {
15019
- const mainFile = aiDir.getMainFile();
15020
- if (mainFile) {
15021
- const mainFilePath = (0, import_node_path109.join)(aiDir.getDirPath(), mainFile.name);
15022
- const newContent = addTrailingNewline(
15023
- stringifyFrontmatter(mainFile.body, mainFile.frontmatter)
15024
- );
15025
- const existingContent = await readFileContentOrNull(mainFilePath);
15026
- if (existingContent !== newContent) {
15027
- return true;
15028
- }
15029
- }
15030
- for (const file of aiDir.getOtherFiles()) {
15031
- const filePath = (0, import_node_path109.join)(aiDir.getDirPath(), file.relativeFilePathToDirPath);
15032
- const newContent = addTrailingNewline(file.fileBuffer.toString("utf-8"));
15033
- const existingContent = await readFileContentOrNull(filePath);
15034
- if (existingContent !== newContent) {
15035
- return true;
15036
- }
15037
- }
15038
- }
15039
- return false;
15040
- }
15041
- async function detectOrphanDirDiff(existingDirs, generatedDirs) {
15042
- const generatedPaths = new Set(generatedDirs.map((d) => d.getDirPath()));
15043
- const orphanDirs = existingDirs.filter((d) => !generatedPaths.has(d.getDirPath()));
15044
- return orphanDirs.length > 0;
15045
- }
15046
14997
 
15047
14998
  // src/utils/result.ts
15048
14999
  function calculateTotalCount(result) {
@@ -15054,9 +15005,9 @@ function logFeatureResult(params) {
15054
15005
  const { count, featureName, isPreview, modePrefix } = params;
15055
15006
  if (count > 0) {
15056
15007
  if (isPreview) {
15057
- logger.info(`${modePrefix} Would generate ${count} ${featureName}`);
15008
+ logger.info(`${modePrefix} Would write ${count} ${featureName}`);
15058
15009
  } else {
15059
- logger.success(`Generated ${count} ${featureName}`);
15010
+ logger.success(`Written ${count} ${featureName}`);
15060
15011
  }
15061
15012
  }
15062
15013
  }
@@ -15151,7 +15102,7 @@ async function generateCommand(options) {
15151
15102
  const totalGenerated = calculateTotalCount(result);
15152
15103
  if (totalGenerated === 0) {
15153
15104
  const enabledFeatures = features.join(", ");
15154
- logger.warn(`\u26A0\uFE0F No files generated for enabled features: ${enabledFeatures}`);
15105
+ logger.info(`\u2713 All files are up to date (${enabledFeatures})`);
15155
15106
  return;
15156
15107
  }
15157
15108
  const parts = [];
@@ -15163,11 +15114,9 @@ async function generateCommand(options) {
15163
15114
  if (result.skillsCount > 0) parts.push(`${result.skillsCount} skills`);
15164
15115
  if (result.hooksCount > 0) parts.push(`${result.hooksCount} hooks`);
15165
15116
  if (isPreview) {
15166
- logger.info(
15167
- `${modePrefix} Would generate ${totalGenerated} file(s) total (${parts.join(" + ")})`
15168
- );
15117
+ logger.info(`${modePrefix} Would write ${totalGenerated} file(s) total (${parts.join(" + ")})`);
15169
15118
  } else {
15170
- logger.success(`\u{1F389} All done! Generated ${totalGenerated} file(s) total (${parts.join(" + ")})`);
15119
+ logger.success(`\u{1F389} All done! Written ${totalGenerated} file(s) total (${parts.join(" + ")})`);
15171
15120
  }
15172
15121
  if (check) {
15173
15122
  if (result.hasDiff) {
@@ -17593,7 +17542,7 @@ async function updateCommand(currentVersion, options) {
17593
17542
  }
17594
17543
 
17595
17544
  // src/cli/index.ts
17596
- var getVersion = () => "6.6.3";
17545
+ var getVersion = () => "6.7.1";
17597
17546
  var main = async () => {
17598
17547
  const program = new import_commander.Command();
17599
17548
  const version = getVersion();
package/dist/index.js CHANGED
@@ -298,16 +298,22 @@ var FeatureProcessor = class {
298
298
  * Returns the number of files written.
299
299
  */
300
300
  async writeAiFiles(aiFiles) {
301
+ let changedCount = 0;
301
302
  for (const aiFile of aiFiles) {
302
303
  const filePath = aiFile.getFilePath();
304
+ const contentWithNewline = addTrailingNewline(aiFile.getFileContent());
305
+ const existingContent = await readFileContentOrNull(filePath);
306
+ if (existingContent === contentWithNewline) {
307
+ continue;
308
+ }
303
309
  if (this.dryRun) {
304
310
  logger.info(`[DRY RUN] Would write: ${filePath}`);
305
311
  } else {
306
- const contentWithNewline = addTrailingNewline(aiFile.getFileContent());
307
312
  await writeFileContent(filePath, contentWithNewline);
308
313
  }
314
+ changedCount++;
309
315
  }
310
- return aiFiles.length;
316
+ return changedCount;
311
317
  }
312
318
  async removeAiFiles(aiFiles) {
313
319
  for (const aiFile of aiFiles) {
@@ -329,6 +335,7 @@ var FeatureProcessor = class {
329
335
  await removeFile(filePath);
330
336
  }
331
337
  }
338
+ return orphanFiles.length;
332
339
  }
333
340
  };
334
341
 
@@ -5422,7 +5429,7 @@ var OpencodeMcp = class _OpencodeMcp extends ToolMcp {
5422
5429
  static getSettablePaths({ global } = {}) {
5423
5430
  if (global) {
5424
5431
  return {
5425
- relativeDirPath: ".",
5432
+ relativeDirPath: join47(".config", "opencode"),
5426
5433
  relativeFilePath: "opencode.json"
5427
5434
  };
5428
5435
  }
@@ -6399,37 +6406,71 @@ var DirFeatureProcessor = class {
6399
6406
  /**
6400
6407
  * Once converted to rulesync/tool dirs, write them to the filesystem.
6401
6408
  * Returns the number of directories written.
6409
+ *
6410
+ * Note: This method uses directory-level change detection. If any file within
6411
+ * a directory has changed, ALL files in that directory are rewritten. This is
6412
+ * an intentional design decision to ensure consistency within directory units.
6402
6413
  */
6403
6414
  async writeAiDirs(aiDirs) {
6415
+ let changedCount = 0;
6404
6416
  for (const aiDir of aiDirs) {
6405
6417
  const dirPath = aiDir.getDirPath();
6418
+ let dirHasChanges = false;
6419
+ const mainFile = aiDir.getMainFile();
6420
+ let mainFileContent;
6421
+ if (mainFile) {
6422
+ const mainFilePath = join55(dirPath, mainFile.name);
6423
+ const content = stringifyFrontmatter(mainFile.body, mainFile.frontmatter);
6424
+ mainFileContent = addTrailingNewline(content);
6425
+ const existingContent = await readFileContentOrNull(mainFilePath);
6426
+ if (existingContent !== mainFileContent) {
6427
+ dirHasChanges = true;
6428
+ }
6429
+ }
6430
+ const otherFiles = aiDir.getOtherFiles();
6431
+ const otherFileContents = [];
6432
+ for (const file of otherFiles) {
6433
+ const contentWithNewline = addTrailingNewline(file.fileBuffer.toString("utf-8"));
6434
+ otherFileContents.push(contentWithNewline);
6435
+ if (!dirHasChanges) {
6436
+ const filePath = join55(dirPath, file.relativeFilePathToDirPath);
6437
+ const existingContent = await readFileContentOrNull(filePath);
6438
+ if (existingContent !== contentWithNewline) {
6439
+ dirHasChanges = true;
6440
+ }
6441
+ }
6442
+ }
6443
+ if (!dirHasChanges) {
6444
+ continue;
6445
+ }
6406
6446
  if (this.dryRun) {
6407
6447
  logger.info(`[DRY RUN] Would create directory: ${dirPath}`);
6408
- const mainFile = aiDir.getMainFile();
6409
6448
  if (mainFile) {
6410
6449
  logger.info(`[DRY RUN] Would write: ${join55(dirPath, mainFile.name)}`);
6411
6450
  }
6412
- for (const file of aiDir.getOtherFiles()) {
6451
+ for (const file of otherFiles) {
6413
6452
  logger.info(`[DRY RUN] Would write: ${join55(dirPath, file.relativeFilePathToDirPath)}`);
6414
6453
  }
6415
6454
  } else {
6416
6455
  await ensureDir(dirPath);
6417
- const mainFile = aiDir.getMainFile();
6418
- if (mainFile) {
6456
+ if (mainFile && mainFileContent) {
6419
6457
  const mainFilePath = join55(dirPath, mainFile.name);
6420
- const content = stringifyFrontmatter(mainFile.body, mainFile.frontmatter);
6421
- const contentWithNewline = addTrailingNewline(content);
6422
- await writeFileContent(mainFilePath, contentWithNewline);
6458
+ await writeFileContent(mainFilePath, mainFileContent);
6423
6459
  }
6424
- const otherFiles = aiDir.getOtherFiles();
6425
- for (const file of otherFiles) {
6460
+ for (const [i, file] of otherFiles.entries()) {
6426
6461
  const filePath = join55(dirPath, file.relativeFilePathToDirPath);
6427
- const contentWithNewline = addTrailingNewline(file.fileBuffer.toString("utf-8"));
6428
- await writeFileContent(filePath, contentWithNewline);
6462
+ const content = otherFileContents[i];
6463
+ if (content === void 0) {
6464
+ throw new Error(
6465
+ `Internal error: content for file ${file.relativeFilePathToDirPath} is undefined. This indicates a synchronization issue between otherFiles and otherFileContents arrays.`
6466
+ );
6467
+ }
6468
+ await writeFileContent(filePath, content);
6429
6469
  }
6430
6470
  }
6471
+ changedCount++;
6431
6472
  }
6432
- return aiDirs.length;
6473
+ return changedCount;
6433
6474
  }
6434
6475
  async removeAiDirs(aiDirs) {
6435
6476
  for (const aiDir of aiDirs) {
@@ -6451,6 +6492,7 @@ var DirFeatureProcessor = class {
6451
6492
  await removeDirectory(dirPath);
6452
6493
  }
6453
6494
  }
6495
+ return orphanDirs.length;
6454
6496
  }
6455
6497
  };
6456
6498
 
@@ -14678,19 +14720,13 @@ async function generateRulesCore(params) {
14678
14720
  });
14679
14721
  const rulesyncFiles = await processor.loadRulesyncFiles();
14680
14722
  const toolFiles = await processor.convertRulesyncFilesToToolFiles(rulesyncFiles);
14681
- if (isPreviewMode) {
14682
- const fileDiff = await detectFileDiff(toolFiles);
14683
- if (fileDiff) hasDiff = true;
14684
- }
14685
14723
  const writtenCount = await processor.writeAiFiles(toolFiles);
14686
14724
  totalCount += writtenCount;
14725
+ if (writtenCount > 0) hasDiff = true;
14687
14726
  if (config.getDelete()) {
14688
14727
  const existingToolFiles = await processor.loadToolFiles({ forDeletion: true });
14689
- if (isPreviewMode) {
14690
- const orphanDiff = await detectOrphanFileDiff(existingToolFiles, toolFiles);
14691
- if (orphanDiff) hasDiff = true;
14692
- }
14693
- await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14728
+ const orphanCount = await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14729
+ if (orphanCount > 0) hasDiff = true;
14694
14730
  }
14695
14731
  }
14696
14732
  }
@@ -14718,26 +14754,18 @@ async function generateIgnoreCore(params) {
14718
14754
  const rulesyncFiles = await processor.loadRulesyncFiles();
14719
14755
  if (rulesyncFiles.length > 0) {
14720
14756
  const toolFiles = await processor.convertRulesyncFilesToToolFiles(rulesyncFiles);
14721
- if (isPreviewMode) {
14722
- const fileDiff = await detectFileDiff(toolFiles);
14723
- if (fileDiff) hasDiff = true;
14724
- }
14725
14757
  const writtenCount = await processor.writeAiFiles(toolFiles);
14726
14758
  totalCount += writtenCount;
14759
+ if (writtenCount > 0) hasDiff = true;
14727
14760
  if (config.getDelete()) {
14728
14761
  const existingToolFiles = await processor.loadToolFiles({ forDeletion: true });
14729
- if (isPreviewMode) {
14730
- const orphanDiff = await detectOrphanFileDiff(existingToolFiles, toolFiles);
14731
- if (orphanDiff) hasDiff = true;
14732
- }
14733
- await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14762
+ const orphanCount = await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14763
+ if (orphanCount > 0) hasDiff = true;
14734
14764
  }
14735
14765
  } else if (config.getDelete()) {
14736
14766
  const existingToolFiles = await processor.loadToolFiles({ forDeletion: true });
14737
- if (isPreviewMode && existingToolFiles.length > 0) {
14738
- hasDiff = true;
14739
- }
14740
- await processor.removeOrphanAiFiles(existingToolFiles, []);
14767
+ const orphanCount = await processor.removeOrphanAiFiles(existingToolFiles, []);
14768
+ if (orphanCount > 0) hasDiff = true;
14741
14769
  }
14742
14770
  } catch (error) {
14743
14771
  logger.warn(
@@ -14772,19 +14800,13 @@ async function generateMcpCore(params) {
14772
14800
  });
14773
14801
  const rulesyncFiles = await processor.loadRulesyncFiles();
14774
14802
  const toolFiles = await processor.convertRulesyncFilesToToolFiles(rulesyncFiles);
14775
- if (isPreviewMode) {
14776
- const fileDiff = await detectFileDiff(toolFiles);
14777
- if (fileDiff) hasDiff = true;
14778
- }
14779
14803
  const writtenCount = await processor.writeAiFiles(toolFiles);
14780
14804
  totalCount += writtenCount;
14805
+ if (writtenCount > 0) hasDiff = true;
14781
14806
  if (config.getDelete()) {
14782
14807
  const existingToolFiles = await processor.loadToolFiles({ forDeletion: true });
14783
- if (isPreviewMode) {
14784
- const orphanDiff = await detectOrphanFileDiff(existingToolFiles, toolFiles);
14785
- if (orphanDiff) hasDiff = true;
14786
- }
14787
- await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14808
+ const orphanCount = await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14809
+ if (orphanCount > 0) hasDiff = true;
14788
14810
  }
14789
14811
  }
14790
14812
  }
@@ -14815,19 +14837,13 @@ async function generateCommandsCore(params) {
14815
14837
  });
14816
14838
  const rulesyncFiles = await processor.loadRulesyncFiles();
14817
14839
  const toolFiles = await processor.convertRulesyncFilesToToolFiles(rulesyncFiles);
14818
- if (isPreviewMode) {
14819
- const fileDiff = await detectFileDiff(toolFiles);
14820
- if (fileDiff) hasDiff = true;
14821
- }
14822
14840
  const writtenCount = await processor.writeAiFiles(toolFiles);
14823
14841
  totalCount += writtenCount;
14842
+ if (writtenCount > 0) hasDiff = true;
14824
14843
  if (config.getDelete()) {
14825
14844
  const existingToolFiles = await processor.loadToolFiles({ forDeletion: true });
14826
- if (isPreviewMode) {
14827
- const orphanDiff = await detectOrphanFileDiff(existingToolFiles, toolFiles);
14828
- if (orphanDiff) hasDiff = true;
14829
- }
14830
- await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14845
+ const orphanCount = await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14846
+ if (orphanCount > 0) hasDiff = true;
14831
14847
  }
14832
14848
  }
14833
14849
  }
@@ -14858,19 +14874,13 @@ async function generateSubagentsCore(params) {
14858
14874
  });
14859
14875
  const rulesyncFiles = await processor.loadRulesyncFiles();
14860
14876
  const toolFiles = await processor.convertRulesyncFilesToToolFiles(rulesyncFiles);
14861
- if (isPreviewMode) {
14862
- const fileDiff = await detectFileDiff(toolFiles);
14863
- if (fileDiff) hasDiff = true;
14864
- }
14865
14877
  const writtenCount = await processor.writeAiFiles(toolFiles);
14866
14878
  totalCount += writtenCount;
14879
+ if (writtenCount > 0) hasDiff = true;
14867
14880
  if (config.getDelete()) {
14868
14881
  const existingToolFiles = await processor.loadToolFiles({ forDeletion: true });
14869
- if (isPreviewMode) {
14870
- const orphanDiff = await detectOrphanFileDiff(existingToolFiles, toolFiles);
14871
- if (orphanDiff) hasDiff = true;
14872
- }
14873
- await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14882
+ const orphanCount = await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14883
+ if (orphanCount > 0) hasDiff = true;
14874
14884
  }
14875
14885
  }
14876
14886
  }
@@ -14907,19 +14917,13 @@ async function generateSkillsCore(params) {
14907
14917
  }
14908
14918
  }
14909
14919
  const toolDirs = await processor.convertRulesyncDirsToToolDirs(rulesyncDirs);
14910
- if (isPreviewMode) {
14911
- const dirDiff = await detectDirDiff(toolDirs);
14912
- if (dirDiff) hasDiff = true;
14913
- }
14914
14920
  const writtenCount = await processor.writeAiDirs(toolDirs);
14915
14921
  totalCount += writtenCount;
14922
+ if (writtenCount > 0) hasDiff = true;
14916
14923
  if (config.getDelete()) {
14917
14924
  const existingToolDirs = await processor.loadToolDirsToDelete();
14918
- if (isPreviewMode) {
14919
- const orphanDiff = await detectOrphanDirDiff(existingToolDirs, toolDirs);
14920
- if (orphanDiff) hasDiff = true;
14921
- }
14922
- await processor.removeOrphanAiDirs(existingToolDirs, toolDirs);
14925
+ const orphanCount = await processor.removeOrphanAiDirs(existingToolDirs, toolDirs);
14926
+ if (orphanCount > 0) hasDiff = true;
14923
14927
  }
14924
14928
  }
14925
14929
  }
@@ -14949,77 +14953,24 @@ async function generateHooksCore(params) {
14949
14953
  if (rulesyncFiles.length === 0) {
14950
14954
  if (config.getDelete()) {
14951
14955
  const existingToolFiles = await processor.loadToolFiles({ forDeletion: true });
14952
- if (isPreviewMode && existingToolFiles.length > 0) {
14953
- hasDiff = true;
14954
- }
14955
- await processor.removeOrphanAiFiles(existingToolFiles, []);
14956
+ const orphanCount = await processor.removeOrphanAiFiles(existingToolFiles, []);
14957
+ if (orphanCount > 0) hasDiff = true;
14956
14958
  }
14957
14959
  continue;
14958
14960
  }
14959
14961
  const toolFiles = await processor.convertRulesyncFilesToToolFiles(rulesyncFiles);
14960
- if (isPreviewMode) {
14961
- const fileDiff = await detectFileDiff(toolFiles);
14962
- if (fileDiff) hasDiff = true;
14963
- }
14964
14962
  const writtenCount = await processor.writeAiFiles(toolFiles);
14965
14963
  totalCount += writtenCount;
14964
+ if (writtenCount > 0) hasDiff = true;
14966
14965
  if (config.getDelete()) {
14967
14966
  const existingToolFiles = await processor.loadToolFiles({ forDeletion: true });
14968
- if (isPreviewMode) {
14969
- const orphanDiff = await detectOrphanFileDiff(existingToolFiles, toolFiles);
14970
- if (orphanDiff) hasDiff = true;
14971
- }
14972
- await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14967
+ const orphanCount = await processor.removeOrphanAiFiles(existingToolFiles, toolFiles);
14968
+ if (orphanCount > 0) hasDiff = true;
14973
14969
  }
14974
14970
  }
14975
14971
  }
14976
14972
  return { count: totalCount, hasDiff };
14977
14973
  }
14978
- async function detectFileDiff(aiFiles) {
14979
- for (const aiFile of aiFiles) {
14980
- const filePath = aiFile.getFilePath();
14981
- const newContent = addTrailingNewline(aiFile.getFileContent());
14982
- const existingContent = await readFileContentOrNull(filePath);
14983
- if (existingContent !== newContent) {
14984
- return true;
14985
- }
14986
- }
14987
- return false;
14988
- }
14989
- async function detectOrphanFileDiff(existingFiles, generatedFiles) {
14990
- const generatedPaths = new Set(generatedFiles.map((f) => f.getFilePath()));
14991
- const orphanFiles = existingFiles.filter((f) => !generatedPaths.has(f.getFilePath()));
14992
- return orphanFiles.length > 0;
14993
- }
14994
- async function detectDirDiff(aiDirs) {
14995
- for (const aiDir of aiDirs) {
14996
- const mainFile = aiDir.getMainFile();
14997
- if (mainFile) {
14998
- const mainFilePath = join108(aiDir.getDirPath(), mainFile.name);
14999
- const newContent = addTrailingNewline(
15000
- stringifyFrontmatter(mainFile.body, mainFile.frontmatter)
15001
- );
15002
- const existingContent = await readFileContentOrNull(mainFilePath);
15003
- if (existingContent !== newContent) {
15004
- return true;
15005
- }
15006
- }
15007
- for (const file of aiDir.getOtherFiles()) {
15008
- const filePath = join108(aiDir.getDirPath(), file.relativeFilePathToDirPath);
15009
- const newContent = addTrailingNewline(file.fileBuffer.toString("utf-8"));
15010
- const existingContent = await readFileContentOrNull(filePath);
15011
- if (existingContent !== newContent) {
15012
- return true;
15013
- }
15014
- }
15015
- }
15016
- return false;
15017
- }
15018
- async function detectOrphanDirDiff(existingDirs, generatedDirs) {
15019
- const generatedPaths = new Set(generatedDirs.map((d) => d.getDirPath()));
15020
- const orphanDirs = existingDirs.filter((d) => !generatedPaths.has(d.getDirPath()));
15021
- return orphanDirs.length > 0;
15022
- }
15023
14974
 
15024
14975
  // src/utils/result.ts
15025
14976
  function calculateTotalCount(result) {
@@ -15031,9 +14982,9 @@ function logFeatureResult(params) {
15031
14982
  const { count, featureName, isPreview, modePrefix } = params;
15032
14983
  if (count > 0) {
15033
14984
  if (isPreview) {
15034
- logger.info(`${modePrefix} Would generate ${count} ${featureName}`);
14985
+ logger.info(`${modePrefix} Would write ${count} ${featureName}`);
15035
14986
  } else {
15036
- logger.success(`Generated ${count} ${featureName}`);
14987
+ logger.success(`Written ${count} ${featureName}`);
15037
14988
  }
15038
14989
  }
15039
14990
  }
@@ -15128,7 +15079,7 @@ async function generateCommand(options) {
15128
15079
  const totalGenerated = calculateTotalCount(result);
15129
15080
  if (totalGenerated === 0) {
15130
15081
  const enabledFeatures = features.join(", ");
15131
- logger.warn(`\u26A0\uFE0F No files generated for enabled features: ${enabledFeatures}`);
15082
+ logger.info(`\u2713 All files are up to date (${enabledFeatures})`);
15132
15083
  return;
15133
15084
  }
15134
15085
  const parts = [];
@@ -15140,11 +15091,9 @@ async function generateCommand(options) {
15140
15091
  if (result.skillsCount > 0) parts.push(`${result.skillsCount} skills`);
15141
15092
  if (result.hooksCount > 0) parts.push(`${result.hooksCount} hooks`);
15142
15093
  if (isPreview) {
15143
- logger.info(
15144
- `${modePrefix} Would generate ${totalGenerated} file(s) total (${parts.join(" + ")})`
15145
- );
15094
+ logger.info(`${modePrefix} Would write ${totalGenerated} file(s) total (${parts.join(" + ")})`);
15146
15095
  } else {
15147
- logger.success(`\u{1F389} All done! Generated ${totalGenerated} file(s) total (${parts.join(" + ")})`);
15096
+ logger.success(`\u{1F389} All done! Written ${totalGenerated} file(s) total (${parts.join(" + ")})`);
15148
15097
  }
15149
15098
  if (check) {
15150
15099
  if (result.hasDiff) {
@@ -17570,7 +17519,7 @@ async function updateCommand(currentVersion, options) {
17570
17519
  }
17571
17520
 
17572
17521
  // src/cli/index.ts
17573
- var getVersion = () => "6.6.3";
17522
+ var getVersion = () => "6.7.1";
17574
17523
  var main = async () => {
17575
17524
  const program = new Command();
17576
17525
  const version = getVersion();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rulesync",
3
- "version": "6.6.3",
3
+ "version": "6.7.1",
4
4
  "description": "Unified AI rules management CLI tool that generates configuration files for various AI development tools",
5
5
  "keywords": [
6
6
  "ai",