rulesync 7.18.2 → 7.19.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.
@@ -26,9 +26,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  // src/cli/index.ts
27
27
  var import_commander = require("commander");
28
28
 
29
- // src/constants/announcements.ts
30
- var ANNOUNCEMENT = "".trim();
31
-
32
29
  // src/types/features.ts
33
30
  var import_mini = require("zod/mini");
34
31
  var ALL_FEATURES = [
@@ -48,6 +45,32 @@ var RulesyncFeaturesSchema = import_mini.z.union([
48
45
  import_mini.z.record(import_mini.z.string(), import_mini.z.array(import_mini.z.enum(ALL_FEATURES_WITH_WILDCARD)))
49
46
  ]);
50
47
 
48
+ // src/types/json-output.ts
49
+ var ErrorCodes = {
50
+ CONFIG_NOT_FOUND: "CONFIG_NOT_FOUND",
51
+ RULESYNC_DIR_NOT_FOUND: "RULESYNC_DIR_NOT_FOUND",
52
+ INVALID_TARGET: "INVALID_TARGET",
53
+ FETCH_FAILED: "FETCH_FAILED",
54
+ WRITE_FAILED: "WRITE_FAILED",
55
+ VALIDATION_FAILED: "VALIDATION_FAILED",
56
+ GENERATION_FAILED: "GENERATION_FAILED",
57
+ IMPORT_FAILED: "IMPORT_FAILED",
58
+ INSTALL_FAILED: "INSTALL_FAILED",
59
+ UPDATE_FAILED: "UPDATE_FAILED",
60
+ GITIGNORE_FAILED: "GITIGNORE_FAILED",
61
+ INIT_FAILED: "INIT_FAILED",
62
+ MCP_FAILED: "MCP_FAILED",
63
+ UNKNOWN_ERROR: "UNKNOWN_ERROR"
64
+ };
65
+ var CLIError = class extends Error {
66
+ constructor(message, code = ErrorCodes.UNKNOWN_ERROR, exitCode = 1) {
67
+ super(message);
68
+ this.code = code;
69
+ this.exitCode = exitCode;
70
+ this.name = "CLIError";
71
+ }
72
+ };
73
+
51
74
  // src/utils/error.ts
52
75
  var import_zod = require("zod");
53
76
  function isZodErrorLike(error) {
@@ -75,16 +98,24 @@ function isEnvTest() {
75
98
 
76
99
  // src/utils/logger.ts
77
100
  var Logger = class {
101
+ /**
102
+ * Create a new Logger instance
103
+ */
104
+ constructor(_version = "0.0.0") {
105
+ this._version = _version;
106
+ }
78
107
  _verbose = false;
79
108
  _silent = false;
109
+ _jsonMode = false;
110
+ _jsonOutputDone = false;
111
+ _commandName = "";
112
+ _jsonData = {};
80
113
  console = import_consola.consola.withDefaults({
81
114
  tag: "rulesync"
82
115
  });
83
116
  /**
84
117
  * Configure logger with verbose and silent mode settings.
85
118
  * Handles conflicting flags where silent takes precedence.
86
- * @param verbose - Enable verbose logging
87
- * @param silent - Enable silent mode (suppresses all output except errors)
88
119
  */
89
120
  configure({ verbose, silent }) {
90
121
  if (verbose && silent) {
@@ -100,34 +131,120 @@ var Logger = class {
100
131
  get silent() {
101
132
  return this._silent;
102
133
  }
134
+ /**
135
+ * Enable JSON output mode
136
+ */
137
+ setJsonMode(enabled, command) {
138
+ this._jsonMode = enabled;
139
+ this._jsonOutputDone = false;
140
+ this._commandName = command;
141
+ if (enabled) {
142
+ this._jsonData = {};
143
+ }
144
+ }
145
+ /**
146
+ * Check if JSON mode is enabled
147
+ */
148
+ get jsonMode() {
149
+ return this._jsonMode;
150
+ }
151
+ /**
152
+ * Capture data for JSON output
153
+ */
154
+ captureData(key, value) {
155
+ if (this._jsonMode) {
156
+ this._jsonData[key] = value;
157
+ }
158
+ }
159
+ /**
160
+ * Get captured JSON data
161
+ */
162
+ getJsonData() {
163
+ return { ...this._jsonData };
164
+ }
165
+ /**
166
+ * Output final JSON result
167
+ */
168
+ outputJson(success, error) {
169
+ if (!this._jsonMode || this._jsonOutputDone) return;
170
+ this._jsonOutputDone = true;
171
+ const output = {
172
+ success,
173
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
174
+ command: this._commandName,
175
+ version: this._version
176
+ };
177
+ if (success) {
178
+ output.data = this._jsonData;
179
+ } else if (error) {
180
+ output.error = {
181
+ code: error.code,
182
+ message: error.message
183
+ };
184
+ if (error.details) {
185
+ output.error.details = error.details;
186
+ }
187
+ if (error.stack) {
188
+ output.error.stack = error.stack;
189
+ }
190
+ }
191
+ const jsonStr = JSON.stringify(output, null, 2);
192
+ if (success) {
193
+ console.log(jsonStr);
194
+ } else {
195
+ console.error(jsonStr);
196
+ }
197
+ }
103
198
  info(message, ...args) {
104
199
  if (isEnvTest() || this._silent) return;
200
+ if (this._jsonMode) return;
105
201
  this.console.info(message, ...args);
106
202
  }
107
- // Success (always shown unless silent)
108
203
  success(message, ...args) {
109
204
  if (isEnvTest() || this._silent) return;
205
+ if (this._jsonMode) return;
110
206
  this.console.success(message, ...args);
111
207
  }
112
- // Warning (always shown unless silent)
113
208
  warn(message, ...args) {
114
209
  if (isEnvTest() || this._silent) return;
210
+ if (this._jsonMode) return;
115
211
  this.console.warn(message, ...args);
116
212
  }
117
- // Error (always shown, even in silent mode)
118
- error(message, ...args) {
213
+ error(message, code, ...args) {
119
214
  if (isEnvTest()) return;
120
- this.console.error(message, ...args);
215
+ const errorMessage = message instanceof Error ? message.message : message;
216
+ if (this._jsonMode) {
217
+ const errorInfo = {
218
+ code: code || ErrorCodes.UNKNOWN_ERROR,
219
+ message: errorMessage
220
+ };
221
+ if (this._verbose && message instanceof Error && message.stack) {
222
+ errorInfo.stack = message.stack;
223
+ }
224
+ this.outputJson(false, errorInfo);
225
+ } else {
226
+ this.console.error(errorMessage, ...args);
227
+ }
121
228
  }
122
- // Debug level (shown only in verbose mode)
123
229
  debug(message, ...args) {
124
230
  if (isEnvTest() || this._silent) return;
231
+ if (this._jsonMode) return;
125
232
  if (this._verbose) {
126
233
  this.console.info(message, ...args);
127
234
  }
128
235
  }
236
+ /**
237
+ * Get the internal console instance (for testing only)
238
+ * @internal
239
+ */
240
+ _getConsole() {
241
+ return this.console;
242
+ }
129
243
  };
130
- var logger = new Logger();
244
+ function createLogger(version) {
245
+ return new Logger(version);
246
+ }
247
+ var logger = new Logger("0.0.0");
131
248
 
132
249
  // src/lib/fetch.ts
133
250
  var import_node_path117 = require("path");
@@ -3008,7 +3125,9 @@ var CLAUDE_HOOK_EVENTS = [
3008
3125
  "preCompact",
3009
3126
  "permissionRequest",
3010
3127
  "notification",
3011
- "setup"
3128
+ "setup",
3129
+ "worktreeCreate",
3130
+ "worktreeRemove"
3012
3131
  ];
3013
3132
  var OPENCODE_HOOK_EVENTS = [
3014
3133
  "sessionStart",
@@ -3075,7 +3194,9 @@ var CANONICAL_TO_CLAUDE_EVENT_NAMES = {
3075
3194
  preCompact: "PreCompact",
3076
3195
  permissionRequest: "PermissionRequest",
3077
3196
  notification: "Notification",
3078
- setup: "Setup"
3197
+ setup: "Setup",
3198
+ worktreeCreate: "WorktreeCreate",
3199
+ worktreeRemove: "WorktreeRemove"
3079
3200
  };
3080
3201
  var CLAUDE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(
3081
3202
  Object.entries(CANONICAL_TO_CLAUDE_EVENT_NAMES).map(([k, v]) => [v, k])
@@ -3201,7 +3322,13 @@ function canonicalToToolHooks({
3201
3322
  else byMatcher.set(key, [def]);
3202
3323
  }
3203
3324
  const entries = [];
3325
+ const isNoMatcherEvent = converterConfig.noMatcherEvents?.has(eventName) ?? false;
3204
3326
  for (const [matcherKey, defs] of byMatcher) {
3327
+ if (isNoMatcherEvent && matcherKey) {
3328
+ logger.warn(
3329
+ `matcher "${matcherKey}" on "${eventName}" hook will be ignored \u2014 this event does not support matchers`
3330
+ );
3331
+ }
3205
3332
  const hooks = defs.map((def) => {
3206
3333
  const commandText = def.command;
3207
3334
  const trimmedCommand = typeof commandText === "string" ? commandText.trimStart() : void 0;
@@ -3214,7 +3341,8 @@ function canonicalToToolHooks({
3214
3341
  ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt }
3215
3342
  };
3216
3343
  });
3217
- entries.push(matcherKey ? { matcher: matcherKey, hooks } : { hooks });
3344
+ const includeMatcher = matcherKey && !isNoMatcherEvent;
3345
+ entries.push(includeMatcher ? { matcher: matcherKey, hooks } : { hooks });
3218
3346
  }
3219
3347
  result[toolEventName] = entries;
3220
3348
  }
@@ -3352,12 +3480,14 @@ var ToolHooks = class extends ToolFile {
3352
3480
  };
3353
3481
 
3354
3482
  // src/features/hooks/claudecode-hooks.ts
3483
+ var CLAUDE_NO_MATCHER_EVENTS = /* @__PURE__ */ new Set(["worktreeCreate", "worktreeRemove"]);
3355
3484
  var CLAUDE_CONVERTER_CONFIG = {
3356
3485
  supportedEvents: CLAUDE_HOOK_EVENTS,
3357
3486
  canonicalToToolEventNames: CANONICAL_TO_CLAUDE_EVENT_NAMES,
3358
3487
  toolToCanonicalEventNames: CLAUDE_TO_CANONICAL_EVENT_NAMES,
3359
3488
  projectDirVar: "$CLAUDE_PROJECT_DIR",
3360
- prefixDotRelativeCommandsOnly: true
3489
+ prefixDotRelativeCommandsOnly: true,
3490
+ noMatcherEvents: CLAUDE_NO_MATCHER_EVENTS
3361
3491
  };
3362
3492
  var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3363
3493
  constructor(params) {
@@ -17144,30 +17274,36 @@ function formatFetchSummary(summary) {
17144
17274
  }
17145
17275
 
17146
17276
  // src/cli/commands/fetch.ts
17147
- async function fetchCommand(options) {
17277
+ async function fetchCommand(logger2, options) {
17148
17278
  const { source, ...fetchOptions } = options;
17149
- logger.configure({
17150
- verbose: fetchOptions.verbose ?? false,
17151
- silent: fetchOptions.silent ?? false
17152
- });
17153
- logger.debug(`Fetching files from ${source}...`);
17279
+ logger2.debug(`Fetching files from ${source}...`);
17154
17280
  try {
17155
17281
  const summary = await fetchFiles({
17156
17282
  source,
17157
17283
  options: fetchOptions
17158
17284
  });
17285
+ if (logger2.jsonMode) {
17286
+ const createdFiles = summary.files.filter((f) => f.status === "created").map((f) => f.relativePath);
17287
+ const overwrittenFiles = summary.files.filter((f) => f.status === "overwritten").map((f) => f.relativePath);
17288
+ const skippedFiles = summary.files.filter((f) => f.status === "skipped").map((f) => f.relativePath);
17289
+ logger2.captureData("source", source);
17290
+ logger2.captureData("path", fetchOptions.path);
17291
+ logger2.captureData("created", createdFiles);
17292
+ logger2.captureData("overwritten", overwrittenFiles);
17293
+ logger2.captureData("skipped", skippedFiles);
17294
+ logger2.captureData("totalFetched", summary.created + summary.overwritten + summary.skipped);
17295
+ }
17159
17296
  const output = formatFetchSummary(summary);
17160
- logger.success(output);
17297
+ logger2.success(output);
17161
17298
  if (summary.created + summary.overwritten === 0 && summary.skipped === 0) {
17162
- logger.warn("No files were fetched.");
17299
+ logger2.warn("No files were fetched.");
17163
17300
  }
17164
17301
  } catch (error) {
17165
17302
  if (error instanceof GitHubClientError) {
17166
- logGitHubAuthHints(error);
17167
- } else {
17168
- logger.error(formatError(error));
17303
+ 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 fetch ...`" : "";
17304
+ throw new CLIError(`GitHub API Error: ${error.message}.${authHint}`, ErrorCodes.FETCH_FAILED);
17169
17305
  }
17170
- process.exit(1);
17306
+ throw error;
17171
17307
  }
17172
17308
  }
17173
17309
 
@@ -17881,110 +18017,92 @@ function calculateTotalCount(result) {
17881
18017
  }
17882
18018
 
17883
18019
  // src/cli/commands/generate.ts
17884
- function logFeatureResult(params) {
18020
+ function logFeatureResult(logger2, params) {
17885
18021
  const { count, paths, featureName, isPreview, modePrefix } = params;
17886
18022
  if (count > 0) {
17887
18023
  if (isPreview) {
17888
- logger.info(`${modePrefix} Would write ${count} ${featureName}`);
18024
+ logger2.info(`${modePrefix} Would write ${count} ${featureName}`);
17889
18025
  } else {
17890
- logger.success(`Written ${count} ${featureName}`);
18026
+ logger2.success(`Written ${count} ${featureName}`);
17891
18027
  }
17892
18028
  for (const p of paths) {
17893
- logger.info(` ${p}`);
18029
+ logger2.info(` ${p}`);
17894
18030
  }
17895
18031
  }
17896
18032
  }
17897
- async function generateCommand(options) {
18033
+ async function generateCommand(logger2, options) {
17898
18034
  const config = await ConfigResolver.resolve(options);
17899
- logger.configure({
17900
- verbose: config.getVerbose(),
17901
- silent: config.getSilent()
17902
- });
17903
18035
  const check = config.getCheck();
17904
18036
  const isPreview = config.isPreviewMode();
17905
18037
  const modePrefix = isPreview ? "[DRY RUN]" : "";
17906
- logger.debug("Generating files...");
18038
+ logger2.debug("Generating files...");
17907
18039
  if (!await checkRulesyncDirExists({ baseDir: process.cwd() })) {
17908
- logger.error("\u274C .rulesync directory not found. Run 'rulesync init' first.");
17909
- process.exit(1);
18040
+ throw new CLIError(
18041
+ ".rulesync directory not found. Run 'rulesync init' first.",
18042
+ ErrorCodes.RULESYNC_DIR_NOT_FOUND
18043
+ );
17910
18044
  }
17911
- logger.debug(`Base directories: ${config.getBaseDirs().join(", ")}`);
18045
+ logger2.debug(`Base directories: ${config.getBaseDirs().join(", ")}`);
17912
18046
  const features = config.getFeatures();
17913
18047
  if (features.includes("ignore")) {
17914
- logger.debug("Generating ignore files...");
18048
+ logger2.debug("Generating ignore files...");
17915
18049
  }
17916
18050
  if (features.includes("mcp")) {
17917
- logger.debug("Generating MCP files...");
18051
+ logger2.debug("Generating MCP files...");
17918
18052
  }
17919
18053
  if (features.includes("commands")) {
17920
- logger.debug("Generating command files...");
18054
+ logger2.debug("Generating command files...");
17921
18055
  }
17922
18056
  if (features.includes("subagents")) {
17923
- logger.debug("Generating subagent files...");
18057
+ logger2.debug("Generating subagent files...");
17924
18058
  }
17925
18059
  if (features.includes("skills")) {
17926
- logger.debug("Generating skill files...");
18060
+ logger2.debug("Generating skill files...");
17927
18061
  }
17928
18062
  if (features.includes("hooks")) {
17929
- logger.debug("Generating hooks...");
18063
+ logger2.debug("Generating hooks...");
17930
18064
  }
17931
18065
  if (features.includes("rules")) {
17932
- logger.debug("Generating rule files...");
18066
+ logger2.debug("Generating rule files...");
17933
18067
  }
17934
18068
  const result = await generate({ config });
17935
- logFeatureResult({
17936
- count: result.ignoreCount,
17937
- paths: result.ignorePaths,
17938
- featureName: "ignore file(s)",
17939
- isPreview,
17940
- modePrefix
17941
- });
17942
- logFeatureResult({
17943
- count: result.mcpCount,
17944
- paths: result.mcpPaths,
17945
- featureName: "MCP configuration(s)",
17946
- isPreview,
17947
- modePrefix
17948
- });
17949
- logFeatureResult({
17950
- count: result.commandsCount,
17951
- paths: result.commandsPaths,
17952
- featureName: "command(s)",
17953
- isPreview,
17954
- modePrefix
17955
- });
17956
- logFeatureResult({
17957
- count: result.subagentsCount,
17958
- paths: result.subagentsPaths,
17959
- featureName: "subagent(s)",
17960
- isPreview,
17961
- modePrefix
17962
- });
17963
- logFeatureResult({
17964
- count: result.skillsCount,
17965
- paths: result.skillsPaths,
17966
- featureName: "skill(s)",
17967
- isPreview,
17968
- modePrefix
17969
- });
17970
- logFeatureResult({
17971
- count: result.hooksCount,
17972
- paths: result.hooksPaths,
17973
- featureName: "hooks file(s)",
17974
- isPreview,
17975
- modePrefix
17976
- });
17977
- logFeatureResult({
17978
- count: result.rulesCount,
17979
- paths: result.rulesPaths,
17980
- featureName: "rule(s)",
17981
- isPreview,
17982
- modePrefix
17983
- });
17984
18069
  const totalGenerated = calculateTotalCount(result);
18070
+ const featureResults = {
18071
+ ignore: { count: result.ignoreCount, paths: result.ignorePaths },
18072
+ mcp: { count: result.mcpCount, paths: result.mcpPaths },
18073
+ commands: { count: result.commandsCount, paths: result.commandsPaths },
18074
+ subagents: { count: result.subagentsCount, paths: result.subagentsPaths },
18075
+ skills: { count: result.skillsCount, paths: result.skillsPaths },
18076
+ hooks: { count: result.hooksCount, paths: result.hooksPaths },
18077
+ rules: { count: result.rulesCount, paths: result.rulesPaths }
18078
+ };
18079
+ const featureLabels = {
18080
+ rules: (count) => `${count === 1 ? "rule" : "rules"}`,
18081
+ ignore: (count) => `${count === 1 ? "ignore file" : "ignore files"}`,
18082
+ mcp: (count) => `${count === 1 ? "MCP file" : "MCP files"}`,
18083
+ commands: (count) => `${count === 1 ? "command" : "commands"}`,
18084
+ subagents: (count) => `${count === 1 ? "subagent" : "subagents"}`,
18085
+ skills: (count) => `${count === 1 ? "skill" : "skills"}`,
18086
+ hooks: (count) => `${count === 1 ? "hooks file" : "hooks files"}`
18087
+ };
18088
+ for (const [feature, data] of Object.entries(featureResults)) {
18089
+ logFeatureResult(logger2, {
18090
+ count: data.count,
18091
+ paths: data.paths,
18092
+ featureName: featureLabels[feature]?.(data.count) ?? feature,
18093
+ isPreview,
18094
+ modePrefix
18095
+ });
18096
+ }
18097
+ if (logger2.jsonMode) {
18098
+ logger2.captureData("features", featureResults);
18099
+ logger2.captureData("totalFiles", totalGenerated);
18100
+ logger2.captureData("hasDiff", result.hasDiff);
18101
+ logger2.captureData("skills", result.skills ?? []);
18102
+ }
17985
18103
  if (totalGenerated === 0) {
17986
18104
  const enabledFeatures = features.join(", ");
17987
- logger.info(`\u2713 All files are up to date (${enabledFeatures})`);
18105
+ logger2.info(`\u2713 All files are up to date (${enabledFeatures})`);
17988
18106
  return;
17989
18107
  }
17990
18108
  const parts = [];
@@ -17996,129 +18114,209 @@ async function generateCommand(options) {
17996
18114
  if (result.skillsCount > 0) parts.push(`${result.skillsCount} skills`);
17997
18115
  if (result.hooksCount > 0) parts.push(`${result.hooksCount} hooks`);
17998
18116
  if (isPreview) {
17999
- logger.info(`${modePrefix} Would write ${totalGenerated} file(s) total (${parts.join(" + ")})`);
18117
+ logger2.info(`${modePrefix} Would write ${totalGenerated} file(s) total (${parts.join(" + ")})`);
18000
18118
  } else {
18001
- logger.success(`\u{1F389} All done! Written ${totalGenerated} file(s) total (${parts.join(" + ")})`);
18119
+ logger2.success(`\u{1F389} All done! Written ${totalGenerated} file(s) total (${parts.join(" + ")})`);
18002
18120
  }
18003
18121
  if (check) {
18004
18122
  if (result.hasDiff) {
18005
- logger.error("\u274C Files are not up to date. Run 'rulesync generate' to update.");
18006
- process.exit(1);
18123
+ throw new CLIError(
18124
+ "Files are not up to date. Run 'rulesync generate' to update.",
18125
+ ErrorCodes.GENERATION_FAILED
18126
+ );
18007
18127
  } else {
18008
- logger.success("\u2713 All files are up to date.");
18128
+ logger2.success("\u2713 All files are up to date.");
18009
18129
  }
18010
18130
  }
18011
18131
  }
18012
18132
 
18013
18133
  // src/cli/commands/gitignore.ts
18014
18134
  var import_node_path121 = require("path");
18015
- var RULESYNC_HEADER = "# Generated by Rulesync";
18016
- var LEGACY_RULESYNC_HEADER = "# Generated by rulesync - AI tool configuration files";
18017
- var RULESYNC_IGNORE_ENTRIES = [
18018
- // Rulesync curated (fetched) skills
18019
- `${RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH}/`,
18135
+
18136
+ // src/cli/commands/gitignore-entries.ts
18137
+ var GITIGNORE_ENTRY_REGISTRY = [
18138
+ // Common / general
18139
+ { target: "common", feature: "general", entry: `${RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH}/` },
18140
+ { target: "common", feature: "general", entry: ".rulesync/rules/*.local.md" },
18141
+ { target: "common", feature: "general", entry: "rulesync.local.jsonc" },
18142
+ { target: "common", feature: "general", entry: "!.rulesync/.aiignore" },
18020
18143
  // AGENTS.md
18021
- "**/AGENTS.md",
18022
- "**/.agents/",
18023
- // Augment
18024
- "**/.augmentignore",
18025
- "**/.augment/rules/",
18026
- "**/.augment-guidelines",
18144
+ { target: "agentsmd", feature: "rules", entry: "**/AGENTS.md" },
18145
+ { target: "agentsmd", feature: "rules", entry: "**/.agents/" },
18146
+ // Augment Code
18147
+ { target: "augmentcode", feature: "rules", entry: "**/.augment/rules/" },
18148
+ { target: "augmentcode", feature: "rules", entry: "**/.augment-guidelines" },
18149
+ { target: "augmentcode", feature: "ignore", entry: "**/.augmentignore" },
18027
18150
  // Claude Code
18028
- "**/CLAUDE.md",
18029
- "**/CLAUDE.local.md",
18030
- "**/.claude/CLAUDE.md",
18031
- "**/.claude/CLAUDE.local.md",
18032
- "**/.claude/memories/",
18033
- "**/.claude/rules/",
18034
- "**/.claude/commands/",
18035
- "**/.claude/agents/",
18036
- "**/.claude/skills/",
18037
- "**/.claude/settings.local.json",
18038
- "**/.mcp.json",
18151
+ { target: "claudecode", feature: "rules", entry: "**/CLAUDE.md" },
18152
+ { target: "claudecode", feature: "rules", entry: "**/CLAUDE.local.md" },
18153
+ { target: "claudecode", feature: "rules", entry: "**/.claude/CLAUDE.md" },
18154
+ { target: "claudecode", feature: "rules", entry: "**/.claude/CLAUDE.local.md" },
18155
+ { target: "claudecode", feature: "rules", entry: "**/.claude/rules/" },
18156
+ { target: "claudecode", feature: "commands", entry: "**/.claude/commands/" },
18157
+ { target: "claudecode", feature: "subagents", entry: "**/.claude/agents/" },
18158
+ { target: "claudecode", feature: "skills", entry: "**/.claude/skills/" },
18159
+ { target: "claudecode", feature: "mcp", entry: "**/.mcp.json" },
18160
+ { target: "claudecode", feature: "general", entry: "**/.claude/memories/" },
18161
+ { target: "claudecode", feature: "general", entry: "**/.claude/settings.local.json" },
18039
18162
  // Cline
18040
- "**/.clinerules/",
18041
- "**/.clinerules/workflows/",
18042
- "**/.clineignore",
18043
- "**/.cline/mcp.json",
18044
- // Codex
18045
- "**/.codexignore",
18046
- "**/.codex/memories/",
18047
- "**/.codex/skills/",
18048
- "**/.codex/agents/",
18049
- "**/.codex/config.toml",
18163
+ { target: "cline", feature: "rules", entry: "**/.clinerules/" },
18164
+ { target: "cline", feature: "commands", entry: "**/.clinerules/workflows/" },
18165
+ { target: "cline", feature: "ignore", entry: "**/.clineignore" },
18166
+ { target: "cline", feature: "mcp", entry: "**/.cline/mcp.json" },
18167
+ // Codex CLI
18168
+ { target: "codexcli", feature: "ignore", entry: "**/.codexignore" },
18169
+ { target: "codexcli", feature: "skills", entry: "**/.codex/skills/" },
18170
+ { target: "codexcli", feature: "subagents", entry: "**/.codex/agents/" },
18171
+ { target: "codexcli", feature: "general", entry: "**/.codex/memories/" },
18172
+ { target: "codexcli", feature: "general", entry: "**/.codex/config.toml" },
18050
18173
  // Cursor
18051
- "**/.cursor/",
18052
- "**/.cursorignore",
18174
+ { target: "cursor", feature: "rules", entry: "**/.cursor/" },
18175
+ { target: "cursor", feature: "ignore", entry: "**/.cursorignore" },
18053
18176
  // Factory Droid
18054
- "**/.factory/rules/",
18055
- "**/.factory/commands/",
18056
- "**/.factory/droids/",
18057
- "**/.factory/skills/",
18058
- "**/.factory/mcp.json",
18059
- "**/.factory/settings.json",
18060
- // Gemini
18061
- "**/GEMINI.md",
18062
- "**/.gemini/memories/",
18063
- "**/.gemini/commands/",
18064
- "**/.gemini/subagents/",
18065
- "**/.gemini/skills/",
18066
- "**/.geminiignore",
18177
+ { target: "factorydroid", feature: "rules", entry: "**/.factory/rules/" },
18178
+ { target: "factorydroid", feature: "commands", entry: "**/.factory/commands/" },
18179
+ { target: "factorydroid", feature: "subagents", entry: "**/.factory/droids/" },
18180
+ { target: "factorydroid", feature: "skills", entry: "**/.factory/skills/" },
18181
+ { target: "factorydroid", feature: "mcp", entry: "**/.factory/mcp.json" },
18182
+ { target: "factorydroid", feature: "general", entry: "**/.factory/settings.json" },
18183
+ // Gemini CLI
18184
+ { target: "geminicli", feature: "rules", entry: "**/GEMINI.md" },
18185
+ { target: "geminicli", feature: "commands", entry: "**/.gemini/commands/" },
18186
+ { target: "geminicli", feature: "subagents", entry: "**/.gemini/subagents/" },
18187
+ { target: "geminicli", feature: "skills", entry: "**/.gemini/skills/" },
18188
+ { target: "geminicli", feature: "ignore", entry: "**/.geminiignore" },
18189
+ { target: "geminicli", feature: "general", entry: "**/.gemini/memories/" },
18067
18190
  // Goose
18068
- "**/.goosehints",
18069
- "**/.goose/",
18070
- "**/.gooseignore",
18191
+ { target: "goose", feature: "rules", entry: "**/.goosehints" },
18192
+ { target: "goose", feature: "rules", entry: "**/.goose/" },
18193
+ { target: "goose", feature: "ignore", entry: "**/.gooseignore" },
18071
18194
  // GitHub Copilot
18072
- "**/.github/copilot-instructions.md",
18073
- "**/.github/instructions/",
18074
- "**/.github/prompts/",
18075
- "**/.github/agents/",
18076
- "**/.github/skills/",
18077
- "**/.github/hooks/",
18078
- "**/.vscode/mcp.json",
18195
+ { target: "copilot", feature: "rules", entry: "**/.github/copilot-instructions.md" },
18196
+ { target: "copilot", feature: "rules", entry: "**/.github/instructions/" },
18197
+ { target: "copilot", feature: "commands", entry: "**/.github/prompts/" },
18198
+ { target: "copilot", feature: "subagents", entry: "**/.github/agents/" },
18199
+ { target: "copilot", feature: "skills", entry: "**/.github/skills/" },
18200
+ { target: "copilot", feature: "hooks", entry: "**/.github/hooks/" },
18201
+ { target: "copilot", feature: "mcp", entry: "**/.vscode/mcp.json" },
18079
18202
  // Junie
18080
- "**/.junie/guidelines.md",
18081
- "**/.junie/mcp.json",
18082
- "**/.junie/skills/",
18083
- "**/.junie/agents/",
18203
+ { target: "junie", feature: "rules", entry: "**/.junie/guidelines.md" },
18204
+ { target: "junie", feature: "mcp", entry: "**/.junie/mcp.json" },
18205
+ { target: "junie", feature: "skills", entry: "**/.junie/skills/" },
18206
+ { target: "junie", feature: "subagents", entry: "**/.junie/agents/" },
18084
18207
  // Kilo Code
18085
- "**/.kilocode/rules/",
18086
- "**/.kilocode/skills/",
18087
- "**/.kilocode/workflows/",
18088
- "**/.kilocode/mcp.json",
18089
- "**/.kilocodeignore",
18208
+ { target: "kilo", feature: "rules", entry: "**/.kilocode/rules/" },
18209
+ { target: "kilo", feature: "skills", entry: "**/.kilocode/skills/" },
18210
+ { target: "kilo", feature: "commands", entry: "**/.kilocode/workflows/" },
18211
+ { target: "kilo", feature: "mcp", entry: "**/.kilocode/mcp.json" },
18212
+ { target: "kilo", feature: "ignore", entry: "**/.kilocodeignore" },
18090
18213
  // Kiro
18091
- "**/.kiro/steering/",
18092
- "**/.kiro/prompts/",
18093
- "**/.kiro/skills/",
18094
- "**/.kiro/agents/",
18095
- "**/.kiro/settings/mcp.json",
18096
- "**/.aiignore",
18214
+ { target: "kiro", feature: "rules", entry: "**/.kiro/steering/" },
18215
+ { target: "kiro", feature: "commands", entry: "**/.kiro/prompts/" },
18216
+ { target: "kiro", feature: "skills", entry: "**/.kiro/skills/" },
18217
+ { target: "kiro", feature: "subagents", entry: "**/.kiro/agents/" },
18218
+ { target: "kiro", feature: "mcp", entry: "**/.kiro/settings/mcp.json" },
18219
+ { target: "kiro", feature: "ignore", entry: "**/.aiignore" },
18097
18220
  // OpenCode
18098
- "**/.opencode/memories/",
18099
- "**/.opencode/command/",
18100
- "**/.opencode/agent/",
18101
- "**/.opencode/skill/",
18102
- "**/.opencode/plugins/",
18103
- // Qwen
18104
- "**/QWEN.md",
18105
- "**/.qwen/memories/",
18221
+ { target: "opencode", feature: "commands", entry: "**/.opencode/command/" },
18222
+ { target: "opencode", feature: "subagents", entry: "**/.opencode/agent/" },
18223
+ { target: "opencode", feature: "skills", entry: "**/.opencode/skill/" },
18224
+ { target: "opencode", feature: "mcp", entry: "**/.opencode/plugins/" },
18225
+ { target: "opencode", feature: "general", entry: "**/.opencode/memories/" },
18226
+ // Qwen Code
18227
+ { target: "qwencode", feature: "rules", entry: "**/QWEN.md" },
18228
+ { target: "qwencode", feature: "general", entry: "**/.qwen/memories/" },
18106
18229
  // Replit
18107
- "**/replit.md",
18230
+ { target: "replit", feature: "rules", entry: "**/replit.md" },
18108
18231
  // Roo
18109
- "**/.roo/rules/",
18110
- "**/.roo/skills/",
18111
- "**/.rooignore",
18112
- "**/.roo/mcp.json",
18113
- "**/.roo/subagents/",
18232
+ { target: "roo", feature: "rules", entry: "**/.roo/rules/" },
18233
+ { target: "roo", feature: "skills", entry: "**/.roo/skills/" },
18234
+ { target: "roo", feature: "ignore", entry: "**/.rooignore" },
18235
+ { target: "roo", feature: "mcp", entry: "**/.roo/mcp.json" },
18236
+ { target: "roo", feature: "subagents", entry: "**/.roo/subagents/" },
18114
18237
  // Warp
18115
- "**/.warp/",
18116
- "**/WARP.md",
18117
- // Others
18118
- ".rulesync/rules/*.local.md",
18119
- "rulesync.local.jsonc",
18120
- "!.rulesync/.aiignore"
18238
+ { target: "warp", feature: "rules", entry: "**/.warp/" },
18239
+ { target: "warp", feature: "rules", entry: "**/WARP.md" }
18121
18240
  ];
18241
+ var ALL_GITIGNORE_ENTRIES = GITIGNORE_ENTRY_REGISTRY.map(
18242
+ (tag) => tag.entry
18243
+ );
18244
+ var isTargetSelected = (target, selectedTargets) => {
18245
+ if (target === "common") return true;
18246
+ if (!selectedTargets || selectedTargets.length === 0) return true;
18247
+ if (selectedTargets.includes("*")) return true;
18248
+ return selectedTargets.includes(target);
18249
+ };
18250
+ var isFeatureSelected = (feature, target, features) => {
18251
+ if (feature === "general") return true;
18252
+ if (!features) return true;
18253
+ if (Array.isArray(features)) {
18254
+ if (features.length === 0) return true;
18255
+ if (features.includes("*")) return true;
18256
+ return features.includes(feature);
18257
+ }
18258
+ if (target === "common") return true;
18259
+ const targetFeatures = features[target];
18260
+ if (!targetFeatures) return true;
18261
+ if (targetFeatures.includes("*")) return true;
18262
+ return targetFeatures.includes(feature);
18263
+ };
18264
+ var warnInvalidTargets = (targets) => {
18265
+ const validTargets = new Set(ALL_TOOL_TARGETS_WITH_WILDCARD);
18266
+ for (const target of targets) {
18267
+ if (!validTargets.has(target)) {
18268
+ logger.warn(
18269
+ `Unknown target '${target}'. Valid targets: ${ALL_TOOL_TARGETS_WITH_WILDCARD.join(", ")}`
18270
+ );
18271
+ }
18272
+ }
18273
+ };
18274
+ var warnInvalidFeatures = (features) => {
18275
+ const validFeatures = new Set(ALL_FEATURES_WITH_WILDCARD);
18276
+ if (Array.isArray(features)) {
18277
+ for (const feature of features) {
18278
+ if (!validFeatures.has(feature)) {
18279
+ logger.warn(
18280
+ `Unknown feature '${feature}'. Valid features: ${ALL_FEATURES_WITH_WILDCARD.join(", ")}`
18281
+ );
18282
+ }
18283
+ }
18284
+ } else {
18285
+ for (const targetFeatures of Object.values(features)) {
18286
+ if (!targetFeatures) continue;
18287
+ for (const feature of targetFeatures) {
18288
+ if (!validFeatures.has(feature)) {
18289
+ logger.warn(
18290
+ `Unknown feature '${feature}'. Valid features: ${ALL_FEATURES_WITH_WILDCARD.join(", ")}`
18291
+ );
18292
+ }
18293
+ }
18294
+ }
18295
+ }
18296
+ };
18297
+ var filterGitignoreEntries = (params) => {
18298
+ const { targets, features } = params ?? {};
18299
+ if (targets && targets.length > 0) {
18300
+ warnInvalidTargets(targets);
18301
+ }
18302
+ if (features) {
18303
+ warnInvalidFeatures(features);
18304
+ }
18305
+ const seen = /* @__PURE__ */ new Set();
18306
+ const result = [];
18307
+ for (const tag of GITIGNORE_ENTRY_REGISTRY) {
18308
+ if (!isTargetSelected(tag.target, targets)) continue;
18309
+ if (!isFeatureSelected(tag.feature, tag.target, features)) continue;
18310
+ if (seen.has(tag.entry)) continue;
18311
+ seen.add(tag.entry);
18312
+ result.push(tag.entry);
18313
+ }
18314
+ return result;
18315
+ };
18316
+
18317
+ // src/cli/commands/gitignore.ts
18318
+ var RULESYNC_HEADER = "# Generated by Rulesync";
18319
+ var LEGACY_RULESYNC_HEADER = "# Generated by rulesync - AI tool configuration files";
18122
18320
  var isRulesyncHeader = (line) => {
18123
18321
  const trimmed = line.trim();
18124
18322
  return trimmed === RULESYNC_HEADER || trimmed === LEGACY_RULESYNC_HEADER;
@@ -18128,7 +18326,7 @@ var isRulesyncEntry = (line) => {
18128
18326
  if (trimmed === "" || isRulesyncHeader(line)) {
18129
18327
  return false;
18130
18328
  }
18131
- return RULESYNC_IGNORE_ENTRIES.includes(trimmed);
18329
+ return ALL_GITIGNORE_ENTRIES.includes(trimmed);
18132
18330
  };
18133
18331
  var removeExistingRulesyncEntries = (content) => {
18134
18332
  const lines = content.split("\n");
@@ -18168,37 +18366,56 @@ var removeExistingRulesyncEntries = (content) => {
18168
18366
  }
18169
18367
  return result;
18170
18368
  };
18171
- var gitignoreCommand = async () => {
18369
+ var gitignoreCommand = async (logger2, options) => {
18172
18370
  const gitignorePath = (0, import_node_path121.join)(process.cwd(), ".gitignore");
18173
18371
  let gitignoreContent = "";
18174
18372
  if (await fileExists(gitignorePath)) {
18175
18373
  gitignoreContent = await readFileContent(gitignorePath);
18176
18374
  }
18177
18375
  const cleanedContent = removeExistingRulesyncEntries(gitignoreContent);
18178
- const rulesyncBlock = [RULESYNC_HEADER, ...RULESYNC_IGNORE_ENTRIES].join("\n");
18376
+ const filteredEntries = filterGitignoreEntries({
18377
+ targets: options?.targets,
18378
+ features: options?.features
18379
+ });
18380
+ const existingEntries = new Set(
18381
+ gitignoreContent.split("\n").map((line) => line.trim()).filter((line) => line !== "" && !isRulesyncHeader(line))
18382
+ );
18383
+ const alreadyExistedEntries = filteredEntries.filter((entry) => existingEntries.has(entry));
18384
+ const entriesToAdd = filteredEntries.filter((entry) => !existingEntries.has(entry));
18385
+ const rulesyncBlock = [RULESYNC_HEADER, ...filteredEntries].join("\n");
18179
18386
  const newContent = cleanedContent.trim() ? `${cleanedContent.trimEnd()}
18180
18387
 
18181
18388
  ${rulesyncBlock}
18182
18389
  ` : `${rulesyncBlock}
18183
18390
  `;
18184
18391
  if (gitignoreContent === newContent) {
18185
- logger.success(".gitignore is already up to date");
18392
+ if (logger2.jsonMode) {
18393
+ logger2.captureData("entriesAdded", []);
18394
+ logger2.captureData("gitignorePath", gitignorePath);
18395
+ logger2.captureData("alreadyExisted", filteredEntries);
18396
+ }
18397
+ logger2.success(".gitignore is already up to date");
18186
18398
  return;
18187
18399
  }
18188
18400
  await writeFileContent(gitignorePath, newContent);
18189
- logger.success("Updated .gitignore with rulesync entries:");
18190
- for (const entry of RULESYNC_IGNORE_ENTRIES) {
18191
- logger.info(` ${entry}`);
18401
+ if (logger2.jsonMode) {
18402
+ logger2.captureData("entriesAdded", entriesToAdd);
18403
+ logger2.captureData("gitignorePath", gitignorePath);
18404
+ logger2.captureData("alreadyExisted", alreadyExistedEntries);
18192
18405
  }
18193
- logger.info("");
18194
- logger.info(
18406
+ logger2.success("Updated .gitignore with rulesync entries:");
18407
+ for (const entry of filteredEntries) {
18408
+ logger2.info(` ${entry}`);
18409
+ }
18410
+ logger2.info("");
18411
+ logger2.info(
18195
18412
  "\u{1F4A1} If you're using Google Antigravity, note that rules, workflows, and skills won't load if they're gitignored."
18196
18413
  );
18197
- logger.info(" You can add the following to .git/info/exclude instead:");
18198
- logger.info(" **/.agent/rules/");
18199
- logger.info(" **/.agent/workflows/");
18200
- logger.info(" **/.agent/skills/");
18201
- logger.info(" For more details: https://github.com/dyoshikawa/rulesync/issues/981");
18414
+ logger2.info(" You can add the following to .git/info/exclude instead:");
18415
+ logger2.info(" **/.agent/rules/");
18416
+ logger2.info(" **/.agent/workflows/");
18417
+ logger2.info(" **/.agent/skills/");
18418
+ logger2.info(" For more details: https://github.com/dyoshikawa/rulesync/issues/981");
18202
18419
  };
18203
18420
 
18204
18421
  // src/lib/import.ts
@@ -18421,29 +18638,36 @@ async function importHooksCore(params) {
18421
18638
  }
18422
18639
 
18423
18640
  // src/cli/commands/import.ts
18424
- async function importCommand(options) {
18641
+ async function importCommand(logger2, options) {
18425
18642
  if (!options.targets) {
18426
- logger.error("No tools found in --targets");
18427
- process.exit(1);
18643
+ throw new CLIError("No tools found in --targets", ErrorCodes.IMPORT_FAILED);
18428
18644
  }
18429
18645
  if (options.targets.length > 1) {
18430
- logger.error("Only one tool can be imported at a time");
18431
- process.exit(1);
18646
+ throw new CLIError("Only one tool can be imported at a time", ErrorCodes.IMPORT_FAILED);
18432
18647
  }
18433
18648
  const config = await ConfigResolver.resolve(options);
18434
- logger.configure({
18435
- verbose: config.getVerbose(),
18436
- silent: config.getSilent()
18437
- });
18438
18649
  const tool = config.getTargets()[0];
18439
- logger.debug(`Importing files from ${tool}...`);
18650
+ logger2.debug(`Importing files from ${tool}...`);
18440
18651
  const result = await importFromTool({ config, tool });
18441
18652
  const totalImported = calculateTotalCount(result);
18442
18653
  if (totalImported === 0) {
18443
18654
  const enabledFeatures = config.getFeatures().join(", ");
18444
- logger.warn(`No files imported for enabled features: ${enabledFeatures}`);
18655
+ logger2.warn(`No files imported for enabled features: ${enabledFeatures}`);
18445
18656
  return;
18446
18657
  }
18658
+ if (logger2.jsonMode) {
18659
+ logger2.captureData("tool", tool);
18660
+ logger2.captureData("features", {
18661
+ rules: { count: result.rulesCount },
18662
+ ignore: { count: result.ignoreCount },
18663
+ mcp: { count: result.mcpCount },
18664
+ commands: { count: result.commandsCount },
18665
+ subagents: { count: result.subagentsCount },
18666
+ skills: { count: result.skillsCount },
18667
+ hooks: { count: result.hooksCount }
18668
+ });
18669
+ logger2.captureData("totalFiles", totalImported);
18670
+ }
18447
18671
  const parts = [];
18448
18672
  if (result.rulesCount > 0) parts.push(`${result.rulesCount} rules`);
18449
18673
  if (result.ignoreCount > 0) parts.push(`${result.ignoreCount} ignore files`);
@@ -18452,7 +18676,7 @@ async function importCommand(options) {
18452
18676
  if (result.subagentsCount > 0) parts.push(`${result.subagentsCount} subagents`);
18453
18677
  if (result.skillsCount > 0) parts.push(`${result.skillsCount} skills`);
18454
18678
  if (result.hooksCount > 0) parts.push(`${result.hooksCount} hooks`);
18455
- logger.success(`Imported ${totalImported} file(s) total (${parts.join(" + ")})`);
18679
+ logger2.success(`Imported ${totalImported} file(s) total (${parts.join(" + ")})`);
18456
18680
  }
18457
18681
 
18458
18682
  // src/lib/init.ts
@@ -18681,28 +18905,38 @@ async function writeIfNotExists(path4, content) {
18681
18905
  }
18682
18906
 
18683
18907
  // src/cli/commands/init.ts
18684
- async function initCommand() {
18685
- logger.debug("Initializing rulesync...");
18908
+ async function initCommand(logger2) {
18909
+ logger2.debug("Initializing rulesync...");
18686
18910
  await ensureDir(RULESYNC_RELATIVE_DIR_PATH);
18687
18911
  const result = await init();
18912
+ const createdFiles = [];
18913
+ const skippedFiles = [];
18688
18914
  for (const file of result.sampleFiles) {
18689
18915
  if (file.created) {
18690
- logger.success(`Created ${file.path}`);
18916
+ createdFiles.push(file.path);
18917
+ logger2.success(`Created ${file.path}`);
18691
18918
  } else {
18692
- logger.info(`Skipped ${file.path} (already exists)`);
18919
+ skippedFiles.push(file.path);
18920
+ logger2.info(`Skipped ${file.path} (already exists)`);
18693
18921
  }
18694
18922
  }
18695
18923
  if (result.configFile.created) {
18696
- logger.success(`Created ${result.configFile.path}`);
18924
+ createdFiles.push(result.configFile.path);
18925
+ logger2.success(`Created ${result.configFile.path}`);
18697
18926
  } else {
18698
- logger.info(`Skipped ${result.configFile.path} (already exists)`);
18927
+ skippedFiles.push(result.configFile.path);
18928
+ logger2.info(`Skipped ${result.configFile.path} (already exists)`);
18699
18929
  }
18700
- logger.success("rulesync initialized successfully!");
18701
- logger.info("Next steps:");
18702
- logger.info(
18930
+ if (logger2.jsonMode) {
18931
+ logger2.captureData("created", createdFiles);
18932
+ logger2.captureData("skipped", skippedFiles);
18933
+ }
18934
+ logger2.success("rulesync initialized successfully!");
18935
+ logger2.info("Next steps:");
18936
+ logger2.info(
18703
18937
  `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}`
18704
18938
  );
18705
- logger.info("2. Run 'rulesync generate' to create configuration files");
18939
+ logger2.info("2. Run 'rulesync generate' to create configuration files");
18706
18940
  }
18707
18941
 
18708
18942
  // src/lib/sources.ts
@@ -19428,11 +19662,7 @@ async function fetchSourceViaGit(params) {
19428
19662
  }
19429
19663
 
19430
19664
  // src/cli/commands/install.ts
19431
- async function installCommand(options) {
19432
- logger.configure({
19433
- verbose: options.verbose ?? false,
19434
- silent: options.silent ?? false
19435
- });
19665
+ async function installCommand(logger2, options) {
19436
19666
  const config = await ConfigResolver.resolve({
19437
19667
  configPath: options.configPath,
19438
19668
  verbose: options.verbose,
@@ -19440,10 +19670,10 @@ async function installCommand(options) {
19440
19670
  });
19441
19671
  const sources = config.getSources();
19442
19672
  if (sources.length === 0) {
19443
- logger.warn("No sources defined in configuration. Nothing to install.");
19673
+ logger2.warn("No sources defined in configuration. Nothing to install.");
19444
19674
  return;
19445
19675
  }
19446
- logger.debug(`Installing skills from ${sources.length} source(s)...`);
19676
+ logger2.debug(`Installing skills from ${sources.length} source(s)...`);
19447
19677
  const result = await resolveAndFetchSources({
19448
19678
  sources,
19449
19679
  baseDir: process.cwd(),
@@ -19453,12 +19683,16 @@ async function installCommand(options) {
19453
19683
  token: options.token
19454
19684
  }
19455
19685
  });
19686
+ if (logger2.jsonMode) {
19687
+ logger2.captureData("sourcesProcessed", result.sourcesProcessed);
19688
+ logger2.captureData("skillsFetched", result.fetchedSkillCount);
19689
+ }
19456
19690
  if (result.fetchedSkillCount > 0) {
19457
- logger.success(
19691
+ logger2.success(
19458
19692
  `Installed ${result.fetchedSkillCount} skill(s) from ${result.sourcesProcessed} source(s).`
19459
19693
  );
19460
19694
  } else {
19461
- logger.success(`All skills up to date (${result.sourcesProcessed} source(s) checked).`);
19695
+ logger2.success(`All skills up to date (${result.sourcesProcessed} source(s) checked).`);
19462
19696
  }
19463
19697
  }
19464
19698
 
@@ -20873,7 +21107,7 @@ var rulesyncTool = {
20873
21107
  };
20874
21108
 
20875
21109
  // src/cli/commands/mcp.ts
20876
- async function mcpCommand({ version }) {
21110
+ async function mcpCommand(logger2, { version }) {
20877
21111
  const server = new import_fastmcp.FastMCP({
20878
21112
  name: "Rulesync MCP Server",
20879
21113
  // eslint-disable-next-line no-type-assertion/no-type-assertion
@@ -20881,7 +21115,7 @@ async function mcpCommand({ version }) {
20881
21115
  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."
20882
21116
  });
20883
21117
  server.addTool(rulesyncTool);
20884
- logger.info("Rulesync MCP server started via stdio");
21118
+ logger2.info("Rulesync MCP server started via stdio");
20885
21119
  void server.start({
20886
21120
  transportType: "stdio"
20887
21121
  });
@@ -21197,158 +21431,185 @@ To upgrade, run:
21197
21431
  }
21198
21432
 
21199
21433
  // src/cli/commands/update.ts
21200
- async function updateCommand(currentVersion, options) {
21201
- const { check = false, force = false, verbose = false, silent = false, token } = options;
21202
- logger.configure({ verbose, silent });
21434
+ async function updateCommand(logger2, currentVersion, options) {
21435
+ const { check = false, force = false, token } = options;
21203
21436
  try {
21204
21437
  const environment = detectExecutionEnvironment();
21205
- logger.debug(`Detected environment: ${environment}`);
21438
+ logger2.debug(`Detected environment: ${environment}`);
21206
21439
  if (environment === "npm") {
21207
- logger.info(getNpmUpgradeInstructions());
21440
+ logger2.info(getNpmUpgradeInstructions());
21208
21441
  return;
21209
21442
  }
21210
21443
  if (environment === "homebrew") {
21211
- logger.info(getHomebrewUpgradeInstructions());
21444
+ logger2.info(getHomebrewUpgradeInstructions());
21212
21445
  return;
21213
21446
  }
21214
21447
  if (check) {
21215
- logger.info("Checking for updates...");
21448
+ logger2.info("Checking for updates...");
21216
21449
  const updateCheck = await checkForUpdate(currentVersion, token);
21450
+ if (logger2.jsonMode) {
21451
+ logger2.captureData("currentVersion", updateCheck.currentVersion);
21452
+ logger2.captureData("latestVersion", updateCheck.latestVersion);
21453
+ logger2.captureData("updateAvailable", updateCheck.hasUpdate);
21454
+ logger2.captureData(
21455
+ "message",
21456
+ updateCheck.hasUpdate ? `Update available: ${updateCheck.currentVersion} -> ${updateCheck.latestVersion}` : `Already at the latest version (${updateCheck.currentVersion})`
21457
+ );
21458
+ }
21217
21459
  if (updateCheck.hasUpdate) {
21218
- logger.success(
21460
+ logger2.success(
21219
21461
  `Update available: ${updateCheck.currentVersion} -> ${updateCheck.latestVersion}`
21220
21462
  );
21221
21463
  } else {
21222
- logger.info(`Already at the latest version (${updateCheck.currentVersion})`);
21464
+ logger2.info(`Already at the latest version (${updateCheck.currentVersion})`);
21223
21465
  }
21224
21466
  return;
21225
21467
  }
21226
- logger.info("Checking for updates...");
21468
+ logger2.info("Checking for updates...");
21227
21469
  const message = await performBinaryUpdate(currentVersion, { force, token });
21228
- logger.success(message);
21470
+ logger2.success(message);
21229
21471
  } catch (error) {
21230
21472
  if (error instanceof GitHubClientError) {
21231
- logGitHubAuthHints(error);
21473
+ 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 ...`" : "";
21474
+ throw new CLIError(
21475
+ `GitHub API Error: ${error.message}.${authHint}`,
21476
+ ErrorCodes.UPDATE_FAILED
21477
+ );
21232
21478
  } else if (error instanceof UpdatePermissionError) {
21233
- logger.error(error.message);
21234
- logger.info("Tip: Run with elevated privileges (e.g., sudo rulesync update)");
21235
- } else {
21236
- logger.error(formatError(error));
21479
+ throw new CLIError(
21480
+ `${error.message} Tip: Run with elevated privileges (e.g., sudo rulesync update)`,
21481
+ ErrorCodes.UPDATE_FAILED
21482
+ );
21237
21483
  }
21238
- process.exit(1);
21484
+ throw error;
21239
21485
  }
21240
21486
  }
21241
21487
 
21242
21488
  // src/cli/index.ts
21243
- var getVersion = () => "7.18.2";
21489
+ var getVersion = () => "7.19.0";
21490
+ function wrapCommand(name, errorCode, handler) {
21491
+ return async (...args) => {
21492
+ const command = args[args.length - 1];
21493
+ const options = args[args.length - 2];
21494
+ const positionalArgs = args.slice(0, -2);
21495
+ const globalOpts = command.parent?.opts() ?? {};
21496
+ const logger2 = createLogger(getVersion());
21497
+ logger2.setJsonMode(Boolean(globalOpts.json), name);
21498
+ logger2.configure({
21499
+ verbose: Boolean(globalOpts.verbose) || Boolean(options.verbose),
21500
+ silent: Boolean(globalOpts.silent) || Boolean(options.silent)
21501
+ });
21502
+ try {
21503
+ await handler(logger2, options, globalOpts, positionalArgs);
21504
+ if (globalOpts.json) {
21505
+ logger2.outputJson(true);
21506
+ }
21507
+ } catch (error) {
21508
+ const code = error instanceof CLIError ? error.code : errorCode;
21509
+ const errorArg = error instanceof Error ? error : formatError(error);
21510
+ logger2.error(errorArg, code);
21511
+ process.exit(error instanceof CLIError ? error.exitCode : 1);
21512
+ }
21513
+ };
21514
+ }
21244
21515
  var main = async () => {
21245
21516
  const program = new import_commander.Command();
21246
21517
  const version = getVersion();
21247
- program.hook("postAction", () => {
21248
- if (ANNOUNCEMENT.length > 0) {
21249
- logger.info(ANNOUNCEMENT);
21518
+ program.name("rulesync").description("Unified AI rules management CLI tool").version(version, "-v, --version", "Show version").option("-j, --json", "Output results as JSON");
21519
+ program.command("init").description("Initialize rulesync in current directory").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
21520
+ wrapCommand("init", "INIT_FAILED", async (logger2) => {
21521
+ await initCommand(logger2);
21522
+ })
21523
+ );
21524
+ program.command("gitignore").description("Add generated files to .gitignore").option(
21525
+ "-t, --targets <tools>",
21526
+ "Comma-separated list of tools to include (e.g., 'claudecode,copilot' or '*' for all)",
21527
+ (value) => {
21528
+ return value.split(",").map((t) => t.trim()).filter(Boolean);
21250
21529
  }
21251
- });
21252
- program.name("rulesync").description("Unified AI rules management CLI tool").version(version, "-v, --version", "Show version");
21253
- program.command("init").description("Initialize rulesync in current directory").action(initCommand);
21254
- program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
21530
+ ).option(
21531
+ "-f, --features <features>",
21532
+ `Comma-separated list of features to include (${ALL_FEATURES.join(",")}) or '*' for all`,
21533
+ (value) => {
21534
+ return value.split(",").map((f) => f.trim()).filter(Boolean);
21535
+ }
21536
+ ).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
21537
+ wrapCommand("gitignore", "GITIGNORE_FAILED", async (logger2, options) => {
21538
+ await gitignoreCommand(logger2, {
21539
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
21540
+ targets: options.targets,
21541
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
21542
+ features: options.features
21543
+ });
21544
+ })
21545
+ );
21255
21546
  program.command("fetch <source>").description("Fetch files from a Git repository (GitHub/GitLab)").option(
21256
21547
  "-t, --target <target>",
21257
21548
  "Target format to interpret files as (e.g., 'rulesync', 'claudecode'). Default: rulesync"
21258
21549
  ).option(
21259
21550
  "-f, --features <features>",
21260
21551
  `Comma-separated list of features to fetch (${ALL_FEATURES.join(",")}) or '*' for all`,
21261
- (value) => {
21262
- return value.split(",").map((f) => f.trim());
21263
- }
21552
+ (value) => value.split(",").map((f) => f.trim())
21264
21553
  ).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(
21265
21554
  "-c, --conflict <strategy>",
21266
21555
  "Conflict resolution strategy: skip, overwrite (default: overwrite)"
21267
- ).option("--token <token>", "Git provider token for private repositories").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(async (source, options) => {
21268
- await fetchCommand({
21269
- source,
21270
- target: options.target,
21271
- features: options.features,
21272
- ref: options.ref,
21273
- path: options.path,
21274
- output: options.output,
21275
- conflict: options.conflict,
21276
- token: options.token,
21277
- verbose: options.verbose,
21278
- silent: options.silent
21279
- });
21280
- });
21556
+ ).option("--token <token>", "Git provider token for private repositories").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
21557
+ wrapCommand("fetch", "FETCH_FAILED", async (logger2, options, _globalOpts, positionalArgs) => {
21558
+ const source = positionalArgs[0];
21559
+ await fetchCommand(logger2, { ...options, source });
21560
+ })
21561
+ );
21281
21562
  program.command("import").description("Import configurations from AI tools to rulesync format").option(
21282
21563
  "-t, --targets <tool>",
21283
21564
  "Tool to import from (e.g., 'copilot', 'cursor', 'cline')",
21284
- (value) => {
21285
- return value.split(",").map((t) => t.trim());
21286
- }
21565
+ (value) => value.split(",").map((t) => t.trim())
21287
21566
  ).option(
21288
21567
  "-f, --features <features>",
21289
21568
  `Comma-separated list of features to import (${ALL_FEATURES.join(",")}) or '*' for all`,
21290
- (value) => {
21291
- return value.split(",").map((f) => f.trim());
21292
- }
21293
- ).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").option("-g, --global", "Import for global(user scope) configuration files").action(async (options) => {
21294
- try {
21295
- await importCommand({
21296
- targets: options.targets,
21297
- features: options.features,
21298
- verbose: options.verbose,
21299
- silent: options.silent,
21300
- configPath: options.config,
21301
- global: options.global
21302
- });
21303
- } catch (error) {
21304
- logger.error(formatError(error));
21305
- process.exit(1);
21306
- }
21307
- });
21308
- program.command("mcp").description("Start MCP server for rulesync").action(async () => {
21309
- try {
21310
- await mcpCommand({ version });
21311
- } catch (error) {
21312
- logger.error(formatError(error));
21313
- process.exit(1);
21314
- }
21315
- });
21569
+ (value) => value.split(",").map((f) => f.trim())
21570
+ ).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").option("-g, --global", "Import for global(user scope) configuration files").action(
21571
+ wrapCommand("import", "IMPORT_FAILED", async (logger2, options) => {
21572
+ await importCommand(logger2, options);
21573
+ })
21574
+ );
21575
+ program.command("mcp").description("Start MCP server for rulesync").action(
21576
+ wrapCommand("mcp", "MCP_FAILED", async (logger2, _options) => {
21577
+ await mcpCommand(logger2, { version });
21578
+ })
21579
+ );
21316
21580
  program.command("install").description("Install skills from declarative sources in rulesync.jsonc").option("--update", "Force re-resolve all source refs, ignoring lockfile").option(
21317
21581
  "--frozen",
21318
21582
  "Fail if lockfile is missing or out of sync (for CI); fetches missing skills using locked refs"
21319
- ).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(async (options) => {
21320
- try {
21321
- await installCommand({
21583
+ ).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(
21584
+ wrapCommand("install", "INSTALL_FAILED", async (logger2, options) => {
21585
+ await installCommand(logger2, {
21586
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
21322
21587
  update: options.update,
21588
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
21323
21589
  frozen: options.frozen,
21590
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
21324
21591
  token: options.token,
21592
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
21325
21593
  configPath: options.config,
21594
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
21326
21595
  verbose: options.verbose,
21596
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
21327
21597
  silent: options.silent
21328
21598
  });
21329
- } catch (error) {
21330
- logger.error(formatError(error));
21331
- process.exit(1);
21332
- }
21333
- });
21599
+ })
21600
+ );
21334
21601
  program.command("generate").description("Generate configuration files for AI tools").option(
21335
21602
  "-t, --targets <tools>",
21336
21603
  "Comma-separated list of tools to generate for (e.g., 'copilot,cursor,cline' or '*' for all)",
21337
- (value) => {
21338
- return value.split(",").map((t) => t.trim());
21339
- }
21604
+ (value) => value.split(",").map((t) => t.trim())
21340
21605
  ).option(
21341
21606
  "-f, --features <features>",
21342
21607
  `Comma-separated list of features to generate (${ALL_FEATURES.join(",")}) or '*' for all`,
21343
- (value) => {
21344
- return value.split(",").map((f) => f.trim());
21345
- }
21608
+ (value) => value.split(",").map((f) => f.trim())
21346
21609
  ).option("--delete", "Delete all existing files in output directories before generating").option(
21347
21610
  "-b, --base-dir <paths>",
21348
21611
  "Base directories to generate files (comma-separated for multiple paths)",
21349
- (value) => {
21350
- return value.split(",").map((p) => p.trim());
21351
- }
21612
+ (value) => value.split(",").map((p) => p.trim())
21352
21613
  ).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(
21353
21614
  "--simulate-commands",
21354
21615
  "Generate simulated commands. This feature is only available for copilot, cursor and codexcli."
@@ -21358,40 +21619,19 @@ var main = async () => {
21358
21619
  ).option(
21359
21620
  "--simulate-skills",
21360
21621
  "Generate simulated skills. This feature is only available for copilot, cursor and codexcli."
21361
- ).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(async (options) => {
21362
- try {
21363
- await generateCommand({
21364
- targets: options.targets,
21365
- features: options.features,
21366
- verbose: options.verbose,
21367
- silent: options.silent,
21368
- delete: options.delete,
21369
- baseDirs: options.baseDir,
21370
- configPath: options.config,
21371
- global: options.global,
21372
- simulateCommands: options.simulateCommands,
21373
- simulateSubagents: options.simulateSubagents,
21374
- simulateSkills: options.simulateSkills,
21375
- dryRun: options.dryRun,
21376
- check: options.check
21377
- });
21378
- } catch (error) {
21379
- logger.error(formatError(error));
21380
- process.exit(1);
21381
- }
21382
- });
21383
- 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(async (options) => {
21384
- await updateCommand(version, {
21385
- check: options.check,
21386
- force: options.force,
21387
- token: options.token,
21388
- verbose: options.verbose,
21389
- silent: options.silent
21390
- });
21391
- });
21622
+ ).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(
21623
+ wrapCommand("generate", "GENERATION_FAILED", async (logger2, options) => {
21624
+ await generateCommand(logger2, options);
21625
+ })
21626
+ );
21627
+ 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(
21628
+ wrapCommand("update", "UPDATE_FAILED", async (logger2, options) => {
21629
+ await updateCommand(logger2, version, options);
21630
+ })
21631
+ );
21392
21632
  program.parse();
21393
21633
  };
21394
21634
  main().catch((error) => {
21395
- logger.error(formatError(error));
21635
+ console.error(formatError(error));
21396
21636
  process.exit(1);
21397
21637
  });