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