rulesync 8.11.0 → 8.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -79,7 +79,7 @@ import {
79
79
  stringifyFrontmatter,
80
80
  toPosixPath,
81
81
  writeFileContent
82
- } from "../chunk-ZX2YPC22.js";
82
+ } from "../chunk-RLRAG4LZ.js";
83
83
 
84
84
  // src/cli/index.ts
85
85
  import { Command } from "commander";
@@ -658,32 +658,32 @@ async function convertFetchedFilesToRulesync(params) {
658
658
  {
659
659
  feature: "rules",
660
660
  getTargets: () => RulesProcessor.getToolTargets({ global: false }),
661
- createProcessor: () => new RulesProcessor({ baseDir: tempDir, toolTarget: target, global: false, logger: logger5 })
661
+ createProcessor: () => new RulesProcessor({ outputRoot: tempDir, toolTarget: target, global: false, logger: logger5 })
662
662
  },
663
663
  {
664
664
  feature: "commands",
665
665
  getTargets: () => CommandsProcessor.getToolTargets({ global: false, includeSimulated: false }),
666
- createProcessor: () => new CommandsProcessor({ baseDir: tempDir, toolTarget: target, global: false, logger: logger5 })
666
+ createProcessor: () => new CommandsProcessor({ outputRoot: tempDir, toolTarget: target, global: false, logger: logger5 })
667
667
  },
668
668
  {
669
669
  feature: "subagents",
670
670
  getTargets: () => SubagentsProcessor.getToolTargets({ global: false, includeSimulated: false }),
671
- createProcessor: () => new SubagentsProcessor({ baseDir: tempDir, toolTarget: target, global: false, logger: logger5 })
671
+ createProcessor: () => new SubagentsProcessor({ outputRoot: tempDir, toolTarget: target, global: false, logger: logger5 })
672
672
  },
673
673
  {
674
674
  feature: "ignore",
675
675
  getTargets: () => IgnoreProcessor.getToolTargets(),
676
- createProcessor: () => new IgnoreProcessor({ baseDir: tempDir, toolTarget: target, logger: logger5 })
676
+ createProcessor: () => new IgnoreProcessor({ outputRoot: tempDir, toolTarget: target, logger: logger5 })
677
677
  },
678
678
  {
679
679
  feature: "mcp",
680
680
  getTargets: () => McpProcessor.getToolTargets({ global: false }),
681
- createProcessor: () => new McpProcessor({ baseDir: tempDir, toolTarget: target, global: false, logger: logger5 })
681
+ createProcessor: () => new McpProcessor({ outputRoot: tempDir, toolTarget: target, global: false, logger: logger5 })
682
682
  },
683
683
  {
684
684
  feature: "hooks",
685
685
  getTargets: () => HooksProcessor.getToolTargets({ global: false }),
686
- createProcessor: () => new HooksProcessor({ baseDir: tempDir, toolTarget: target, global: false, logger: logger5 })
686
+ createProcessor: () => new HooksProcessor({ outputRoot: tempDir, toolTarget: target, global: false, logger: logger5 })
687
687
  }
688
688
  ];
689
689
  for (const config of featureConfigs) {
@@ -728,7 +728,7 @@ function isNotFoundError(error) {
728
728
  return false;
729
729
  }
730
730
  async function fetchFiles(params) {
731
- const { source, options = {}, baseDir = process.cwd(), logger: logger5 } = params;
731
+ const { source, options = {}, outputRoot = process.cwd(), logger: logger5 } = params;
732
732
  const parsed = parseSource(source);
733
733
  if (parsed.provider === "gitlab") {
734
734
  throw new Error(
@@ -743,7 +743,7 @@ async function fetchFiles(params) {
743
743
  const target = options.target ?? "rulesync";
744
744
  checkPathTraversal({
745
745
  relativePath: outputDir,
746
- intendedRootDir: baseDir
746
+ intendedRootDir: outputRoot
747
747
  });
748
748
  const token = GitHubClient.resolveToken(options.token);
749
749
  const client = new GitHubClient({ token });
@@ -766,7 +766,7 @@ async function fetchFiles(params) {
766
766
  enabledFeatures,
767
767
  target,
768
768
  outputDir,
769
- baseDir,
769
+ outputRoot,
770
770
  conflictStrategy,
771
771
  logger: logger5
772
772
  });
@@ -793,7 +793,7 @@ async function fetchFiles(params) {
793
793
  skipped: 0
794
794
  };
795
795
  }
796
- const outputBasePath = join(baseDir, outputDir);
796
+ const outputBasePath = join(outputRoot, outputDir);
797
797
  for (const { relativePath, size } of filesToFetch) {
798
798
  checkPathTraversal({
799
799
  relativePath,
@@ -907,7 +907,7 @@ async function fetchAndConvertToolFiles(params) {
907
907
  enabledFeatures,
908
908
  target,
909
909
  outputDir,
910
- baseDir,
910
+ outputRoot,
911
911
  conflictStrategy: _conflictStrategy,
912
912
  logger: logger5
913
913
  } = params;
@@ -956,7 +956,7 @@ async function fetchAndConvertToolFiles(params) {
956
956
  logger5.debug(`Fetched to temp: ${toolRelativePath}`);
957
957
  })
958
958
  );
959
- const outputBasePath = join(baseDir, outputDir);
959
+ const outputBasePath = join(outputRoot, outputDir);
960
960
  const { converted, convertedPaths } = await convertFetchedFilesToRulesync({
961
961
  tempDir,
962
962
  outputDir: outputBasePath,
@@ -1110,6 +1110,15 @@ async function fetchCommand(logger5, options) {
1110
1110
  }
1111
1111
 
1112
1112
  // src/cli/commands/generate.ts
1113
+ function sameDirSets(a, b) {
1114
+ const aSet = new Set(a);
1115
+ const bSet = new Set(b);
1116
+ if (aSet.size !== bSet.size) return false;
1117
+ for (const v of aSet) {
1118
+ if (!bSet.has(v)) return false;
1119
+ }
1120
+ return true;
1121
+ }
1113
1122
  function logFeatureResult(logger5, params) {
1114
1123
  const { count, paths, featureName, isPreview, modePrefix } = params;
1115
1124
  if (count > 0) {
@@ -1124,18 +1133,33 @@ function logFeatureResult(logger5, params) {
1124
1133
  }
1125
1134
  }
1126
1135
  async function generateCommand(logger5, options) {
1127
- const config = await ConfigResolver.resolve(options);
1136
+ const { baseDir, outputRoots, ...rest } = options;
1137
+ if (baseDir !== void 0) {
1138
+ logger5.warn(
1139
+ "--base-dir is deprecated; use --output-roots instead. It will be removed in a future major release."
1140
+ );
1141
+ }
1142
+ const outputRootsResolved = outputRoots !== void 0 && outputRoots.length > 0 ? outputRoots : baseDir;
1143
+ if (baseDir !== void 0 && outputRoots !== void 0 && baseDir.length > 0 && outputRoots.length > 0 && !sameDirSets(outputRoots, baseDir)) {
1144
+ logger5.warn(
1145
+ `Both '--output-roots' and '--base-dir' were provided with differing values; using '--output-roots' (${JSON.stringify(outputRoots)}) and ignoring '--base-dir' (${JSON.stringify(baseDir)}).`
1146
+ );
1147
+ }
1148
+ const config = await ConfigResolver.resolve(
1149
+ { ...rest, outputRoots: outputRootsResolved },
1150
+ { logger: logger5 }
1151
+ );
1128
1152
  const check = config.getCheck();
1129
1153
  const isPreview = config.isPreviewMode();
1130
1154
  const modePrefix = isPreview ? "[DRY RUN]" : "";
1131
1155
  logger5.debug("Generating files...");
1132
- if (!await checkRulesyncDirExists({ baseDir: process.cwd() })) {
1156
+ if (!await checkRulesyncDirExists({ inputRoot: config.getInputRoot() })) {
1133
1157
  throw new CLIError(
1134
1158
  ".rulesync directory not found. Run 'rulesync init' first.",
1135
1159
  ErrorCodes.RULESYNC_DIR_NOT_FOUND
1136
1160
  );
1137
1161
  }
1138
- logger5.debug(`Base directories: ${config.getBaseDirs().join(", ")}`);
1162
+ logger5.debug(`Output roots: ${config.getOutputRoots().join(", ")}`);
1139
1163
  const features = config.getFeatures();
1140
1164
  if (features.includes("ignore")) {
1141
1165
  logger5.debug("Generating ignore files...");
@@ -1747,7 +1771,7 @@ async function importCommand(logger5, options) {
1747
1771
  if (options.targets.length > 1) {
1748
1772
  throw new CLIError("Only one tool can be imported at a time", ErrorCodes.IMPORT_FAILED);
1749
1773
  }
1750
- const config = await ConfigResolver.resolve(options);
1774
+ const config = await ConfigResolver.resolve(options, { logger: logger5 });
1751
1775
  const tool = config.getTargets()[0];
1752
1776
  logger5.debug(`Importing files from ${tool}...`);
1753
1777
  const result = await importFromTool({ config, tool, logger: logger5 });
@@ -1805,7 +1829,7 @@ async function createConfigFile() {
1805
1829
  $schema: RULESYNC_CONFIG_SCHEMA_URL,
1806
1830
  targets: ["copilot", "cursor", "claudecode", "codexcli"],
1807
1831
  features: ["rules", "ignore", "mcp", "commands", "subagents", "skills", "hooks"],
1808
- baseDirs: ["."],
1832
+ outputRoots: ["."],
1809
1833
  delete: true,
1810
1834
  verbose: false,
1811
1835
  silent: false,
@@ -2087,8 +2111,8 @@ var ApmLockSchema = z4.looseObject({
2087
2111
  dependencies: z4.array(ApmLockDependencySchema),
2088
2112
  mcp_servers: optional(z4.array(z4.string()))
2089
2113
  });
2090
- function getApmLockPath(baseDir) {
2091
- return join4(baseDir, APM_LOCKFILE_FILE_NAME);
2114
+ function getApmLockPath(projectRoot) {
2115
+ return join4(projectRoot, APM_LOCKFILE_FILE_NAME);
2092
2116
  }
2093
2117
  function createEmptyApmLock(params) {
2094
2118
  const base = params.existingLock ? { ...params.existingLock } : {};
@@ -2121,8 +2145,8 @@ ${issues}`);
2121
2145
  }
2122
2146
  return parsed.data;
2123
2147
  }
2124
- async function readApmLock(baseDir) {
2125
- const path2 = getApmLockPath(baseDir);
2148
+ async function readApmLock(projectRoot) {
2149
+ const path2 = getApmLockPath(projectRoot);
2126
2150
  if (!await fileExists(path2)) {
2127
2151
  return null;
2128
2152
  }
@@ -2130,7 +2154,7 @@ async function readApmLock(baseDir) {
2130
2154
  return parseApmLock(content);
2131
2155
  }
2132
2156
  async function writeApmLock(params) {
2133
- const path2 = getApmLockPath(params.baseDir);
2157
+ const path2 = getApmLockPath(params.projectRoot);
2134
2158
  const content = serializeApmLock(params.lock);
2135
2159
  await writeFileContent(path2, content);
2136
2160
  }
@@ -2164,11 +2188,11 @@ var ApmManifestSchema = z5.looseObject({
2164
2188
  })
2165
2189
  )
2166
2190
  });
2167
- function getApmManifestPath(baseDir) {
2168
- return join5(baseDir, APM_MANIFEST_FILE_NAME);
2191
+ function getApmManifestPath(projectRoot) {
2192
+ return join5(projectRoot, APM_MANIFEST_FILE_NAME);
2169
2193
  }
2170
- async function apmManifestExists(baseDir) {
2171
- return fileExists(getApmManifestPath(baseDir));
2194
+ async function apmManifestExists(projectRoot) {
2195
+ return fileExists(getApmManifestPath(projectRoot));
2172
2196
  }
2173
2197
  function parseApmManifest(content) {
2174
2198
  const loaded = load2(content);
@@ -2190,8 +2214,8 @@ function parseApmManifest(content) {
2190
2214
  dependencies
2191
2215
  };
2192
2216
  }
2193
- async function readApmManifest(baseDir) {
2194
- const path2 = getApmManifestPath(baseDir);
2217
+ async function readApmManifest(projectRoot) {
2218
+ const path2 = getApmManifestPath(projectRoot);
2195
2219
  const content = await readFileContent(path2);
2196
2220
  return parseApmManifest(content);
2197
2221
  }
@@ -2344,13 +2368,13 @@ var APM_PRIMITIVES = [
2344
2368
  }
2345
2369
  ];
2346
2370
  async function installApm(params) {
2347
- const { baseDir, options = {}, logger: logger5 } = params;
2348
- const manifest = await readApmManifest(baseDir);
2371
+ const { projectRoot, options = {}, logger: logger5 } = params;
2372
+ const manifest = await readApmManifest(projectRoot);
2349
2373
  if (manifest.dependencies.length === 0) {
2350
2374
  logger5.warn("apm.yml has no dependencies.apm entries. Nothing to install.");
2351
2375
  return { dependenciesProcessed: 0, deployedFileCount: 0, failedDependencyCount: 0 };
2352
2376
  }
2353
- const existingLock = await readApmLock(baseDir);
2377
+ const existingLock = await readApmLock(projectRoot);
2354
2378
  if (options.frozen) {
2355
2379
  if (!existingLock) {
2356
2380
  throw new Error(
@@ -2394,7 +2418,7 @@ async function installApm(params) {
2394
2418
  dep,
2395
2419
  client,
2396
2420
  semaphore,
2397
- baseDir,
2421
+ projectRoot,
2398
2422
  existingLock,
2399
2423
  frozen,
2400
2424
  update: options.update ?? false,
@@ -2449,19 +2473,19 @@ async function installApm(params) {
2449
2473
  continue;
2450
2474
  }
2451
2475
  try {
2452
- checkPathTraversal({ relativePath, intendedRootDir: baseDir });
2476
+ checkPathTraversal({ relativePath, intendedRootDir: projectRoot });
2453
2477
  } catch {
2454
- logger5.warn(`Refusing to remove stale apm file outside baseDir: "${relativePath}".`);
2478
+ logger5.warn(`Refusing to remove stale apm file outside projectRoot: "${relativePath}".`);
2455
2479
  continue;
2456
2480
  }
2457
- const absolute = join6(baseDir, relativePath);
2481
+ const absolute = join6(projectRoot, relativePath);
2458
2482
  await removeFile(absolute);
2459
2483
  logger5.debug(`Removed stale apm file: ${relativePath}`);
2460
2484
  }
2461
2485
  }
2462
2486
  if (!frozen) {
2463
2487
  newLock.generated_at = (/* @__PURE__ */ new Date()).toISOString();
2464
- await writeApmLock({ baseDir, lock: newLock });
2488
+ await writeApmLock({ projectRoot, lock: newLock });
2465
2489
  if (failedCount === 0) {
2466
2490
  logger5.debug("rulesync-apm.lock.yaml updated.");
2467
2491
  } else {
@@ -2477,7 +2501,7 @@ async function installApm(params) {
2477
2501
  };
2478
2502
  }
2479
2503
  async function installDependency(params) {
2480
- const { dep, client, semaphore, baseDir, existingLock, frozen, update, logger: logger5 } = params;
2504
+ const { dep, client, semaphore, projectRoot, existingLock, frozen, update, logger: logger5 } = params;
2481
2505
  const repoUrl = canonicalRepoUrl(dep);
2482
2506
  const locked = existingLock ? findApmLockDependency(existingLock, repoUrl) : void 0;
2483
2507
  let resolvedRef;
@@ -2521,7 +2545,7 @@ async function installDependency(params) {
2521
2545
  const deployRelative = toPosixPath(join6(primitive.deployDir, relativeToBase));
2522
2546
  checkPathTraversal({
2523
2547
  relativePath: deployRelative,
2524
- intendedRootDir: baseDir
2548
+ intendedRootDir: projectRoot
2525
2549
  });
2526
2550
  const content = await withSemaphore(
2527
2551
  semaphore,
@@ -2536,7 +2560,7 @@ async function installDependency(params) {
2536
2560
  }
2537
2561
  deployed.push({ path: deployRelative, content });
2538
2562
  if (!frozen) {
2539
- await writeFileContent(join6(baseDir, deployRelative), content);
2563
+ await writeFileContent(join6(projectRoot, deployRelative), content);
2540
2564
  }
2541
2565
  }
2542
2566
  }
@@ -2558,7 +2582,7 @@ async function installDependency(params) {
2558
2582
  }
2559
2583
  if (frozen) {
2560
2584
  for (const { path: deployRelative, content } of deployed) {
2561
- await writeFileContent(join6(baseDir, deployRelative), content);
2585
+ await writeFileContent(join6(projectRoot, deployRelative), content);
2562
2586
  }
2563
2587
  }
2564
2588
  const lockEntry = {
@@ -2706,8 +2730,8 @@ var GhLockSchema = z6.looseObject({
2706
2730
  generated_at: z6.string(),
2707
2731
  installations: z6.array(GhLockInstallationSchema)
2708
2732
  });
2709
- function getGhLockPath(baseDir) {
2710
- return join7(baseDir, GH_LOCKFILE_FILE_NAME);
2733
+ function getGhLockPath(projectRoot) {
2734
+ return join7(projectRoot, GH_LOCKFILE_FILE_NAME);
2711
2735
  }
2712
2736
  function createEmptyGhLock(params) {
2713
2737
  const base = params?.existingLock ? { ...params.existingLock } : {};
@@ -2739,8 +2763,8 @@ ${issues}`);
2739
2763
  }
2740
2764
  return parsed.data;
2741
2765
  }
2742
- async function readGhLock(baseDir) {
2743
- const path2 = getGhLockPath(baseDir);
2766
+ async function readGhLock(projectRoot) {
2767
+ const path2 = getGhLockPath(projectRoot);
2744
2768
  if (!await fileExists(path2)) {
2745
2769
  return null;
2746
2770
  }
@@ -2748,7 +2772,7 @@ async function readGhLock(baseDir) {
2748
2772
  return parseGhLock(content);
2749
2773
  }
2750
2774
  async function writeGhLock(params) {
2751
- const path2 = getGhLockPath(params.baseDir);
2775
+ const path2 = getGhLockPath(params.projectRoot);
2752
2776
  const content = serializeGhLock(params.lock);
2753
2777
  await writeFileContent(path2, content);
2754
2778
  }
@@ -2800,7 +2824,7 @@ function relativeInstallDirFor(params) {
2800
2824
  var SKILLS_REMOTE_DIR = "skills";
2801
2825
  var SKILL_FILE_NAME2 = "SKILL.md";
2802
2826
  async function installGh(params) {
2803
- const { baseDir, sources, options = {}, logger: logger5 } = params;
2827
+ const { projectRoot, sources, options = {}, logger: logger5 } = params;
2804
2828
  if (sources.length === 0) {
2805
2829
  return { sourcesProcessed: 0, installedSkillCount: 0, failedSourceCount: 0 };
2806
2830
  }
@@ -2837,7 +2861,7 @@ async function installGh(params) {
2837
2861
  scope
2838
2862
  };
2839
2863
  });
2840
- const existingLock = await readGhLock(baseDir);
2864
+ const existingLock = await readGhLock(projectRoot);
2841
2865
  const frozen = options.frozen ?? false;
2842
2866
  const update = options.update ?? false;
2843
2867
  if (frozen && !existingLock) {
@@ -2888,7 +2912,7 @@ async function installGh(params) {
2888
2912
  rs,
2889
2913
  client,
2890
2914
  semaphore,
2891
- baseDir,
2915
+ projectRoot,
2892
2916
  existingLock,
2893
2917
  frozen,
2894
2918
  update,
@@ -2951,7 +2975,7 @@ async function installGh(params) {
2951
2975
  await removeStaleFile({
2952
2976
  relativePath: deployed,
2953
2977
  scope: prev.scope === "user" ? "user" : "project",
2954
- baseDir,
2978
+ projectRoot,
2955
2979
  logger: logger5
2956
2980
  });
2957
2981
  }
@@ -2959,7 +2983,7 @@ async function installGh(params) {
2959
2983
  }
2960
2984
  if (!frozen) {
2961
2985
  newLock.generated_at = (/* @__PURE__ */ new Date()).toISOString();
2962
- await writeGhLock({ baseDir, lock: newLock });
2986
+ await writeGhLock({ projectRoot, lock: newLock });
2963
2987
  if (failedCount === 0) {
2964
2988
  logger5.debug("rulesync-gh.lock.yaml updated.");
2965
2989
  } else {
@@ -2975,7 +2999,7 @@ async function installGh(params) {
2975
2999
  };
2976
3000
  }
2977
3001
  async function installSource(params) {
2978
- const { rs, client, semaphore, baseDir, existingLock, frozen, update, logger: logger5 } = params;
3002
+ const { rs, client, semaphore, projectRoot, existingLock, frozen, update, logger: logger5 } = params;
2979
3003
  const { entry, owner, repo, agent, scope } = rs;
2980
3004
  const sourceKey = entry.source;
2981
3005
  let resolvedRef;
@@ -3050,7 +3074,7 @@ async function installSource(params) {
3050
3074
  }
3051
3075
  const results = [];
3052
3076
  const installRelDir = relativeInstallDirFor({ agent, scope });
3053
- const scopeRoot = scope === "user" ? getHomeDirectory() : baseDir;
3077
+ const scopeRoot = scope === "user" ? getHomeDirectory() : projectRoot;
3054
3078
  const sourceUrl = `https://github.com/${owner}/${repo}`;
3055
3079
  const repository = `${owner}/${repo}`;
3056
3080
  const provenanceRef = usedTag ? resolvedRef : resolvedSha;
@@ -3166,12 +3190,12 @@ ${content}`;
3166
3190
  return results;
3167
3191
  }
3168
3192
  async function removeStaleFile(params) {
3169
- const { relativePath, scope, baseDir, logger: logger5 } = params;
3193
+ const { relativePath, scope, projectRoot, logger: logger5 } = params;
3170
3194
  if (posix3.isAbsolute(relativePath) || relativePath.split(/[/\\]/).includes("..")) {
3171
3195
  logger5.warn(`Refusing to remove stale gh file with suspicious path: "${relativePath}".`);
3172
3196
  return;
3173
3197
  }
3174
- const scopeRoot = scope === "user" ? getHomeDirectory() : baseDir;
3198
+ const scopeRoot = scope === "user" ? getHomeDirectory() : projectRoot;
3175
3199
  try {
3176
3200
  checkPathTraversal({ relativePath, intendedRootDir: scopeRoot });
3177
3201
  } catch {
@@ -3343,7 +3367,7 @@ async function fetchSkillFiles(params) {
3343
3367
  var MAX_WALK_DEPTH = 20;
3344
3368
  var MAX_TOTAL_FILES = 1e4;
3345
3369
  var MAX_TOTAL_SIZE = 100 * 1024 * 1024;
3346
- async function walkDirectory(dir, baseDir, depth = 0, ctx = { totalFiles: 0, totalSize: 0 }, logger5) {
3370
+ async function walkDirectory(dir, outputRoot, depth = 0, ctx = { totalFiles: 0, totalSize: 0 }, logger5) {
3347
3371
  if (depth > MAX_WALK_DEPTH) {
3348
3372
  throw new GitClientError(
3349
3373
  `Directory tree exceeds max depth of ${MAX_WALK_DEPTH}: "${dir}". Aborting to prevent resource exhaustion.`
@@ -3358,7 +3382,7 @@ async function walkDirectory(dir, baseDir, depth = 0, ctx = { totalFiles: 0, tot
3358
3382
  continue;
3359
3383
  }
3360
3384
  if (await directoryExists(fullPath)) {
3361
- results.push(...await walkDirectory(fullPath, baseDir, depth + 1, ctx, logger5));
3385
+ results.push(...await walkDirectory(fullPath, outputRoot, depth + 1, ctx, logger5));
3362
3386
  } else {
3363
3387
  const size = await getFileSize(fullPath);
3364
3388
  if (size > MAX_FILE_SIZE) {
@@ -3380,7 +3404,7 @@ async function walkDirectory(dir, baseDir, depth = 0, ctx = { totalFiles: 0, tot
3380
3404
  );
3381
3405
  }
3382
3406
  const content = await readFileContent(fullPath);
3383
- results.push({ relativePath: relative(baseDir, fullPath), content, size });
3407
+ results.push({ relativePath: relative(outputRoot, fullPath), content, size });
3384
3408
  }
3385
3409
  }
3386
3410
  return results;
@@ -3434,7 +3458,7 @@ function createEmptyLock() {
3434
3458
  }
3435
3459
  async function readLockFile(params) {
3436
3460
  const { logger: logger5 } = params;
3437
- const lockPath = join11(params.baseDir, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH);
3461
+ const lockPath = join11(params.projectRoot, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH);
3438
3462
  if (!await fileExists(lockPath)) {
3439
3463
  logger5.debug("No sources lockfile found, starting fresh.");
3440
3464
  return createEmptyLock();
@@ -3463,7 +3487,7 @@ async function readLockFile(params) {
3463
3487
  }
3464
3488
  async function writeLockFile(params) {
3465
3489
  const { logger: logger5 } = params;
3466
- const lockPath = join11(params.baseDir, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH);
3490
+ const lockPath = join11(params.projectRoot, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH);
3467
3491
  const content = JSON.stringify(params.lock, null, 2) + "\n";
3468
3492
  await writeFileContent(lockPath, content);
3469
3493
  logger5.debug(`Wrote sources lockfile to ${lockPath}`);
@@ -3538,7 +3562,7 @@ function getLockedSkillNames(entry) {
3538
3562
 
3539
3563
  // src/lib/sources.ts
3540
3564
  async function resolveAndFetchSources(params) {
3541
- const { sources, baseDir, options = {}, logger: logger5 } = params;
3565
+ const { sources, projectRoot, options = {}, logger: logger5 } = params;
3542
3566
  if (sources.length === 0) {
3543
3567
  return { fetchedSkillCount: 0, sourcesProcessed: 0 };
3544
3568
  }
@@ -3546,7 +3570,7 @@ async function resolveAndFetchSources(params) {
3546
3570
  logger5.info("Skipping source fetching.");
3547
3571
  return { fetchedSkillCount: 0, sourcesProcessed: 0 };
3548
3572
  }
3549
- let lock = options.updateSources ? createEmptyLock() : await readLockFile({ baseDir, logger: logger5 });
3573
+ let lock = options.updateSources ? createEmptyLock() : await readLockFile({ projectRoot, logger: logger5 });
3550
3574
  if (options.frozen) {
3551
3575
  const missingKeys = [];
3552
3576
  for (const source of sources) {
@@ -3564,7 +3588,7 @@ async function resolveAndFetchSources(params) {
3564
3588
  const originalLockJson = JSON.stringify(lock);
3565
3589
  const token = GitHubClient.resolveToken(options.token);
3566
3590
  const client = new GitHubClient({ token });
3567
- const localSkillNames = await getLocalSkillDirNames(baseDir);
3591
+ const localSkillNames = await getLocalSkillDirNames(projectRoot);
3568
3592
  let totalSkillCount = 0;
3569
3593
  const allFetchedSkillNames = /* @__PURE__ */ new Set();
3570
3594
  for (const sourceEntry of sources) {
@@ -3574,7 +3598,7 @@ async function resolveAndFetchSources(params) {
3574
3598
  if (transport === "git") {
3575
3599
  result = await fetchSourceViaGit({
3576
3600
  sourceEntry,
3577
- baseDir,
3601
+ projectRoot,
3578
3602
  lock,
3579
3603
  localSkillNames,
3580
3604
  alreadyFetchedSkillNames: allFetchedSkillNames,
@@ -3586,7 +3610,7 @@ async function resolveAndFetchSources(params) {
3586
3610
  result = await fetchSource({
3587
3611
  sourceEntry,
3588
3612
  client,
3589
- baseDir,
3613
+ projectRoot,
3590
3614
  lock,
3591
3615
  localSkillNames,
3592
3616
  alreadyFetchedSkillNames: allFetchedSkillNames,
@@ -3620,7 +3644,7 @@ async function resolveAndFetchSources(params) {
3620
3644
  }
3621
3645
  lock = { lockfileVersion: lock.lockfileVersion, sources: prunedSources };
3622
3646
  if (!options.frozen && JSON.stringify(lock) !== originalLockJson) {
3623
- await writeLockFile({ baseDir, lock, logger: logger5 });
3647
+ await writeLockFile({ projectRoot, lock, logger: logger5 });
3624
3648
  } else {
3625
3649
  logger5.debug("Lockfile unchanged, skipping write.");
3626
3650
  }
@@ -3771,7 +3795,7 @@ async function fetchSource(params) {
3771
3795
  const {
3772
3796
  sourceEntry,
3773
3797
  client,
3774
- baseDir,
3798
+ projectRoot,
3775
3799
  localSkillNames,
3776
3800
  alreadyFetchedSkillNames,
3777
3801
  updateSources,
@@ -3800,7 +3824,7 @@ async function fetchSource(params) {
3800
3824
  ref = resolvedSha;
3801
3825
  logger5.debug(`Resolved ${sourceKey} ref "${requestedRef}" to SHA: ${resolvedSha}`);
3802
3826
  }
3803
- const curatedDir = join12(baseDir, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH);
3827
+ const curatedDir = join12(projectRoot, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH);
3804
3828
  if (locked && resolvedSha === locked.resolvedRef && !updateSources) {
3805
3829
  const allExist = await checkLockedSkillsExist(curatedDir, lockedSkillNames);
3806
3830
  if (allExist) {
@@ -3948,7 +3972,7 @@ async function fetchSource(params) {
3948
3972
  async function fetchSourceViaGit(params) {
3949
3973
  const {
3950
3974
  sourceEntry,
3951
- baseDir,
3975
+ projectRoot,
3952
3976
  localSkillNames,
3953
3977
  alreadyFetchedSkillNames,
3954
3978
  updateSources,
@@ -3975,7 +3999,7 @@ async function fetchSourceViaGit(params) {
3975
3999
  requestedRef = def.ref;
3976
4000
  resolvedSha = def.sha;
3977
4001
  }
3978
- const curatedDir = join12(baseDir, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH);
4002
+ const curatedDir = join12(projectRoot, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH);
3979
4003
  if (locked && resolvedSha === locked.resolvedRef && !updateSources) {
3980
4004
  if (await checkLockedSkillsExist(curatedDir, lockedSkillNames)) {
3981
4005
  return { skillCount: 0, fetchedSkillNames: lockedSkillNames, updatedLock: lock };
@@ -4057,8 +4081,8 @@ async function installCommand(logger5, options) {
4057
4081
  await runRulesyncInstall(logger5, options);
4058
4082
  }
4059
4083
  async function runRulesyncInstall(logger5, options) {
4060
- const baseDir = process.cwd();
4061
- const apmExists = await apmManifestExists(baseDir);
4084
+ const projectRoot = process.cwd();
4085
+ const apmExists = await apmManifestExists(projectRoot);
4062
4086
  const config = await ConfigResolver.resolve({
4063
4087
  configPath: options.configPath,
4064
4088
  verbose: options.verbose,
@@ -4083,7 +4107,7 @@ async function runRulesyncInstall(logger5, options) {
4083
4107
  logger5.debug(`Installing skills from ${sources.length} source(s)...`);
4084
4108
  const result = await resolveAndFetchSources({
4085
4109
  sources,
4086
- baseDir,
4110
+ projectRoot,
4087
4111
  options: {
4088
4112
  updateSources: options.update,
4089
4113
  frozen: options.frozen,
@@ -4104,14 +4128,14 @@ async function runRulesyncInstall(logger5, options) {
4104
4128
  }
4105
4129
  }
4106
4130
  async function runApmInstall(logger5, options) {
4107
- const baseDir = process.cwd();
4108
- if (!await apmManifestExists(baseDir)) {
4131
+ const projectRoot = process.cwd();
4132
+ if (!await apmManifestExists(projectRoot)) {
4109
4133
  throw new Error(
4110
4134
  "--mode apm requires an apm.yml at the project root. Create one or drop --mode apm to fall back to rulesync mode."
4111
4135
  );
4112
4136
  }
4113
4137
  const result = await installApm({
4114
- baseDir,
4138
+ projectRoot,
4115
4139
  options: {
4116
4140
  update: options.update,
4117
4141
  frozen: options.frozen,
@@ -4138,7 +4162,7 @@ async function runApmInstall(logger5, options) {
4138
4162
  }
4139
4163
  }
4140
4164
  async function runGhInstall(logger5, options) {
4141
- const baseDir = process.cwd();
4165
+ const projectRoot = process.cwd();
4142
4166
  const config = await ConfigResolver.resolve({
4143
4167
  configPath: options.configPath,
4144
4168
  verbose: options.verbose,
@@ -4150,7 +4174,7 @@ async function runGhInstall(logger5, options) {
4150
4174
  return;
4151
4175
  }
4152
4176
  const result = await installGh({
4153
- baseDir,
4177
+ projectRoot,
4154
4178
  sources,
4155
4179
  options: {
4156
4180
  update: options.update,
@@ -4273,7 +4297,7 @@ async function putCommand({
4273
4297
  }
4274
4298
  const fileContent = stringifyFrontmatter(body, frontmatter);
4275
4299
  const command = new RulesyncCommand({
4276
- baseDir: process.cwd(),
4300
+ outputRoot: process.cwd(),
4277
4301
  relativeDirPath: RULESYNC_COMMANDS_RELATIVE_DIR_PATH,
4278
4302
  relativeFilePath: filename,
4279
4303
  frontmatter,
@@ -4418,7 +4442,7 @@ async function executeConvert(options) {
4418
4442
  features: options.features ?? ["*"],
4419
4443
  global: options.global,
4420
4444
  dryRun: options.dryRun,
4421
- // Always use default baseDirs (process.cwd()) and configPath
4445
+ // Always use default outputRoots (process.cwd()) and configPath
4422
4446
  // verbose and silent are meaningless in MCP context
4423
4447
  verbose: false,
4424
4448
  silent: true
@@ -4486,7 +4510,7 @@ var generateOptionsSchema = z10.object({
4486
4510
  });
4487
4511
  async function executeGenerate(options = {}) {
4488
4512
  try {
4489
- const exists = await checkRulesyncDirExists({ baseDir: process.cwd() });
4513
+ const exists = await checkRulesyncDirExists({ inputRoot: process.cwd() });
4490
4514
  if (!exists) {
4491
4515
  return {
4492
4516
  success: false,
@@ -4503,7 +4527,7 @@ async function executeGenerate(options = {}) {
4503
4527
  simulateCommands: options.simulateCommands,
4504
4528
  simulateSubagents: options.simulateSubagents,
4505
4529
  simulateSkills: options.simulateSkills,
4506
- // Always use default baseDirs (process.cwd()) and configPath
4530
+ // Always use default outputRoots (process.cwd()) and configPath
4507
4531
  // verbose and silent are meaningless in MCP context
4508
4532
  verbose: false,
4509
4533
  silent: true
@@ -4603,19 +4627,19 @@ async function putHooksFile({ content }) {
4603
4627
  );
4604
4628
  }
4605
4629
  try {
4606
- const baseDir = process.cwd();
4630
+ const outputRoot = process.cwd();
4607
4631
  const paths = RulesyncHooks.getSettablePaths();
4608
4632
  const relativeDirPath = paths.relativeDirPath;
4609
4633
  const relativeFilePath = paths.relativeFilePath;
4610
- const fullPath = join14(baseDir, relativeDirPath, relativeFilePath);
4634
+ const fullPath = join14(outputRoot, relativeDirPath, relativeFilePath);
4611
4635
  const rulesyncHooks = new RulesyncHooks({
4612
- baseDir,
4636
+ outputRoot,
4613
4637
  relativeDirPath,
4614
4638
  relativeFilePath,
4615
4639
  fileContent: content,
4616
4640
  validate: true
4617
4641
  });
4618
- await ensureDir(join14(baseDir, relativeDirPath));
4642
+ await ensureDir(join14(outputRoot, relativeDirPath));
4619
4643
  await writeFileContent(fullPath, content);
4620
4644
  const relativePathFromCwd = join14(relativeDirPath, relativeFilePath);
4621
4645
  return {
@@ -4633,9 +4657,9 @@ async function putHooksFile({ content }) {
4633
4657
  }
4634
4658
  async function deleteHooksFile() {
4635
4659
  try {
4636
- const baseDir = process.cwd();
4660
+ const outputRoot = process.cwd();
4637
4661
  const paths = RulesyncHooks.getSettablePaths();
4638
- const filePath = join14(baseDir, paths.relativeDirPath, paths.relativeFilePath);
4662
+ const filePath = join14(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
4639
4663
  await removeFile(filePath);
4640
4664
  const relativePathFromCwd = join14(paths.relativeDirPath, paths.relativeFilePath);
4641
4665
  return {
@@ -4809,7 +4833,7 @@ async function executeImport(options) {
4809
4833
  // eslint-disable-next-line no-type-assertion/no-type-assertion
4810
4834
  features: options.features,
4811
4835
  global: options.global,
4812
- // Always use default baseDirs (process.cwd()) and configPath
4836
+ // Always use default outputRoots (process.cwd()) and configPath
4813
4837
  // verbose and silent are meaningless in MCP context
4814
4838
  verbose: false,
4815
4839
  silent: true
@@ -4906,19 +4930,19 @@ async function putMcpFile({ content }) {
4906
4930
  );
4907
4931
  }
4908
4932
  try {
4909
- const baseDir = process.cwd();
4933
+ const outputRoot = process.cwd();
4910
4934
  const paths = RulesyncMcp.getSettablePaths();
4911
4935
  const relativeDirPath = paths.recommended.relativeDirPath;
4912
4936
  const relativeFilePath = paths.recommended.relativeFilePath;
4913
- const fullPath = join16(baseDir, relativeDirPath, relativeFilePath);
4937
+ const fullPath = join16(outputRoot, relativeDirPath, relativeFilePath);
4914
4938
  const rulesyncMcp = new RulesyncMcp({
4915
- baseDir,
4939
+ outputRoot,
4916
4940
  relativeDirPath,
4917
4941
  relativeFilePath,
4918
4942
  fileContent: content,
4919
4943
  validate: true
4920
4944
  });
4921
- await ensureDir(join16(baseDir, relativeDirPath));
4945
+ await ensureDir(join16(outputRoot, relativeDirPath));
4922
4946
  await writeFileContent(fullPath, content);
4923
4947
  const relativePathFromCwd = join16(relativeDirPath, relativeFilePath);
4924
4948
  return {
@@ -4936,14 +4960,18 @@ async function putMcpFile({ content }) {
4936
4960
  }
4937
4961
  async function deleteMcpFile() {
4938
4962
  try {
4939
- const baseDir = process.cwd();
4963
+ const outputRoot = process.cwd();
4940
4964
  const paths = RulesyncMcp.getSettablePaths();
4941
4965
  const recommendedPath = join16(
4942
- baseDir,
4966
+ outputRoot,
4943
4967
  paths.recommended.relativeDirPath,
4944
4968
  paths.recommended.relativeFilePath
4945
4969
  );
4946
- const legacyPath = join16(baseDir, paths.legacy.relativeDirPath, paths.legacy.relativeFilePath);
4970
+ const legacyPath = join16(
4971
+ outputRoot,
4972
+ paths.legacy.relativeDirPath,
4973
+ paths.legacy.relativeFilePath
4974
+ );
4947
4975
  await removeFile(recommendedPath);
4948
4976
  await removeFile(legacyPath);
4949
4977
  const relativePathFromCwd = join16(
@@ -5042,19 +5070,19 @@ async function putPermissionsFile({ content }) {
5042
5070
  );
5043
5071
  }
5044
5072
  try {
5045
- const baseDir = process.cwd();
5073
+ const outputRoot = process.cwd();
5046
5074
  const paths = RulesyncPermissions.getSettablePaths();
5047
5075
  const relativeDirPath = paths.relativeDirPath;
5048
5076
  const relativeFilePath = paths.relativeFilePath;
5049
- const fullPath = join17(baseDir, relativeDirPath, relativeFilePath);
5077
+ const fullPath = join17(outputRoot, relativeDirPath, relativeFilePath);
5050
5078
  const rulesyncPermissions = new RulesyncPermissions({
5051
- baseDir,
5079
+ outputRoot,
5052
5080
  relativeDirPath,
5053
5081
  relativeFilePath,
5054
5082
  fileContent: content,
5055
5083
  validate: true
5056
5084
  });
5057
- await ensureDir(join17(baseDir, relativeDirPath));
5085
+ await ensureDir(join17(outputRoot, relativeDirPath));
5058
5086
  await writeFileContent(fullPath, content);
5059
5087
  const relativePathFromCwd = join17(relativeDirPath, relativeFilePath);
5060
5088
  return {
@@ -5072,9 +5100,9 @@ async function putPermissionsFile({ content }) {
5072
5100
  }
5073
5101
  async function deletePermissionsFile() {
5074
5102
  try {
5075
- const baseDir = process.cwd();
5103
+ const outputRoot = process.cwd();
5076
5104
  const paths = RulesyncPermissions.getSettablePaths();
5077
- const filePath = join17(baseDir, paths.relativeDirPath, paths.relativeFilePath);
5105
+ const filePath = join17(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
5078
5106
  await removeFile(filePath);
5079
5107
  const relativePathFromCwd = join17(paths.relativeDirPath, paths.relativeFilePath);
5080
5108
  return {
@@ -5212,7 +5240,7 @@ async function putRule({
5212
5240
  );
5213
5241
  }
5214
5242
  const rule = new RulesyncRule({
5215
- baseDir: process.cwd(),
5243
+ outputRoot: process.cwd(),
5216
5244
  relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
5217
5245
  relativeFilePath: filename,
5218
5246
  frontmatter,
@@ -5419,7 +5447,7 @@ async function putSkill({
5419
5447
  }
5420
5448
  const aiDirFiles = otherFiles.map(mcpSkillFileToAiDirFile);
5421
5449
  const skill = new RulesyncSkill({
5422
- baseDir: process.cwd(),
5450
+ outputRoot: process.cwd(),
5423
5451
  relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
5424
5452
  dirName,
5425
5453
  frontmatter,
@@ -5636,7 +5664,7 @@ async function putSubagent({
5636
5664
  );
5637
5665
  }
5638
5666
  const subagent = new RulesyncSubagent({
5639
- baseDir: process.cwd(),
5667
+ outputRoot: process.cwd(),
5640
5668
  relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
5641
5669
  relativeFilePath: filename,
5642
5670
  frontmatter,
@@ -6435,7 +6463,7 @@ function wrapCommand({
6435
6463
  }
6436
6464
 
6437
6465
  // src/cli/index.ts
6438
- var getVersion = () => "8.11.0";
6466
+ var getVersion = () => "8.13.0";
6439
6467
  function wrapCommand2(name, errorCode, handler) {
6440
6468
  return wrapCommand({ name, errorCode, handler, getVersion });
6441
6469
  }
@@ -6552,8 +6580,12 @@ var main = async () => {
6552
6580
  `Comma-separated list of features to generate (${ALL_FEATURES.join(",")}) or '*' for all`,
6553
6581
  parseCommaSeparatedList
6554
6582
  ).option("--delete", "Delete all existing files in output directories before generating").option(
6583
+ "-o, --output-roots <paths>",
6584
+ "Output root directories to generate files into (comma-separated for multiple paths)",
6585
+ parseCommaSeparatedList
6586
+ ).option(
6555
6587
  "-b, --base-dir <paths>",
6556
- "Base directories to generate files (comma-separated for multiple paths)",
6588
+ "[Deprecated] Use --output-roots instead. Output root directories (comma-separated for multiple paths)",
6557
6589
  parseCommaSeparatedList
6558
6590
  ).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(
6559
6591
  "--simulate-commands",
@@ -6564,6 +6596,9 @@ var main = async () => {
6564
6596
  ).option(
6565
6597
  "--simulate-skills",
6566
6598
  "Generate simulated skills. This feature is only available for copilot, cursor and codexcli."
6599
+ ).option(
6600
+ "--input-root <path>",
6601
+ "Path to the directory containing .rulesync/ (parent of .rulesync/)"
6567
6602
  ).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(
6568
6603
  wrapCommand2("generate", "GENERATION_FAILED", async (logger5, options) => {
6569
6604
  await generateCommand(logger5, options);