rulesync 7.20.0 → 7.22.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/dist/cli/index.js CHANGED
@@ -28,6 +28,7 @@ import {
28
28
  RULESYNC_MCP_RELATIVE_FILE_PATH,
29
29
  RULESYNC_MCP_SCHEMA_URL,
30
30
  RULESYNC_OVERVIEW_FILE_NAME,
31
+ RULESYNC_PERMISSIONS_FILE_NAME,
31
32
  RULESYNC_RELATIVE_DIR_PATH,
32
33
  RULESYNC_RULES_RELATIVE_DIR_PATH,
33
34
  RULESYNC_SKILLS_RELATIVE_DIR_PATH,
@@ -63,18 +64,20 @@ import {
63
64
  importFromTool,
64
65
  isSymlink,
65
66
  listDirectoryFiles,
66
- logger,
67
67
  readFileContent,
68
68
  removeDirectory,
69
69
  removeFile,
70
70
  removeTempDirectory,
71
71
  stringifyFrontmatter,
72
72
  writeFileContent
73
- } from "../chunk-5OPNV62F.js";
73
+ } from "../chunk-4WYBBN4I.js";
74
74
 
75
75
  // src/cli/index.ts
76
76
  import { Command } from "commander";
77
77
 
78
+ // src/utils/parse-comma-separated-list.ts
79
+ var parseCommaSeparatedList = (value) => value.split(",").map((s) => s.trim()).filter(Boolean);
80
+
78
81
  // src/lib/fetch.ts
79
82
  import { join } from "path";
80
83
  import { Semaphore } from "es-toolkit/promise";
@@ -140,13 +143,14 @@ var GitHubClientError = class extends Error {
140
143
  this.name = "GitHubClientError";
141
144
  }
142
145
  };
143
- function logGitHubAuthHints(error) {
144
- logger.error(`GitHub API Error: ${error.message}`);
146
+ function logGitHubAuthHints(params) {
147
+ const { error, logger: logger5 } = params;
148
+ logger5.error(`GitHub API Error: ${error.message}`);
145
149
  if (error.statusCode === 401 || error.statusCode === 403) {
146
- logger.info(
150
+ logger5.info(
147
151
  "Tip: Set GITHUB_TOKEN or GH_TOKEN environment variable for private repositories or better rate limits."
148
152
  );
149
- logger.info(
153
+ logger5.info(
150
154
  "Tip: If you use GitHub CLI, you can use `GITHUB_TOKEN=$(gh auth token) rulesync fetch ...`"
151
155
  );
152
156
  }
@@ -534,7 +538,8 @@ var FEATURE_PATHS = {
534
538
  skills: ["skills"],
535
539
  ignore: [RULESYNC_AIIGNORE_FILE_NAME],
536
540
  mcp: [RULESYNC_MCP_FILE_NAME],
537
- hooks: [RULESYNC_HOOKS_FILE_NAME]
541
+ hooks: [RULESYNC_HOOKS_FILE_NAME],
542
+ permissions: [RULESYNC_PERMISSIONS_FILE_NAME]
538
543
  };
539
544
  function isToolTarget(target) {
540
545
  return target !== "rulesync";
@@ -563,38 +568,38 @@ async function processFeatureConversion(params) {
563
568
  return { paths };
564
569
  }
565
570
  async function convertFetchedFilesToRulesync(params) {
566
- const { tempDir, outputDir, target, features } = params;
571
+ const { tempDir, outputDir, target, features, logger: logger5 } = params;
567
572
  const convertedPaths = [];
568
573
  const featureConfigs = [
569
574
  {
570
575
  feature: "rules",
571
576
  getTargets: () => RulesProcessor.getToolTargets({ global: false }),
572
- createProcessor: () => new RulesProcessor({ baseDir: tempDir, toolTarget: target, global: false })
577
+ createProcessor: () => new RulesProcessor({ baseDir: tempDir, toolTarget: target, global: false, logger: logger5 })
573
578
  },
574
579
  {
575
580
  feature: "commands",
576
581
  getTargets: () => CommandsProcessor.getToolTargets({ global: false, includeSimulated: false }),
577
- createProcessor: () => new CommandsProcessor({ baseDir: tempDir, toolTarget: target, global: false })
582
+ createProcessor: () => new CommandsProcessor({ baseDir: tempDir, toolTarget: target, global: false, logger: logger5 })
578
583
  },
579
584
  {
580
585
  feature: "subagents",
581
586
  getTargets: () => SubagentsProcessor.getToolTargets({ global: false, includeSimulated: false }),
582
- createProcessor: () => new SubagentsProcessor({ baseDir: tempDir, toolTarget: target, global: false })
587
+ createProcessor: () => new SubagentsProcessor({ baseDir: tempDir, toolTarget: target, global: false, logger: logger5 })
583
588
  },
584
589
  {
585
590
  feature: "ignore",
586
591
  getTargets: () => IgnoreProcessor.getToolTargets(),
587
- createProcessor: () => new IgnoreProcessor({ baseDir: tempDir, toolTarget: target })
592
+ createProcessor: () => new IgnoreProcessor({ baseDir: tempDir, toolTarget: target, logger: logger5 })
588
593
  },
589
594
  {
590
595
  feature: "mcp",
591
596
  getTargets: () => McpProcessor.getToolTargets({ global: false }),
592
- createProcessor: () => new McpProcessor({ baseDir: tempDir, toolTarget: target, global: false })
597
+ createProcessor: () => new McpProcessor({ baseDir: tempDir, toolTarget: target, global: false, logger: logger5 })
593
598
  },
594
599
  {
595
600
  feature: "hooks",
596
601
  getTargets: () => HooksProcessor.getToolTargets({ global: false }),
597
- createProcessor: () => new HooksProcessor({ baseDir: tempDir, toolTarget: target, global: false })
602
+ createProcessor: () => new HooksProcessor({ baseDir: tempDir, toolTarget: target, global: false, logger: logger5 })
598
603
  }
599
604
  ];
600
605
  for (const config of featureConfigs) {
@@ -610,7 +615,7 @@ async function convertFetchedFilesToRulesync(params) {
610
615
  convertedPaths.push(...result.paths);
611
616
  }
612
617
  if (features.includes("skills")) {
613
- logger.debug(
618
+ logger5.debug(
614
619
  "Skills conversion is not yet supported in fetch command. Use import command instead."
615
620
  );
616
621
  }
@@ -639,7 +644,7 @@ function isNotFoundError(error) {
639
644
  return false;
640
645
  }
641
646
  async function fetchFiles(params) {
642
- const { source, options = {}, baseDir = process.cwd() } = params;
647
+ const { source, options = {}, baseDir = process.cwd(), logger: logger5 } = params;
643
648
  const parsed = parseSource(source);
644
649
  if (parsed.provider === "gitlab") {
645
650
  throw new Error(
@@ -658,7 +663,7 @@ async function fetchFiles(params) {
658
663
  });
659
664
  const token = GitHubClient.resolveToken(options.token);
660
665
  const client = new GitHubClient({ token });
661
- logger.debug(`Validating repository: ${parsed.owner}/${parsed.repo}`);
666
+ logger5.debug(`Validating repository: ${parsed.owner}/${parsed.repo}`);
662
667
  const isValid = await client.validateRepository(parsed.owner, parsed.repo);
663
668
  if (!isValid) {
664
669
  throw new GitHubClientError(
@@ -667,7 +672,7 @@ async function fetchFiles(params) {
667
672
  );
668
673
  }
669
674
  const ref = resolvedRef ?? await client.getDefaultBranch(parsed.owner, parsed.repo);
670
- logger.debug(`Using ref: ${ref}`);
675
+ logger5.debug(`Using ref: ${ref}`);
671
676
  if (isToolTarget(target)) {
672
677
  return fetchAndConvertToolFiles({
673
678
  client,
@@ -678,7 +683,8 @@ async function fetchFiles(params) {
678
683
  target,
679
684
  outputDir,
680
685
  baseDir,
681
- conflictStrategy
686
+ conflictStrategy,
687
+ logger: logger5
682
688
  });
683
689
  }
684
690
  const semaphore = new Semaphore(FETCH_CONCURRENCY_LIMIT);
@@ -689,10 +695,11 @@ async function fetchFiles(params) {
689
695
  basePath: resolvedPath,
690
696
  ref,
691
697
  enabledFeatures,
692
- semaphore
698
+ semaphore,
699
+ logger: logger5
693
700
  });
694
701
  if (filesToFetch.length === 0) {
695
- logger.warn(`No files found matching enabled features: ${enabledFeatures.join(", ")}`);
702
+ logger5.warn(`No files found matching enabled features: ${enabledFeatures.join(", ")}`);
696
703
  return {
697
704
  source: `${parsed.owner}/${parsed.repo}`,
698
705
  ref,
@@ -715,7 +722,7 @@ async function fetchFiles(params) {
715
722
  const localPath = join(outputBasePath, relativePath);
716
723
  const exists = await fileExists(localPath);
717
724
  if (exists && conflictStrategy === "skip") {
718
- logger.debug(`Skipping existing file: ${relativePath}`);
725
+ logger5.debug(`Skipping existing file: ${relativePath}`);
719
726
  return { relativePath, status: "skipped" };
720
727
  }
721
728
  const content = await withSemaphore(
@@ -724,7 +731,7 @@ async function fetchFiles(params) {
724
731
  );
725
732
  await writeFileContent(localPath, content);
726
733
  const status = exists ? "overwritten" : "created";
727
- logger.debug(`Wrote: ${relativePath} (${status})`);
734
+ logger5.debug(`Wrote: ${relativePath} (${status})`);
728
735
  return { relativePath, status };
729
736
  })
730
737
  );
@@ -739,7 +746,7 @@ async function fetchFiles(params) {
739
746
  return summary;
740
747
  }
741
748
  async function collectFeatureFiles(params) {
742
- const { client, owner, repo, basePath, ref, enabledFeatures, semaphore } = params;
749
+ const { client, owner, repo, basePath, ref, enabledFeatures, semaphore, logger: logger5 } = params;
743
750
  const dirCache = /* @__PURE__ */ new Map();
744
751
  async function getCachedDirectory(path2) {
745
752
  let promise = dirCache.get(path2);
@@ -772,7 +779,7 @@ async function collectFeatureFiles(params) {
772
779
  }
773
780
  } catch (error) {
774
781
  if (isNotFoundError(error)) {
775
- logger.debug(`File not found: ${fullPath}`);
782
+ logger5.debug(`File not found: ${fullPath}`);
776
783
  } else {
777
784
  throw error;
778
785
  }
@@ -797,7 +804,7 @@ async function collectFeatureFiles(params) {
797
804
  }
798
805
  } catch (error) {
799
806
  if (isNotFoundError(error)) {
800
- logger.debug(`Feature not found: ${fullPath}`);
807
+ logger5.debug(`Feature not found: ${fullPath}`);
801
808
  return collected;
802
809
  }
803
810
  throw error;
@@ -817,10 +824,11 @@ async function fetchAndConvertToolFiles(params) {
817
824
  target,
818
825
  outputDir,
819
826
  baseDir,
820
- conflictStrategy: _conflictStrategy
827
+ conflictStrategy: _conflictStrategy,
828
+ logger: logger5
821
829
  } = params;
822
830
  const tempDir = await createTempDirectory();
823
- logger.debug(`Created temp directory: ${tempDir}`);
831
+ logger5.debug(`Created temp directory: ${tempDir}`);
824
832
  const semaphore = new Semaphore(FETCH_CONCURRENCY_LIMIT);
825
833
  try {
826
834
  const filesToFetch = await collectFeatureFiles({
@@ -830,10 +838,11 @@ async function fetchAndConvertToolFiles(params) {
830
838
  basePath: resolvedPath,
831
839
  ref,
832
840
  enabledFeatures,
833
- semaphore
841
+ semaphore,
842
+ logger: logger5
834
843
  });
835
844
  if (filesToFetch.length === 0) {
836
- logger.warn(`No files found matching enabled features: ${enabledFeatures.join(", ")}`);
845
+ logger5.warn(`No files found matching enabled features: ${enabledFeatures.join(", ")}`);
837
846
  return {
838
847
  source: `${parsed.owner}/${parsed.repo}`,
839
848
  ref,
@@ -860,7 +869,7 @@ async function fetchAndConvertToolFiles(params) {
860
869
  () => client.getFileContent(parsed.owner, parsed.repo, remotePath, ref)
861
870
  );
862
871
  await writeFileContent(localPath, content);
863
- logger.debug(`Fetched to temp: ${toolRelativePath}`);
872
+ logger5.debug(`Fetched to temp: ${toolRelativePath}`);
864
873
  })
865
874
  );
866
875
  const outputBasePath = join(baseDir, outputDir);
@@ -868,13 +877,14 @@ async function fetchAndConvertToolFiles(params) {
868
877
  tempDir,
869
878
  outputDir: outputBasePath,
870
879
  target,
871
- features: enabledFeatures
880
+ features: enabledFeatures,
881
+ logger: logger5
872
882
  });
873
883
  const results = convertedPaths.map((relativePath) => ({
874
884
  relativePath,
875
885
  status: "created"
876
886
  }));
877
- logger.debug(`Converted ${converted} files from ${target} format to rulesync format`);
887
+ logger5.debug(`Converted ${converted} files from ${target} format to rulesync format`);
878
888
  return {
879
889
  source: `${parsed.owner}/${parsed.repo}`,
880
890
  ref,
@@ -981,29 +991,30 @@ function formatFetchSummary(summary) {
981
991
  }
982
992
 
983
993
  // src/cli/commands/fetch.ts
984
- async function fetchCommand(logger2, options) {
994
+ async function fetchCommand(logger5, options) {
985
995
  const { source, ...fetchOptions } = options;
986
- logger2.debug(`Fetching files from ${source}...`);
996
+ logger5.debug(`Fetching files from ${source}...`);
987
997
  try {
988
998
  const summary = await fetchFiles({
989
999
  source,
990
- options: fetchOptions
1000
+ options: fetchOptions,
1001
+ logger: logger5
991
1002
  });
992
- if (logger2.jsonMode) {
1003
+ if (logger5.jsonMode) {
993
1004
  const createdFiles = summary.files.filter((f) => f.status === "created").map((f) => f.relativePath);
994
1005
  const overwrittenFiles = summary.files.filter((f) => f.status === "overwritten").map((f) => f.relativePath);
995
1006
  const skippedFiles = summary.files.filter((f) => f.status === "skipped").map((f) => f.relativePath);
996
- logger2.captureData("source", source);
997
- logger2.captureData("path", fetchOptions.path);
998
- logger2.captureData("created", createdFiles);
999
- logger2.captureData("overwritten", overwrittenFiles);
1000
- logger2.captureData("skipped", skippedFiles);
1001
- logger2.captureData("totalFetched", summary.created + summary.overwritten + summary.skipped);
1007
+ logger5.captureData("source", source);
1008
+ logger5.captureData("path", fetchOptions.path);
1009
+ logger5.captureData("created", createdFiles);
1010
+ logger5.captureData("overwritten", overwrittenFiles);
1011
+ logger5.captureData("skipped", skippedFiles);
1012
+ logger5.captureData("totalFetched", summary.created + summary.overwritten + summary.skipped);
1002
1013
  }
1003
1014
  const output = formatFetchSummary(summary);
1004
- logger2.success(output);
1015
+ logger5.success(output);
1005
1016
  if (summary.created + summary.overwritten === 0 && summary.skipped === 0) {
1006
- logger2.warn("No files were fetched.");
1017
+ logger5.warn("No files were fetched.");
1007
1018
  }
1008
1019
  } catch (error) {
1009
1020
  if (error instanceof GitHubClientError) {
@@ -1020,55 +1031,55 @@ function calculateTotalCount(result) {
1020
1031
  }
1021
1032
 
1022
1033
  // src/cli/commands/generate.ts
1023
- function logFeatureResult(logger2, params) {
1034
+ function logFeatureResult(logger5, params) {
1024
1035
  const { count, paths, featureName, isPreview, modePrefix } = params;
1025
1036
  if (count > 0) {
1026
1037
  if (isPreview) {
1027
- logger2.info(`${modePrefix} Would write ${count} ${featureName}`);
1038
+ logger5.info(`${modePrefix} Would write ${count} ${featureName}`);
1028
1039
  } else {
1029
- logger2.success(`Written ${count} ${featureName}`);
1040
+ logger5.success(`Written ${count} ${featureName}`);
1030
1041
  }
1031
1042
  for (const p of paths) {
1032
- logger2.info(` ${p}`);
1043
+ logger5.info(` ${p}`);
1033
1044
  }
1034
1045
  }
1035
1046
  }
1036
- async function generateCommand(logger2, options) {
1047
+ async function generateCommand(logger5, options) {
1037
1048
  const config = await ConfigResolver.resolve(options);
1038
1049
  const check = config.getCheck();
1039
1050
  const isPreview = config.isPreviewMode();
1040
1051
  const modePrefix = isPreview ? "[DRY RUN]" : "";
1041
- logger2.debug("Generating files...");
1052
+ logger5.debug("Generating files...");
1042
1053
  if (!await checkRulesyncDirExists({ baseDir: process.cwd() })) {
1043
1054
  throw new CLIError(
1044
1055
  ".rulesync directory not found. Run 'rulesync init' first.",
1045
1056
  ErrorCodes.RULESYNC_DIR_NOT_FOUND
1046
1057
  );
1047
1058
  }
1048
- logger2.debug(`Base directories: ${config.getBaseDirs().join(", ")}`);
1059
+ logger5.debug(`Base directories: ${config.getBaseDirs().join(", ")}`);
1049
1060
  const features = config.getFeatures();
1050
1061
  if (features.includes("ignore")) {
1051
- logger2.debug("Generating ignore files...");
1062
+ logger5.debug("Generating ignore files...");
1052
1063
  }
1053
1064
  if (features.includes("mcp")) {
1054
- logger2.debug("Generating MCP files...");
1065
+ logger5.debug("Generating MCP files...");
1055
1066
  }
1056
1067
  if (features.includes("commands")) {
1057
- logger2.debug("Generating command files...");
1068
+ logger5.debug("Generating command files...");
1058
1069
  }
1059
1070
  if (features.includes("subagents")) {
1060
- logger2.debug("Generating subagent files...");
1071
+ logger5.debug("Generating subagent files...");
1061
1072
  }
1062
1073
  if (features.includes("skills")) {
1063
- logger2.debug("Generating skill files...");
1074
+ logger5.debug("Generating skill files...");
1064
1075
  }
1065
1076
  if (features.includes("hooks")) {
1066
- logger2.debug("Generating hooks...");
1077
+ logger5.debug("Generating hooks...");
1067
1078
  }
1068
1079
  if (features.includes("rules")) {
1069
- logger2.debug("Generating rule files...");
1080
+ logger5.debug("Generating rule files...");
1070
1081
  }
1071
- const result = await generate({ config });
1082
+ const result = await generate({ config, logger: logger5 });
1072
1083
  const totalGenerated = calculateTotalCount(result);
1073
1084
  const featureResults = {
1074
1085
  ignore: { count: result.ignoreCount, paths: result.ignorePaths },
@@ -1089,7 +1100,7 @@ async function generateCommand(logger2, options) {
1089
1100
  hooks: (count) => `${count === 1 ? "hooks file" : "hooks files"}`
1090
1101
  };
1091
1102
  for (const [feature, data] of Object.entries(featureResults)) {
1092
- logFeatureResult(logger2, {
1103
+ logFeatureResult(logger5, {
1093
1104
  count: data.count,
1094
1105
  paths: data.paths,
1095
1106
  featureName: featureLabels[feature]?.(data.count) ?? feature,
@@ -1097,15 +1108,15 @@ async function generateCommand(logger2, options) {
1097
1108
  modePrefix
1098
1109
  });
1099
1110
  }
1100
- if (logger2.jsonMode) {
1101
- logger2.captureData("features", featureResults);
1102
- logger2.captureData("totalFiles", totalGenerated);
1103
- logger2.captureData("hasDiff", result.hasDiff);
1104
- logger2.captureData("skills", result.skills ?? []);
1111
+ if (logger5.jsonMode) {
1112
+ logger5.captureData("features", featureResults);
1113
+ logger5.captureData("totalFiles", totalGenerated);
1114
+ logger5.captureData("hasDiff", result.hasDiff);
1115
+ logger5.captureData("skills", result.skills ?? []);
1105
1116
  }
1106
1117
  if (totalGenerated === 0) {
1107
1118
  const enabledFeatures = features.join(", ");
1108
- logger2.info(`\u2713 All files are up to date (${enabledFeatures})`);
1119
+ logger5.info(`\u2713 All files are up to date (${enabledFeatures})`);
1109
1120
  return;
1110
1121
  }
1111
1122
  const parts = [];
@@ -1117,9 +1128,9 @@ async function generateCommand(logger2, options) {
1117
1128
  if (result.skillsCount > 0) parts.push(`${result.skillsCount} skills`);
1118
1129
  if (result.hooksCount > 0) parts.push(`${result.hooksCount} hooks`);
1119
1130
  if (isPreview) {
1120
- logger2.info(`${modePrefix} Would write ${totalGenerated} file(s) total (${parts.join(" + ")})`);
1131
+ logger5.info(`${modePrefix} Would write ${totalGenerated} file(s) total (${parts.join(" + ")})`);
1121
1132
  } else {
1122
- logger2.success(`\u{1F389} All done! Written ${totalGenerated} file(s) total (${parts.join(" + ")})`);
1133
+ logger5.success(`\u{1F389} All done! Written ${totalGenerated} file(s) total (${parts.join(" + ")})`);
1123
1134
  }
1124
1135
  if (check) {
1125
1136
  if (result.hasDiff) {
@@ -1128,7 +1139,7 @@ async function generateCommand(logger2, options) {
1128
1139
  ErrorCodes.GENERATION_FAILED
1129
1140
  );
1130
1141
  } else {
1131
- logger2.success("\u2713 All files are up to date.");
1142
+ logger5.success("\u2713 All files are up to date.");
1132
1143
  }
1133
1144
  }
1134
1145
  }
@@ -1202,6 +1213,8 @@ var GITIGNORE_ENTRY_REGISTRY = [
1202
1213
  { target: "copilot", feature: "skills", entry: "**/.github/skills/" },
1203
1214
  { target: "copilot", feature: "hooks", entry: "**/.github/hooks/" },
1204
1215
  { target: "copilot", feature: "mcp", entry: "**/.vscode/mcp.json" },
1216
+ // GitHub Copilot CLI
1217
+ { target: "copilotcli", feature: "mcp", entry: "**/.copilot/mcp-config.json" },
1205
1218
  // Junie
1206
1219
  { target: "junie", feature: "rules", entry: "**/.junie/guidelines.md" },
1207
1220
  { target: "junie", feature: "mcp", entry: "**/.junie/mcp.json" },
@@ -1264,46 +1277,47 @@ var isFeatureSelected = (feature, target, features) => {
1264
1277
  if (targetFeatures.includes("*")) return true;
1265
1278
  return targetFeatures.includes(feature);
1266
1279
  };
1267
- var warnInvalidTargets = (targets) => {
1280
+ var warnInvalidTargets = (targets, logger5) => {
1268
1281
  const validTargets = new Set(ALL_TOOL_TARGETS_WITH_WILDCARD);
1269
1282
  for (const target of targets) {
1270
1283
  if (!validTargets.has(target)) {
1271
- logger.warn(
1284
+ logger5?.warn(
1272
1285
  `Unknown target '${target}'. Valid targets: ${ALL_TOOL_TARGETS_WITH_WILDCARD.join(", ")}`
1273
1286
  );
1274
1287
  }
1275
1288
  }
1276
1289
  };
1277
- var warnInvalidFeatures = (features) => {
1290
+ var warnInvalidFeatures = (features, logger5) => {
1278
1291
  const validFeatures = new Set(ALL_FEATURES_WITH_WILDCARD);
1292
+ const warned = /* @__PURE__ */ new Set();
1293
+ const warnOnce = (feature) => {
1294
+ if (!validFeatures.has(feature) && !warned.has(feature)) {
1295
+ warned.add(feature);
1296
+ logger5?.warn(
1297
+ `Unknown feature '${feature}'. Valid features: ${ALL_FEATURES_WITH_WILDCARD.join(", ")}`
1298
+ );
1299
+ }
1300
+ };
1279
1301
  if (Array.isArray(features)) {
1280
1302
  for (const feature of features) {
1281
- if (!validFeatures.has(feature)) {
1282
- logger.warn(
1283
- `Unknown feature '${feature}'. Valid features: ${ALL_FEATURES_WITH_WILDCARD.join(", ")}`
1284
- );
1285
- }
1303
+ warnOnce(feature);
1286
1304
  }
1287
1305
  } else {
1288
1306
  for (const targetFeatures of Object.values(features)) {
1289
1307
  if (!targetFeatures) continue;
1290
1308
  for (const feature of targetFeatures) {
1291
- if (!validFeatures.has(feature)) {
1292
- logger.warn(
1293
- `Unknown feature '${feature}'. Valid features: ${ALL_FEATURES_WITH_WILDCARD.join(", ")}`
1294
- );
1295
- }
1309
+ warnOnce(feature);
1296
1310
  }
1297
1311
  }
1298
1312
  }
1299
1313
  };
1300
1314
  var filterGitignoreEntries = (params) => {
1301
- const { targets, features } = params ?? {};
1315
+ const { targets, features, logger: logger5 } = params ?? {};
1302
1316
  if (targets && targets.length > 0) {
1303
- warnInvalidTargets(targets);
1317
+ warnInvalidTargets(targets, logger5);
1304
1318
  }
1305
1319
  if (features) {
1306
- warnInvalidFeatures(features);
1320
+ warnInvalidFeatures(features, logger5);
1307
1321
  }
1308
1322
  const seen = /* @__PURE__ */ new Set();
1309
1323
  const result = [];
@@ -1369,7 +1383,7 @@ var removeExistingRulesyncEntries = (content) => {
1369
1383
  }
1370
1384
  return result;
1371
1385
  };
1372
- var gitignoreCommand = async (logger2, options) => {
1386
+ var gitignoreCommand = async (logger5, options) => {
1373
1387
  const gitignorePath = join2(process.cwd(), ".gitignore");
1374
1388
  let gitignoreContent = "";
1375
1389
  if (await fileExists(gitignorePath)) {
@@ -1378,7 +1392,8 @@ var gitignoreCommand = async (logger2, options) => {
1378
1392
  const cleanedContent = removeExistingRulesyncEntries(gitignoreContent);
1379
1393
  const filteredEntries = filterGitignoreEntries({
1380
1394
  targets: options?.targets,
1381
- features: options?.features
1395
+ features: options?.features,
1396
+ logger: logger5
1382
1397
  });
1383
1398
  const existingEntries = new Set(
1384
1399
  gitignoreContent.split("\n").map((line) => line.trim()).filter((line) => line !== "" && !isRulesyncHeader(line))
@@ -1392,37 +1407,37 @@ ${rulesyncBlock}
1392
1407
  ` : `${rulesyncBlock}
1393
1408
  `;
1394
1409
  if (gitignoreContent === newContent) {
1395
- if (logger2.jsonMode) {
1396
- logger2.captureData("entriesAdded", []);
1397
- logger2.captureData("gitignorePath", gitignorePath);
1398
- logger2.captureData("alreadyExisted", filteredEntries);
1410
+ if (logger5.jsonMode) {
1411
+ logger5.captureData("entriesAdded", []);
1412
+ logger5.captureData("gitignorePath", gitignorePath);
1413
+ logger5.captureData("alreadyExisted", filteredEntries);
1399
1414
  }
1400
- logger2.success(".gitignore is already up to date");
1415
+ logger5.success(".gitignore is already up to date");
1401
1416
  return;
1402
1417
  }
1403
1418
  await writeFileContent(gitignorePath, newContent);
1404
- if (logger2.jsonMode) {
1405
- logger2.captureData("entriesAdded", entriesToAdd);
1406
- logger2.captureData("gitignorePath", gitignorePath);
1407
- logger2.captureData("alreadyExisted", alreadyExistedEntries);
1419
+ if (logger5.jsonMode) {
1420
+ logger5.captureData("entriesAdded", entriesToAdd);
1421
+ logger5.captureData("gitignorePath", gitignorePath);
1422
+ logger5.captureData("alreadyExisted", alreadyExistedEntries);
1408
1423
  }
1409
- logger2.success("Updated .gitignore with rulesync entries:");
1424
+ logger5.success("Updated .gitignore with rulesync entries:");
1410
1425
  for (const entry of filteredEntries) {
1411
- logger2.info(` ${entry}`);
1426
+ logger5.info(` ${entry}`);
1412
1427
  }
1413
- logger2.info("");
1414
- logger2.info(
1428
+ logger5.info("");
1429
+ logger5.info(
1415
1430
  "\u{1F4A1} If you're using Google Antigravity, note that rules, workflows, and skills won't load if they're gitignored."
1416
1431
  );
1417
- logger2.info(" You can add the following to .git/info/exclude instead:");
1418
- logger2.info(" **/.agent/rules/");
1419
- logger2.info(" **/.agent/workflows/");
1420
- logger2.info(" **/.agent/skills/");
1421
- logger2.info(" For more details: https://github.com/dyoshikawa/rulesync/issues/981");
1432
+ logger5.info(" You can add the following to .git/info/exclude instead:");
1433
+ logger5.info(" **/.agent/rules/");
1434
+ logger5.info(" **/.agent/workflows/");
1435
+ logger5.info(" **/.agent/skills/");
1436
+ logger5.info(" For more details: https://github.com/dyoshikawa/rulesync/issues/981");
1422
1437
  };
1423
1438
 
1424
1439
  // src/cli/commands/import.ts
1425
- async function importCommand(logger2, options) {
1440
+ async function importCommand(logger5, options) {
1426
1441
  if (!options.targets) {
1427
1442
  throw new CLIError("No tools found in --targets", ErrorCodes.IMPORT_FAILED);
1428
1443
  }
@@ -1431,17 +1446,17 @@ async function importCommand(logger2, options) {
1431
1446
  }
1432
1447
  const config = await ConfigResolver.resolve(options);
1433
1448
  const tool = config.getTargets()[0];
1434
- logger2.debug(`Importing files from ${tool}...`);
1435
- const result = await importFromTool({ config, tool });
1449
+ logger5.debug(`Importing files from ${tool}...`);
1450
+ const result = await importFromTool({ config, tool, logger: logger5 });
1436
1451
  const totalImported = calculateTotalCount(result);
1437
1452
  if (totalImported === 0) {
1438
1453
  const enabledFeatures = config.getFeatures().join(", ");
1439
- logger2.warn(`No files imported for enabled features: ${enabledFeatures}`);
1454
+ logger5.warn(`No files imported for enabled features: ${enabledFeatures}`);
1440
1455
  return;
1441
1456
  }
1442
- if (logger2.jsonMode) {
1443
- logger2.captureData("tool", tool);
1444
- logger2.captureData("features", {
1457
+ if (logger5.jsonMode) {
1458
+ logger5.captureData("tool", tool);
1459
+ logger5.captureData("features", {
1445
1460
  rules: { count: result.rulesCount },
1446
1461
  ignore: { count: result.ignoreCount },
1447
1462
  mcp: { count: result.mcpCount },
@@ -1450,7 +1465,7 @@ async function importCommand(logger2, options) {
1450
1465
  skills: { count: result.skillsCount },
1451
1466
  hooks: { count: result.hooksCount }
1452
1467
  });
1453
- logger2.captureData("totalFiles", totalImported);
1468
+ logger5.captureData("totalFiles", totalImported);
1454
1469
  }
1455
1470
  const parts = [];
1456
1471
  if (result.rulesCount > 0) parts.push(`${result.rulesCount} rules`);
@@ -1460,7 +1475,7 @@ async function importCommand(logger2, options) {
1460
1475
  if (result.subagentsCount > 0) parts.push(`${result.subagentsCount} subagents`);
1461
1476
  if (result.skillsCount > 0) parts.push(`${result.skillsCount} skills`);
1462
1477
  if (result.hooksCount > 0) parts.push(`${result.hooksCount} hooks`);
1463
- logger2.success(`Imported ${totalImported} file(s) total (${parts.join(" + ")})`);
1478
+ logger5.success(`Imported ${totalImported} file(s) total (${parts.join(" + ")})`);
1464
1479
  }
1465
1480
 
1466
1481
  // src/lib/init.ts
@@ -1689,8 +1704,8 @@ async function writeIfNotExists(path2, content) {
1689
1704
  }
1690
1705
 
1691
1706
  // src/cli/commands/init.ts
1692
- async function initCommand(logger2) {
1693
- logger2.debug("Initializing rulesync...");
1707
+ async function initCommand(logger5) {
1708
+ logger5.debug("Initializing rulesync...");
1694
1709
  await ensureDir(RULESYNC_RELATIVE_DIR_PATH);
1695
1710
  const result = await init();
1696
1711
  const createdFiles = [];
@@ -1698,29 +1713,29 @@ async function initCommand(logger2) {
1698
1713
  for (const file of result.sampleFiles) {
1699
1714
  if (file.created) {
1700
1715
  createdFiles.push(file.path);
1701
- logger2.success(`Created ${file.path}`);
1716
+ logger5.success(`Created ${file.path}`);
1702
1717
  } else {
1703
1718
  skippedFiles.push(file.path);
1704
- logger2.info(`Skipped ${file.path} (already exists)`);
1719
+ logger5.info(`Skipped ${file.path} (already exists)`);
1705
1720
  }
1706
1721
  }
1707
1722
  if (result.configFile.created) {
1708
1723
  createdFiles.push(result.configFile.path);
1709
- logger2.success(`Created ${result.configFile.path}`);
1724
+ logger5.success(`Created ${result.configFile.path}`);
1710
1725
  } else {
1711
1726
  skippedFiles.push(result.configFile.path);
1712
- logger2.info(`Skipped ${result.configFile.path} (already exists)`);
1727
+ logger5.info(`Skipped ${result.configFile.path} (already exists)`);
1713
1728
  }
1714
- if (logger2.jsonMode) {
1715
- logger2.captureData("created", createdFiles);
1716
- logger2.captureData("skipped", skippedFiles);
1729
+ if (logger5.jsonMode) {
1730
+ logger5.captureData("created", createdFiles);
1731
+ logger5.captureData("skipped", skippedFiles);
1717
1732
  }
1718
- logger2.success("rulesync initialized successfully!");
1719
- logger2.info("Next steps:");
1720
- logger2.info(
1733
+ logger5.success("rulesync initialized successfully!");
1734
+ logger5.info("Next steps:");
1735
+ logger5.info(
1721
1736
  `1. Edit ${RULESYNC_RELATIVE_DIR_PATH}/**/*.md, ${RULESYNC_RELATIVE_DIR_PATH}/skills/*/${SKILL_FILE_NAME}, ${RULESYNC_MCP_RELATIVE_FILE_PATH}, ${RULESYNC_HOOKS_RELATIVE_FILE_PATH} and ${RULESYNC_AIIGNORE_RELATIVE_FILE_PATH}`
1722
1737
  );
1723
- logger2.info("2. Run 'rulesync generate' to create configuration files");
1738
+ logger5.info("2. Run 'rulesync generate' to create configuration files");
1724
1739
  }
1725
1740
 
1726
1741
  // src/lib/sources.ts
@@ -1741,7 +1756,7 @@ var GitClientError = class extends Error {
1741
1756
  this.name = "GitClientError";
1742
1757
  }
1743
1758
  };
1744
- function validateGitUrl(url) {
1759
+ function validateGitUrl(url, options) {
1745
1760
  const ctrl = findControlCharacter(url);
1746
1761
  if (ctrl) {
1747
1762
  throw new GitClientError(
@@ -1754,7 +1769,7 @@ function validateGitUrl(url) {
1754
1769
  );
1755
1770
  }
1756
1771
  if (INSECURE_URL_SCHEMES.test(url)) {
1757
- logger.warn(
1772
+ options?.logger?.warn(
1758
1773
  `URL "${url}" uses an unencrypted protocol. Consider using https:// or ssh:// instead.`
1759
1774
  );
1760
1775
  }
@@ -1814,8 +1829,8 @@ async function resolveRefToSha(url, ref) {
1814
1829
  }
1815
1830
  }
1816
1831
  async function fetchSkillFiles(params) {
1817
- const { url, ref, skillsPath } = params;
1818
- validateGitUrl(url);
1832
+ const { url, ref, skillsPath, logger: logger5 } = params;
1833
+ validateGitUrl(url, { logger: logger5 });
1819
1834
  validateRef(ref);
1820
1835
  if (skillsPath.split(/[/\\]/).includes("..") || isAbsolute(skillsPath)) {
1821
1836
  throw new GitClientError(
@@ -1853,7 +1868,7 @@ async function fetchSkillFiles(params) {
1853
1868
  await execFileAsync("git", ["-C", tmpDir, "checkout"], { timeout: GIT_TIMEOUT_MS });
1854
1869
  const skillsDir = join4(tmpDir, skillsPath);
1855
1870
  if (!await directoryExists(skillsDir)) return [];
1856
- return await walkDirectory(skillsDir, skillsDir);
1871
+ return await walkDirectory(skillsDir, skillsDir, 0, { totalFiles: 0, totalSize: 0 }, logger5);
1857
1872
  } catch (error) {
1858
1873
  if (error instanceof GitClientError) throw error;
1859
1874
  throw new GitClientError(`Failed to fetch skill files from ${url}`, error);
@@ -1864,7 +1879,7 @@ async function fetchSkillFiles(params) {
1864
1879
  var MAX_WALK_DEPTH = 20;
1865
1880
  var MAX_TOTAL_FILES = 1e4;
1866
1881
  var MAX_TOTAL_SIZE = 100 * 1024 * 1024;
1867
- async function walkDirectory(dir, baseDir, depth = 0, ctx = { totalFiles: 0, totalSize: 0 }) {
1882
+ async function walkDirectory(dir, baseDir, depth = 0, ctx = { totalFiles: 0, totalSize: 0 }, logger5) {
1868
1883
  if (depth > MAX_WALK_DEPTH) {
1869
1884
  throw new GitClientError(
1870
1885
  `Directory tree exceeds max depth of ${MAX_WALK_DEPTH}: "${dir}". Aborting to prevent resource exhaustion.`
@@ -1875,15 +1890,15 @@ async function walkDirectory(dir, baseDir, depth = 0, ctx = { totalFiles: 0, tot
1875
1890
  if (name === ".git") continue;
1876
1891
  const fullPath = join4(dir, name);
1877
1892
  if (await isSymlink(fullPath)) {
1878
- logger.warn(`Skipping symlink "${fullPath}".`);
1893
+ logger5?.warn(`Skipping symlink "${fullPath}".`);
1879
1894
  continue;
1880
1895
  }
1881
1896
  if (await directoryExists(fullPath)) {
1882
- results.push(...await walkDirectory(fullPath, baseDir, depth + 1, ctx));
1897
+ results.push(...await walkDirectory(fullPath, baseDir, depth + 1, ctx, logger5));
1883
1898
  } else {
1884
1899
  const size = await getFileSize(fullPath);
1885
1900
  if (size > MAX_FILE_SIZE) {
1886
- logger.warn(
1901
+ logger5?.warn(
1887
1902
  `Skipping file "${fullPath}" (${(size / 1024 / 1024).toFixed(2)}MB exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit).`
1888
1903
  );
1889
1904
  continue;
@@ -1932,7 +1947,8 @@ var LegacyLockedSourceSchema = z4.object({
1932
1947
  var LegacySourcesLockSchema = z4.object({
1933
1948
  sources: z4.record(z4.string(), LegacyLockedSourceSchema)
1934
1949
  });
1935
- function migrateLegacyLock(legacy) {
1950
+ function migrateLegacyLock(params) {
1951
+ const { legacy, logger: logger5 } = params;
1936
1952
  const sources = {};
1937
1953
  for (const [key, entry] of Object.entries(legacy.sources)) {
1938
1954
  const skills = {};
@@ -1944,7 +1960,7 @@ function migrateLegacyLock(legacy) {
1944
1960
  skills
1945
1961
  };
1946
1962
  }
1947
- logger.info(
1963
+ logger5.info(
1948
1964
  "Migrated legacy sources lockfile to version 1. Run 'rulesync install --update' to populate integrity hashes."
1949
1965
  );
1950
1966
  return { lockfileVersion: LOCKFILE_VERSION, sources };
@@ -1953,9 +1969,10 @@ function createEmptyLock() {
1953
1969
  return { lockfileVersion: LOCKFILE_VERSION, sources: {} };
1954
1970
  }
1955
1971
  async function readLockFile(params) {
1972
+ const { logger: logger5 } = params;
1956
1973
  const lockPath = join5(params.baseDir, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH);
1957
1974
  if (!await fileExists(lockPath)) {
1958
- logger.debug("No sources lockfile found, starting fresh.");
1975
+ logger5.debug("No sources lockfile found, starting fresh.");
1959
1976
  return createEmptyLock();
1960
1977
  }
1961
1978
  try {
@@ -1967,24 +1984,25 @@ async function readLockFile(params) {
1967
1984
  }
1968
1985
  const legacyResult = LegacySourcesLockSchema.safeParse(data);
1969
1986
  if (legacyResult.success) {
1970
- return migrateLegacyLock(legacyResult.data);
1987
+ return migrateLegacyLock({ legacy: legacyResult.data, logger: logger5 });
1971
1988
  }
1972
- logger.warn(
1989
+ logger5.warn(
1973
1990
  `Invalid sources lockfile format (${RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH}). Starting fresh.`
1974
1991
  );
1975
1992
  return createEmptyLock();
1976
1993
  } catch {
1977
- logger.warn(
1994
+ logger5.warn(
1978
1995
  `Failed to read sources lockfile (${RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH}). Starting fresh.`
1979
1996
  );
1980
1997
  return createEmptyLock();
1981
1998
  }
1982
1999
  }
1983
2000
  async function writeLockFile(params) {
2001
+ const { logger: logger5 } = params;
1984
2002
  const lockPath = join5(params.baseDir, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH);
1985
2003
  const content = JSON.stringify(params.lock, null, 2) + "\n";
1986
2004
  await writeFileContent(lockPath, content);
1987
- logger.debug(`Wrote sources lockfile to ${lockPath}`);
2005
+ logger5.debug(`Wrote sources lockfile to ${lockPath}`);
1988
2006
  }
1989
2007
  function computeSkillIntegrity(files) {
1990
2008
  const hash = createHash("sha256");
@@ -2056,15 +2074,15 @@ function getLockedSkillNames(entry) {
2056
2074
 
2057
2075
  // src/lib/sources.ts
2058
2076
  async function resolveAndFetchSources(params) {
2059
- const { sources, baseDir, options = {} } = params;
2077
+ const { sources, baseDir, options = {}, logger: logger5 } = params;
2060
2078
  if (sources.length === 0) {
2061
2079
  return { fetchedSkillCount: 0, sourcesProcessed: 0 };
2062
2080
  }
2063
2081
  if (options.skipSources) {
2064
- logger.info("Skipping source fetching.");
2082
+ logger5.info("Skipping source fetching.");
2065
2083
  return { fetchedSkillCount: 0, sourcesProcessed: 0 };
2066
2084
  }
2067
- let lock = options.updateSources ? createEmptyLock() : await readLockFile({ baseDir });
2085
+ let lock = options.updateSources ? createEmptyLock() : await readLockFile({ baseDir, logger: logger5 });
2068
2086
  if (options.frozen) {
2069
2087
  const missingKeys = [];
2070
2088
  for (const source of sources) {
@@ -2097,7 +2115,8 @@ async function resolveAndFetchSources(params) {
2097
2115
  localSkillNames,
2098
2116
  alreadyFetchedSkillNames: allFetchedSkillNames,
2099
2117
  updateSources: options.updateSources ?? false,
2100
- frozen: options.frozen ?? false
2118
+ frozen: options.frozen ?? false,
2119
+ logger: logger5
2101
2120
  });
2102
2121
  } else {
2103
2122
  result = await fetchSource({
@@ -2107,7 +2126,8 @@ async function resolveAndFetchSources(params) {
2107
2126
  lock,
2108
2127
  localSkillNames,
2109
2128
  alreadyFetchedSkillNames: allFetchedSkillNames,
2110
- updateSources: options.updateSources ?? false
2129
+ updateSources: options.updateSources ?? false,
2130
+ logger: logger5
2111
2131
  });
2112
2132
  }
2113
2133
  const { skillCount, fetchedSkillNames, updatedLock } = result;
@@ -2117,11 +2137,11 @@ async function resolveAndFetchSources(params) {
2117
2137
  allFetchedSkillNames.add(name);
2118
2138
  }
2119
2139
  } catch (error) {
2120
- logger.error(`Failed to fetch source "${sourceEntry.source}": ${formatError(error)}`);
2140
+ logger5.error(`Failed to fetch source "${sourceEntry.source}": ${formatError(error)}`);
2121
2141
  if (error instanceof GitHubClientError) {
2122
- logGitHubAuthHints(error);
2142
+ logGitHubAuthHints({ error, logger: logger5 });
2123
2143
  } else if (error instanceof GitClientError) {
2124
- logGitClientHints(error);
2144
+ logGitClientHints({ error, logger: logger5 });
2125
2145
  }
2126
2146
  }
2127
2147
  }
@@ -2131,22 +2151,23 @@ async function resolveAndFetchSources(params) {
2131
2151
  if (sourceKeys.has(normalizeSourceKey(key))) {
2132
2152
  prunedSources[key] = value;
2133
2153
  } else {
2134
- logger.debug(`Pruned stale lockfile entry: ${key}`);
2154
+ logger5.debug(`Pruned stale lockfile entry: ${key}`);
2135
2155
  }
2136
2156
  }
2137
2157
  lock = { lockfileVersion: lock.lockfileVersion, sources: prunedSources };
2138
2158
  if (!options.frozen && JSON.stringify(lock) !== originalLockJson) {
2139
- await writeLockFile({ baseDir, lock });
2159
+ await writeLockFile({ baseDir, lock, logger: logger5 });
2140
2160
  } else {
2141
- logger.debug("Lockfile unchanged, skipping write.");
2161
+ logger5.debug("Lockfile unchanged, skipping write.");
2142
2162
  }
2143
2163
  return { fetchedSkillCount: totalSkillCount, sourcesProcessed: sources.length };
2144
2164
  }
2145
- function logGitClientHints(error) {
2165
+ function logGitClientHints(params) {
2166
+ const { error, logger: logger5 } = params;
2146
2167
  if (error.message.includes("not installed")) {
2147
- logger.info("Hint: Install git and ensure it is available on your PATH.");
2168
+ logger5.info("Hint: Install git and ensure it is available on your PATH.");
2148
2169
  } else {
2149
- logger.info("Hint: Check your git credentials (SSH keys, credential helper, or access token).");
2170
+ logger5.info("Hint: Check your git credentials (SSH keys, credential helper, or access token).");
2150
2171
  }
2151
2172
  }
2152
2173
  async function checkLockedSkillsExist(curatedDir, skillNames) {
@@ -2158,12 +2179,13 @@ async function checkLockedSkillsExist(curatedDir, skillNames) {
2158
2179
  }
2159
2180
  return true;
2160
2181
  }
2161
- async function cleanPreviousCuratedSkills(curatedDir, lockedSkillNames) {
2182
+ async function cleanPreviousCuratedSkills(params) {
2183
+ const { curatedDir, lockedSkillNames, logger: logger5 } = params;
2162
2184
  const resolvedCuratedDir = resolve(curatedDir);
2163
2185
  for (const prevSkill of lockedSkillNames) {
2164
2186
  const prevDir = join6(curatedDir, prevSkill);
2165
2187
  if (!resolve(prevDir).startsWith(resolvedCuratedDir + sep)) {
2166
- logger.warn(
2188
+ logger5.warn(
2167
2189
  `Skipping removal of "${prevSkill}": resolved path is outside the curated directory.`
2168
2190
  );
2169
2191
  continue;
@@ -2174,21 +2196,21 @@ async function cleanPreviousCuratedSkills(curatedDir, lockedSkillNames) {
2174
2196
  }
2175
2197
  }
2176
2198
  function shouldSkipSkill(params) {
2177
- const { skillName, sourceKey, localSkillNames, alreadyFetchedSkillNames } = params;
2199
+ const { skillName, sourceKey, localSkillNames, alreadyFetchedSkillNames, logger: logger5 } = params;
2178
2200
  if (skillName.includes("..") || skillName.includes("/") || skillName.includes("\\")) {
2179
- logger.warn(
2201
+ logger5.warn(
2180
2202
  `Skipping skill with invalid name "${skillName}" from ${sourceKey}: contains path traversal characters.`
2181
2203
  );
2182
2204
  return true;
2183
2205
  }
2184
2206
  if (localSkillNames.has(skillName)) {
2185
- logger.debug(
2207
+ logger5.debug(
2186
2208
  `Skipping remote skill "${skillName}" from ${sourceKey}: local skill takes precedence.`
2187
2209
  );
2188
2210
  return true;
2189
2211
  }
2190
2212
  if (alreadyFetchedSkillNames.has(skillName)) {
2191
- logger.warn(
2213
+ logger5.warn(
2192
2214
  `Skipping duplicate skill "${skillName}" from ${sourceKey}: already fetched from another source.`
2193
2215
  );
2194
2216
  return true;
@@ -2196,7 +2218,7 @@ function shouldSkipSkill(params) {
2196
2218
  return false;
2197
2219
  }
2198
2220
  async function writeSkillAndComputeIntegrity(params) {
2199
- const { skillName, files, curatedDir, locked, resolvedSha, sourceKey } = params;
2221
+ const { skillName, files, curatedDir, locked, resolvedSha, sourceKey, logger: logger5 } = params;
2200
2222
  const written = [];
2201
2223
  for (const file of files) {
2202
2224
  checkPathTraversal({
@@ -2209,14 +2231,23 @@ async function writeSkillAndComputeIntegrity(params) {
2209
2231
  const integrity = computeSkillIntegrity(written);
2210
2232
  const lockedSkillEntry = locked?.skills[skillName];
2211
2233
  if (lockedSkillEntry?.integrity && lockedSkillEntry.integrity !== integrity && resolvedSha === locked?.resolvedRef) {
2212
- logger.warn(
2234
+ logger5.warn(
2213
2235
  `Integrity mismatch for skill "${skillName}" from ${sourceKey}: expected "${lockedSkillEntry.integrity}", got "${integrity}". Content may have been tampered with.`
2214
2236
  );
2215
2237
  }
2216
2238
  return { integrity };
2217
2239
  }
2218
2240
  function buildLockUpdate(params) {
2219
- const { lock, sourceKey, fetchedSkills, locked, requestedRef, resolvedSha, remoteSkillNames } = params;
2241
+ const {
2242
+ lock,
2243
+ sourceKey,
2244
+ fetchedSkills,
2245
+ locked,
2246
+ requestedRef,
2247
+ resolvedSha,
2248
+ remoteSkillNames,
2249
+ logger: logger5
2250
+ } = params;
2220
2251
  const fetchedNames = Object.keys(fetchedSkills);
2221
2252
  const remoteSet = new Set(remoteSkillNames);
2222
2253
  const mergedSkills = { ...fetchedSkills };
@@ -2233,17 +2264,25 @@ function buildLockUpdate(params) {
2233
2264
  resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
2234
2265
  skills: mergedSkills
2235
2266
  });
2236
- logger.info(
2267
+ logger5.info(
2237
2268
  `Fetched ${fetchedNames.length} skill(s) from ${sourceKey}: ${fetchedNames.join(", ") || "(none)"}`
2238
2269
  );
2239
2270
  return { updatedLock, fetchedNames };
2240
2271
  }
2241
2272
  async function fetchSource(params) {
2242
- const { sourceEntry, client, baseDir, localSkillNames, alreadyFetchedSkillNames, updateSources } = params;
2273
+ const {
2274
+ sourceEntry,
2275
+ client,
2276
+ baseDir,
2277
+ localSkillNames,
2278
+ alreadyFetchedSkillNames,
2279
+ updateSources,
2280
+ logger: logger5
2281
+ } = params;
2243
2282
  const { lock } = params;
2244
2283
  const parsed = parseSource(sourceEntry.source);
2245
2284
  if (parsed.provider === "gitlab") {
2246
- logger.warn(`GitLab sources are not yet supported. Skipping "${sourceEntry.source}".`);
2285
+ logger5.warn(`GitLab sources are not yet supported. Skipping "${sourceEntry.source}".`);
2247
2286
  return { skillCount: 0, fetchedSkillNames: [], updatedLock: lock };
2248
2287
  }
2249
2288
  const sourceKey = sourceEntry.source;
@@ -2256,18 +2295,18 @@ async function fetchSource(params) {
2256
2295
  ref = locked.resolvedRef;
2257
2296
  resolvedSha = locked.resolvedRef;
2258
2297
  requestedRef = locked.requestedRef;
2259
- logger.debug(`Using locked ref for ${sourceKey}: ${resolvedSha}`);
2298
+ logger5.debug(`Using locked ref for ${sourceKey}: ${resolvedSha}`);
2260
2299
  } else {
2261
2300
  requestedRef = parsed.ref ?? await client.getDefaultBranch(parsed.owner, parsed.repo);
2262
2301
  resolvedSha = await client.resolveRefToSha(parsed.owner, parsed.repo, requestedRef);
2263
2302
  ref = resolvedSha;
2264
- logger.debug(`Resolved ${sourceKey} ref "${requestedRef}" to SHA: ${resolvedSha}`);
2303
+ logger5.debug(`Resolved ${sourceKey} ref "${requestedRef}" to SHA: ${resolvedSha}`);
2265
2304
  }
2266
2305
  const curatedDir = join6(baseDir, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH);
2267
2306
  if (locked && resolvedSha === locked.resolvedRef && !updateSources) {
2268
2307
  const allExist = await checkLockedSkillsExist(curatedDir, lockedSkillNames);
2269
2308
  if (allExist) {
2270
- logger.debug(`SHA unchanged for ${sourceKey}, skipping re-fetch.`);
2309
+ logger5.debug(`SHA unchanged for ${sourceKey}, skipping re-fetch.`);
2271
2310
  return {
2272
2311
  skillCount: 0,
2273
2312
  fetchedSkillNames: lockedSkillNames,
@@ -2284,7 +2323,7 @@ async function fetchSource(params) {
2284
2323
  remoteSkillDirs = entries.filter((e) => e.type === "dir").map((e) => ({ name: e.name, path: e.path }));
2285
2324
  } catch (error) {
2286
2325
  if (error instanceof GitHubClientError && error.statusCode === 404) {
2287
- logger.warn(`No skills/ directory found in ${sourceKey}. Skipping.`);
2326
+ logger5.warn(`No skills/ directory found in ${sourceKey}. Skipping.`);
2288
2327
  return { skillCount: 0, fetchedSkillNames: [], updatedLock: lock };
2289
2328
  }
2290
2329
  throw error;
@@ -2293,14 +2332,15 @@ async function fetchSource(params) {
2293
2332
  const semaphore = new Semaphore2(FETCH_CONCURRENCY_LIMIT);
2294
2333
  const fetchedSkills = {};
2295
2334
  if (locked) {
2296
- await cleanPreviousCuratedSkills(curatedDir, lockedSkillNames);
2335
+ await cleanPreviousCuratedSkills({ curatedDir, lockedSkillNames, logger: logger5 });
2297
2336
  }
2298
2337
  for (const skillDir of filteredDirs) {
2299
2338
  if (shouldSkipSkill({
2300
2339
  skillName: skillDir.name,
2301
2340
  sourceKey,
2302
2341
  localSkillNames,
2303
- alreadyFetchedSkillNames
2342
+ alreadyFetchedSkillNames,
2343
+ logger: logger5
2304
2344
  })) {
2305
2345
  continue;
2306
2346
  }
@@ -2314,7 +2354,7 @@ async function fetchSource(params) {
2314
2354
  });
2315
2355
  const files = allFiles.filter((file) => {
2316
2356
  if (file.size > MAX_FILE_SIZE) {
2317
- logger.warn(
2357
+ logger5.warn(
2318
2358
  `Skipping file "${file.path}" (${(file.size / 1024 / 1024).toFixed(2)}MB exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit).`
2319
2359
  );
2320
2360
  return false;
@@ -2336,9 +2376,10 @@ async function fetchSource(params) {
2336
2376
  curatedDir,
2337
2377
  locked,
2338
2378
  resolvedSha,
2339
- sourceKey
2379
+ sourceKey,
2380
+ logger: logger5
2340
2381
  });
2341
- logger.debug(`Fetched skill "${skillDir.name}" from ${sourceKey}`);
2382
+ logger5.debug(`Fetched skill "${skillDir.name}" from ${sourceKey}`);
2342
2383
  }
2343
2384
  const result = buildLockUpdate({
2344
2385
  lock,
@@ -2347,7 +2388,8 @@ async function fetchSource(params) {
2347
2388
  locked,
2348
2389
  requestedRef,
2349
2390
  resolvedSha,
2350
- remoteSkillNames: filteredDirs.map((d) => d.name)
2391
+ remoteSkillNames: filteredDirs.map((d) => d.name),
2392
+ logger: logger5
2351
2393
  });
2352
2394
  return {
2353
2395
  skillCount: result.fetchedNames.length,
@@ -2356,7 +2398,15 @@ async function fetchSource(params) {
2356
2398
  };
2357
2399
  }
2358
2400
  async function fetchSourceViaGit(params) {
2359
- const { sourceEntry, baseDir, localSkillNames, alreadyFetchedSkillNames, updateSources, frozen } = params;
2401
+ const {
2402
+ sourceEntry,
2403
+ baseDir,
2404
+ localSkillNames,
2405
+ alreadyFetchedSkillNames,
2406
+ updateSources,
2407
+ frozen,
2408
+ logger: logger5
2409
+ } = params;
2360
2410
  const { lock } = params;
2361
2411
  const url = sourceEntry.source;
2362
2412
  const locked = getLockedSource(lock, url);
@@ -2413,11 +2463,17 @@ async function fetchSourceViaGit(params) {
2413
2463
  const allNames = [...skillFileMap.keys()];
2414
2464
  const filteredNames = isWildcard ? allNames : allNames.filter((n) => skillFilter.includes(n));
2415
2465
  if (locked) {
2416
- await cleanPreviousCuratedSkills(curatedDir, lockedSkillNames);
2466
+ await cleanPreviousCuratedSkills({ curatedDir, lockedSkillNames, logger: logger5 });
2417
2467
  }
2418
2468
  const fetchedSkills = {};
2419
2469
  for (const skillName of filteredNames) {
2420
- if (shouldSkipSkill({ skillName, sourceKey: url, localSkillNames, alreadyFetchedSkillNames })) {
2470
+ if (shouldSkipSkill({
2471
+ skillName,
2472
+ sourceKey: url,
2473
+ localSkillNames,
2474
+ alreadyFetchedSkillNames,
2475
+ logger: logger5
2476
+ })) {
2421
2477
  continue;
2422
2478
  }
2423
2479
  fetchedSkills[skillName] = await writeSkillAndComputeIntegrity({
@@ -2426,7 +2482,8 @@ async function fetchSourceViaGit(params) {
2426
2482
  curatedDir,
2427
2483
  locked,
2428
2484
  resolvedSha,
2429
- sourceKey: url
2485
+ sourceKey: url,
2486
+ logger: logger5
2430
2487
  });
2431
2488
  }
2432
2489
  const result = buildLockUpdate({
@@ -2436,7 +2493,8 @@ async function fetchSourceViaGit(params) {
2436
2493
  locked,
2437
2494
  requestedRef,
2438
2495
  resolvedSha,
2439
- remoteSkillNames: filteredNames
2496
+ remoteSkillNames: filteredNames,
2497
+ logger: logger5
2440
2498
  });
2441
2499
  return {
2442
2500
  skillCount: result.fetchedNames.length,
@@ -2446,7 +2504,7 @@ async function fetchSourceViaGit(params) {
2446
2504
  }
2447
2505
 
2448
2506
  // src/cli/commands/install.ts
2449
- async function installCommand(logger2, options) {
2507
+ async function installCommand(logger5, options) {
2450
2508
  const config = await ConfigResolver.resolve({
2451
2509
  configPath: options.configPath,
2452
2510
  verbose: options.verbose,
@@ -2454,10 +2512,10 @@ async function installCommand(logger2, options) {
2454
2512
  });
2455
2513
  const sources = config.getSources();
2456
2514
  if (sources.length === 0) {
2457
- logger2.warn("No sources defined in configuration. Nothing to install.");
2515
+ logger5.warn("No sources defined in configuration. Nothing to install.");
2458
2516
  return;
2459
2517
  }
2460
- logger2.debug(`Installing skills from ${sources.length} source(s)...`);
2518
+ logger5.debug(`Installing skills from ${sources.length} source(s)...`);
2461
2519
  const result = await resolveAndFetchSources({
2462
2520
  sources,
2463
2521
  baseDir: process.cwd(),
@@ -2465,18 +2523,19 @@ async function installCommand(logger2, options) {
2465
2523
  updateSources: options.update,
2466
2524
  frozen: options.frozen,
2467
2525
  token: options.token
2468
- }
2526
+ },
2527
+ logger: logger5
2469
2528
  });
2470
- if (logger2.jsonMode) {
2471
- logger2.captureData("sourcesProcessed", result.sourcesProcessed);
2472
- logger2.captureData("skillsFetched", result.fetchedSkillCount);
2529
+ if (logger5.jsonMode) {
2530
+ logger5.captureData("sourcesProcessed", result.sourcesProcessed);
2531
+ logger5.captureData("skillsFetched", result.fetchedSkillCount);
2473
2532
  }
2474
2533
  if (result.fetchedSkillCount > 0) {
2475
- logger2.success(
2534
+ logger5.success(
2476
2535
  `Installed ${result.fetchedSkillCount} skill(s) from ${result.sourcesProcessed} source(s).`
2477
2536
  );
2478
2537
  } else {
2479
- logger2.success(`All skills up to date (${result.sourcesProcessed} source(s) checked).`);
2538
+ logger5.success(`All skills up to date (${result.sourcesProcessed} source(s) checked).`);
2480
2539
  }
2481
2540
  }
2482
2541
 
@@ -2489,6 +2548,7 @@ import { z as z13 } from "zod/mini";
2489
2548
  // src/mcp/commands.ts
2490
2549
  import { basename, join as join7 } from "path";
2491
2550
  import { z as z5 } from "zod/mini";
2551
+ var logger = new ConsoleLogger({ verbose: false, silent: true });
2492
2552
  var maxCommandSizeBytes = 1024 * 1024;
2493
2553
  var maxCommandsCount = 1e3;
2494
2554
  async function listCommands() {
@@ -2707,7 +2767,8 @@ async function executeGenerate(options = {}) {
2707
2767
  verbose: false,
2708
2768
  silent: true
2709
2769
  });
2710
- const generateResult = await generate({ config });
2770
+ const logger5 = new ConsoleLogger({ verbose: false, silent: true });
2771
+ const generateResult = await generate({ config, logger: logger5 });
2711
2772
  return buildSuccessResponse({ generateResult, config });
2712
2773
  } catch (error) {
2713
2774
  return {
@@ -2885,7 +2946,8 @@ async function executeImport(options) {
2885
2946
  silent: true
2886
2947
  });
2887
2948
  const tool = config.getTargets()[0];
2888
- const importResult = await importFromTool({ config, tool });
2949
+ const logger5 = new ConsoleLogger({ verbose: false, silent: true });
2950
+ const importResult = await importFromTool({ config, tool, logger: logger5 });
2889
2951
  return buildSuccessResponse2({ importResult, config, tool });
2890
2952
  } catch (error) {
2891
2953
  return {
@@ -3070,6 +3132,7 @@ var mcpTools = {
3070
3132
  // src/mcp/rules.ts
3071
3133
  import { basename as basename2, join as join10 } from "path";
3072
3134
  import { z as z10 } from "zod/mini";
3135
+ var logger2 = new ConsoleLogger({ verbose: false, silent: true });
3073
3136
  var maxRuleSizeBytes = 1024 * 1024;
3074
3137
  var maxRulesCount = 1e3;
3075
3138
  async function listRules() {
@@ -3090,14 +3153,14 @@ async function listRules() {
3090
3153
  frontmatter
3091
3154
  };
3092
3155
  } catch (error) {
3093
- logger.error(`Failed to read rule file ${file}: ${formatError(error)}`);
3156
+ logger2.error(`Failed to read rule file ${file}: ${formatError(error)}`);
3094
3157
  return null;
3095
3158
  }
3096
3159
  })
3097
3160
  );
3098
3161
  return rules.filter((rule) => rule !== null);
3099
3162
  } catch (error) {
3100
- logger.error(
3163
+ logger2.error(
3101
3164
  `Failed to read rules directory (${RULESYNC_RULES_RELATIVE_DIR_PATH}): ${formatError(error)}`
3102
3165
  );
3103
3166
  return [];
@@ -3252,6 +3315,7 @@ var ruleTools = {
3252
3315
  // src/mcp/skills.ts
3253
3316
  import { basename as basename3, dirname, join as join11 } from "path";
3254
3317
  import { z as z11 } from "zod/mini";
3318
+ var logger3 = new ConsoleLogger({ verbose: false, silent: true });
3255
3319
  var maxSkillSizeBytes = 1024 * 1024;
3256
3320
  var maxSkillsCount = 1e3;
3257
3321
  function aiDirFileToMcpSkillFile(file) {
@@ -3291,14 +3355,14 @@ async function listSkills() {
3291
3355
  frontmatter
3292
3356
  };
3293
3357
  } catch (error) {
3294
- logger.error(`Failed to read skill directory ${dirName}: ${formatError(error)}`);
3358
+ logger3.error(`Failed to read skill directory ${dirName}: ${formatError(error)}`);
3295
3359
  return null;
3296
3360
  }
3297
3361
  })
3298
3362
  );
3299
3363
  return skills.filter((skill) => skill !== null);
3300
3364
  } catch (error) {
3301
- logger.error(
3365
+ logger3.error(
3302
3366
  `Failed to read skills directory (${RULESYNC_SKILLS_RELATIVE_DIR_PATH}): ${formatError(error)}`
3303
3367
  );
3304
3368
  return [];
@@ -3490,6 +3554,7 @@ var skillTools = {
3490
3554
  // src/mcp/subagents.ts
3491
3555
  import { basename as basename4, join as join12 } from "path";
3492
3556
  import { z as z12 } from "zod/mini";
3557
+ var logger4 = new ConsoleLogger({ verbose: false, silent: true });
3493
3558
  var maxSubagentSizeBytes = 1024 * 1024;
3494
3559
  var maxSubagentsCount = 1e3;
3495
3560
  async function listSubagents() {
@@ -3510,7 +3575,7 @@ async function listSubagents() {
3510
3575
  frontmatter
3511
3576
  };
3512
3577
  } catch (error) {
3513
- logger.error(`Failed to read subagent file ${file}: ${formatError(error)}`);
3578
+ logger4.error(`Failed to read subagent file ${file}: ${formatError(error)}`);
3514
3579
  return null;
3515
3580
  }
3516
3581
  })
@@ -3519,7 +3584,7 @@ async function listSubagents() {
3519
3584
  (subagent) => subagent !== null
3520
3585
  );
3521
3586
  } catch (error) {
3522
- logger.error(
3587
+ logger4.error(
3523
3588
  `Failed to read subagents directory (${RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH}): ${formatError(error)}`
3524
3589
  );
3525
3590
  return [];
@@ -3891,7 +3956,7 @@ var rulesyncTool = {
3891
3956
  };
3892
3957
 
3893
3958
  // src/cli/commands/mcp.ts
3894
- async function mcpCommand(logger2, { version }) {
3959
+ async function mcpCommand(logger5, { version }) {
3895
3960
  const server = new FastMCP({
3896
3961
  name: "Rulesync MCP Server",
3897
3962
  // eslint-disable-next-line no-type-assertion/no-type-assertion
@@ -3899,7 +3964,7 @@ async function mcpCommand(logger2, { version }) {
3899
3964
  instructions: "This server handles Rulesync files including rules, commands, MCP, ignore files, subagents and skills for any AI agents. It should be used when you need those files."
3900
3965
  });
3901
3966
  server.addTool(rulesyncTool);
3902
- logger2.info("Rulesync MCP server started via stdio");
3967
+ logger5.info("Rulesync MCP server started via stdio");
3903
3968
  void server.start({
3904
3969
  transportType: "stdio"
3905
3970
  });
@@ -4215,43 +4280,43 @@ To upgrade, run:
4215
4280
  }
4216
4281
 
4217
4282
  // src/cli/commands/update.ts
4218
- async function updateCommand(logger2, currentVersion, options) {
4283
+ async function updateCommand(logger5, currentVersion, options) {
4219
4284
  const { check = false, force = false, token } = options;
4220
4285
  try {
4221
4286
  const environment = detectExecutionEnvironment();
4222
- logger2.debug(`Detected environment: ${environment}`);
4287
+ logger5.debug(`Detected environment: ${environment}`);
4223
4288
  if (environment === "npm") {
4224
- logger2.info(getNpmUpgradeInstructions());
4289
+ logger5.info(getNpmUpgradeInstructions());
4225
4290
  return;
4226
4291
  }
4227
4292
  if (environment === "homebrew") {
4228
- logger2.info(getHomebrewUpgradeInstructions());
4293
+ logger5.info(getHomebrewUpgradeInstructions());
4229
4294
  return;
4230
4295
  }
4231
4296
  if (check) {
4232
- logger2.info("Checking for updates...");
4297
+ logger5.info("Checking for updates...");
4233
4298
  const updateCheck = await checkForUpdate(currentVersion, token);
4234
- if (logger2.jsonMode) {
4235
- logger2.captureData("currentVersion", updateCheck.currentVersion);
4236
- logger2.captureData("latestVersion", updateCheck.latestVersion);
4237
- logger2.captureData("updateAvailable", updateCheck.hasUpdate);
4238
- logger2.captureData(
4299
+ if (logger5.jsonMode) {
4300
+ logger5.captureData("currentVersion", updateCheck.currentVersion);
4301
+ logger5.captureData("latestVersion", updateCheck.latestVersion);
4302
+ logger5.captureData("updateAvailable", updateCheck.hasUpdate);
4303
+ logger5.captureData(
4239
4304
  "message",
4240
4305
  updateCheck.hasUpdate ? `Update available: ${updateCheck.currentVersion} -> ${updateCheck.latestVersion}` : `Already at the latest version (${updateCheck.currentVersion})`
4241
4306
  );
4242
4307
  }
4243
4308
  if (updateCheck.hasUpdate) {
4244
- logger2.success(
4309
+ logger5.success(
4245
4310
  `Update available: ${updateCheck.currentVersion} -> ${updateCheck.latestVersion}`
4246
4311
  );
4247
4312
  } else {
4248
- logger2.info(`Already at the latest version (${updateCheck.currentVersion})`);
4313
+ logger5.info(`Already at the latest version (${updateCheck.currentVersion})`);
4249
4314
  }
4250
4315
  return;
4251
4316
  }
4252
- logger2.info("Checking for updates...");
4317
+ logger5.info("Checking for updates...");
4253
4318
  const message = await performBinaryUpdate(currentVersion, { force, token });
4254
- logger2.success(message);
4319
+ logger5.success(message);
4255
4320
  } catch (error) {
4256
4321
  if (error instanceof GitHubClientError) {
4257
4322
  const authHint = error.statusCode === 401 || error.statusCode === 403 ? " Tip: Set GITHUB_TOKEN or GH_TOKEN environment variable, or use `GITHUB_TOKEN=$(gh auth token) rulesync update ...`" : "";
@@ -4269,54 +4334,68 @@ async function updateCommand(logger2, currentVersion, options) {
4269
4334
  }
4270
4335
  }
4271
4336
 
4272
- // src/cli/index.ts
4273
- var getVersion = () => "7.20.0";
4274
- function wrapCommand(name, errorCode, handler) {
4337
+ // src/cli/wrap-command.ts
4338
+ function createLogger({
4339
+ name,
4340
+ globalOpts,
4341
+ getVersion: getVersion2
4342
+ }) {
4343
+ return globalOpts.json ? new JsonLogger({ command: name, version: getVersion2() }) : new ConsoleLogger();
4344
+ }
4345
+ function wrapCommand({
4346
+ name,
4347
+ errorCode,
4348
+ handler,
4349
+ getVersion: getVersion2,
4350
+ loggerFactory = createLogger
4351
+ }) {
4275
4352
  return async (...args) => {
4276
4353
  const command = args[args.length - 1];
4277
4354
  const options = args[args.length - 2];
4278
4355
  const positionalArgs = args.slice(0, -2);
4279
4356
  const globalOpts = command.parent?.opts() ?? {};
4280
- const logger2 = globalOpts.json ? new JsonLogger({ command: name, version: getVersion() }) : new ConsoleLogger();
4281
- logger2.configure({
4357
+ const logger5 = loggerFactory({ name, globalOpts, getVersion: getVersion2 });
4358
+ logger5.configure({
4282
4359
  verbose: Boolean(globalOpts.verbose) || Boolean(options.verbose),
4283
4360
  silent: Boolean(globalOpts.silent) || Boolean(options.silent)
4284
4361
  });
4285
4362
  try {
4286
- await handler(logger2, options, globalOpts, positionalArgs);
4287
- logger2.outputJson(true);
4363
+ await handler(logger5, options, globalOpts, positionalArgs);
4364
+ logger5.outputJson(true);
4288
4365
  } catch (error) {
4289
4366
  const code = error instanceof CLIError ? error.code : errorCode;
4290
4367
  const errorArg = error instanceof Error ? error : formatError(error);
4291
- logger2.error(errorArg, code);
4368
+ logger5.error(errorArg, code);
4292
4369
  process.exit(error instanceof CLIError ? error.exitCode : 1);
4293
4370
  }
4294
4371
  };
4295
4372
  }
4373
+
4374
+ // src/cli/index.ts
4375
+ var getVersion = () => "7.22.0";
4376
+ function wrapCommand2(name, errorCode, handler) {
4377
+ return wrapCommand({ name, errorCode, handler, getVersion });
4378
+ }
4296
4379
  var main = async () => {
4297
4380
  const program = new Command();
4298
4381
  const version = getVersion();
4299
4382
  program.name("rulesync").description("Unified AI rules management CLI tool").version(version, "-v, --version", "Show version").option("-j, --json", "Output results as JSON");
4300
4383
  program.command("init").description("Initialize rulesync in current directory").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
4301
- wrapCommand("init", "INIT_FAILED", async (logger2) => {
4302
- await initCommand(logger2);
4384
+ wrapCommand2("init", "INIT_FAILED", async (logger5) => {
4385
+ await initCommand(logger5);
4303
4386
  })
4304
4387
  );
4305
4388
  program.command("gitignore").description("Add generated files to .gitignore").option(
4306
4389
  "-t, --targets <tools>",
4307
4390
  "Comma-separated list of tools to include (e.g., 'claudecode,copilot' or '*' for all)",
4308
- (value) => {
4309
- return value.split(",").map((t) => t.trim()).filter(Boolean);
4310
- }
4391
+ parseCommaSeparatedList
4311
4392
  ).option(
4312
4393
  "-f, --features <features>",
4313
4394
  `Comma-separated list of features to include (${ALL_FEATURES.join(",")}) or '*' for all`,
4314
- (value) => {
4315
- return value.split(",").map((f) => f.trim()).filter(Boolean);
4316
- }
4395
+ parseCommaSeparatedList
4317
4396
  ).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
4318
- wrapCommand("gitignore", "GITIGNORE_FAILED", async (logger2, options) => {
4319
- await gitignoreCommand(logger2, {
4397
+ wrapCommand2("gitignore", "GITIGNORE_FAILED", async (logger5, options) => {
4398
+ await gitignoreCommand(logger5, {
4320
4399
  // eslint-disable-next-line no-type-assertion/no-type-assertion
4321
4400
  targets: options.targets,
4322
4401
  // eslint-disable-next-line no-type-assertion/no-type-assertion
@@ -4330,40 +4409,40 @@ var main = async () => {
4330
4409
  ).option(
4331
4410
  "-f, --features <features>",
4332
4411
  `Comma-separated list of features to fetch (${ALL_FEATURES.join(",")}) or '*' for all`,
4333
- (value) => value.split(",").map((f) => f.trim())
4412
+ parseCommaSeparatedList
4334
4413
  ).option("-r, --ref <ref>", "Branch, tag, or commit SHA to fetch from").option("-p, --path <path>", "Subdirectory path within the repository").option("-o, --output <dir>", "Output directory (default: .rulesync)").option(
4335
4414
  "-c, --conflict <strategy>",
4336
4415
  "Conflict resolution strategy: skip, overwrite (default: overwrite)"
4337
4416
  ).option("--token <token>", "Git provider token for private repositories").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
4338
- wrapCommand("fetch", "FETCH_FAILED", async (logger2, options, _globalOpts, positionalArgs) => {
4417
+ wrapCommand2("fetch", "FETCH_FAILED", async (logger5, options, _globalOpts, positionalArgs) => {
4339
4418
  const source = positionalArgs[0];
4340
- await fetchCommand(logger2, { ...options, source });
4419
+ await fetchCommand(logger5, { ...options, source });
4341
4420
  })
4342
4421
  );
4343
4422
  program.command("import").description("Import configurations from AI tools to rulesync format").option(
4344
4423
  "-t, --targets <tool>",
4345
4424
  "Tool to import from (e.g., 'copilot', 'cursor', 'cline')",
4346
- (value) => value.split(",").map((t) => t.trim())
4425
+ parseCommaSeparatedList
4347
4426
  ).option(
4348
4427
  "-f, --features <features>",
4349
4428
  `Comma-separated list of features to import (${ALL_FEATURES.join(",")}) or '*' for all`,
4350
- (value) => value.split(",").map((f) => f.trim())
4429
+ parseCommaSeparatedList
4351
4430
  ).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").option("-g, --global", "Import for global(user scope) configuration files").action(
4352
- wrapCommand("import", "IMPORT_FAILED", async (logger2, options) => {
4353
- await importCommand(logger2, options);
4431
+ wrapCommand2("import", "IMPORT_FAILED", async (logger5, options) => {
4432
+ await importCommand(logger5, options);
4354
4433
  })
4355
4434
  );
4356
4435
  program.command("mcp").description("Start MCP server for rulesync").action(
4357
- wrapCommand("mcp", "MCP_FAILED", async (logger2, _options) => {
4358
- await mcpCommand(logger2, { version });
4436
+ wrapCommand2("mcp", "MCP_FAILED", async (logger5, _options) => {
4437
+ await mcpCommand(logger5, { version });
4359
4438
  })
4360
4439
  );
4361
4440
  program.command("install").description("Install skills from declarative sources in rulesync.jsonc").option("--update", "Force re-resolve all source refs, ignoring lockfile").option(
4362
4441
  "--frozen",
4363
4442
  "Fail if lockfile is missing or out of sync (for CI); fetches missing skills using locked refs"
4364
4443
  ).option("--token <token>", "GitHub token for private repos").option("-c, --config <path>", "Path to configuration file").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
4365
- wrapCommand("install", "INSTALL_FAILED", async (logger2, options) => {
4366
- await installCommand(logger2, {
4444
+ wrapCommand2("install", "INSTALL_FAILED", async (logger5, options) => {
4445
+ await installCommand(logger5, {
4367
4446
  // eslint-disable-next-line no-type-assertion/no-type-assertion
4368
4447
  update: options.update,
4369
4448
  // eslint-disable-next-line no-type-assertion/no-type-assertion
@@ -4382,15 +4461,15 @@ var main = async () => {
4382
4461
  program.command("generate").description("Generate configuration files for AI tools").option(
4383
4462
  "-t, --targets <tools>",
4384
4463
  "Comma-separated list of tools to generate for (e.g., 'copilot,cursor,cline' or '*' for all)",
4385
- (value) => value.split(",").map((t) => t.trim())
4464
+ parseCommaSeparatedList
4386
4465
  ).option(
4387
4466
  "-f, --features <features>",
4388
4467
  `Comma-separated list of features to generate (${ALL_FEATURES.join(",")}) or '*' for all`,
4389
- (value) => value.split(",").map((f) => f.trim())
4468
+ parseCommaSeparatedList
4390
4469
  ).option("--delete", "Delete all existing files in output directories before generating").option(
4391
4470
  "-b, --base-dir <paths>",
4392
4471
  "Base directories to generate files (comma-separated for multiple paths)",
4393
- (value) => value.split(",").map((p) => p.trim())
4472
+ parseCommaSeparatedList
4394
4473
  ).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").option("-c, --config <path>", "Path to configuration file").option("-g, --global", "Generate for global(user scope) configuration files").option(
4395
4474
  "--simulate-commands",
4396
4475
  "Generate simulated commands. This feature is only available for copilot, cursor and codexcli."
@@ -4401,13 +4480,13 @@ var main = async () => {
4401
4480
  "--simulate-skills",
4402
4481
  "Generate simulated skills. This feature is only available for copilot, cursor and codexcli."
4403
4482
  ).option("--dry-run", "Dry run: show changes without writing files").option("--check", "Check if files are up to date (exits with code 1 if changes needed)").action(
4404
- wrapCommand("generate", "GENERATION_FAILED", async (logger2, options) => {
4405
- await generateCommand(logger2, options);
4483
+ wrapCommand2("generate", "GENERATION_FAILED", async (logger5, options) => {
4484
+ await generateCommand(logger5, options);
4406
4485
  })
4407
4486
  );
4408
4487
  program.command("update").description("Update rulesync to the latest version").option("--check", "Check for updates without installing").option("--force", "Force update even if already at latest version").option("--token <token>", "GitHub token for API access").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
4409
- wrapCommand("update", "UPDATE_FAILED", async (logger2, options) => {
4410
- await updateCommand(logger2, version, options);
4488
+ wrapCommand2("update", "UPDATE_FAILED", async (logger5, options) => {
4489
+ await updateCommand(logger5, version, options);
4411
4490
  })
4412
4491
  );
4413
4492
  program.parse();