rulesync 36.0.0 → 36.0.2

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.
@@ -1691,7 +1691,8 @@ async function listDirectoryRecursive(params) {
1691
1691
  else if (entry.type === "dir") directories.push(entry);
1692
1692
  else dropped = true;
1693
1693
  if (dropped) onIncompleteDirectory?.(path);
1694
- const subResults = await Promise.all(directories.map((dir) => listDirectoryRecursive({
1694
+ const failures = [];
1695
+ const settled = await Promise.allSettled(directories.map((dir) => listDirectoryRecursive({
1695
1696
  client,
1696
1697
  owner,
1697
1698
  repo,
@@ -1700,7 +1701,12 @@ async function listDirectoryRecursive(params) {
1700
1701
  depth: depth + 1,
1701
1702
  semaphore,
1702
1703
  onIncompleteDirectory
1704
+ }).catch((error) => {
1705
+ failures.push(error);
1706
+ throw error;
1703
1707
  })));
1708
+ if (failures.length > 0) throw failures[0];
1709
+ const subResults = settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
1704
1710
  return [...files, ...subResults.flat()];
1705
1711
  }
1706
1712
  /**
@@ -2411,6 +2417,12 @@ async function resolveAndFetchSources(params) {
2411
2417
  failedSourceCount
2412
2418
  };
2413
2419
  }
2420
+ /**
2421
+ * The claim of a source the prefetch does not list -- a git or npm transport
2422
+ * source -- whose selection is a wildcard: it may take any
2423
+ * name, so later sources download nothing a name decides.
2424
+ */
2425
+ const ANY_NAME = Symbol("any name");
2414
2426
  function createNameClaim() {
2415
2427
  let settle = () => {};
2416
2428
  return {
@@ -2425,7 +2437,12 @@ function createClaimSlot(params) {
2425
2437
  return {
2426
2438
  earlier: async () => {
2427
2439
  const claimed = await Promise.all(earlier.map((claim) => claim.names));
2428
- return /* @__PURE__ */ new Set([...reserved, ...claimed.flat()]);
2440
+ const mayTakeAny = claimed.includes(ANY_NAME);
2441
+ const names = /* @__PURE__ */ new Set([...reserved, ...claimed.flatMap((claim) => claim === ANY_NAME ? [] : claim)]);
2442
+ return {
2443
+ names,
2444
+ has: (name) => mayTakeAny || names.has(name)
2445
+ };
2429
2446
  },
2430
2447
  settle: own.settle
2431
2448
  };
@@ -2450,13 +2467,30 @@ async function prefetchGithubSources(params) {
2450
2467
  ...rest,
2451
2468
  semaphore: new es_toolkit_promise.Semaphore(10)
2452
2469
  };
2453
- const items = sources.filter((sourceEntry) => (sourceEntry.transport ?? "github") === "github" && !hasEmptySelection(sourceEntry)).map((sourceEntry) => ({
2470
+ const items = sources.filter((sourceEntry) => !hasEmptySelection(sourceEntry)).map((sourceEntry) => ({
2454
2471
  sourceEntry,
2472
+ listedThroughApi: isListedThroughGithubApi(sourceEntry),
2455
2473
  skills: createNameClaim(),
2456
2474
  rules: createNameClaim()
2457
2475
  }));
2476
+ for (const item of items.filter((candidate) => !candidate.listedThroughApi)) {
2477
+ if (isGitlabSource(item.sourceEntry)) {
2478
+ item.skills.settle([]);
2479
+ item.rules.settle([]);
2480
+ continue;
2481
+ }
2482
+ const filters = getSourceFilters(item.sourceEntry);
2483
+ item.skills.settle(getSelectionClaim({
2484
+ selection: filters.skills,
2485
+ normalize: (name) => name
2486
+ }));
2487
+ item.rules.settle(getSelectionClaim({
2488
+ selection: filters.rules,
2489
+ normalize: normalizeRuleFilterName
2490
+ }));
2491
+ }
2458
2492
  await require_import.mapWithConcurrency({
2459
- items,
2493
+ items: items.filter((item) => item.listedThroughApi),
2460
2494
  limit: 4,
2461
2495
  mapper: async (item) => {
2462
2496
  const earlier = items.slice(0, items.indexOf(item));
@@ -2485,14 +2519,36 @@ async function prefetchGithubSources(params) {
2485
2519
  }
2486
2520
  });
2487
2521
  }
2522
+ /** Whether the prefetch lists and downloads this source through the GitHub API. */
2523
+ function isListedThroughGithubApi(sourceEntry) {
2524
+ if ((sourceEntry.transport ?? "github") !== "github") return false;
2525
+ try {
2526
+ return parseSource(sourceEntry.source).provider !== "gitlab";
2527
+ } catch {
2528
+ return true;
2529
+ }
2530
+ }
2531
+ /** Whether this is a GitLab source, which the serial pass does not support yet. */
2532
+ function isGitlabSource(sourceEntry) {
2533
+ if ((sourceEntry.transport ?? "github") !== "github") return false;
2534
+ try {
2535
+ return parseSource(sourceEntry.source).provider === "gitlab";
2536
+ } catch {
2537
+ return false;
2538
+ }
2539
+ }
2540
+ /** What a source the prefetch does not list may take, judged by its selection alone. */
2541
+ function getSelectionClaim(params) {
2542
+ const { selection, normalize } = params;
2543
+ if (selection === void 0) return [];
2544
+ return selection.includes("*") ? ANY_NAME : selection.map(normalize);
2545
+ }
2488
2546
  async function prefetchGithubSource(params) {
2489
2547
  const { sourceEntry, lock, context, skillClaim, ruleClaim } = params;
2490
2548
  const parsedFromSource = parseSource(sourceEntry.source);
2491
- if (parsedFromSource.provider === "gitlab") return;
2492
2549
  const parsed = {
2493
2550
  ...parsedFromSource,
2494
- ref: sourceEntry.ref ?? parsedFromSource.ref,
2495
- path: sourceEntry.path ?? parsedFromSource.path
2551
+ ref: sourceEntry.ref ?? parsedFromSource.ref
2496
2552
  };
2497
2553
  const locked = getLockedSource(lock, sourceEntry.source);
2498
2554
  const ref = await resolvePrefetchRef({
@@ -2555,7 +2611,7 @@ async function prefetchGithubSkills(params) {
2555
2611
  }
2556
2612
  const skillFilter = sourceEntry.skills ?? ["*"];
2557
2613
  const isWildcard = skillFilter.length === 1 && skillFilter[0] === "*";
2558
- const entries = await withSemaphore(semaphore, () => client.listDirectory(parsed.owner, parsed.repo, parsed.path ?? "skills", ref));
2614
+ const entries = await withSemaphore(semaphore, () => client.listDirectory(parsed.owner, parsed.repo, resolveSkillsSourcePath(sourceEntry), ref));
2559
2615
  const skillDirs = entries.filter((entry) => entry.type === "dir" && (isWildcard || skillFilter.includes(entry.name)));
2560
2616
  const useRootFallback = shouldUseRootFallback({
2561
2617
  skillFilter,
@@ -2624,13 +2680,13 @@ async function prefetchGithubRules(params) {
2624
2680
  sourceEntry,
2625
2681
  lockedRuleNames: getLockedRuleNames(locked),
2626
2682
  localRuleNames: context.localRuleNames,
2627
- alreadyFetchedRuleNames: claimed,
2683
+ alreadyFetchedRuleNames: claimed.names,
2628
2684
  curatedDir: (0, node_path.join)(context.projectRoot, require_import.RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH)
2629
2685
  })) {
2630
2686
  claim.settle(getLockedRuleNames(locked));
2631
2687
  return;
2632
2688
  }
2633
- const entries = await withSemaphore(context.semaphore, () => context.client.listDirectory(parsed.owner, parsed.repo, sourceEntry.rulesPath ?? "rules", ref));
2689
+ const entries = await withSemaphore(context.semaphore, () => context.client.listDirectory(parsed.owner, parsed.repo, resolveRulesSourcePath(sourceEntry), ref));
2634
2690
  const ruleFilter = (sourceEntry.rules ?? []).map(normalizeRuleFilterName);
2635
2691
  const isWildcard = ruleFilter.length === 1 && ruleFilter[0] === "*";
2636
2692
  const files = entries.filter((entry) => {
@@ -3475,16 +3531,31 @@ async function canReuseLockedSkills(params) {
3475
3531
  }) && await checkLockedSkillsExist(curatedDir, lockedSkillNames);
3476
3532
  }
3477
3533
  /**
3478
- * The normalized path the skills of a source are read from: the source's
3534
+ * The path the skills of a source are read from, as declared: the source's
3479
3535
  * `path`, else (for the github transport) the path embedded in the source
3480
- * spec, else `skills`.
3536
+ * spec, else `skills`. Every transport fetches from this path, and the
3537
+ * lockfile records its normalized form ({@link resolveSkillsPath}), so the
3538
+ * fetch and the lock comparison cannot pick different paths.
3481
3539
  */
3482
- function resolveSkillsPath(sourceEntry) {
3540
+ function resolveSkillsSourcePath(sourceEntry) {
3483
3541
  const embeddedPath = (sourceEntry.transport ?? "github") === "github" ? parseSource(sourceEntry.source).path : void 0;
3484
- return normalizeSourceDirPath(sourceEntry.path ?? embeddedPath ?? "skills");
3542
+ return sourceEntry.path ?? embeddedPath ?? "skills";
3485
3543
  }
3486
- function normalizeRulesPath(rulesPath) {
3487
- return normalizeSourceDirPath(rulesPath ?? "rules");
3544
+ /** The skills path of a source in the form the lockfile records and compares. */
3545
+ function resolveSkillsPath(sourceEntry) {
3546
+ return normalizeSourceDirPath(resolveSkillsSourcePath(sourceEntry));
3547
+ }
3548
+ /**
3549
+ * The path the rules of a source are read from, as declared: the source's
3550
+ * `rulesPath`, else `rules`. The rules counterpart of
3551
+ * {@link resolveSkillsSourcePath}.
3552
+ */
3553
+ function resolveRulesSourcePath(sourceEntry) {
3554
+ return sourceEntry.rulesPath ?? "rules";
3555
+ }
3556
+ /** The rules path of a source in the form the lockfile records and compares. */
3557
+ function resolveRulesPath(sourceEntry) {
3558
+ return normalizeSourceDirPath(resolveRulesSourcePath(sourceEntry));
3488
3559
  }
3489
3560
  /**
3490
3561
  * The form a source directory path is recorded in the lockfile and compared
@@ -3497,7 +3568,7 @@ function lockedRuleConfigMatches(params) {
3497
3568
  const rules = getSourceFilters(params.sourceEntry).rules;
3498
3569
  if (rules === void 0 || params.locked.ruleSelection === void 0) return false;
3499
3570
  const selection = normalizeRuleSelection(rules);
3500
- return selection.length === params.locked.ruleSelection.length && selection.every((ruleName, index) => ruleName === params.locked.ruleSelection?.[index]) && normalizeRulesPath(params.sourceEntry.rulesPath) === params.locked.rulesPath;
3571
+ return selection.length === params.locked.ruleSelection.length && selection.every((ruleName, index) => ruleName === params.locked.ruleSelection?.[index]) && resolveRulesPath(params.sourceEntry) === params.locked.rulesPath;
3501
3572
  }
3502
3573
  function assertMatchingRulesFound(params) {
3503
3574
  if (params.ruleNames.length === 0) throw new Error(`No matching rules found in ${params.source}.`);
@@ -3543,7 +3614,7 @@ async function fetchRulesViaGithub(params) {
3543
3614
  }
3544
3615
  const ruleFilter = (sourceEntry.rules ?? []).map(normalizeRuleFilterName);
3545
3616
  const isWildcard = ruleFilter.length === 1 && ruleFilter[0] === "*";
3546
- const rulesPath = sourceEntry.rulesPath ?? "rules";
3617
+ const rulesPath = resolveRulesSourcePath(sourceEntry);
3547
3618
  let entries;
3548
3619
  try {
3549
3620
  entries = await client.listDirectory(parsed.owner, parsed.repo, rulesPath, ref);
@@ -3598,7 +3669,7 @@ async function fetchRulesViaGithub(params) {
3598
3669
  requestedRef,
3599
3670
  resolvedRef: resolvedSha,
3600
3671
  ruleSelection: normalizeRuleSelection(sourceEntry.rules ?? []),
3601
- rulesPath: normalizeRulesPath(sourceEntry.rulesPath),
3672
+ rulesPath: resolveRulesPath(sourceEntry),
3602
3673
  resolvedRuleNames: remoteRuleNames,
3603
3674
  logger
3604
3675
  });
@@ -3643,7 +3714,7 @@ async function fetchRulesViaGit(params) {
3643
3714
  url: sourceKey,
3644
3715
  ref: requestedRef,
3645
3716
  resolvedRef,
3646
- skillsPath: sourceEntry.rulesPath ?? "rules",
3717
+ skillsPath: resolveRulesSourcePath(sourceEntry),
3647
3718
  logger
3648
3719
  });
3649
3720
  const ruleFilter = (sourceEntry.rules ?? []).map(normalizeRuleFilterName);
@@ -3676,7 +3747,7 @@ async function fetchRulesViaGit(params) {
3676
3747
  requestedRef,
3677
3748
  resolvedRef,
3678
3749
  ruleSelection: normalizeRuleSelection(sourceEntry.rules ?? []),
3679
- rulesPath: normalizeRulesPath(sourceEntry.rulesPath),
3750
+ rulesPath: resolveRulesPath(sourceEntry),
3680
3751
  resolvedRuleNames: remoteRuleNames,
3681
3752
  logger
3682
3753
  });
@@ -3823,8 +3894,7 @@ async function fetchGithubSkillDir(params) {
3823
3894
  * names already written into `fetchedSkills`.
3824
3895
  */
3825
3896
  async function discoverGithubSkillDirs(params) {
3826
- const { parsed, ref, resolvedSha, skillFilter, isWildcard, curatedDir, locked, sourceKey, localSkillNames, alreadyFetchedSkillNames, client, semaphore, getExecutablePaths, fetchedSkills, logger } = params;
3827
- const skillsBasePath = parsed.path ?? "skills";
3897
+ const { parsed, skillsBasePath, ref, resolvedSha, skillFilter, isWildcard, curatedDir, locked, sourceKey, localSkillNames, alreadyFetchedSkillNames, client, semaphore, getExecutablePaths, fetchedSkills, logger } = params;
3828
3898
  try {
3829
3899
  const entries = await client.listDirectory(parsed.owner, parsed.repo, skillsBasePath, ref);
3830
3900
  const remoteSkillDirs = entries.filter((e) => e.type === "dir").map((e) => ({
@@ -3889,8 +3959,7 @@ async function fetchSource(params) {
3889
3959
  const parsedFromSource = parseSource(sourceEntry.source);
3890
3960
  const parsed = {
3891
3961
  ...parsedFromSource,
3892
- ref: sourceEntry.ref ?? parsedFromSource.ref,
3893
- path: sourceEntry.path ?? parsedFromSource.path
3962
+ ref: sourceEntry.ref ?? parsedFromSource.ref
3894
3963
  };
3895
3964
  if (parsed.provider === "gitlab") throw new Error(`GitLab sources are not yet supported: "${sourceEntry.source}".`);
3896
3965
  const sourceKey = sourceEntry.source;
@@ -3934,6 +4003,7 @@ async function fetchSource(params) {
3934
4003
  const fetchedSkills = {};
3935
4004
  const discovery = await discoverGithubSkillDirs({
3936
4005
  parsed,
4006
+ skillsBasePath: resolveSkillsSourcePath(sourceEntry),
3937
4007
  ref,
3938
4008
  resolvedSha,
3939
4009
  skillFilter,
@@ -4046,7 +4116,7 @@ async function fetchSourceViaGit(params) {
4046
4116
  url,
4047
4117
  ref: requestedRef,
4048
4118
  resolvedRef: resolvedSha,
4049
- skillsPath: sourceEntry.path ?? "skills",
4119
+ skillsPath: resolveSkillsSourcePath(sourceEntry),
4050
4120
  logger
4051
4121
  }),
4052
4122
  skillFilter,
@@ -4270,7 +4340,7 @@ function buildNpmLockEntry(params) {
4270
4340
  ...sourceEntry.rules !== void 0 && {
4271
4341
  rules: mergedRules,
4272
4342
  ruleSelection: normalizeRuleSelection(sourceEntry.rules),
4273
- rulesPath: normalizeRulesPath(sourceEntry.rulesPath),
4343
+ rulesPath: resolveRulesPath(sourceEntry),
4274
4344
  resolvedRuleNames
4275
4345
  }
4276
4346
  };
@@ -4285,7 +4355,7 @@ async function fetchNpmSkills(params) {
4285
4355
  const declaredWildcard = skillFilter.length === 1 && skillFilter[0] === "*";
4286
4356
  const selectedFiles = selectNpmSkillFiles({
4287
4357
  allFiles,
4288
- skillsPath: sourceEntry.path ?? "skills",
4358
+ skillsPath: resolveSkillsSourcePath(sourceEntry),
4289
4359
  skillFilter,
4290
4360
  isWildcard: declaredWildcard,
4291
4361
  packageName
@@ -4333,7 +4403,7 @@ async function fetchNpmRules(params) {
4333
4403
  resolvedRuleNames: []
4334
4404
  };
4335
4405
  const { allFiles, sourceEntry, packageName, lockedForIntegrityCheck, lockedRuleNames, curatedRulesDir, localRuleNames, alreadyFetchedRuleNames, resolvedVersion, updateSources, logger } = params;
4336
- const normalizedRulesPath = normalizeRulesPath(sourceEntry.rulesPath);
4406
+ const normalizedRulesPath = resolveRulesPath(sourceEntry);
4337
4407
  const rulePrefix = normalizedRulesPath === "." ? "" : `${normalizedRulesPath}/`;
4338
4408
  const ruleFilter = (sourceEntry.rules ?? []).map(normalizeRuleFilterName);
4339
4409
  const isWildcard = ruleFilter.length === 1 && ruleFilter[0] === "*";
@@ -14033,7 +14103,7 @@ function wrapCommand$1({ name, errorCode, handler, getVersion, loggerFactory = c
14033
14103
  }
14034
14104
  //#endregion
14035
14105
  //#region src/cli/program.ts
14036
- const getVersion = () => "36.0.0";
14106
+ const getVersion = () => "36.0.2";
14037
14107
  const FEATURES_HELP = `${require_import.ALL_FEATURES.join(",")}; ignore is deprecated, use permissions`;
14038
14108
  function wrapCommand(name, errorCode, handler) {
14039
14109
  return wrapCommand$1({
package/dist/cli/index.js CHANGED
@@ -1688,7 +1688,8 @@ async function listDirectoryRecursive(params) {
1688
1688
  else if (entry.type === "dir") directories.push(entry);
1689
1689
  else dropped = true;
1690
1690
  if (dropped) onIncompleteDirectory?.(path);
1691
- const subResults = await Promise.all(directories.map((dir) => listDirectoryRecursive({
1691
+ const failures = [];
1692
+ const settled = await Promise.allSettled(directories.map((dir) => listDirectoryRecursive({
1692
1693
  client,
1693
1694
  owner,
1694
1695
  repo,
@@ -1697,7 +1698,12 @@ async function listDirectoryRecursive(params) {
1697
1698
  depth: depth + 1,
1698
1699
  semaphore,
1699
1700
  onIncompleteDirectory
1701
+ }).catch((error) => {
1702
+ failures.push(error);
1703
+ throw error;
1700
1704
  })));
1705
+ if (failures.length > 0) throw failures[0];
1706
+ const subResults = settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
1701
1707
  return [...files, ...subResults.flat()];
1702
1708
  }
1703
1709
  /**
@@ -2408,6 +2414,12 @@ async function resolveAndFetchSources(params) {
2408
2414
  failedSourceCount
2409
2415
  };
2410
2416
  }
2417
+ /**
2418
+ * The claim of a source the prefetch does not list -- a git or npm transport
2419
+ * source -- whose selection is a wildcard: it may take any
2420
+ * name, so later sources download nothing a name decides.
2421
+ */
2422
+ const ANY_NAME = Symbol("any name");
2411
2423
  function createNameClaim() {
2412
2424
  let settle = () => {};
2413
2425
  return {
@@ -2422,7 +2434,12 @@ function createClaimSlot(params) {
2422
2434
  return {
2423
2435
  earlier: async () => {
2424
2436
  const claimed = await Promise.all(earlier.map((claim) => claim.names));
2425
- return /* @__PURE__ */ new Set([...reserved, ...claimed.flat()]);
2437
+ const mayTakeAny = claimed.includes(ANY_NAME);
2438
+ const names = /* @__PURE__ */ new Set([...reserved, ...claimed.flatMap((claim) => claim === ANY_NAME ? [] : claim)]);
2439
+ return {
2440
+ names,
2441
+ has: (name) => mayTakeAny || names.has(name)
2442
+ };
2426
2443
  },
2427
2444
  settle: own.settle
2428
2445
  };
@@ -2447,13 +2464,30 @@ async function prefetchGithubSources(params) {
2447
2464
  ...rest,
2448
2465
  semaphore: new Semaphore(10)
2449
2466
  };
2450
- const items = sources.filter((sourceEntry) => (sourceEntry.transport ?? "github") === "github" && !hasEmptySelection(sourceEntry)).map((sourceEntry) => ({
2467
+ const items = sources.filter((sourceEntry) => !hasEmptySelection(sourceEntry)).map((sourceEntry) => ({
2451
2468
  sourceEntry,
2469
+ listedThroughApi: isListedThroughGithubApi(sourceEntry),
2452
2470
  skills: createNameClaim(),
2453
2471
  rules: createNameClaim()
2454
2472
  }));
2473
+ for (const item of items.filter((candidate) => !candidate.listedThroughApi)) {
2474
+ if (isGitlabSource(item.sourceEntry)) {
2475
+ item.skills.settle([]);
2476
+ item.rules.settle([]);
2477
+ continue;
2478
+ }
2479
+ const filters = getSourceFilters(item.sourceEntry);
2480
+ item.skills.settle(getSelectionClaim({
2481
+ selection: filters.skills,
2482
+ normalize: (name) => name
2483
+ }));
2484
+ item.rules.settle(getSelectionClaim({
2485
+ selection: filters.rules,
2486
+ normalize: normalizeRuleFilterName
2487
+ }));
2488
+ }
2455
2489
  await mapWithConcurrency({
2456
- items,
2490
+ items: items.filter((item) => item.listedThroughApi),
2457
2491
  limit: 4,
2458
2492
  mapper: async (item) => {
2459
2493
  const earlier = items.slice(0, items.indexOf(item));
@@ -2482,14 +2516,36 @@ async function prefetchGithubSources(params) {
2482
2516
  }
2483
2517
  });
2484
2518
  }
2519
+ /** Whether the prefetch lists and downloads this source through the GitHub API. */
2520
+ function isListedThroughGithubApi(sourceEntry) {
2521
+ if ((sourceEntry.transport ?? "github") !== "github") return false;
2522
+ try {
2523
+ return parseSource(sourceEntry.source).provider !== "gitlab";
2524
+ } catch {
2525
+ return true;
2526
+ }
2527
+ }
2528
+ /** Whether this is a GitLab source, which the serial pass does not support yet. */
2529
+ function isGitlabSource(sourceEntry) {
2530
+ if ((sourceEntry.transport ?? "github") !== "github") return false;
2531
+ try {
2532
+ return parseSource(sourceEntry.source).provider === "gitlab";
2533
+ } catch {
2534
+ return false;
2535
+ }
2536
+ }
2537
+ /** What a source the prefetch does not list may take, judged by its selection alone. */
2538
+ function getSelectionClaim(params) {
2539
+ const { selection, normalize } = params;
2540
+ if (selection === void 0) return [];
2541
+ return selection.includes("*") ? ANY_NAME : selection.map(normalize);
2542
+ }
2485
2543
  async function prefetchGithubSource(params) {
2486
2544
  const { sourceEntry, lock, context, skillClaim, ruleClaim } = params;
2487
2545
  const parsedFromSource = parseSource(sourceEntry.source);
2488
- if (parsedFromSource.provider === "gitlab") return;
2489
2546
  const parsed = {
2490
2547
  ...parsedFromSource,
2491
- ref: sourceEntry.ref ?? parsedFromSource.ref,
2492
- path: sourceEntry.path ?? parsedFromSource.path
2548
+ ref: sourceEntry.ref ?? parsedFromSource.ref
2493
2549
  };
2494
2550
  const locked = getLockedSource(lock, sourceEntry.source);
2495
2551
  const ref = await resolvePrefetchRef({
@@ -2552,7 +2608,7 @@ async function prefetchGithubSkills(params) {
2552
2608
  }
2553
2609
  const skillFilter = sourceEntry.skills ?? ["*"];
2554
2610
  const isWildcard = skillFilter.length === 1 && skillFilter[0] === "*";
2555
- const entries = await withSemaphore(semaphore, () => client.listDirectory(parsed.owner, parsed.repo, parsed.path ?? "skills", ref));
2611
+ const entries = await withSemaphore(semaphore, () => client.listDirectory(parsed.owner, parsed.repo, resolveSkillsSourcePath(sourceEntry), ref));
2556
2612
  const skillDirs = entries.filter((entry) => entry.type === "dir" && (isWildcard || skillFilter.includes(entry.name)));
2557
2613
  const useRootFallback = shouldUseRootFallback({
2558
2614
  skillFilter,
@@ -2621,13 +2677,13 @@ async function prefetchGithubRules(params) {
2621
2677
  sourceEntry,
2622
2678
  lockedRuleNames: getLockedRuleNames(locked),
2623
2679
  localRuleNames: context.localRuleNames,
2624
- alreadyFetchedRuleNames: claimed,
2680
+ alreadyFetchedRuleNames: claimed.names,
2625
2681
  curatedDir: join(context.projectRoot, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH)
2626
2682
  })) {
2627
2683
  claim.settle(getLockedRuleNames(locked));
2628
2684
  return;
2629
2685
  }
2630
- const entries = await withSemaphore(context.semaphore, () => context.client.listDirectory(parsed.owner, parsed.repo, sourceEntry.rulesPath ?? "rules", ref));
2686
+ const entries = await withSemaphore(context.semaphore, () => context.client.listDirectory(parsed.owner, parsed.repo, resolveRulesSourcePath(sourceEntry), ref));
2631
2687
  const ruleFilter = (sourceEntry.rules ?? []).map(normalizeRuleFilterName);
2632
2688
  const isWildcard = ruleFilter.length === 1 && ruleFilter[0] === "*";
2633
2689
  const files = entries.filter((entry) => {
@@ -3472,16 +3528,31 @@ async function canReuseLockedSkills(params) {
3472
3528
  }) && await checkLockedSkillsExist(curatedDir, lockedSkillNames);
3473
3529
  }
3474
3530
  /**
3475
- * The normalized path the skills of a source are read from: the source's
3531
+ * The path the skills of a source are read from, as declared: the source's
3476
3532
  * `path`, else (for the github transport) the path embedded in the source
3477
- * spec, else `skills`.
3533
+ * spec, else `skills`. Every transport fetches from this path, and the
3534
+ * lockfile records its normalized form ({@link resolveSkillsPath}), so the
3535
+ * fetch and the lock comparison cannot pick different paths.
3478
3536
  */
3479
- function resolveSkillsPath(sourceEntry) {
3537
+ function resolveSkillsSourcePath(sourceEntry) {
3480
3538
  const embeddedPath = (sourceEntry.transport ?? "github") === "github" ? parseSource(sourceEntry.source).path : void 0;
3481
- return normalizeSourceDirPath(sourceEntry.path ?? embeddedPath ?? "skills");
3539
+ return sourceEntry.path ?? embeddedPath ?? "skills";
3482
3540
  }
3483
- function normalizeRulesPath(rulesPath) {
3484
- return normalizeSourceDirPath(rulesPath ?? "rules");
3541
+ /** The skills path of a source in the form the lockfile records and compares. */
3542
+ function resolveSkillsPath(sourceEntry) {
3543
+ return normalizeSourceDirPath(resolveSkillsSourcePath(sourceEntry));
3544
+ }
3545
+ /**
3546
+ * The path the rules of a source are read from, as declared: the source's
3547
+ * `rulesPath`, else `rules`. The rules counterpart of
3548
+ * {@link resolveSkillsSourcePath}.
3549
+ */
3550
+ function resolveRulesSourcePath(sourceEntry) {
3551
+ return sourceEntry.rulesPath ?? "rules";
3552
+ }
3553
+ /** The rules path of a source in the form the lockfile records and compares. */
3554
+ function resolveRulesPath(sourceEntry) {
3555
+ return normalizeSourceDirPath(resolveRulesSourcePath(sourceEntry));
3485
3556
  }
3486
3557
  /**
3487
3558
  * The form a source directory path is recorded in the lockfile and compared
@@ -3494,7 +3565,7 @@ function lockedRuleConfigMatches(params) {
3494
3565
  const rules = getSourceFilters(params.sourceEntry).rules;
3495
3566
  if (rules === void 0 || params.locked.ruleSelection === void 0) return false;
3496
3567
  const selection = normalizeRuleSelection(rules);
3497
- return selection.length === params.locked.ruleSelection.length && selection.every((ruleName, index) => ruleName === params.locked.ruleSelection?.[index]) && normalizeRulesPath(params.sourceEntry.rulesPath) === params.locked.rulesPath;
3568
+ return selection.length === params.locked.ruleSelection.length && selection.every((ruleName, index) => ruleName === params.locked.ruleSelection?.[index]) && resolveRulesPath(params.sourceEntry) === params.locked.rulesPath;
3498
3569
  }
3499
3570
  function assertMatchingRulesFound(params) {
3500
3571
  if (params.ruleNames.length === 0) throw new Error(`No matching rules found in ${params.source}.`);
@@ -3540,7 +3611,7 @@ async function fetchRulesViaGithub(params) {
3540
3611
  }
3541
3612
  const ruleFilter = (sourceEntry.rules ?? []).map(normalizeRuleFilterName);
3542
3613
  const isWildcard = ruleFilter.length === 1 && ruleFilter[0] === "*";
3543
- const rulesPath = sourceEntry.rulesPath ?? "rules";
3614
+ const rulesPath = resolveRulesSourcePath(sourceEntry);
3544
3615
  let entries;
3545
3616
  try {
3546
3617
  entries = await client.listDirectory(parsed.owner, parsed.repo, rulesPath, ref);
@@ -3595,7 +3666,7 @@ async function fetchRulesViaGithub(params) {
3595
3666
  requestedRef,
3596
3667
  resolvedRef: resolvedSha,
3597
3668
  ruleSelection: normalizeRuleSelection(sourceEntry.rules ?? []),
3598
- rulesPath: normalizeRulesPath(sourceEntry.rulesPath),
3669
+ rulesPath: resolveRulesPath(sourceEntry),
3599
3670
  resolvedRuleNames: remoteRuleNames,
3600
3671
  logger
3601
3672
  });
@@ -3640,7 +3711,7 @@ async function fetchRulesViaGit(params) {
3640
3711
  url: sourceKey,
3641
3712
  ref: requestedRef,
3642
3713
  resolvedRef,
3643
- skillsPath: sourceEntry.rulesPath ?? "rules",
3714
+ skillsPath: resolveRulesSourcePath(sourceEntry),
3644
3715
  logger
3645
3716
  });
3646
3717
  const ruleFilter = (sourceEntry.rules ?? []).map(normalizeRuleFilterName);
@@ -3673,7 +3744,7 @@ async function fetchRulesViaGit(params) {
3673
3744
  requestedRef,
3674
3745
  resolvedRef,
3675
3746
  ruleSelection: normalizeRuleSelection(sourceEntry.rules ?? []),
3676
- rulesPath: normalizeRulesPath(sourceEntry.rulesPath),
3747
+ rulesPath: resolveRulesPath(sourceEntry),
3677
3748
  resolvedRuleNames: remoteRuleNames,
3678
3749
  logger
3679
3750
  });
@@ -3820,8 +3891,7 @@ async function fetchGithubSkillDir(params) {
3820
3891
  * names already written into `fetchedSkills`.
3821
3892
  */
3822
3893
  async function discoverGithubSkillDirs(params) {
3823
- const { parsed, ref, resolvedSha, skillFilter, isWildcard, curatedDir, locked, sourceKey, localSkillNames, alreadyFetchedSkillNames, client, semaphore, getExecutablePaths, fetchedSkills, logger } = params;
3824
- const skillsBasePath = parsed.path ?? "skills";
3894
+ const { parsed, skillsBasePath, ref, resolvedSha, skillFilter, isWildcard, curatedDir, locked, sourceKey, localSkillNames, alreadyFetchedSkillNames, client, semaphore, getExecutablePaths, fetchedSkills, logger } = params;
3825
3895
  try {
3826
3896
  const entries = await client.listDirectory(parsed.owner, parsed.repo, skillsBasePath, ref);
3827
3897
  const remoteSkillDirs = entries.filter((e) => e.type === "dir").map((e) => ({
@@ -3886,8 +3956,7 @@ async function fetchSource(params) {
3886
3956
  const parsedFromSource = parseSource(sourceEntry.source);
3887
3957
  const parsed = {
3888
3958
  ...parsedFromSource,
3889
- ref: sourceEntry.ref ?? parsedFromSource.ref,
3890
- path: sourceEntry.path ?? parsedFromSource.path
3959
+ ref: sourceEntry.ref ?? parsedFromSource.ref
3891
3960
  };
3892
3961
  if (parsed.provider === "gitlab") throw new Error(`GitLab sources are not yet supported: "${sourceEntry.source}".`);
3893
3962
  const sourceKey = sourceEntry.source;
@@ -3931,6 +4000,7 @@ async function fetchSource(params) {
3931
4000
  const fetchedSkills = {};
3932
4001
  const discovery = await discoverGithubSkillDirs({
3933
4002
  parsed,
4003
+ skillsBasePath: resolveSkillsSourcePath(sourceEntry),
3934
4004
  ref,
3935
4005
  resolvedSha,
3936
4006
  skillFilter,
@@ -4043,7 +4113,7 @@ async function fetchSourceViaGit(params) {
4043
4113
  url,
4044
4114
  ref: requestedRef,
4045
4115
  resolvedRef: resolvedSha,
4046
- skillsPath: sourceEntry.path ?? "skills",
4116
+ skillsPath: resolveSkillsSourcePath(sourceEntry),
4047
4117
  logger
4048
4118
  }),
4049
4119
  skillFilter,
@@ -4267,7 +4337,7 @@ function buildNpmLockEntry(params) {
4267
4337
  ...sourceEntry.rules !== void 0 && {
4268
4338
  rules: mergedRules,
4269
4339
  ruleSelection: normalizeRuleSelection(sourceEntry.rules),
4270
- rulesPath: normalizeRulesPath(sourceEntry.rulesPath),
4340
+ rulesPath: resolveRulesPath(sourceEntry),
4271
4341
  resolvedRuleNames
4272
4342
  }
4273
4343
  };
@@ -4282,7 +4352,7 @@ async function fetchNpmSkills(params) {
4282
4352
  const declaredWildcard = skillFilter.length === 1 && skillFilter[0] === "*";
4283
4353
  const selectedFiles = selectNpmSkillFiles({
4284
4354
  allFiles,
4285
- skillsPath: sourceEntry.path ?? "skills",
4355
+ skillsPath: resolveSkillsSourcePath(sourceEntry),
4286
4356
  skillFilter,
4287
4357
  isWildcard: declaredWildcard,
4288
4358
  packageName
@@ -4330,7 +4400,7 @@ async function fetchNpmRules(params) {
4330
4400
  resolvedRuleNames: []
4331
4401
  };
4332
4402
  const { allFiles, sourceEntry, packageName, lockedForIntegrityCheck, lockedRuleNames, curatedRulesDir, localRuleNames, alreadyFetchedRuleNames, resolvedVersion, updateSources, logger } = params;
4333
- const normalizedRulesPath = normalizeRulesPath(sourceEntry.rulesPath);
4403
+ const normalizedRulesPath = resolveRulesPath(sourceEntry);
4334
4404
  const rulePrefix = normalizedRulesPath === "." ? "" : `${normalizedRulesPath}/`;
4335
4405
  const ruleFilter = (sourceEntry.rules ?? []).map(normalizeRuleFilterName);
4336
4406
  const isWildcard = ruleFilter.length === 1 && ruleFilter[0] === "*";
@@ -14030,7 +14100,7 @@ function wrapCommand$1({ name, errorCode, handler, getVersion, loggerFactory = c
14030
14100
  }
14031
14101
  //#endregion
14032
14102
  //#region src/cli/program.ts
14033
- const getVersion = () => "36.0.0";
14103
+ const getVersion = () => "36.0.2";
14034
14104
  const FEATURES_HELP = `${ALL_FEATURES.join(",")}; ignore is deprecated, use permissions`;
14035
14105
  function wrapCommand(name, errorCode, handler) {
14036
14106
  return wrapCommand$1({