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