rulesync 7.18.2 → 7.20.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) {
@@ -65,69 +88,141 @@ function formatError(error) {
65
88
  return String(error);
66
89
  }
67
90
 
68
- // src/utils/logger.ts
69
- var import_consola = require("consola");
70
-
71
91
  // src/utils/vitest.ts
72
92
  function isEnvTest() {
73
93
  return process.env.NODE_ENV === "test";
74
94
  }
75
95
 
76
96
  // src/utils/logger.ts
77
- var Logger = class {
97
+ var BaseLogger = class {
78
98
  _verbose = false;
79
99
  _silent = false;
80
- console = import_consola.consola.withDefaults({
81
- tag: "rulesync"
82
- });
83
- /**
84
- * Configure logger with verbose and silent mode settings.
85
- * 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
- */
100
+ get verbose() {
101
+ return this._verbose;
102
+ }
103
+ get silent() {
104
+ return this._silent;
105
+ }
89
106
  configure({ verbose, silent }) {
90
107
  if (verbose && silent) {
91
108
  this._silent = false;
92
- this.warn("Both --verbose and --silent specified; --silent takes precedence");
109
+ if (!isEnvTest()) {
110
+ console.warn("Both --verbose and --silent specified; --silent takes precedence");
111
+ }
93
112
  }
94
113
  this._silent = silent;
95
114
  this._verbose = verbose && !silent;
96
115
  }
97
- get verbose() {
98
- return this._verbose;
116
+ };
117
+ var ConsoleLogger = class extends BaseLogger {
118
+ isSuppressed() {
119
+ return isEnvTest() || this._silent;
99
120
  }
100
- get silent() {
101
- return this._silent;
121
+ get jsonMode() {
122
+ return false;
123
+ }
124
+ captureData(_key, _value) {
125
+ }
126
+ getJsonData() {
127
+ return {};
128
+ }
129
+ outputJson(_success, _error) {
102
130
  }
103
131
  info(message, ...args) {
104
- if (isEnvTest() || this._silent) return;
105
- this.console.info(message, ...args);
132
+ if (this.isSuppressed()) return;
133
+ console.log(message, ...args);
106
134
  }
107
- // Success (always shown unless silent)
108
135
  success(message, ...args) {
109
- if (isEnvTest() || this._silent) return;
110
- this.console.success(message, ...args);
136
+ if (this.isSuppressed()) return;
137
+ console.log(message, ...args);
111
138
  }
112
- // Warning (always shown unless silent)
113
139
  warn(message, ...args) {
114
- if (isEnvTest() || this._silent) return;
115
- this.console.warn(message, ...args);
140
+ if (this.isSuppressed()) return;
141
+ console.warn(message, ...args);
116
142
  }
117
- // Error (always shown, even in silent mode)
118
- error(message, ...args) {
143
+ error(message, _code, ...args) {
119
144
  if (isEnvTest()) return;
120
- this.console.error(message, ...args);
145
+ const errorMessage = message instanceof Error ? message.message : message;
146
+ console.error(errorMessage, ...args);
121
147
  }
122
- // Debug level (shown only in verbose mode)
123
148
  debug(message, ...args) {
124
- if (isEnvTest() || this._silent) return;
149
+ if (this.isSuppressed()) return;
125
150
  if (this._verbose) {
126
- this.console.info(message, ...args);
151
+ console.log(message, ...args);
127
152
  }
128
153
  }
129
154
  };
130
- var logger = new Logger();
155
+ var JsonLogger = class extends BaseLogger {
156
+ _jsonOutputDone = false;
157
+ _jsonData = {};
158
+ _commandName;
159
+ _version;
160
+ constructor({ command, version }) {
161
+ super();
162
+ this._commandName = command;
163
+ this._version = version;
164
+ }
165
+ get jsonMode() {
166
+ return true;
167
+ }
168
+ captureData(key, value) {
169
+ this._jsonData[key] = value;
170
+ }
171
+ getJsonData() {
172
+ return { ...this._jsonData };
173
+ }
174
+ outputJson(success, error) {
175
+ if (this._jsonOutputDone) return;
176
+ this._jsonOutputDone = true;
177
+ const output = {
178
+ success,
179
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
180
+ command: this._commandName,
181
+ version: this._version
182
+ };
183
+ if (success) {
184
+ output.data = this._jsonData;
185
+ } else if (error) {
186
+ output.error = {
187
+ code: error.code,
188
+ message: error.message
189
+ };
190
+ if (error.details) {
191
+ output.error.details = error.details;
192
+ }
193
+ if (error.stack) {
194
+ output.error.stack = error.stack;
195
+ }
196
+ }
197
+ const jsonStr = JSON.stringify(output, null, 2);
198
+ if (success) {
199
+ console.log(jsonStr);
200
+ } else {
201
+ console.error(jsonStr);
202
+ }
203
+ }
204
+ info(_message, ..._args) {
205
+ }
206
+ success(_message, ..._args) {
207
+ }
208
+ warn(_message, ..._args) {
209
+ }
210
+ error(message, code, ..._args) {
211
+ if (isEnvTest()) return;
212
+ const errorMessage = message instanceof Error ? message.message : message;
213
+ const errorInfo = {
214
+ code: code || ErrorCodes.UNKNOWN_ERROR,
215
+ message: errorMessage
216
+ };
217
+ if (this._verbose && message instanceof Error && message.stack) {
218
+ errorInfo.stack = message.stack;
219
+ }
220
+ this.outputJson(false, errorInfo);
221
+ }
222
+ debug(_message, ..._args) {
223
+ }
224
+ };
225
+ var logger = new ConsoleLogger();
131
226
 
132
227
  // src/lib/fetch.ts
133
228
  var import_node_path117 = require("path");
@@ -3008,7 +3103,9 @@ var CLAUDE_HOOK_EVENTS = [
3008
3103
  "preCompact",
3009
3104
  "permissionRequest",
3010
3105
  "notification",
3011
- "setup"
3106
+ "setup",
3107
+ "worktreeCreate",
3108
+ "worktreeRemove"
3012
3109
  ];
3013
3110
  var OPENCODE_HOOK_EVENTS = [
3014
3111
  "sessionStart",
@@ -3075,7 +3172,9 @@ var CANONICAL_TO_CLAUDE_EVENT_NAMES = {
3075
3172
  preCompact: "PreCompact",
3076
3173
  permissionRequest: "PermissionRequest",
3077
3174
  notification: "Notification",
3078
- setup: "Setup"
3175
+ setup: "Setup",
3176
+ worktreeCreate: "WorktreeCreate",
3177
+ worktreeRemove: "WorktreeRemove"
3079
3178
  };
3080
3179
  var CLAUDE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(
3081
3180
  Object.entries(CANONICAL_TO_CLAUDE_EVENT_NAMES).map(([k, v]) => [v, k])
@@ -3201,7 +3300,13 @@ function canonicalToToolHooks({
3201
3300
  else byMatcher.set(key, [def]);
3202
3301
  }
3203
3302
  const entries = [];
3303
+ const isNoMatcherEvent = converterConfig.noMatcherEvents?.has(eventName) ?? false;
3204
3304
  for (const [matcherKey, defs] of byMatcher) {
3305
+ if (isNoMatcherEvent && matcherKey) {
3306
+ logger.warn(
3307
+ `matcher "${matcherKey}" on "${eventName}" hook will be ignored \u2014 this event does not support matchers`
3308
+ );
3309
+ }
3205
3310
  const hooks = defs.map((def) => {
3206
3311
  const commandText = def.command;
3207
3312
  const trimmedCommand = typeof commandText === "string" ? commandText.trimStart() : void 0;
@@ -3214,7 +3319,8 @@ function canonicalToToolHooks({
3214
3319
  ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt }
3215
3320
  };
3216
3321
  });
3217
- entries.push(matcherKey ? { matcher: matcherKey, hooks } : { hooks });
3322
+ const includeMatcher = matcherKey && !isNoMatcherEvent;
3323
+ entries.push(includeMatcher ? { matcher: matcherKey, hooks } : { hooks });
3218
3324
  }
3219
3325
  result[toolEventName] = entries;
3220
3326
  }
@@ -3352,12 +3458,14 @@ var ToolHooks = class extends ToolFile {
3352
3458
  };
3353
3459
 
3354
3460
  // src/features/hooks/claudecode-hooks.ts
3461
+ var CLAUDE_NO_MATCHER_EVENTS = /* @__PURE__ */ new Set(["worktreeCreate", "worktreeRemove"]);
3355
3462
  var CLAUDE_CONVERTER_CONFIG = {
3356
3463
  supportedEvents: CLAUDE_HOOK_EVENTS,
3357
3464
  canonicalToToolEventNames: CANONICAL_TO_CLAUDE_EVENT_NAMES,
3358
3465
  toolToCanonicalEventNames: CLAUDE_TO_CANONICAL_EVENT_NAMES,
3359
3466
  projectDirVar: "$CLAUDE_PROJECT_DIR",
3360
- prefixDotRelativeCommandsOnly: true
3467
+ prefixDotRelativeCommandsOnly: true,
3468
+ noMatcherEvents: CLAUDE_NO_MATCHER_EVENTS
3361
3469
  };
3362
3470
  var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3363
3471
  constructor(params) {
@@ -17144,30 +17252,36 @@ function formatFetchSummary(summary) {
17144
17252
  }
17145
17253
 
17146
17254
  // src/cli/commands/fetch.ts
17147
- async function fetchCommand(options) {
17255
+ async function fetchCommand(logger2, options) {
17148
17256
  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}...`);
17257
+ logger2.debug(`Fetching files from ${source}...`);
17154
17258
  try {
17155
17259
  const summary = await fetchFiles({
17156
17260
  source,
17157
17261
  options: fetchOptions
17158
17262
  });
17263
+ if (logger2.jsonMode) {
17264
+ const createdFiles = summary.files.filter((f) => f.status === "created").map((f) => f.relativePath);
17265
+ const overwrittenFiles = summary.files.filter((f) => f.status === "overwritten").map((f) => f.relativePath);
17266
+ const skippedFiles = summary.files.filter((f) => f.status === "skipped").map((f) => f.relativePath);
17267
+ logger2.captureData("source", source);
17268
+ logger2.captureData("path", fetchOptions.path);
17269
+ logger2.captureData("created", createdFiles);
17270
+ logger2.captureData("overwritten", overwrittenFiles);
17271
+ logger2.captureData("skipped", skippedFiles);
17272
+ logger2.captureData("totalFetched", summary.created + summary.overwritten + summary.skipped);
17273
+ }
17159
17274
  const output = formatFetchSummary(summary);
17160
- logger.success(output);
17275
+ logger2.success(output);
17161
17276
  if (summary.created + summary.overwritten === 0 && summary.skipped === 0) {
17162
- logger.warn("No files were fetched.");
17277
+ logger2.warn("No files were fetched.");
17163
17278
  }
17164
17279
  } catch (error) {
17165
17280
  if (error instanceof GitHubClientError) {
17166
- logGitHubAuthHints(error);
17167
- } else {
17168
- logger.error(formatError(error));
17281
+ 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 ...`" : "";
17282
+ throw new CLIError(`GitHub API Error: ${error.message}.${authHint}`, ErrorCodes.FETCH_FAILED);
17169
17283
  }
17170
- process.exit(1);
17284
+ throw error;
17171
17285
  }
17172
17286
  }
17173
17287
 
@@ -17881,110 +17995,92 @@ function calculateTotalCount(result) {
17881
17995
  }
17882
17996
 
17883
17997
  // src/cli/commands/generate.ts
17884
- function logFeatureResult(params) {
17998
+ function logFeatureResult(logger2, params) {
17885
17999
  const { count, paths, featureName, isPreview, modePrefix } = params;
17886
18000
  if (count > 0) {
17887
18001
  if (isPreview) {
17888
- logger.info(`${modePrefix} Would write ${count} ${featureName}`);
18002
+ logger2.info(`${modePrefix} Would write ${count} ${featureName}`);
17889
18003
  } else {
17890
- logger.success(`Written ${count} ${featureName}`);
18004
+ logger2.success(`Written ${count} ${featureName}`);
17891
18005
  }
17892
18006
  for (const p of paths) {
17893
- logger.info(` ${p}`);
18007
+ logger2.info(` ${p}`);
17894
18008
  }
17895
18009
  }
17896
18010
  }
17897
- async function generateCommand(options) {
18011
+ async function generateCommand(logger2, options) {
17898
18012
  const config = await ConfigResolver.resolve(options);
17899
- logger.configure({
17900
- verbose: config.getVerbose(),
17901
- silent: config.getSilent()
17902
- });
17903
18013
  const check = config.getCheck();
17904
18014
  const isPreview = config.isPreviewMode();
17905
18015
  const modePrefix = isPreview ? "[DRY RUN]" : "";
17906
- logger.debug("Generating files...");
18016
+ logger2.debug("Generating files...");
17907
18017
  if (!await checkRulesyncDirExists({ baseDir: process.cwd() })) {
17908
- logger.error("\u274C .rulesync directory not found. Run 'rulesync init' first.");
17909
- process.exit(1);
18018
+ throw new CLIError(
18019
+ ".rulesync directory not found. Run 'rulesync init' first.",
18020
+ ErrorCodes.RULESYNC_DIR_NOT_FOUND
18021
+ );
17910
18022
  }
17911
- logger.debug(`Base directories: ${config.getBaseDirs().join(", ")}`);
18023
+ logger2.debug(`Base directories: ${config.getBaseDirs().join(", ")}`);
17912
18024
  const features = config.getFeatures();
17913
18025
  if (features.includes("ignore")) {
17914
- logger.debug("Generating ignore files...");
18026
+ logger2.debug("Generating ignore files...");
17915
18027
  }
17916
18028
  if (features.includes("mcp")) {
17917
- logger.debug("Generating MCP files...");
18029
+ logger2.debug("Generating MCP files...");
17918
18030
  }
17919
18031
  if (features.includes("commands")) {
17920
- logger.debug("Generating command files...");
18032
+ logger2.debug("Generating command files...");
17921
18033
  }
17922
18034
  if (features.includes("subagents")) {
17923
- logger.debug("Generating subagent files...");
18035
+ logger2.debug("Generating subagent files...");
17924
18036
  }
17925
18037
  if (features.includes("skills")) {
17926
- logger.debug("Generating skill files...");
18038
+ logger2.debug("Generating skill files...");
17927
18039
  }
17928
18040
  if (features.includes("hooks")) {
17929
- logger.debug("Generating hooks...");
18041
+ logger2.debug("Generating hooks...");
17930
18042
  }
17931
18043
  if (features.includes("rules")) {
17932
- logger.debug("Generating rule files...");
18044
+ logger2.debug("Generating rule files...");
17933
18045
  }
17934
18046
  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
18047
  const totalGenerated = calculateTotalCount(result);
18048
+ const featureResults = {
18049
+ ignore: { count: result.ignoreCount, paths: result.ignorePaths },
18050
+ mcp: { count: result.mcpCount, paths: result.mcpPaths },
18051
+ commands: { count: result.commandsCount, paths: result.commandsPaths },
18052
+ subagents: { count: result.subagentsCount, paths: result.subagentsPaths },
18053
+ skills: { count: result.skillsCount, paths: result.skillsPaths },
18054
+ hooks: { count: result.hooksCount, paths: result.hooksPaths },
18055
+ rules: { count: result.rulesCount, paths: result.rulesPaths }
18056
+ };
18057
+ const featureLabels = {
18058
+ rules: (count) => `${count === 1 ? "rule" : "rules"}`,
18059
+ ignore: (count) => `${count === 1 ? "ignore file" : "ignore files"}`,
18060
+ mcp: (count) => `${count === 1 ? "MCP file" : "MCP files"}`,
18061
+ commands: (count) => `${count === 1 ? "command" : "commands"}`,
18062
+ subagents: (count) => `${count === 1 ? "subagent" : "subagents"}`,
18063
+ skills: (count) => `${count === 1 ? "skill" : "skills"}`,
18064
+ hooks: (count) => `${count === 1 ? "hooks file" : "hooks files"}`
18065
+ };
18066
+ for (const [feature, data] of Object.entries(featureResults)) {
18067
+ logFeatureResult(logger2, {
18068
+ count: data.count,
18069
+ paths: data.paths,
18070
+ featureName: featureLabels[feature]?.(data.count) ?? feature,
18071
+ isPreview,
18072
+ modePrefix
18073
+ });
18074
+ }
18075
+ if (logger2.jsonMode) {
18076
+ logger2.captureData("features", featureResults);
18077
+ logger2.captureData("totalFiles", totalGenerated);
18078
+ logger2.captureData("hasDiff", result.hasDiff);
18079
+ logger2.captureData("skills", result.skills ?? []);
18080
+ }
17985
18081
  if (totalGenerated === 0) {
17986
18082
  const enabledFeatures = features.join(", ");
17987
- logger.info(`\u2713 All files are up to date (${enabledFeatures})`);
18083
+ logger2.info(`\u2713 All files are up to date (${enabledFeatures})`);
17988
18084
  return;
17989
18085
  }
17990
18086
  const parts = [];
@@ -17996,129 +18092,209 @@ async function generateCommand(options) {
17996
18092
  if (result.skillsCount > 0) parts.push(`${result.skillsCount} skills`);
17997
18093
  if (result.hooksCount > 0) parts.push(`${result.hooksCount} hooks`);
17998
18094
  if (isPreview) {
17999
- logger.info(`${modePrefix} Would write ${totalGenerated} file(s) total (${parts.join(" + ")})`);
18095
+ logger2.info(`${modePrefix} Would write ${totalGenerated} file(s) total (${parts.join(" + ")})`);
18000
18096
  } else {
18001
- logger.success(`\u{1F389} All done! Written ${totalGenerated} file(s) total (${parts.join(" + ")})`);
18097
+ logger2.success(`\u{1F389} All done! Written ${totalGenerated} file(s) total (${parts.join(" + ")})`);
18002
18098
  }
18003
18099
  if (check) {
18004
18100
  if (result.hasDiff) {
18005
- logger.error("\u274C Files are not up to date. Run 'rulesync generate' to update.");
18006
- process.exit(1);
18101
+ throw new CLIError(
18102
+ "Files are not up to date. Run 'rulesync generate' to update.",
18103
+ ErrorCodes.GENERATION_FAILED
18104
+ );
18007
18105
  } else {
18008
- logger.success("\u2713 All files are up to date.");
18106
+ logger2.success("\u2713 All files are up to date.");
18009
18107
  }
18010
18108
  }
18011
18109
  }
18012
18110
 
18013
18111
  // src/cli/commands/gitignore.ts
18014
18112
  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}/`,
18113
+
18114
+ // src/cli/commands/gitignore-entries.ts
18115
+ var GITIGNORE_ENTRY_REGISTRY = [
18116
+ // Common / general
18117
+ { target: "common", feature: "general", entry: `${RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH}/` },
18118
+ { target: "common", feature: "general", entry: ".rulesync/rules/*.local.md" },
18119
+ { target: "common", feature: "general", entry: "rulesync.local.jsonc" },
18120
+ { target: "common", feature: "general", entry: "!.rulesync/.aiignore" },
18020
18121
  // AGENTS.md
18021
- "**/AGENTS.md",
18022
- "**/.agents/",
18023
- // Augment
18024
- "**/.augmentignore",
18025
- "**/.augment/rules/",
18026
- "**/.augment-guidelines",
18122
+ { target: "agentsmd", feature: "rules", entry: "**/AGENTS.md" },
18123
+ { target: "agentsmd", feature: "rules", entry: "**/.agents/" },
18124
+ // Augment Code
18125
+ { target: "augmentcode", feature: "rules", entry: "**/.augment/rules/" },
18126
+ { target: "augmentcode", feature: "rules", entry: "**/.augment-guidelines" },
18127
+ { target: "augmentcode", feature: "ignore", entry: "**/.augmentignore" },
18027
18128
  // 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",
18129
+ { target: "claudecode", feature: "rules", entry: "**/CLAUDE.md" },
18130
+ { target: "claudecode", feature: "rules", entry: "**/CLAUDE.local.md" },
18131
+ { target: "claudecode", feature: "rules", entry: "**/.claude/CLAUDE.md" },
18132
+ { target: "claudecode", feature: "rules", entry: "**/.claude/CLAUDE.local.md" },
18133
+ { target: "claudecode", feature: "rules", entry: "**/.claude/rules/" },
18134
+ { target: "claudecode", feature: "commands", entry: "**/.claude/commands/" },
18135
+ { target: "claudecode", feature: "subagents", entry: "**/.claude/agents/" },
18136
+ { target: "claudecode", feature: "skills", entry: "**/.claude/skills/" },
18137
+ { target: "claudecode", feature: "mcp", entry: "**/.mcp.json" },
18138
+ { target: "claudecode", feature: "general", entry: "**/.claude/memories/" },
18139
+ { target: "claudecode", feature: "general", entry: "**/.claude/settings.local.json" },
18039
18140
  // 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",
18141
+ { target: "cline", feature: "rules", entry: "**/.clinerules/" },
18142
+ { target: "cline", feature: "commands", entry: "**/.clinerules/workflows/" },
18143
+ { target: "cline", feature: "ignore", entry: "**/.clineignore" },
18144
+ { target: "cline", feature: "mcp", entry: "**/.cline/mcp.json" },
18145
+ // Codex CLI
18146
+ { target: "codexcli", feature: "ignore", entry: "**/.codexignore" },
18147
+ { target: "codexcli", feature: "skills", entry: "**/.codex/skills/" },
18148
+ { target: "codexcli", feature: "subagents", entry: "**/.codex/agents/" },
18149
+ { target: "codexcli", feature: "general", entry: "**/.codex/memories/" },
18150
+ { target: "codexcli", feature: "general", entry: "**/.codex/config.toml" },
18050
18151
  // Cursor
18051
- "**/.cursor/",
18052
- "**/.cursorignore",
18152
+ { target: "cursor", feature: "rules", entry: "**/.cursor/" },
18153
+ { target: "cursor", feature: "ignore", entry: "**/.cursorignore" },
18053
18154
  // 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",
18155
+ { target: "factorydroid", feature: "rules", entry: "**/.factory/rules/" },
18156
+ { target: "factorydroid", feature: "commands", entry: "**/.factory/commands/" },
18157
+ { target: "factorydroid", feature: "subagents", entry: "**/.factory/droids/" },
18158
+ { target: "factorydroid", feature: "skills", entry: "**/.factory/skills/" },
18159
+ { target: "factorydroid", feature: "mcp", entry: "**/.factory/mcp.json" },
18160
+ { target: "factorydroid", feature: "general", entry: "**/.factory/settings.json" },
18161
+ // Gemini CLI
18162
+ { target: "geminicli", feature: "rules", entry: "**/GEMINI.md" },
18163
+ { target: "geminicli", feature: "commands", entry: "**/.gemini/commands/" },
18164
+ { target: "geminicli", feature: "subagents", entry: "**/.gemini/subagents/" },
18165
+ { target: "geminicli", feature: "skills", entry: "**/.gemini/skills/" },
18166
+ { target: "geminicli", feature: "ignore", entry: "**/.geminiignore" },
18167
+ { target: "geminicli", feature: "general", entry: "**/.gemini/memories/" },
18067
18168
  // Goose
18068
- "**/.goosehints",
18069
- "**/.goose/",
18070
- "**/.gooseignore",
18169
+ { target: "goose", feature: "rules", entry: "**/.goosehints" },
18170
+ { target: "goose", feature: "rules", entry: "**/.goose/" },
18171
+ { target: "goose", feature: "ignore", entry: "**/.gooseignore" },
18071
18172
  // 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",
18173
+ { target: "copilot", feature: "rules", entry: "**/.github/copilot-instructions.md" },
18174
+ { target: "copilot", feature: "rules", entry: "**/.github/instructions/" },
18175
+ { target: "copilot", feature: "commands", entry: "**/.github/prompts/" },
18176
+ { target: "copilot", feature: "subagents", entry: "**/.github/agents/" },
18177
+ { target: "copilot", feature: "skills", entry: "**/.github/skills/" },
18178
+ { target: "copilot", feature: "hooks", entry: "**/.github/hooks/" },
18179
+ { target: "copilot", feature: "mcp", entry: "**/.vscode/mcp.json" },
18079
18180
  // Junie
18080
- "**/.junie/guidelines.md",
18081
- "**/.junie/mcp.json",
18082
- "**/.junie/skills/",
18083
- "**/.junie/agents/",
18181
+ { target: "junie", feature: "rules", entry: "**/.junie/guidelines.md" },
18182
+ { target: "junie", feature: "mcp", entry: "**/.junie/mcp.json" },
18183
+ { target: "junie", feature: "skills", entry: "**/.junie/skills/" },
18184
+ { target: "junie", feature: "subagents", entry: "**/.junie/agents/" },
18084
18185
  // Kilo Code
18085
- "**/.kilocode/rules/",
18086
- "**/.kilocode/skills/",
18087
- "**/.kilocode/workflows/",
18088
- "**/.kilocode/mcp.json",
18089
- "**/.kilocodeignore",
18186
+ { target: "kilo", feature: "rules", entry: "**/.kilocode/rules/" },
18187
+ { target: "kilo", feature: "skills", entry: "**/.kilocode/skills/" },
18188
+ { target: "kilo", feature: "commands", entry: "**/.kilocode/workflows/" },
18189
+ { target: "kilo", feature: "mcp", entry: "**/.kilocode/mcp.json" },
18190
+ { target: "kilo", feature: "ignore", entry: "**/.kilocodeignore" },
18090
18191
  // Kiro
18091
- "**/.kiro/steering/",
18092
- "**/.kiro/prompts/",
18093
- "**/.kiro/skills/",
18094
- "**/.kiro/agents/",
18095
- "**/.kiro/settings/mcp.json",
18096
- "**/.aiignore",
18192
+ { target: "kiro", feature: "rules", entry: "**/.kiro/steering/" },
18193
+ { target: "kiro", feature: "commands", entry: "**/.kiro/prompts/" },
18194
+ { target: "kiro", feature: "skills", entry: "**/.kiro/skills/" },
18195
+ { target: "kiro", feature: "subagents", entry: "**/.kiro/agents/" },
18196
+ { target: "kiro", feature: "mcp", entry: "**/.kiro/settings/mcp.json" },
18197
+ { target: "kiro", feature: "ignore", entry: "**/.aiignore" },
18097
18198
  // OpenCode
18098
- "**/.opencode/memories/",
18099
- "**/.opencode/command/",
18100
- "**/.opencode/agent/",
18101
- "**/.opencode/skill/",
18102
- "**/.opencode/plugins/",
18103
- // Qwen
18104
- "**/QWEN.md",
18105
- "**/.qwen/memories/",
18199
+ { target: "opencode", feature: "commands", entry: "**/.opencode/command/" },
18200
+ { target: "opencode", feature: "subagents", entry: "**/.opencode/agent/" },
18201
+ { target: "opencode", feature: "skills", entry: "**/.opencode/skill/" },
18202
+ { target: "opencode", feature: "mcp", entry: "**/.opencode/plugins/" },
18203
+ { target: "opencode", feature: "general", entry: "**/.opencode/memories/" },
18204
+ // Qwen Code
18205
+ { target: "qwencode", feature: "rules", entry: "**/QWEN.md" },
18206
+ { target: "qwencode", feature: "general", entry: "**/.qwen/memories/" },
18106
18207
  // Replit
18107
- "**/replit.md",
18208
+ { target: "replit", feature: "rules", entry: "**/replit.md" },
18108
18209
  // Roo
18109
- "**/.roo/rules/",
18110
- "**/.roo/skills/",
18111
- "**/.rooignore",
18112
- "**/.roo/mcp.json",
18113
- "**/.roo/subagents/",
18210
+ { target: "roo", feature: "rules", entry: "**/.roo/rules/" },
18211
+ { target: "roo", feature: "skills", entry: "**/.roo/skills/" },
18212
+ { target: "roo", feature: "ignore", entry: "**/.rooignore" },
18213
+ { target: "roo", feature: "mcp", entry: "**/.roo/mcp.json" },
18214
+ { target: "roo", feature: "subagents", entry: "**/.roo/subagents/" },
18114
18215
  // Warp
18115
- "**/.warp/",
18116
- "**/WARP.md",
18117
- // Others
18118
- ".rulesync/rules/*.local.md",
18119
- "rulesync.local.jsonc",
18120
- "!.rulesync/.aiignore"
18216
+ { target: "warp", feature: "rules", entry: "**/.warp/" },
18217
+ { target: "warp", feature: "rules", entry: "**/WARP.md" }
18121
18218
  ];
18219
+ var ALL_GITIGNORE_ENTRIES = GITIGNORE_ENTRY_REGISTRY.map(
18220
+ (tag) => tag.entry
18221
+ );
18222
+ var isTargetSelected = (target, selectedTargets) => {
18223
+ if (target === "common") return true;
18224
+ if (!selectedTargets || selectedTargets.length === 0) return true;
18225
+ if (selectedTargets.includes("*")) return true;
18226
+ return selectedTargets.includes(target);
18227
+ };
18228
+ var isFeatureSelected = (feature, target, features) => {
18229
+ if (feature === "general") return true;
18230
+ if (!features) return true;
18231
+ if (Array.isArray(features)) {
18232
+ if (features.length === 0) return true;
18233
+ if (features.includes("*")) return true;
18234
+ return features.includes(feature);
18235
+ }
18236
+ if (target === "common") return true;
18237
+ const targetFeatures = features[target];
18238
+ if (!targetFeatures) return true;
18239
+ if (targetFeatures.includes("*")) return true;
18240
+ return targetFeatures.includes(feature);
18241
+ };
18242
+ var warnInvalidTargets = (targets) => {
18243
+ const validTargets = new Set(ALL_TOOL_TARGETS_WITH_WILDCARD);
18244
+ for (const target of targets) {
18245
+ if (!validTargets.has(target)) {
18246
+ logger.warn(
18247
+ `Unknown target '${target}'. Valid targets: ${ALL_TOOL_TARGETS_WITH_WILDCARD.join(", ")}`
18248
+ );
18249
+ }
18250
+ }
18251
+ };
18252
+ var warnInvalidFeatures = (features) => {
18253
+ const validFeatures = new Set(ALL_FEATURES_WITH_WILDCARD);
18254
+ if (Array.isArray(features)) {
18255
+ for (const feature of features) {
18256
+ if (!validFeatures.has(feature)) {
18257
+ logger.warn(
18258
+ `Unknown feature '${feature}'. Valid features: ${ALL_FEATURES_WITH_WILDCARD.join(", ")}`
18259
+ );
18260
+ }
18261
+ }
18262
+ } else {
18263
+ for (const targetFeatures of Object.values(features)) {
18264
+ if (!targetFeatures) continue;
18265
+ for (const feature of targetFeatures) {
18266
+ if (!validFeatures.has(feature)) {
18267
+ logger.warn(
18268
+ `Unknown feature '${feature}'. Valid features: ${ALL_FEATURES_WITH_WILDCARD.join(", ")}`
18269
+ );
18270
+ }
18271
+ }
18272
+ }
18273
+ }
18274
+ };
18275
+ var filterGitignoreEntries = (params) => {
18276
+ const { targets, features } = params ?? {};
18277
+ if (targets && targets.length > 0) {
18278
+ warnInvalidTargets(targets);
18279
+ }
18280
+ if (features) {
18281
+ warnInvalidFeatures(features);
18282
+ }
18283
+ const seen = /* @__PURE__ */ new Set();
18284
+ const result = [];
18285
+ for (const tag of GITIGNORE_ENTRY_REGISTRY) {
18286
+ if (!isTargetSelected(tag.target, targets)) continue;
18287
+ if (!isFeatureSelected(tag.feature, tag.target, features)) continue;
18288
+ if (seen.has(tag.entry)) continue;
18289
+ seen.add(tag.entry);
18290
+ result.push(tag.entry);
18291
+ }
18292
+ return result;
18293
+ };
18294
+
18295
+ // src/cli/commands/gitignore.ts
18296
+ var RULESYNC_HEADER = "# Generated by Rulesync";
18297
+ var LEGACY_RULESYNC_HEADER = "# Generated by rulesync - AI tool configuration files";
18122
18298
  var isRulesyncHeader = (line) => {
18123
18299
  const trimmed = line.trim();
18124
18300
  return trimmed === RULESYNC_HEADER || trimmed === LEGACY_RULESYNC_HEADER;
@@ -18128,7 +18304,7 @@ var isRulesyncEntry = (line) => {
18128
18304
  if (trimmed === "" || isRulesyncHeader(line)) {
18129
18305
  return false;
18130
18306
  }
18131
- return RULESYNC_IGNORE_ENTRIES.includes(trimmed);
18307
+ return ALL_GITIGNORE_ENTRIES.includes(trimmed);
18132
18308
  };
18133
18309
  var removeExistingRulesyncEntries = (content) => {
18134
18310
  const lines = content.split("\n");
@@ -18168,37 +18344,56 @@ var removeExistingRulesyncEntries = (content) => {
18168
18344
  }
18169
18345
  return result;
18170
18346
  };
18171
- var gitignoreCommand = async () => {
18347
+ var gitignoreCommand = async (logger2, options) => {
18172
18348
  const gitignorePath = (0, import_node_path121.join)(process.cwd(), ".gitignore");
18173
18349
  let gitignoreContent = "";
18174
18350
  if (await fileExists(gitignorePath)) {
18175
18351
  gitignoreContent = await readFileContent(gitignorePath);
18176
18352
  }
18177
18353
  const cleanedContent = removeExistingRulesyncEntries(gitignoreContent);
18178
- const rulesyncBlock = [RULESYNC_HEADER, ...RULESYNC_IGNORE_ENTRIES].join("\n");
18354
+ const filteredEntries = filterGitignoreEntries({
18355
+ targets: options?.targets,
18356
+ features: options?.features
18357
+ });
18358
+ const existingEntries = new Set(
18359
+ gitignoreContent.split("\n").map((line) => line.trim()).filter((line) => line !== "" && !isRulesyncHeader(line))
18360
+ );
18361
+ const alreadyExistedEntries = filteredEntries.filter((entry) => existingEntries.has(entry));
18362
+ const entriesToAdd = filteredEntries.filter((entry) => !existingEntries.has(entry));
18363
+ const rulesyncBlock = [RULESYNC_HEADER, ...filteredEntries].join("\n");
18179
18364
  const newContent = cleanedContent.trim() ? `${cleanedContent.trimEnd()}
18180
18365
 
18181
18366
  ${rulesyncBlock}
18182
18367
  ` : `${rulesyncBlock}
18183
18368
  `;
18184
18369
  if (gitignoreContent === newContent) {
18185
- logger.success(".gitignore is already up to date");
18370
+ if (logger2.jsonMode) {
18371
+ logger2.captureData("entriesAdded", []);
18372
+ logger2.captureData("gitignorePath", gitignorePath);
18373
+ logger2.captureData("alreadyExisted", filteredEntries);
18374
+ }
18375
+ logger2.success(".gitignore is already up to date");
18186
18376
  return;
18187
18377
  }
18188
18378
  await writeFileContent(gitignorePath, newContent);
18189
- logger.success("Updated .gitignore with rulesync entries:");
18190
- for (const entry of RULESYNC_IGNORE_ENTRIES) {
18191
- logger.info(` ${entry}`);
18379
+ if (logger2.jsonMode) {
18380
+ logger2.captureData("entriesAdded", entriesToAdd);
18381
+ logger2.captureData("gitignorePath", gitignorePath);
18382
+ logger2.captureData("alreadyExisted", alreadyExistedEntries);
18192
18383
  }
18193
- logger.info("");
18194
- logger.info(
18384
+ logger2.success("Updated .gitignore with rulesync entries:");
18385
+ for (const entry of filteredEntries) {
18386
+ logger2.info(` ${entry}`);
18387
+ }
18388
+ logger2.info("");
18389
+ logger2.info(
18195
18390
  "\u{1F4A1} If you're using Google Antigravity, note that rules, workflows, and skills won't load if they're gitignored."
18196
18391
  );
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");
18392
+ logger2.info(" You can add the following to .git/info/exclude instead:");
18393
+ logger2.info(" **/.agent/rules/");
18394
+ logger2.info(" **/.agent/workflows/");
18395
+ logger2.info(" **/.agent/skills/");
18396
+ logger2.info(" For more details: https://github.com/dyoshikawa/rulesync/issues/981");
18202
18397
  };
18203
18398
 
18204
18399
  // src/lib/import.ts
@@ -18421,29 +18616,36 @@ async function importHooksCore(params) {
18421
18616
  }
18422
18617
 
18423
18618
  // src/cli/commands/import.ts
18424
- async function importCommand(options) {
18619
+ async function importCommand(logger2, options) {
18425
18620
  if (!options.targets) {
18426
- logger.error("No tools found in --targets");
18427
- process.exit(1);
18621
+ throw new CLIError("No tools found in --targets", ErrorCodes.IMPORT_FAILED);
18428
18622
  }
18429
18623
  if (options.targets.length > 1) {
18430
- logger.error("Only one tool can be imported at a time");
18431
- process.exit(1);
18624
+ throw new CLIError("Only one tool can be imported at a time", ErrorCodes.IMPORT_FAILED);
18432
18625
  }
18433
18626
  const config = await ConfigResolver.resolve(options);
18434
- logger.configure({
18435
- verbose: config.getVerbose(),
18436
- silent: config.getSilent()
18437
- });
18438
18627
  const tool = config.getTargets()[0];
18439
- logger.debug(`Importing files from ${tool}...`);
18628
+ logger2.debug(`Importing files from ${tool}...`);
18440
18629
  const result = await importFromTool({ config, tool });
18441
18630
  const totalImported = calculateTotalCount(result);
18442
18631
  if (totalImported === 0) {
18443
18632
  const enabledFeatures = config.getFeatures().join(", ");
18444
- logger.warn(`No files imported for enabled features: ${enabledFeatures}`);
18633
+ logger2.warn(`No files imported for enabled features: ${enabledFeatures}`);
18445
18634
  return;
18446
18635
  }
18636
+ if (logger2.jsonMode) {
18637
+ logger2.captureData("tool", tool);
18638
+ logger2.captureData("features", {
18639
+ rules: { count: result.rulesCount },
18640
+ ignore: { count: result.ignoreCount },
18641
+ mcp: { count: result.mcpCount },
18642
+ commands: { count: result.commandsCount },
18643
+ subagents: { count: result.subagentsCount },
18644
+ skills: { count: result.skillsCount },
18645
+ hooks: { count: result.hooksCount }
18646
+ });
18647
+ logger2.captureData("totalFiles", totalImported);
18648
+ }
18447
18649
  const parts = [];
18448
18650
  if (result.rulesCount > 0) parts.push(`${result.rulesCount} rules`);
18449
18651
  if (result.ignoreCount > 0) parts.push(`${result.ignoreCount} ignore files`);
@@ -18452,7 +18654,7 @@ async function importCommand(options) {
18452
18654
  if (result.subagentsCount > 0) parts.push(`${result.subagentsCount} subagents`);
18453
18655
  if (result.skillsCount > 0) parts.push(`${result.skillsCount} skills`);
18454
18656
  if (result.hooksCount > 0) parts.push(`${result.hooksCount} hooks`);
18455
- logger.success(`Imported ${totalImported} file(s) total (${parts.join(" + ")})`);
18657
+ logger2.success(`Imported ${totalImported} file(s) total (${parts.join(" + ")})`);
18456
18658
  }
18457
18659
 
18458
18660
  // src/lib/init.ts
@@ -18681,28 +18883,38 @@ async function writeIfNotExists(path4, content) {
18681
18883
  }
18682
18884
 
18683
18885
  // src/cli/commands/init.ts
18684
- async function initCommand() {
18685
- logger.debug("Initializing rulesync...");
18886
+ async function initCommand(logger2) {
18887
+ logger2.debug("Initializing rulesync...");
18686
18888
  await ensureDir(RULESYNC_RELATIVE_DIR_PATH);
18687
18889
  const result = await init();
18890
+ const createdFiles = [];
18891
+ const skippedFiles = [];
18688
18892
  for (const file of result.sampleFiles) {
18689
18893
  if (file.created) {
18690
- logger.success(`Created ${file.path}`);
18894
+ createdFiles.push(file.path);
18895
+ logger2.success(`Created ${file.path}`);
18691
18896
  } else {
18692
- logger.info(`Skipped ${file.path} (already exists)`);
18897
+ skippedFiles.push(file.path);
18898
+ logger2.info(`Skipped ${file.path} (already exists)`);
18693
18899
  }
18694
18900
  }
18695
18901
  if (result.configFile.created) {
18696
- logger.success(`Created ${result.configFile.path}`);
18902
+ createdFiles.push(result.configFile.path);
18903
+ logger2.success(`Created ${result.configFile.path}`);
18697
18904
  } else {
18698
- logger.info(`Skipped ${result.configFile.path} (already exists)`);
18905
+ skippedFiles.push(result.configFile.path);
18906
+ logger2.info(`Skipped ${result.configFile.path} (already exists)`);
18699
18907
  }
18700
- logger.success("rulesync initialized successfully!");
18701
- logger.info("Next steps:");
18702
- logger.info(
18908
+ if (logger2.jsonMode) {
18909
+ logger2.captureData("created", createdFiles);
18910
+ logger2.captureData("skipped", skippedFiles);
18911
+ }
18912
+ logger2.success("rulesync initialized successfully!");
18913
+ logger2.info("Next steps:");
18914
+ logger2.info(
18703
18915
  `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
18916
  );
18705
- logger.info("2. Run 'rulesync generate' to create configuration files");
18917
+ logger2.info("2. Run 'rulesync generate' to create configuration files");
18706
18918
  }
18707
18919
 
18708
18920
  // src/lib/sources.ts
@@ -19428,11 +19640,7 @@ async function fetchSourceViaGit(params) {
19428
19640
  }
19429
19641
 
19430
19642
  // src/cli/commands/install.ts
19431
- async function installCommand(options) {
19432
- logger.configure({
19433
- verbose: options.verbose ?? false,
19434
- silent: options.silent ?? false
19435
- });
19643
+ async function installCommand(logger2, options) {
19436
19644
  const config = await ConfigResolver.resolve({
19437
19645
  configPath: options.configPath,
19438
19646
  verbose: options.verbose,
@@ -19440,10 +19648,10 @@ async function installCommand(options) {
19440
19648
  });
19441
19649
  const sources = config.getSources();
19442
19650
  if (sources.length === 0) {
19443
- logger.warn("No sources defined in configuration. Nothing to install.");
19651
+ logger2.warn("No sources defined in configuration. Nothing to install.");
19444
19652
  return;
19445
19653
  }
19446
- logger.debug(`Installing skills from ${sources.length} source(s)...`);
19654
+ logger2.debug(`Installing skills from ${sources.length} source(s)...`);
19447
19655
  const result = await resolveAndFetchSources({
19448
19656
  sources,
19449
19657
  baseDir: process.cwd(),
@@ -19453,12 +19661,16 @@ async function installCommand(options) {
19453
19661
  token: options.token
19454
19662
  }
19455
19663
  });
19664
+ if (logger2.jsonMode) {
19665
+ logger2.captureData("sourcesProcessed", result.sourcesProcessed);
19666
+ logger2.captureData("skillsFetched", result.fetchedSkillCount);
19667
+ }
19456
19668
  if (result.fetchedSkillCount > 0) {
19457
- logger.success(
19669
+ logger2.success(
19458
19670
  `Installed ${result.fetchedSkillCount} skill(s) from ${result.sourcesProcessed} source(s).`
19459
19671
  );
19460
19672
  } else {
19461
- logger.success(`All skills up to date (${result.sourcesProcessed} source(s) checked).`);
19673
+ logger2.success(`All skills up to date (${result.sourcesProcessed} source(s) checked).`);
19462
19674
  }
19463
19675
  }
19464
19676
 
@@ -20873,7 +21085,7 @@ var rulesyncTool = {
20873
21085
  };
20874
21086
 
20875
21087
  // src/cli/commands/mcp.ts
20876
- async function mcpCommand({ version }) {
21088
+ async function mcpCommand(logger2, { version }) {
20877
21089
  const server = new import_fastmcp.FastMCP({
20878
21090
  name: "Rulesync MCP Server",
20879
21091
  // eslint-disable-next-line no-type-assertion/no-type-assertion
@@ -20881,7 +21093,7 @@ async function mcpCommand({ version }) {
20881
21093
  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
21094
  });
20883
21095
  server.addTool(rulesyncTool);
20884
- logger.info("Rulesync MCP server started via stdio");
21096
+ logger2.info("Rulesync MCP server started via stdio");
20885
21097
  void server.start({
20886
21098
  transportType: "stdio"
20887
21099
  });
@@ -21197,158 +21409,182 @@ To upgrade, run:
21197
21409
  }
21198
21410
 
21199
21411
  // 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 });
21412
+ async function updateCommand(logger2, currentVersion, options) {
21413
+ const { check = false, force = false, token } = options;
21203
21414
  try {
21204
21415
  const environment = detectExecutionEnvironment();
21205
- logger.debug(`Detected environment: ${environment}`);
21416
+ logger2.debug(`Detected environment: ${environment}`);
21206
21417
  if (environment === "npm") {
21207
- logger.info(getNpmUpgradeInstructions());
21418
+ logger2.info(getNpmUpgradeInstructions());
21208
21419
  return;
21209
21420
  }
21210
21421
  if (environment === "homebrew") {
21211
- logger.info(getHomebrewUpgradeInstructions());
21422
+ logger2.info(getHomebrewUpgradeInstructions());
21212
21423
  return;
21213
21424
  }
21214
21425
  if (check) {
21215
- logger.info("Checking for updates...");
21426
+ logger2.info("Checking for updates...");
21216
21427
  const updateCheck = await checkForUpdate(currentVersion, token);
21428
+ if (logger2.jsonMode) {
21429
+ logger2.captureData("currentVersion", updateCheck.currentVersion);
21430
+ logger2.captureData("latestVersion", updateCheck.latestVersion);
21431
+ logger2.captureData("updateAvailable", updateCheck.hasUpdate);
21432
+ logger2.captureData(
21433
+ "message",
21434
+ updateCheck.hasUpdate ? `Update available: ${updateCheck.currentVersion} -> ${updateCheck.latestVersion}` : `Already at the latest version (${updateCheck.currentVersion})`
21435
+ );
21436
+ }
21217
21437
  if (updateCheck.hasUpdate) {
21218
- logger.success(
21438
+ logger2.success(
21219
21439
  `Update available: ${updateCheck.currentVersion} -> ${updateCheck.latestVersion}`
21220
21440
  );
21221
21441
  } else {
21222
- logger.info(`Already at the latest version (${updateCheck.currentVersion})`);
21442
+ logger2.info(`Already at the latest version (${updateCheck.currentVersion})`);
21223
21443
  }
21224
21444
  return;
21225
21445
  }
21226
- logger.info("Checking for updates...");
21446
+ logger2.info("Checking for updates...");
21227
21447
  const message = await performBinaryUpdate(currentVersion, { force, token });
21228
- logger.success(message);
21448
+ logger2.success(message);
21229
21449
  } catch (error) {
21230
21450
  if (error instanceof GitHubClientError) {
21231
- logGitHubAuthHints(error);
21451
+ 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 ...`" : "";
21452
+ throw new CLIError(
21453
+ `GitHub API Error: ${error.message}.${authHint}`,
21454
+ ErrorCodes.UPDATE_FAILED
21455
+ );
21232
21456
  } 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));
21457
+ throw new CLIError(
21458
+ `${error.message} Tip: Run with elevated privileges (e.g., sudo rulesync update)`,
21459
+ ErrorCodes.UPDATE_FAILED
21460
+ );
21237
21461
  }
21238
- process.exit(1);
21462
+ throw error;
21239
21463
  }
21240
21464
  }
21241
21465
 
21242
21466
  // src/cli/index.ts
21243
- var getVersion = () => "7.18.2";
21467
+ var getVersion = () => "7.20.0";
21468
+ function wrapCommand(name, errorCode, handler) {
21469
+ return async (...args) => {
21470
+ const command = args[args.length - 1];
21471
+ const options = args[args.length - 2];
21472
+ const positionalArgs = args.slice(0, -2);
21473
+ const globalOpts = command.parent?.opts() ?? {};
21474
+ const logger2 = globalOpts.json ? new JsonLogger({ command: name, version: getVersion() }) : new ConsoleLogger();
21475
+ logger2.configure({
21476
+ verbose: Boolean(globalOpts.verbose) || Boolean(options.verbose),
21477
+ silent: Boolean(globalOpts.silent) || Boolean(options.silent)
21478
+ });
21479
+ try {
21480
+ await handler(logger2, options, globalOpts, positionalArgs);
21481
+ logger2.outputJson(true);
21482
+ } catch (error) {
21483
+ const code = error instanceof CLIError ? error.code : errorCode;
21484
+ const errorArg = error instanceof Error ? error : formatError(error);
21485
+ logger2.error(errorArg, code);
21486
+ process.exit(error instanceof CLIError ? error.exitCode : 1);
21487
+ }
21488
+ };
21489
+ }
21244
21490
  var main = async () => {
21245
21491
  const program = new import_commander.Command();
21246
21492
  const version = getVersion();
21247
- program.hook("postAction", () => {
21248
- if (ANNOUNCEMENT.length > 0) {
21249
- logger.info(ANNOUNCEMENT);
21493
+ program.name("rulesync").description("Unified AI rules management CLI tool").version(version, "-v, --version", "Show version").option("-j, --json", "Output results as JSON");
21494
+ program.command("init").description("Initialize rulesync in current directory").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
21495
+ wrapCommand("init", "INIT_FAILED", async (logger2) => {
21496
+ await initCommand(logger2);
21497
+ })
21498
+ );
21499
+ program.command("gitignore").description("Add generated files to .gitignore").option(
21500
+ "-t, --targets <tools>",
21501
+ "Comma-separated list of tools to include (e.g., 'claudecode,copilot' or '*' for all)",
21502
+ (value) => {
21503
+ return value.split(",").map((t) => t.trim()).filter(Boolean);
21250
21504
  }
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);
21505
+ ).option(
21506
+ "-f, --features <features>",
21507
+ `Comma-separated list of features to include (${ALL_FEATURES.join(",")}) or '*' for all`,
21508
+ (value) => {
21509
+ return value.split(",").map((f) => f.trim()).filter(Boolean);
21510
+ }
21511
+ ).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
21512
+ wrapCommand("gitignore", "GITIGNORE_FAILED", async (logger2, options) => {
21513
+ await gitignoreCommand(logger2, {
21514
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
21515
+ targets: options.targets,
21516
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
21517
+ features: options.features
21518
+ });
21519
+ })
21520
+ );
21255
21521
  program.command("fetch <source>").description("Fetch files from a Git repository (GitHub/GitLab)").option(
21256
21522
  "-t, --target <target>",
21257
21523
  "Target format to interpret files as (e.g., 'rulesync', 'claudecode'). Default: rulesync"
21258
21524
  ).option(
21259
21525
  "-f, --features <features>",
21260
21526
  `Comma-separated list of features to fetch (${ALL_FEATURES.join(",")}) or '*' for all`,
21261
- (value) => {
21262
- return value.split(",").map((f) => f.trim());
21263
- }
21527
+ (value) => value.split(",").map((f) => f.trim())
21264
21528
  ).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
21529
  "-c, --conflict <strategy>",
21266
21530
  "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
- });
21531
+ ).option("--token <token>", "Git provider token for private repositories").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
21532
+ wrapCommand("fetch", "FETCH_FAILED", async (logger2, options, _globalOpts, positionalArgs) => {
21533
+ const source = positionalArgs[0];
21534
+ await fetchCommand(logger2, { ...options, source });
21535
+ })
21536
+ );
21281
21537
  program.command("import").description("Import configurations from AI tools to rulesync format").option(
21282
21538
  "-t, --targets <tool>",
21283
21539
  "Tool to import from (e.g., 'copilot', 'cursor', 'cline')",
21284
- (value) => {
21285
- return value.split(",").map((t) => t.trim());
21286
- }
21540
+ (value) => value.split(",").map((t) => t.trim())
21287
21541
  ).option(
21288
21542
  "-f, --features <features>",
21289
21543
  `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
- });
21544
+ (value) => value.split(",").map((f) => f.trim())
21545
+ ).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").option("-g, --global", "Import for global(user scope) configuration files").action(
21546
+ wrapCommand("import", "IMPORT_FAILED", async (logger2, options) => {
21547
+ await importCommand(logger2, options);
21548
+ })
21549
+ );
21550
+ program.command("mcp").description("Start MCP server for rulesync").action(
21551
+ wrapCommand("mcp", "MCP_FAILED", async (logger2, _options) => {
21552
+ await mcpCommand(logger2, { version });
21553
+ })
21554
+ );
21316
21555
  program.command("install").description("Install skills from declarative sources in rulesync.jsonc").option("--update", "Force re-resolve all source refs, ignoring lockfile").option(
21317
21556
  "--frozen",
21318
21557
  "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({
21558
+ ).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(
21559
+ wrapCommand("install", "INSTALL_FAILED", async (logger2, options) => {
21560
+ await installCommand(logger2, {
21561
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
21322
21562
  update: options.update,
21563
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
21323
21564
  frozen: options.frozen,
21565
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
21324
21566
  token: options.token,
21567
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
21325
21568
  configPath: options.config,
21569
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
21326
21570
  verbose: options.verbose,
21571
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
21327
21572
  silent: options.silent
21328
21573
  });
21329
- } catch (error) {
21330
- logger.error(formatError(error));
21331
- process.exit(1);
21332
- }
21333
- });
21574
+ })
21575
+ );
21334
21576
  program.command("generate").description("Generate configuration files for AI tools").option(
21335
21577
  "-t, --targets <tools>",
21336
21578
  "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
- }
21579
+ (value) => value.split(",").map((t) => t.trim())
21340
21580
  ).option(
21341
21581
  "-f, --features <features>",
21342
21582
  `Comma-separated list of features to generate (${ALL_FEATURES.join(",")}) or '*' for all`,
21343
- (value) => {
21344
- return value.split(",").map((f) => f.trim());
21345
- }
21583
+ (value) => value.split(",").map((f) => f.trim())
21346
21584
  ).option("--delete", "Delete all existing files in output directories before generating").option(
21347
21585
  "-b, --base-dir <paths>",
21348
21586
  "Base directories to generate files (comma-separated for multiple paths)",
21349
- (value) => {
21350
- return value.split(",").map((p) => p.trim());
21351
- }
21587
+ (value) => value.split(",").map((p) => p.trim())
21352
21588
  ).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
21589
  "--simulate-commands",
21354
21590
  "Generate simulated commands. This feature is only available for copilot, cursor and codexcli."
@@ -21358,40 +21594,19 @@ var main = async () => {
21358
21594
  ).option(
21359
21595
  "--simulate-skills",
21360
21596
  "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
- });
21597
+ ).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(
21598
+ wrapCommand("generate", "GENERATION_FAILED", async (logger2, options) => {
21599
+ await generateCommand(logger2, options);
21600
+ })
21601
+ );
21602
+ 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(
21603
+ wrapCommand("update", "UPDATE_FAILED", async (logger2, options) => {
21604
+ await updateCommand(logger2, version, options);
21605
+ })
21606
+ );
21392
21607
  program.parse();
21393
21608
  };
21394
21609
  main().catch((error) => {
21395
- logger.error(formatError(error));
21610
+ console.error(formatError(error));
21396
21611
  process.exit(1);
21397
21612
  });