rulesync 14.2.0 → 15.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -53,7 +53,7 @@ See [Installation docs](https://dyoshikawa.github.io/rulesync/getting-started/in
53
53
  rulesync init
54
54
 
55
55
  # Install official skills (recommended)
56
- rulesync fetch dyoshikawa/rulesync --features skills
56
+ rulesync fetch dyoshikawa/rulesync
57
57
 
58
58
  # Generate unified configurations with all features
59
59
  rulesync generate --targets "*" --features "*"
@@ -86,41 +86,44 @@ The tables below show whether each tool supports a given feature (✅ = supporte
86
86
 
87
87
  <!-- SUPPORTED_TOOLS_AI:BEGIN -->
88
88
 
89
- | Tool | rules | ignore | mcp | commands | subagents | skills | hooks | permissions | checks |
90
- | ---------------------- | :---: | :----: | :-: | :------: | :-------: | :----: | :---: | :---------: | :----: |
91
- | Amp | ✅ | | ✅ | | | ✅ | ✅ | ✅ | ✅ |
92
- | Claude Code | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
93
- | Codex CLI || | ✅ | ✅ | ✅ | ✅ | ✅ || |
94
- | GitHub Copilot | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
95
- | GitHub Copilot CLI | ✅ | | ✅ | | ✅ | ✅ | ✅ | | |
96
- | Goose | ✅ || ✅ || ✅ | ✅ | ✅ || |
97
- | Hermes Agent | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ ||
98
- | Grok CLI | ✅ | | ✅ | | ✅ | ✅ | ✅ | ✅ | |
99
- | Cursor | ✅ || ✅ || ✅ | ✅ | ✅ | ✅ | |
100
- | deepagents-cli | ✅ | | ✅ | | ✅ | ✅ | ✅ | | |
101
- | Factory Droid | ✅ | | ✅ || ✅ | ✅ | ✅ || |
102
- | OpenCode | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
103
- | Cline | ✅ || ✅ | ✅ | ✅ | ✅ | | ✅ | |
104
- | Kilo Code | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ || ✅ | |
105
- | Roo Code | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | |
106
- | Rovodev (Atlassian) | ✅ | | ✅ || ✅ | ✅ | | ✅ | |
107
- | Takt | ✅ | | ✅ | ✅ | ✅ | ✅ | || |
108
- | Vibe Code | ✅ || ✅ | | ✅ | ✅ || ✅ | |
109
- | Qwen Code | ✅ || ✅ | ✅ | ✅ | ✅ || ✅ | |
110
- | Reasonix | ✅ | | ✅ || ✅ | ✅ | ✅ | ✅ | |
111
- | Kiro ⚠️ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
112
- | Kiro CLI | ✅ || ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
113
- | Kiro IDE | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
114
- | Google Antigravity IDE | ✅ | | ✅ | ✅ | | ✅ | ✅ | ✅ | |
115
- | Google Antigravity CLI | ✅ | ✅ | ✅ | ✅ | | ✅ | ✅ | ✅ | |
116
- | JetBrains AI Assistant | ✅ || ✅ | | | ✅ | | | |
117
- | JetBrains Junie | ✅ | ✅ | ✅ | ✅ || ✅ | ✅ | ✅ | |
118
- | AugmentCode | ✅ || ✅ ||| ✅ | ✅ || |
119
- | Devin Desktop | ✅ | ✅ | ✅ ||| ✅ ||| |
120
- | Warp | ✅ | ✅ | ✅ | | | ✅ | | ✅ | |
121
- | Replit | ✅ | | | | | ✅ | | | |
122
- | Pi Coding Agent | ✅ | | | ✅ | | ✅ | ✅ | | |
123
- | Zed | ✅ | ✅ | ✅ | | | ✅ | | ✅ | |
89
+ | Tool | rules | ignore | mcp | commands | subagents | skills | hooks | permissions | checks |
90
+ | ------------------------- | :---: | :----: | :-: | :------: | :-------: | :----: | :---: | :---------: | :----: |
91
+ | Amp | ✅ | | ✅ | | | ✅ | ✅ | ✅ | ✅ |
92
+ | Claude Code | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
93
+ | Claude Code plugin | | | ✅ | ✅ | ✅ | ✅ | ✅ | | |
94
+ | Codex CLI | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
95
+ | GitHub Copilot | ✅ | | ✅ || ✅ | ✅ | ✅ || |
96
+ | GitHub Copilot CLI | ✅ | | ✅ | | ✅ | ✅ | ✅ | | |
97
+ | Goose | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
98
+ | Hermes Agent | ✅ || ✅ || ✅ | ✅ | ✅ | ✅ ||
99
+ | Grok CLI | ✅ | | ✅ | | ✅ | ✅ | ✅ | ✅ | |
100
+ | Cursor | ✅ || ✅ || ✅ | ✅ | ✅ || |
101
+ | deepagents-cli | ✅ | | ✅ | | ✅ | ✅ | ✅ | | |
102
+ | Factory Droid | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
103
+ | OpenCode | ✅ | | ✅ | ✅ | ✅ | ✅ || ✅ | |
104
+ | Cline | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | ✅ | |
105
+ | Kilo Code | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ ||| |
106
+ | Kimi Code | ✅ | | ✅ | | ✅ | ✅ || ✅ | |
107
+ | Roo Code | ✅ || ✅ | ✅ | ✅ | ✅ | | | |
108
+ | Rovodev (Atlassian) | ✅ | | ✅ || ✅ | ✅ | | ✅ | |
109
+ | Takt | ✅ | | ✅ | ✅ | ✅ | ✅ | | ✅ | |
110
+ | Vibe Code | ✅ || ✅ | | ✅ | ✅ | ✅ | ✅ | |
111
+ | Qwen Code | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
112
+ | Reasonix | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
113
+ | Kiro ⚠️ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
114
+ | Kiro CLI | ✅ || ✅ | ✅ || ✅ | ✅ | ✅ | |
115
+ | Kiro IDE | ✅ | ✅ | ✅ | ✅ || ✅ | ✅ | ✅ | |
116
+ | Google Antigravity IDE | ✅ | | ✅ || | ✅ ||| |
117
+ | Google Antigravity CLI | ✅ | ✅ | ✅ | ✅ | | ✅ | ✅ | ✅ | |
118
+ | Google Antigravity plugin | ✅ | | ✅ | | | ✅ | ✅ | | |
119
+ | JetBrains AI Assistant | ✅ | ✅ | ✅ | | | ✅ | | | |
120
+ | JetBrains Junie | ✅ | ✅ | ✅ ||| ✅ || ✅ | |
121
+ | AugmentCode | ✅ ||||| ✅ ||| |
122
+ | Devin Desktop | ✅ ||| ✅ || ✅ | ✅ || |
123
+ | Warp | ✅ | ✅ | ✅ | | | ✅ | | ✅ | |
124
+ | Replit | ✅ | | | | | ✅ | | | |
125
+ | Pi Coding Agent | ✅ | | | ✅ | | ✅ | ✅ | | |
126
+ | Zed | ✅ | ✅ | ✅ | | | ✅ | | ✅ | |
124
127
 
125
128
  <!-- SUPPORTED_TOOLS_AI:END -->
126
129
 
@@ -137,12 +140,14 @@ The tables below show whether each tool supports a given feature (✅ = supporte
137
140
 
138
141
  - ⚠️: Deprecated — still supported, but see the note below
139
142
 
140
- ### Deprecation notes
143
+ ### Target and deprecation notes
141
144
 
145
+ - **Ignore feature** — The `ignore` feature is deprecated in favor of the more expressive `permissions` feature. Existing ignore configurations remain supported throughout Rulesync 14.x; removal, if any, will be decided separately and will not occur before a future major release. New `rulesync init` projects scaffold permissions without enabling or creating ignore files. See the [migration guide](https://dyoshikawa.github.io/rulesync/reference/file-formats#rulesync-aiignore-or-rulesyncignore-deprecated).
142
146
  - **Google Antigravity (`antigravity-ide` / `antigravity-cli`)** — Antigravity 2.0 splits into two products with separate global config trees: the desktop **`antigravity-ide`** and the **`antigravity-cli`** (`agy`). For project-scope rules, **both `antigravity-ide` and `antigravity-cli`** emit the root rule as a plain cross-tool **`AGENTS.md`** at the project root (the Gemini-lineage discovery order is `AGENTS.md`, `CONTEXT.md`, `GEMINI.md`; the IDE has read `AGENTS.md` since v1.20.3) and non-root rules under `.agents/rules/`.
143
- - **Kiro (`kiro`)** — Kiro's IDE and CLI use diverging config formats (IDE: Markdown subagents `.kiro/agents/*.md` and structured JSON hooks `.kiro/hooks/*.json`; CLI: JSON agent-config subagents `.kiro/agents/*.json` and hooks in `.kiro/agents/default.json`), so `kiro` is split into **`kiro-cli`** and **`kiro-ide`**. The legacy `kiro` target remains as a **deprecated alias** with its current behavior unchanged. The two targets share every surface except **subagents** (Markdown vs JSON) and **hooks**; Kiro IDE hooks are emitted as a single `.kiro/hooks/rulesync.json` (`{ "version": "v1", "hooks": [ ... ] }`) in project (`.kiro/hooks/`) and global (`~/.kiro/hooks/`) scope, while the CLI uses `.kiro/agents/default.json`. Global skills (`~/.kiro/skills/`) and global Kiro IDE subagents (`~/.kiro/agents/`) are supported too, as are global Kiro CLI commands (`~/.kiro/prompts/`) and subagents (`~/.kiro/agents/`).
147
+ - **Plugin packaging (`claudecode-plugin` / `antigravity-plugin`)** — These project-only targets generate and import Rulesync-managed components inside an existing plugin directory selected with `--output-roots` (generate) or `--output-root` (import). They are excluded from `--targets "*"` to avoid writing package-level `skills/`, `rules/`, or `commands/` directories into ordinary projects. Rulesync preserves plugin manifests, marketplace metadata, scripts, and other non-component assets. See the [Plugin Packaging guide](https://dyoshikawa.github.io/rulesync/guide/plugin-packaging).
148
+ - **Kiro (`kiro`)** — Kiro's IDE and CLI use diverging config formats (IDE: Markdown subagents `.kiro/agents/*.md` and structured JSON hooks `.kiro/hooks/*.json`; CLI: JSON agent-config subagents `.kiro/agents/*.json` and hooks in `.kiro/agents/default.json`), so `kiro` is split into **`kiro-cli`** and **`kiro-ide`**. The legacy `kiro` target remains as a **deprecated alias** with its current behavior unchanged. The two targets share every surface except **subagents** (Markdown vs JSON) and **hooks**; Kiro IDE hooks are emitted as a single `.kiro/hooks/rulesync.json` (`{ "version": "v1", "hooks": [ ... ] }`) in project (`.kiro/hooks/`) and global (`~/.kiro/hooks/`) scope, while the CLI uses `.kiro/agents/default.json`. Global skills (`~/.kiro/skills/`), global ignore (`~/.kiro/settings/kiroignore`), and global Kiro IDE subagents (`~/.kiro/agents/`) are supported too, as are global Kiro CLI commands (`~/.kiro/prompts/`) and subagents (`~/.kiro/agents/`). Kiro MCP generation preserves per-server `disabledTools`, and Kiro CLI hook caching maps `cacheTtl` to `cache_ttl_seconds`.
144
149
 
145
- Some features accept per-feature options (e.g., Claude Code's `ignore` feature supports `fileMode: "local"` to write to `settings.local.json` instead of `settings.json`). See [Configuration > Per-feature options](https://dyoshikawa.github.io/rulesync/guide/configuration#per-feature-options) for details.
150
+ Some features accept per-feature options. See [Configuration > Per-feature options](https://dyoshikawa.github.io/rulesync/guide/configuration#per-feature-options) for details.
146
151
 
147
152
  ## Documentation
148
153
 
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const require_import = require("../import-Ds8vYv6h.cjs");
2
+ const require_import = require("../import-DRN7goJ_.cjs");
3
3
  let commander = require("commander");
4
4
  let zod_mini = require("zod/mini");
5
5
  let node_fs_promises = require("node:fs/promises");
@@ -278,8 +278,14 @@ function singletonTemplate(feature) {
278
278
  "src/**": "allow"
279
279
  },
280
280
  "read": {
281
- ".env": "deny"
281
+ ".env": "deny",
282
+ "credentials/**": "deny"
282
283
  }
284
+ },
285
+ "codexcli": {
286
+ "approval_policy": "on-request",
287
+ "approvals_reviewer": "auto_review",
288
+ "base_permission_profile": ":danger-full-access"
283
289
  }
284
290
  }
285
291
  `;
@@ -319,25 +325,19 @@ function createFeatureScaffold({ feature, name }) {
319
325
  });
320
326
  case "mcp": {
321
327
  const paths = require_import.RulesyncMcp.getSettablePaths();
322
- const relativeFilePath = (0, node_path.join)(paths.recommended.relativeDirPath, paths.recommended.relativeFilePath);
323
328
  return {
324
329
  feature,
325
- relativeFilePath,
326
- candidateRelativeFilePaths: [
327
- ...paths.jsonc ? [(0, node_path.join)(paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath)] : [],
328
- relativeFilePath,
329
- ...paths.legacy ? [(0, node_path.join)(paths.legacy.relativeDirPath, paths.legacy.relativeFilePath)] : []
330
- ],
330
+ relativeFilePath: (0, node_path.join)(paths.recommended.relativeDirPath, paths.recommended.relativeFilePath),
331
+ candidateRelativeFilePaths: require_import.getRulesyncSourceCandidates({ paths }).map((candidate) => (0, node_path.join)(candidate.relativeDirPath, candidate.relativeFilePath)),
331
332
  content: singletonTemplate(feature)
332
333
  };
333
334
  }
334
335
  case "hooks": {
335
336
  const paths = require_import.RulesyncHooks.getSettablePaths();
336
- const relativeFilePath = (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath);
337
337
  return {
338
338
  feature,
339
- relativeFilePath,
340
- candidateRelativeFilePaths: [...paths.jsonc ? [(0, node_path.join)(paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath)] : [], relativeFilePath],
339
+ relativeFilePath: (0, node_path.join)(paths.recommended.relativeDirPath, paths.recommended.relativeFilePath),
340
+ candidateRelativeFilePaths: require_import.getRulesyncSourceCandidates({ paths }).map((candidate) => (0, node_path.join)(candidate.relativeDirPath, candidate.relativeFilePath)),
341
341
  content: singletonTemplate(feature)
342
342
  };
343
343
  }
@@ -353,11 +353,10 @@ function createFeatureScaffold({ feature, name }) {
353
353
  }
354
354
  case "permissions": {
355
355
  const paths = require_import.RulesyncPermissions.getSettablePaths();
356
- const relativeFilePath = (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath);
357
356
  return {
358
357
  feature,
359
- relativeFilePath,
360
- candidateRelativeFilePaths: [...paths.jsonc ? [(0, node_path.join)(paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath)] : [], relativeFilePath],
358
+ relativeFilePath: (0, node_path.join)(paths.recommended.relativeDirPath, paths.recommended.relativeFilePath),
359
+ candidateRelativeFilePaths: require_import.getRulesyncSourceCandidates({ paths }).map((candidate) => (0, node_path.join)(candidate.relativeDirPath, candidate.relativeFilePath)),
361
360
  content: singletonTemplate(feature)
362
361
  };
363
362
  }
@@ -3797,6 +3796,8 @@ async function convertCommand(logger, options) {
3797
3796
  const fromTool = parseToolTarget$1(options.from ?? "", "source");
3798
3797
  const toToolsRaw = (options.to ?? []).map((t) => parseToolTarget$1(t, "destination"));
3799
3798
  const toTools = Array.from(new Set(toToolsRaw));
3799
+ const packagingTarget = [fromTool, ...toTools].find(require_import.isPackagingToolTarget);
3800
+ if (packagingTarget) throw new require_import.CLIError(`Plugin packaging target '${packagingTarget}' is not supported by convert. Use import --output-root and generate --output-roots with an explicit plugin directory.`, require_import.ErrorCodes.CONVERT_FAILED);
3800
3801
  if (toTools.includes(fromTool)) throw new require_import.CLIError(`Destination tools must not include the source tool '${fromTool}'. Converting a tool onto itself is likely a mistake and may cause lossy round-trips.`, require_import.ErrorCodes.CONVERT_FAILED);
3801
3802
  const config = await require_import.ConfigResolver.resolve({
3802
3803
  ...options,
@@ -3861,9 +3862,9 @@ const FEATURE_PATHS = {
3861
3862
  skills: ["skills"],
3862
3863
  checks: ["checks"],
3863
3864
  ignore: [require_import.RULESYNC_AIIGNORE_FILE_NAME],
3864
- mcp: [require_import.RULESYNC_MCP_FILE_NAME, require_import.RULESYNC_MCP_JSONC_FILE_NAME],
3865
- hooks: [require_import.RULESYNC_HOOKS_FILE_NAME, require_import.RULESYNC_HOOKS_JSONC_FILE_NAME],
3866
- permissions: [require_import.RULESYNC_PERMISSIONS_FILE_NAME, require_import.RULESYNC_PERMISSIONS_JSONC_FILE_NAME]
3865
+ mcp: [require_import.RULESYNC_MCP_FILE_NAME, require_import.RULESYNC_MCP_LEGACY_FILE_NAME],
3866
+ hooks: [require_import.RULESYNC_HOOKS_FILE_NAME, require_import.RULESYNC_HOOKS_LEGACY_FILE_NAME],
3867
+ permissions: [require_import.RULESYNC_PERMISSIONS_FILE_NAME, require_import.RULESYNC_PERMISSIONS_LEGACY_FILE_NAME]
3867
3868
  };
3868
3869
  /**
3869
3870
  * Check if target is a tool target (not rulesync)
@@ -4001,10 +4002,11 @@ async function convertFetchedFilesToRulesync(params) {
4001
4002
  };
4002
4003
  }
4003
4004
  /**
4004
- * Resolve features from options, handling wildcard
4005
+ * Resolve features from options, defaulting to skills and handling wildcard.
4005
4006
  */
4006
4007
  function resolveFeatures(features) {
4007
- if (!features || features.length === 0 || features.includes("*")) return [...require_import.ALL_FEATURES];
4008
+ if (features === void 0) return ["skills"];
4009
+ if (features.includes("*")) return [...require_import.ALL_FEATURES];
4008
4010
  return features.filter((f) => require_import.ALL_FEATURES.includes(f));
4009
4011
  }
4010
4012
  /**
@@ -4823,6 +4825,7 @@ const ALL_GITIGNORE_ENTRIES = (() => {
4823
4825
  const seen = /* @__PURE__ */ new Set();
4824
4826
  const result = [];
4825
4827
  for (const tag of GITIGNORE_ENTRY_REGISTRY) {
4828
+ if (normalizeGitignoreEntryTargets(tag.target).every((target) => require_import.PACKAGING_TOOL_TARGETS.includes(target))) continue;
4826
4829
  if (seen.has(tag.entry)) continue;
4827
4830
  seen.add(tag.entry);
4828
4831
  result.push(tag.entry);
@@ -4832,14 +4835,13 @@ const ALL_GITIGNORE_ENTRIES = (() => {
4832
4835
  const isTargetSelected = (target, selectedTargets) => {
4833
4836
  const targets = normalizeGitignoreEntryTargets(target);
4834
4837
  if (targets.includes("common")) return true;
4835
- if (!selectedTargets || selectedTargets.length === 0) return true;
4836
- if (selectedTargets.includes("*")) return true;
4838
+ if (!selectedTargets || selectedTargets.length === 0 || selectedTargets.includes("*")) return targets.some((candidate) => selectedTargets?.includes(candidate) || !require_import.PACKAGING_TOOL_TARGETS.includes(candidate));
4837
4839
  return targets.some((candidate) => selectedTargets.includes(candidate));
4838
4840
  };
4839
4841
  const getSelectedGitignoreEntryTargets = (target, selectedTargets) => {
4840
4842
  const targets = normalizeGitignoreEntryTargets(target);
4841
4843
  if (targets.includes("common")) return ["common"];
4842
- if (!selectedTargets || selectedTargets.length === 0 || selectedTargets.includes("*")) return targets;
4844
+ if (!selectedTargets || selectedTargets.length === 0 || selectedTargets.includes("*")) return targets.filter((candidate) => selectedTargets?.includes(candidate) || !require_import.PACKAGING_TOOL_TARGETS.includes(candidate));
4843
4845
  return targets.filter((candidate) => selectedTargets.includes(candidate));
4844
4846
  };
4845
4847
  const isFeatureSelected = (feature, features) => {
@@ -5160,16 +5162,13 @@ async function createConfigFile() {
5160
5162
  await require_import.writeFileContent(path, JSON.stringify({
5161
5163
  $schema: require_import.RULESYNC_CONFIG_SCHEMA_URL,
5162
5164
  targets: [
5163
- "copilot",
5164
- "cursor",
5165
+ "codexcli",
5165
5166
  "claudecode",
5166
- "codexcli"
5167
+ "opencode"
5167
5168
  ],
5168
5169
  features: [
5169
5170
  "rules",
5170
- "ignore",
5171
5171
  "mcp",
5172
- "commands",
5173
5172
  "subagents",
5174
5173
  "skills",
5175
5174
  "hooks",
@@ -5197,10 +5196,6 @@ async function createSampleFiles() {
5197
5196
  name: "overview"
5198
5197
  }),
5199
5198
  createFeatureScaffold({ feature: "mcp" }),
5200
- createFeatureScaffold({
5201
- feature: "command",
5202
- name: "review-pr"
5203
- }),
5204
5199
  createFeatureScaffold({
5205
5200
  feature: "subagent",
5206
5201
  name: "planner"
@@ -5209,21 +5204,24 @@ async function createSampleFiles() {
5209
5204
  feature: "skill",
5210
5205
  name: "project-context"
5211
5206
  }),
5212
- createFeatureScaffold({ feature: "ignore" }),
5213
5207
  createFeatureScaffold({ feature: "hooks" }),
5214
5208
  createFeatureScaffold({ feature: "permissions" })
5215
5209
  ];
5216
5210
  const results = [];
5217
5211
  for (const sample of samples) {
5218
5212
  await require_import.ensureDir((0, node_path.dirname)(sample.relativeFilePath));
5219
- results.push(await writeIfNotExists(sample.relativeFilePath, sample.content));
5213
+ results.push(await writeIfNotExists({
5214
+ path: sample.relativeFilePath,
5215
+ candidatePaths: sample.candidateRelativeFilePaths,
5216
+ content: sample.content
5217
+ }));
5220
5218
  }
5221
5219
  return results;
5222
5220
  }
5223
- async function writeIfNotExists(path, content) {
5224
- if (await require_import.fileExists(path)) return {
5221
+ async function writeIfNotExists({ path, candidatePaths, content }) {
5222
+ for (const candidatePath of candidatePaths) if (await require_import.fileExists(candidatePath)) return {
5225
5223
  created: false,
5226
- path
5224
+ path: candidatePath
5227
5225
  };
5228
5226
  await require_import.writeFileContent(path, content);
5229
5227
  return {
@@ -5259,7 +5257,7 @@ async function initCommand(logger) {
5259
5257
  }
5260
5258
  logger.success("rulesync initialized successfully!");
5261
5259
  logger.info("Next steps:");
5262
- logger.info(`1. Edit ${require_import.RULESYNC_RELATIVE_DIR_PATH}/**/*.md, ${require_import.RULESYNC_RELATIVE_DIR_PATH}/skills/*/${require_import.SKILL_FILE_NAME}, ${require_import.RULESYNC_MCP_RELATIVE_FILE_PATH}, ${require_import.RULESYNC_HOOKS_RELATIVE_FILE_PATH}, ${require_import.RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH} and ${require_import.RULESYNC_AIIGNORE_RELATIVE_FILE_PATH}`);
5260
+ logger.info(`1. Edit ${require_import.RULESYNC_RELATIVE_DIR_PATH}/**/*.md, ${require_import.RULESYNC_RELATIVE_DIR_PATH}/skills/*/${require_import.SKILL_FILE_NAME}, ${require_import.RULESYNC_MCP_RELATIVE_FILE_PATH}, ${require_import.RULESYNC_HOOKS_RELATIVE_FILE_PATH} and ${require_import.RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH}`);
5263
5261
  logger.info("2. Run 'rulesync generate' to create configuration files");
5264
5262
  }
5265
5263
  //#endregion
@@ -7237,17 +7235,16 @@ async function getHooksFile() {
7237
7235
  async function putHooksFile({ content }) {
7238
7236
  if (content.length > maxHooksSizeBytes) throw new Error(`Hooks file size ${content.length} bytes exceeds maximum ${maxHooksSizeBytes} bytes (1MB) for ${require_import.RULESYNC_HOOKS_RELATIVE_FILE_PATH}`);
7239
7237
  try {
7240
- JSON.parse(content);
7238
+ require_import.parseJsonc(content);
7241
7239
  } catch (error) {
7242
- throw new Error(`Invalid JSON format in hooks file (${require_import.RULESYNC_HOOKS_RELATIVE_FILE_PATH}): ${require_import.formatError(error)}`, { cause: error });
7240
+ throw new Error(`Invalid JSONC format in hooks file (${require_import.RULESYNC_HOOKS_RELATIVE_FILE_PATH}): ${require_import.formatError(error)}`, { cause: error });
7243
7241
  }
7244
7242
  try {
7245
7243
  const outputRoot = process.cwd();
7246
- const paths = require_import.RulesyncHooks.getSettablePaths();
7247
- const jsoncRelativePath = (0, node_path.join)(paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath);
7248
- if (await require_import.fileExists((0, node_path.join)(outputRoot, jsoncRelativePath))) throw new Error(`${jsoncRelativePath} exists and takes precedence over ${require_import.RULESYNC_HOOKS_RELATIVE_FILE_PATH}. Edit ${jsoncRelativePath} directly instead.`);
7249
- const relativeDirPath = paths.relativeDirPath;
7250
- const relativeFilePath = paths.relativeFilePath;
7244
+ const { relativeDirPath, relativeFilePath } = await require_import.resolveRulesyncSourceWritePath({
7245
+ outputRoot,
7246
+ paths: require_import.RulesyncHooks.getSettablePaths()
7247
+ });
7251
7248
  const fullPath = (0, node_path.join)(outputRoot, relativeDirPath, relativeFilePath);
7252
7249
  const rulesyncHooks = new require_import.RulesyncHooks({
7253
7250
  outputRoot,
@@ -7273,9 +7270,8 @@ async function deleteHooksFile() {
7273
7270
  try {
7274
7271
  const outputRoot = process.cwd();
7275
7272
  const paths = require_import.RulesyncHooks.getSettablePaths();
7276
- await require_import.removeFile((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
7277
- await require_import.removeFile((0, node_path.join)(outputRoot, paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath));
7278
- return { relativePathFromCwd: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath) };
7273
+ for (const candidate of require_import.getRulesyncSourceCandidates({ paths })) await require_import.removeFile((0, node_path.join)(outputRoot, candidate.relativeDirPath, candidate.relativeFilePath));
7274
+ return { relativePathFromCwd: (0, node_path.join)(paths.recommended.relativeDirPath, paths.recommended.relativeFilePath) };
7279
7275
  } catch (error) {
7280
7276
  throw new Error(`Failed to delete hooks file (${require_import.RULESYNC_HOOKS_RELATIVE_FILE_PATH}): ${require_import.formatError(error)}`, { cause: error });
7281
7277
  }
@@ -7303,7 +7299,7 @@ const hooksTools = {
7303
7299
  },
7304
7300
  putHooksFile: {
7305
7301
  name: "putHooksFile",
7306
- description: "Create or update the hooks configuration file (upsert operation). content parameter is required and must be valid JSON.",
7302
+ description: "Create or update the hooks configuration file (upsert operation). content parameter is required and must be valid JSONC.",
7307
7303
  parameters: hooksToolSchemas.putHooksFile,
7308
7304
  execute: async (args) => {
7309
7305
  const result = await putHooksFile({ content: args.content });
@@ -7519,17 +7515,16 @@ async function getMcpFile() {
7519
7515
  async function putMcpFile({ content }) {
7520
7516
  if (content.length > maxMcpSizeBytes) throw new Error(`MCP file size ${content.length} bytes exceeds maximum ${maxMcpSizeBytes} bytes (1MB) for ${require_import.RULESYNC_MCP_RELATIVE_FILE_PATH}`);
7521
7517
  try {
7522
- JSON.parse(content);
7518
+ require_import.parseJsonc(content);
7523
7519
  } catch (error) {
7524
- throw new Error(`Invalid JSON format in MCP file (${require_import.RULESYNC_MCP_RELATIVE_FILE_PATH}): ${require_import.formatError(error)}`, { cause: error });
7520
+ throw new Error(`Invalid JSONC format in MCP file (${require_import.RULESYNC_MCP_RELATIVE_FILE_PATH}): ${require_import.formatError(error)}`, { cause: error });
7525
7521
  }
7526
7522
  try {
7527
7523
  const outputRoot = process.cwd();
7528
- const paths = require_import.RulesyncMcp.getSettablePaths();
7529
- const jsoncRelativePath = (0, node_path.join)(paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath);
7530
- if (await require_import.fileExists((0, node_path.join)(outputRoot, jsoncRelativePath))) throw new Error(`${jsoncRelativePath} exists and takes precedence over ${require_import.RULESYNC_MCP_RELATIVE_FILE_PATH}. Edit ${jsoncRelativePath} directly instead.`);
7531
- const relativeDirPath = paths.recommended.relativeDirPath;
7532
- const relativeFilePath = paths.recommended.relativeFilePath;
7524
+ const { relativeDirPath, relativeFilePath } = await require_import.resolveRulesyncSourceWritePath({
7525
+ outputRoot,
7526
+ paths: require_import.RulesyncMcp.getSettablePaths()
7527
+ });
7533
7528
  const fullPath = (0, node_path.join)(outputRoot, relativeDirPath, relativeFilePath);
7534
7529
  const rulesyncMcp = new require_import.RulesyncMcp({
7535
7530
  outputRoot,
@@ -7555,11 +7550,7 @@ async function deleteMcpFile() {
7555
7550
  try {
7556
7551
  const outputRoot = process.cwd();
7557
7552
  const paths = require_import.RulesyncMcp.getSettablePaths();
7558
- const recommendedPath = (0, node_path.join)(outputRoot, paths.recommended.relativeDirPath, paths.recommended.relativeFilePath);
7559
- const legacyPath = (0, node_path.join)(outputRoot, paths.legacy.relativeDirPath, paths.legacy.relativeFilePath);
7560
- await require_import.removeFile(recommendedPath);
7561
- await require_import.removeFile((0, node_path.join)(outputRoot, paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath));
7562
- await require_import.removeFile(legacyPath);
7553
+ for (const candidate of require_import.getRulesyncSourceCandidates({ paths })) await require_import.removeFile((0, node_path.join)(outputRoot, candidate.relativeDirPath, candidate.relativeFilePath));
7563
7554
  return { relativePathFromCwd: (0, node_path.join)(paths.recommended.relativeDirPath, paths.recommended.relativeFilePath) };
7564
7555
  } catch (error) {
7565
7556
  throw new Error(`Failed to delete MCP file (${require_import.RULESYNC_MCP_RELATIVE_FILE_PATH}): ${require_import.formatError(error)}`, { cause: error });
@@ -7588,7 +7579,7 @@ const mcpTools = {
7588
7579
  },
7589
7580
  putMcpFile: {
7590
7581
  name: "putMcpFile",
7591
- description: "Create or update the MCP configuration file (upsert operation). content parameter is required and must be valid JSON.",
7582
+ description: "Create or update the MCP configuration file (upsert operation). content parameter is required and must be valid JSONC.",
7592
7583
  parameters: mcpToolSchemas.putMcpFile,
7593
7584
  execute: async (args) => {
7594
7585
  const result = await putMcpFile({ content: args.content });
@@ -7628,17 +7619,16 @@ async function getPermissionsFile() {
7628
7619
  async function putPermissionsFile({ content }) {
7629
7620
  if (content.length > maxPermissionsSizeBytes) throw new Error(`Permissions file size ${content.length} bytes exceeds maximum ${maxPermissionsSizeBytes} bytes (1MB) for ${require_import.RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH}`);
7630
7621
  try {
7631
- JSON.parse(content);
7622
+ require_import.parseJsonc(content);
7632
7623
  } catch (error) {
7633
- throw new Error(`Invalid JSON format in permissions file (${require_import.RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH}): ${require_import.formatError(error)}`, { cause: error });
7624
+ throw new Error(`Invalid JSONC format in permissions file (${require_import.RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH}): ${require_import.formatError(error)}`, { cause: error });
7634
7625
  }
7635
7626
  try {
7636
7627
  const outputRoot = process.cwd();
7637
- const paths = require_import.RulesyncPermissions.getSettablePaths();
7638
- const jsoncRelativePath = (0, node_path.join)(paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath);
7639
- if (await require_import.fileExists((0, node_path.join)(outputRoot, jsoncRelativePath))) throw new Error(`${jsoncRelativePath} exists and takes precedence over ${require_import.RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH}. Edit ${jsoncRelativePath} directly instead.`);
7640
- const relativeDirPath = paths.relativeDirPath;
7641
- const relativeFilePath = paths.relativeFilePath;
7628
+ const { relativeDirPath, relativeFilePath } = await require_import.resolveRulesyncSourceWritePath({
7629
+ outputRoot,
7630
+ paths: require_import.RulesyncPermissions.getSettablePaths()
7631
+ });
7642
7632
  const fullPath = (0, node_path.join)(outputRoot, relativeDirPath, relativeFilePath);
7643
7633
  const rulesyncPermissions = new require_import.RulesyncPermissions({
7644
7634
  outputRoot,
@@ -7664,9 +7654,8 @@ async function deletePermissionsFile() {
7664
7654
  try {
7665
7655
  const outputRoot = process.cwd();
7666
7656
  const paths = require_import.RulesyncPermissions.getSettablePaths();
7667
- await require_import.removeFile((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
7668
- await require_import.removeFile((0, node_path.join)(outputRoot, paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath));
7669
- return { relativePathFromCwd: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath) };
7657
+ for (const candidate of require_import.getRulesyncSourceCandidates({ paths })) await require_import.removeFile((0, node_path.join)(outputRoot, candidate.relativeDirPath, candidate.relativeFilePath));
7658
+ return { relativePathFromCwd: (0, node_path.join)(paths.recommended.relativeDirPath, paths.recommended.relativeFilePath) };
7670
7659
  } catch (error) {
7671
7660
  throw new Error(`Failed to delete permissions file (${require_import.RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH}): ${require_import.formatError(error)}`, { cause: error });
7672
7661
  }
@@ -7694,7 +7683,7 @@ const permissionsTools = {
7694
7683
  },
7695
7684
  putPermissionsFile: {
7696
7685
  name: "putPermissionsFile",
7697
- description: "Create or update the permissions configuration file (upsert operation). content parameter is required and must be valid JSON.",
7686
+ description: "Create or update the permissions configuration file (upsert operation). content parameter is required and must be valid JSONC.",
7698
7687
  parameters: permissionsToolSchemas.putPermissionsFile,
7699
7688
  execute: async (args) => {
7700
7689
  const result = await putPermissionsFile({ content: args.content });
@@ -8991,8 +8980,9 @@ function wrapCommand$1({ name, errorCode, handler, getVersion, loggerFactory = c
8991
8980
  };
8992
8981
  }
8993
8982
  //#endregion
8994
- //#region src/cli/index.ts
8995
- const getVersion = () => "14.2.0";
8983
+ //#region src/cli/program.ts
8984
+ const getVersion = () => "15.0.0";
8985
+ const FEATURES_HELP = `${require_import.ALL_FEATURES.join(",")}; ignore is deprecated, use permissions`;
8996
8986
  function wrapCommand(name, errorCode, handler) {
8997
8987
  return wrapCommand$1({
8998
8988
  name,
@@ -9001,14 +8991,14 @@ function wrapCommand(name, errorCode, handler) {
9001
8991
  getVersion
9002
8992
  });
9003
8993
  }
9004
- const main = async () => {
8994
+ function createProgram() {
9005
8995
  const program = new commander.Command();
9006
8996
  const version = getVersion();
9007
8997
  program.name("rulesync").description("Unified AI rules management CLI tool").version(version, "-v, --version", "Show version").option("-j, --json", "Output results as JSON");
9008
8998
  program.command("init").description("Initialize rulesync in current directory").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(wrapCommand("init", "INIT_FAILED", async (logger) => {
9009
8999
  await initCommand(logger);
9010
9000
  }));
9011
- program.command("gitignore").description("Add generated files to .gitignore").option("-t, --targets <tools>", "Comma-separated list of tools to include (e.g., 'claudecode,copilot' or '*' for all)", parseCommaSeparatedList).option("-f, --features <features>", `Comma-separated list of features to include (${require_import.ALL_FEATURES.join(",")}) or '*' for all`, parseCommaSeparatedList).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(wrapCommand("gitignore", "GITIGNORE_FAILED", async (logger, options) => {
9001
+ program.command("gitignore").description("Add generated files to .gitignore").option("-t, --targets <tools>", "Comma-separated list of tools to include (e.g., 'claudecode,copilot' or '*' for all)", parseCommaSeparatedList).option("-f, --features <features>", `Comma-separated list of features to include (${FEATURES_HELP}) or '*' for all`, parseCommaSeparatedList).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(wrapCommand("gitignore", "GITIGNORE_FAILED", async (logger, options) => {
9012
9002
  const cliTargets = options.targets;
9013
9003
  const cliFeatures = options.features;
9014
9004
  const resolvedTargets = await resolveGitignoreTargets({ cliTargets });
@@ -9019,7 +9009,7 @@ const main = async () => {
9019
9009
  silent: options.silent
9020
9010
  });
9021
9011
  }));
9022
- program.command("add <source>").description("Add a Rulesync feature file or install a declarative rule or skill source").option("--name <name>", "Name for a rule, command, subagent, skill, or check scaffold").option("-f, --force", "Overwrite an existing scaffold file without prompting").option("--skills <skills>", "Comma-separated skill names to install", parseCommaSeparatedList).option("--rules <rules>", "Comma-separated rule names to install", parseCommaSeparatedList).option("--transport <transport>", "Source transport: github, git, or npm").option("-r, --ref <ref>", "Git ref, npm version, or npm dist-tag").option("-p, --path <path>", "Skills path within the source").option("--rules-path <path>", "Rules path within the source").option("--registry <url>", "npm-compatible registry URL").option("--token-env <name>", "Environment variable containing the npm registry token").option("--token <token>", "GitHub token for private repositories").option("-c, --config <path>", "Path to configuration file").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(wrapCommand("add", "ADD_FAILED", async (logger, options, _globalOpts, positionalArgs) => {
9012
+ program.command("add <source>").description("Add a Rulesync feature file (ignore is deprecated; use permissions) or install a declarative rule or skill source").option("--name <name>", "Name for a rule, command, subagent, skill, or check scaffold").option("-f, --force", "Overwrite an existing scaffold file without prompting").option("--skills <skills>", "Comma-separated skill names to install", parseCommaSeparatedList).option("--rules <rules>", "Comma-separated rule names to install", parseCommaSeparatedList).option("--transport <transport>", "Source transport: github, git, or npm").option("-r, --ref <ref>", "Git ref, npm version, or npm dist-tag").option("-p, --path <path>", "Skills path within the source").option("--rules-path <path>", "Rules path within the source").option("--registry <url>", "npm-compatible registry URL").option("--token-env <name>", "Environment variable containing the npm registry token").option("--token <token>", "GitHub token for private repositories").option("-c, --config <path>", "Path to configuration file").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(wrapCommand("add", "ADD_FAILED", async (logger, options, _globalOpts, positionalArgs) => {
9023
9013
  const source = positionalArgs[0];
9024
9014
  const addOptions = options;
9025
9015
  await addCommand(logger, {
@@ -9028,17 +9018,21 @@ const main = async () => {
9028
9018
  configPath: addOptions.config
9029
9019
  });
9030
9020
  }));
9031
- program.command("fetch <source>").description("Fetch files from a Git repository (GitHub/GitLab)").option("-t, --target <target>", "Target format to interpret files as (e.g., 'rulesync', 'claudecode'). Default: rulesync").option("-f, --features <features>", `Comma-separated list of features to fetch (${require_import.ALL_FEATURES.join(",")}) or '*' for all`, parseCommaSeparatedList).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("-c, --conflict <strategy>", "Conflict resolution strategy: skip, overwrite (default: overwrite)").option("--token <token>", "Git provider token for private repositories").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(wrapCommand("fetch", "FETCH_FAILED", async (logger, options, _globalOpts, positionalArgs) => {
9021
+ program.command("fetch <source>").description("Fetch files from a Git repository (GitHub/GitLab)").option("-t, --target <target>", "Target format to interpret files as (e.g., 'rulesync', 'claudecode'). Default: rulesync").option("-f, --features <features>", `Comma-separated list of features to fetch (${FEATURES_HELP}) or '*' for all. Default: skills`, parseCommaSeparatedList).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("-c, --conflict <strategy>", "Conflict resolution strategy: skip, overwrite (default: overwrite)").option("--token <token>", "Git provider token for private repositories").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(wrapCommand("fetch", "FETCH_FAILED", async (logger, options, _globalOpts, positionalArgs) => {
9032
9022
  const source = positionalArgs[0];
9033
9023
  await fetchCommand(logger, {
9034
9024
  ...options,
9035
9025
  source
9036
9026
  });
9037
9027
  }));
9038
- program.command("import").description("Import configurations from AI tools to rulesync format").option("-t, --targets <tool>", "Tool to import from (e.g., 'copilot', 'cursor', 'cline')", parseCommaSeparatedList).option("-f, --features <features>", `Comma-separated list of features to import (${require_import.ALL_FEATURES.join(",")}) or '*' for all`, parseCommaSeparatedList).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").option("-g, --global", "Import for global(user scope) configuration files").action(wrapCommand("import", "IMPORT_FAILED", async (logger, options) => {
9039
- await importCommand(logger, options);
9028
+ program.command("import").description("Import configurations from AI tools to rulesync format").option("-t, --targets <tool>", "Tool to import from (e.g., 'copilot', 'cursor', 'cline')", parseCommaSeparatedList).option("-f, --features <features>", `Comma-separated list of features to import (${FEATURES_HELP}) or '*' for all`, parseCommaSeparatedList).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").option("-g, --global", "Import for global(user scope) configuration files").option("-o, --output-root <path>", "Root directory containing the tool configuration to import").action(wrapCommand("import", "IMPORT_FAILED", async (logger, options) => {
9029
+ const { outputRoot, ...importOptions } = options;
9030
+ await importCommand(logger, {
9031
+ ...importOptions,
9032
+ outputRoots: outputRoot ? [outputRoot] : void 0
9033
+ });
9040
9034
  }));
9041
- program.command("convert").description("Convert configurations from one AI tool to other AI tools without writing .rulesync/ files").requiredOption("--from <tool>", "Source tool to convert from (e.g., 'cursor', 'claudecode')").requiredOption("--to <tools>", "Comma-separated list of destination tools (e.g., 'copilot,claudecode')", parseCommaSeparatedList).option("-f, --features <features>", `Comma-separated list of features to convert (${require_import.ALL_FEATURES.join(",")}) or '*' for all`, parseCommaSeparatedList).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").option("-g, --global", "Convert for global(user scope) configuration files").option("--dry-run", "Dry run: show changes without writing files").action(wrapCommand("convert", "CONVERT_FAILED", async (logger, options) => {
9035
+ program.command("convert").description("Convert configurations from one AI tool to other AI tools without writing .rulesync/ files").requiredOption("--from <tool>", "Source tool to convert from (e.g., 'cursor', 'claudecode')").requiredOption("--to <tools>", "Comma-separated list of destination tools (e.g., 'copilot,claudecode')", parseCommaSeparatedList).option("-f, --features <features>", `Comma-separated list of features to convert (${FEATURES_HELP}) or '*' for all`, parseCommaSeparatedList).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").option("-g, --global", "Convert for global(user scope) configuration files").option("--dry-run", "Dry run: show changes without writing files").action(wrapCommand("convert", "CONVERT_FAILED", async (logger, options) => {
9042
9036
  await convertCommand(logger, options);
9043
9037
  }));
9044
9038
  program.command("mcp").description("Start MCP server for rulesync").action(wrapCommand("mcp", "MCP_FAILED", async (logger, _options) => {
@@ -9056,20 +9050,25 @@ const main = async () => {
9056
9050
  silent: options.silent
9057
9051
  });
9058
9052
  }));
9059
- program.command("generate").description("Generate configuration files for AI tools").option("-t, --targets <tools>", "Comma-separated list of tools to generate for (e.g., 'copilot,cursor,cline' or '*' for all)", parseCommaSeparatedList).option("-f, --features <features>", `Comma-separated list of features to generate (${require_import.ALL_FEATURES.join(",")}) or '*' for all`, parseCommaSeparatedList).option("--delete", "Delete all existing files in output directories before generating").option("-o, --output-roots <paths>", "Output root directories to generate files into (comma-separated for multiple paths)", parseCommaSeparatedList).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("--simulate-commands", "Generate simulated commands. This feature is only available for copilot, cursor and codexcli.").option("--simulate-subagents", "Generate simulated subagents. This feature is only available for copilot and codexcli.").option("--simulate-skills", "Generate simulated skills. This feature is only available for copilot, cursor and codexcli.").option("--input-root <path>", "Path to the directory containing .rulesync/ (parent of .rulesync/)").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(wrapCommand("generate", "GENERATION_FAILED", async (logger, options) => {
9053
+ program.command("generate").description("Generate configuration files for AI tools").option("-t, --targets <tools>", "Comma-separated list of tools to generate for (e.g., 'copilot,cursor,cline' or '*' for all)", parseCommaSeparatedList).option("-f, --features <features>", `Comma-separated list of features to generate (${FEATURES_HELP}) or '*' for all`, parseCommaSeparatedList).option("--delete", "Delete all existing files in output directories before generating").option("-o, --output-roots <paths>", "Output root directories to generate files into (comma-separated for multiple paths)", parseCommaSeparatedList).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("--simulate-commands", "Generate simulated commands. This feature is only available for copilot, cursor and codexcli.").option("--simulate-subagents", "Generate simulated subagents. This feature is only available for copilot and codexcli.").option("--simulate-skills", "Generate simulated skills. This feature is only available for copilot, cursor and codexcli.").option("--input-root <path>", "Path to the directory containing .rulesync/ (parent of .rulesync/)").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(wrapCommand("generate", "GENERATION_FAILED", async (logger, options) => {
9060
9054
  await generateCommand(logger, options);
9061
9055
  }));
9062
9056
  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(wrapCommand("update", "UPDATE_FAILED", async (logger, options) => {
9063
9057
  await updateCommand(logger, version, options);
9064
9058
  }));
9065
- program.parse();
9066
- };
9059
+ return program;
9060
+ }
9067
9061
  function parseInstallMode(raw) {
9068
9062
  if (raw === void 0) return void 0;
9069
9063
  const match = INSTALL_MODES.find((m) => m === raw);
9070
9064
  if (!match) throw new Error(`Invalid --mode value "${raw}". Expected one of: ${INSTALL_MODES.join(", ")}.`);
9071
9065
  return match;
9072
9066
  }
9067
+ //#endregion
9068
+ //#region src/cli/index.ts
9069
+ async function main() {
9070
+ createProgram().parse();
9071
+ }
9073
9072
  main().catch((error) => {
9074
9073
  console.error(require_import.formatError(error));
9075
9074
  process.exit(1);